Source: TY.js

window.TY = window.TY || {};
(function (TY) {
  TY.SERVER_URL= null;
  TY.WS_URL=null;
  TY.URL = {
    initServerUrl: function (url) {
      TY.SERVER_URL = url;
      TY.WS_URL = url;
      var urls = this.initReqUrl();
      Object.assign( TY.URL,urls);
    },
    initReqUrl(){
      return {
        rtcStats: TY.SERVER_URL + "/ops/api/metrics", //设备注册
        activate: TY.SERVER_URL + "/ops/api/license/activate", //设备注册
        service: TY.SERVER_URL + "/ops/api/service", //设备注册
        license: TY.SERVER_URL + "/ops/api/license", //设备注册
        DeviceManager:{
          addDevice: TY.SERVER_URL + "/user/device/add", //设备注册
          deleteDevice: TY.SERVER_URL + "/device/del", //设备删除
          modifyDevice: TY.SERVER_URL + "/device/upd", //设备信息修改包括回放密码
          getDevicesByParams: TY.SERVER_URL + "/user/device", //设备list
          deviceInfo:TY.SERVER_URL + "/device/info",
          getScanUrl: TY.SERVER_URL + "/path", //获取服务器地址
          getBindedScanUrl: TY.SERVER_URL + "/user/registered/end",//设备绑定二维码
          getUnBindedDevice: TY.SERVER_URL + "/user/device/undistributed",//未绑定的设备列表
          getUnAllocatedDevice: TY.SERVER_URL + "/user/device/undistributed/id",//未分配的设备列表
          getAllocatedDevice: TY.SERVER_URL + "/user/device/allocated/id", //已分配的设备列表
          bindDevice: TY.SERVER_URL + "/user/device/bound", //绑定设备
          unBindDevice: TY.SERVER_URL + "/user/device/remove/bound", //解绑设备
          allocateDevice: TY.SERVER_URL + "/user/device/shared/assignments/with/one/click", //设备分配 这个会删除你们共有而对方没有的
          AllocateDevice: TY.SERVER_URL + "/user/device/shared", //设备分配 这个是添加方式
          unAllocateDevice: TY.SERVER_URL + "/user/device/removePower", //移除全部分配设备
          disableDevice: TY.SERVER_URL + "/device/disable", //设备禁用
          transferDevice: TY.SERVER_URL + "/user/device/transfer", //设备调拔
          loadBindUser: TY.SERVER_URL + "/user/department/all/user", //获取设备绑定界面的用户列表
        },
        deleteGroup:TY.SERVER_URL +  "/user/community/del",
        getBrocastList:TY.SERVER_URL +  "/user/radio/query",
        notGroupUsers: TY.SERVER_URL + "/menu/child",//未分配到群组的成员
        loadMenu: TY.SERVER_URL + "/menu/child",//菜单
        sos: TY.SERVER_URL + "/user/sos",
        loadDepartmentCough: TY.SERVER_URL + "/user/department/cough",//单位信息
        allDepartment: TY.SERVER_URL + "/user/department/all",//单位信息
        settingDevice: TY.SERVER_URL +"/user/device/add",//单位用户信息
        deleteUser:TY.SERVER_URL + "/user/del",
        getUserInfo: TY.SERVER_URL + "/user/info",
        expCsv:TY.SERVER_URL + "/user/exp_csv",
        loadDepartentDevice:TY.SERVER_URL +  "/user/department/device",
        deleteDevices:TY.SERVER_URL +  "/device/dels",
        modifyPwd:  TY.SERVER_URL +"/user/upd",
        modifyGroup:  TY.SERVER_URL + "/user/community/aver",
        modifyGroupUsers:  TY.SERVER_URL + "/user/community/aver",
        deleteDepartment:TY.SERVER_URL + "/user/department/del",
        addDepartment: TY.SERVER_URL +"/user/department/add",
        modifyDepartment: TY.SERVER_URL +"/user/department/upd",
        transferDepartment:  TY.SERVER_URL +"/user/tool/department",
        loadAllocatedDevice: TY.SERVER_URL +"/user/device/allocated/id",
        loadUnAllocatedDevice:TY.SERVER_URL + "/user/device/undistributed/id",
        updateAllocateDevices:TY.SERVER_URL + "/user/device/shared/assignments/with/one/click",
        loadUnBindDevice: TY.SERVER_URL +"/user/device/undistributed",
        loadGroupUser:  TY.SERVER_URL +"/device/list",
        loadUnGroupUser: TY.SERVER_URL +"/device/list/not",
        loadGroupUsers:TY.SERVER_URL + "/user/community/personnel",
        loadDeviceList: TY.SERVER_URL + "/user/device",
        loadBindUser: TY.SERVER_URL + "/user/department/all/user",
        loadUserList:  TY.SERVER_URL +"/user/query",
        bindScanUrl: TY.SERVER_URL +"/user/registered/end",
        signPlaceAdd: TY.SERVER_URL + "/sign/add2edit",
        loadGroupList: TY.SERVER_URL + "/user/community",
        //deleteDepartment: TY.SERVER_URL+"/user/department/use"//单位用户信息
        downLogo: TY.SERVER_URL + '/',
        userInfo: TY.SERVER_URL + "/user/info",
        loadAllUser: TY.SERVER_URL +"/user/department/all/user",
        addUser: TY.SERVER_URL +"/user/add",//添加用戶
        modifyUser: TY.SERVER_URL +"/user/upd",
        login:TY.SERVER_URL + "/user/authorize",
        startTalk:TY.SERVER_URL + "/intercom/apply",
        stopTalk:TY.SERVER_URL + "/intercom/end",
        createDispatch:TY.SERVER_URL + "/intercom/create/scheduling",
        loadTalkDevice:TY.SERVER_URL + "/intercom/group/device",
        loadConferenceDevice:TY.SERVER_URL + "/meeting/group/use",
        loadTalkGroup:TY.SERVER_URL + "/intercom/group",
        createConference:TY.SERVER_URL + "/meeting/create/token",
        activeConference:TY.SERVER_URL + "/meeting/create/token",
        removeConfMember:TY.SERVER_URL + "/meeting/remove",
        inviteConfMember:TY.SERVER_URL + "/meeting/invite",
        stopConference:TY.SERVER_URL + "/meeting/dissolution/msg", //解散会议
        removeDispatchUser: TY.SERVER_URL + "/intercom/leave/use",//调度踢出调度对讲成员
        addDispatchUser: TY.SERVER_URL + "/intercom/add",//添加调度对讲成员
        leaveConference:TY.SERVER_URL + "/meeting/leave/msg", //解散会议
        stopDispatch:TY.SERVER_URL + "/intercom/leave/msg",
        loadDepartment:TY.SERVER_URL + "/user/department", //单位信息
        loadUser:TY.SERVER_URL + "/user/department/use", //单位用户信息
        removeGroupUser:TY.SERVER_URL + "/intercom/leave/use", //踢出对讲成员
        addGroupUser:TY.SERVER_URL + "/intercom/add", //踢出对讲成员
        joinConference:TY.SERVER_URL + "/meeting/join", //加入会议
        outConference:TY.SERVER_URL + "/meeting/remove", //踢出会议
        inviteConference:TY.SERVER_URL + "/meeting/invite", //邀请加入会议
        createMoniter:TY.SERVER_URL + "/monitoring/create/token", //监控
        addMoniter:TY.SERVER_URL + "/monitoring/join", //添加监控成员
        removeMoniter:TY.SERVER_URL + "/monitoring/remove/msg", //移除监控成员
        stopMoniter:TY.SERVER_URL + "/monitoring/leave/msg", //结束监控
        loadConferenceGroup:TY.SERVER_URL + "/meeting/group", //会议组
        changeTalkGroup:TY.SERVER_URL + "/intercom/switch",
        unsubscribeGps:TY.SERVER_URL + "/user/gps/end", //取消订阅gps
        subscribeGpsByDevice:TY.SERVER_URL + "/user/gps", //gps订阅
        subscribeGpsByGroup:TY.SERVER_URL + "/user/gps/room", //gps订阅组
        uploadPicture:TY.SERVER_URL + "/intercom/uploadPicture", //上传截屏图片
        loadFile:TY.SERVER_URL + "/device/video/retrieve", //视频检索
        loadTrackGps:TY.SERVER_URL + "/user/trajectory", //轨迹
        brocastMsg:TY.SERVER_URL + "/user/radio", //根据地址获取用户
        loadDevice:TY.SERVER_URL + "/user/device",
        downloadFile:TY.SERVER_URL + "/download",
        videoDown:TY.SERVER_URL + "/user/download/",
        loadUsers:TY.SERVER_URL + "/user/query",
        loadDevices:TY.SERVER_URL + "/user/department/device",
        deleteFile:TY.SERVER_URL + "/device/remove/file",
        uLoginLog:TY.SERVER_URL + "/user/loginLog",
        importFile:TY.SERVER_URL + "/device/import/file",
        loadRoles:TY.SERVER_URL + "/user/role/query",
        setPermission:TY.SERVER_URL + "/user/role/pinless",
        uploadImg:TY.SERVER_URL + "/user/upload",
        beginUploadFile:TY.SERVER_URL + "/user/media/begin",
        test:TY.SERVER_URL + "/intercom/switch",
        querySystemConfig: TY.SERVER_URL + "/sys_config/query", //查询系统参数设置
        setSystemConfig: TY.SERVER_URL + "/sys_config/set", //设置系统参数
        uploadUpPackage: TY.SERVER_URL + "/exit", //上传平台升级包,进行升级,
        getFencesByParams: TY.SERVER_URL +"/fence/list", //查询围栏列表
        getFenceRecordsByParams: TY.SERVER_URL +"/fence/recordList", //查询围栏列表
        setFence:  TY.SERVER_URL + "/fence/add2edit", //设置围栏
        getFenceInfo:  TY.SERVER_URL + "/fence/detail", //围栏详情
        deviceBindFence:  TY.SERVER_URL + "/fence/bind", //设备绑定到围栏
        deleteFence:  TY.SERVER_URL + "/fence/delete", //删除围栏
        getUnBindFences:  TY.SERVER_URL + "/fence/unAllocateList", //未绑定围栏的设备列表
        getBindFences:  TY.SERVER_URL + "/fence/allocatedList", //已绑定围栏的设备列表
        getSignPlacesByParams:  TY.SERVER_URL +"/sign/list", //签到点列表
        allSignPlaces: TY.SERVER_URL + "/sign/listAll", //全部签到点列表
        signPlaceInfo: TY.SERVER_URL + "/sign/detail", //签到点详情
        setSignPlace: TY.SERVER_URL + "/sign/add2edit", //设置签到点
        deleteSignPlace: TY.SERVER_URL + "/sign/delete", //删除签到点
        getWorkShiftsByParams:  TY.SERVER_URL + "/banci/list", //签到班次列表
        deviceBindWorkShift:  TY.SERVER_URL + "/banci/bind", //设备绑定到签到班次
        setWorkShift:  TY.SERVER_URL + "/banci/add2edit", //设置签到班次
        deleteWorkShift: TY.SERVER_URL + "/banci/delete", //删除签到班次
        workShiftInfo: TY.SERVER_URL + "/banci/detail", //签到班次详情
        unBindWorkShiftDevices:  TY.SERVER_URL + "/banci/unAllocateList", //未绑定签到班次的设备列表
        bindWorkShiftDevices:  TY.SERVER_URL + "/banci/allocatedList", //已绑定签到班次的设备列表
        getSignsByParams: TY.SERVER_URL + "/sign/records", //考勤记录
        exportSigns: TY.SERVER_URL + "/sign/downRecord", //导出考勤记录
        getChildRightsList: TY.SERVER_URL + "/menu", //获取下级权限列表
        modifyRights: TY.SERVER_URL + "/menu/save", //添加&修改权限
        getRightsInfo: TY.SERVER_URL + "/menu/detail", //获取权限详情
        deleteRights: TY.SERVER_URL + "/menu/del", //删除权限
        getSubRights: TY.SERVER_URL + "/menu/child", //获取所有下级的权限列表
        getRoleList: TY.SERVER_URL + "/role_mgr", //获取角色列表
        modifyRole: TY.SERVER_URL + "/role_mgr/save", //添加&修改角色,更改绑定的权限
        getRoleInfo: TY.SERVER_URL + "/role_mgr/detail", //获取角色详情
        deleteRole: TY.SERVER_URL + "/role_mgr/del", //删除角色,清除权限绑定关系
        getDeviceLoginLogsByParams: TY.SERVER_URL + "/device/loginLog", //获取设备登录日志
        getUserLoginLogsByParams: TY.SERVER_URL + "/user/loginLog", //获取用户登录日志
        getUserOperateLogsByParams: TY.SERVER_URL + "/user/log", //获取操作日志
        groupInfo: TY.SERVER_URL + "/user/community/info", //群组信息详情
        deleteFace: TY.SERVER_URL + "/face/rm",//删除人脸库
        getFacesByParams: TY.SERVER_URL + "/face/list",//人脸库列表
        loadFaceImg: TY.SERVER_URL + "/face/down",//人脸图片下载
        getResultsByParams: TY.SERVER_URL + "/face/hs_list",//识别结果列表
        deleteResult: TY.SERVER_URL + "/face/hs_rm",//删除识别结果
        getUserByUnit: TY.SERVER_URL + "/user/query",//获取部门下的人员列表
        getUnitsByParams: TY.SERVER_URL + "/user/department",//部门列表
        addUnit: TY.SERVER_URL + "/user/department/add",//添加部门
        deleteUnit: TY.SERVER_URL + "/user/department/del",//删除部门
        modifyUnit: TY.SERVER_URL + "/user/department/upd",//修改部门
        modifyApp: TY.SERVER_URL + "/version/type_add2edit",//添加/修改App
        getAppInfo: TY.SERVER_URL + "/version/type_detail",//App详情
        deleteApp: TY.SERVER_URL + "/version/type_delete",//删除App
        getAppList: TY.SERVER_URL + "/version/type_list",//App列表
        modifyAppVersion: TY.SERVER_URL + "/version/add2edit",//添加/修改App版本
        getAppVersionInfo: TY.SERVER_URL + "/version/detail",//App版本详情
        deleteAppVersion: TY.SERVER_URL + "/version/delete",//删除App版本
        getAppVersionList: TY.SERVER_URL + "/version/list",//App版本列表,
        evidence: {
          sortList: TY.SERVER_URL + '/evidence/sort',//文件类型
          caseTopicList: TY.SERVER_URL + '/evidence/caseTopic',//案例专题列表
          fileList: TY.SERVER_URL + '/evidence/file',//文件列表
          deptList: TY.SERVER_URL + '/evidence/file/dept',//部门列表
          devList: TY.SERVER_URL + '/evidence/file/dev',//文件列表
          fileDetail: TY.SERVER_URL + '/evidence/file/detail',//文件信息
          delFile: TY.SERVER_URL + '/evidence/file/del',//删除文件
          saveFile: TY.SERVER_URL + '/evidence/file/save',//编辑文件信息
          downFile: TY.SERVER_URL + '/evidence/file/down',//下载文件
          thumb: TY.SERVER_URL + '/evidence/file/thumb', //下载图片
          preFile: TY.SERVER_URL + '/evidence/file/preview',//预览文件
          exportCsv: TY.SERVER_URL + '/evidence/file/exportCsv',//导出文件
          openN: TY.SERVER_URL + '/evidence/file/openN',//记录播放数
          uploadFile: TY.SERVER_URL + '/evidence/file/uploadFile',//上传文件
          majorCount: TY.SERVER_URL + '/evidence/count/major',//文档重要级别统计
          importCount: TY.SERVER_URL + '/evidence/count/import',//文档导入时段统计
          personnelCount: TY.SERVER_URL + '/evidence/count/personnel',//人员资料统计
          assessCount: TY.SERVER_URL + '/evidence/count/assess',//人员考核统计
        }
      }
    },
  };
  TY.USER = {
    CMD: { //指令码
      INIT: 1,
      INIT_RESP: 2,
      msg: {
        UPDATE_TOKEN: 0,
        CONNECT_FAIL: 1,
        ONLINE: 2,
        OFFLINE: 3,
        BROCAST: 4,
        SUBSCRIBE_GPS: 5,
        FILE_PROGRESS: 6,
        FILE_UPLOAD: 9,
        FACE_RECORD: 12,
        SOS: 13,
        RECORD_STATUS: 14,
        SOMEONE_LOGIN: 15,
        FILE_IMPORT: 16,
        UPDATE_CERT:999
      },
      intercom: {
        CHANGE_GROUP: 0,
        ADD_MENBER: 1,
        REMOVE_MEMBER: 2,
        LEAVE_GROUP: 3,
        TALKING: 4,
        STOP_TALKING: 5,
        ONLINE: 6,
        OFFLINE: 7,
        ADD_GROUP: 8,
        DELE_GROUP: 9
      },
      meeting: {
        JOIN_CONFERENCE: 0,
        ADD_MEMBER: 1,
        REMOVE_MEMBER: 2,
        STOP_CONFERENCE: 3,
        ONLINE: 4,
        OFFLINE: 5
      },
      monitoring: {
        ONLINE: 2,
        OFFLINE: 3,
        REQ_JOIN_CONFERENCE: 4,
        LEAVE_CONFERENCE: 5,
        JOIN_CONFERENCE: 6
      }
    },
    setUser(token, uid) {//登录设置信息
      this.token = token;
      this.uid = uid;
      this._login=true;
    },
    toCallback:{
      // "record":
    },
    checkTokenInvalidEvent: null,
    addCheckTokenInvalidEvent: function(resolve){
      if(resolve) TY.USER.checkTokenInvalidEvent = resolve;
    },
    initSocket: function (resolve, reject) {//初始化socket
      reject=reject?reject:resolve;
      var socket = io.connect(TY.WS_URL, {
        reconnection: true
      });
      socket.on('connect', function (data) {
        socket.emit("init", {
          "token": TY.USER.token
        }, function (resp) {
          if (resp.cmd == TY.RcCode.SOCKET_INIT_SUCCESS.code) {
            console.log("==============消息通道已启动");
            self._socket = socket;
            resolve&&resolve();
            var callback = TY.EventManager.callbacks[TY.EventManager.EVENTS.SOCKET_INIT];
            callback&&callback(resp);
          } else {
            console.log("======消息通道启动失败=======", resp);
            reject&&reject();
          }
        });
        socket.on('intercom', function (data) {
          if (!TY.talkManager) {
            return;
          }
          var callback=TY.talkManager.callback;
          console.log(new Date().toUTCString(), "======intercom*************=========", JSON.stringify(data));
          switch (data.cmd) {
            case TY.USER.CMD.intercom.TALKING:
              if(data.uid==TY.USER.uid){
                console.log("=====自己对讲");
                return;
              }
              TY.talkManager.listenTalk(data.uid);
              callback = callback?callback:TY.EventManager.callbacks[TY.EventManager.EVENTS.SOMEONE_TALKING];
              console.log("=========有人对讲***********************************************************", data.uid);
              callback&&callback(data);
              break;
            case TY.USER.CMD.intercom.STOP_TALKING:
              console.log("=========有人结束对讲", data.uid);
              callback = callback?callback:TY.EventManager.callbacks[TY.EventManager.EVENTS.SOMEONE_STOP_TALKING];
              console.log("=========有人对讲***********************************************************", data.uid);
              callback&&callback(data);
              break;
            case TY.USER.CMD.intercom.ADD_GROUP://添加组
              TY.talkManager.callback&&TY.talkManager.callback(data);
              break;
            case TY.USER.CMD.intercom.DELE_GROUP://删除组
              TY.talkManager.callback&&TY.talkManager.callback(data);
              break;
            case TY.USER.CMD.intercom.OFFLINE://下线
              console.log("============对讲下线====================", data.uid);
              break;
            case TY.USER.CMD.intercom.ONLINE://上线
              console.log("=============对讲上线===================", data.uid);
              break;
            case TY.USER.CMD.intercom.REMOVE_MEMBER:
              break;
            case TY.USER.CMD.intercom.CHANGE_GROUP://切换组
              TY.talkManager.switchDispatchedGroup({id:data.id},function (resp) {
                callback = callback?callback:TY.EventManager.callbacks[TY.EventManager.EVENTS.START_DISPATCHED];
                console.log("=========被调度命令***********************************************************", data.uid);
                callback&&callback(data);
              });
              break;
            case TY.USER.CMD.intercom.LEAVE_GROUP://解散组
              TY.talkManager.leaveGroup();
              callback = callback?callback:TY.EventManager.callbacks[TY.EventManager.EVENTS.STOP_DISPATCHED];
              console.log("=========解散命令***********************************************************", data.uid);
              callback&&callback(data);
              break;
            default:
              break;
          }
          TY.talkManager.callback&&TY.talkManager.callback(data);
        });
        socket.on('msg', function (data) {
          console.log(new Date().toUTCString(), "===============================msg===================", data);
          var callback;
          switch (data.cmd) {
            case TY.USER.CMD.msg.SOS://警报通知
              callback = callback?callback:TY.EventManager.callbacks[TY.EventManager.EVENTS.SOS];
              callback&&callback(data);
              break;
            case TY.USER.CMD.msg.FACE_RECORD://人脸识别通知
              callback = callback?callback:TY.EventManager.callbacks[TY.EventManager.EVENTS.FACE_RECORD];
              callback&&callback(data);
              break;
            case TY.USER.CMD.msg.ONLINE://警报通知
              callback = callback?callback:TY.EventManager.callbacks[TY.EventManager.EVENTS.ONLINE];
              console.log("=========有人上线***********************************************************", data.id);
              callback&&callback(data);
              break;
            case TY.USER.CMD.msg.OFFLINE://警报通知
              callback = callback?callback:TY.EventManager.callbacks[TY.EventManager.EVENTS.OFFLINE];
              console.log("=========有人下线***********************************************************", data.id);
              callback&&callback(data);
              break;
            case TY.USER.CMD.msg.SUBSCRIBE_GPS://gps实时通知
              callback = callback?callback:TY.EventManager.callbacks[TY.EventManager.EVENTS.SUBSCRIBE_GPS];
              callback&&callback(data);
              break;
            case TY.USER.CMD.msg.RECORD_STATUS://录像录音状态实时通知
              var callback = TY.EventManager.callbacks[TY.EventManager.EVENTS.MEDIA];
              callback&&callback(data);
              break;
            case TY.USER.CMD.msg.FILE_PROGRESS://文件下载进度
              var callback = TY.EventManager.callbacks[TY.EventManager.EVENTS.FILE_PROGRESS];
              callback&&callback(data);
              break;
            case TY.USER.CMD.msg.FILE_IMPORT://文件导入进度
              var callback = TY.EventManager.callbacks[TY.EventManager.EVENTS.FILE_IMPORT];
              callback&&callback(data);
              break;
            case TY.USER.CMD.msg.UPDATE_CERT://更换Https证书提醒
              var callback = TY.EventManager.callbacks[TY.EventManager.EVENTS.UPDATE_CERT];
              callback&&callback(data);
              break;
            case TY.USER.CMD.msg.SOMEONE_LOGIN://有人在抢登啦
              callback = TY.EventManager.callbacks[TY.EventManager.EVENTS.SOMEONE_LOGIN];
              callback&&callback(data);
              break;
            default:
              break;
          }
        });
        socket.on('pong', function(data){
          var callback = TY.EventManager.callbacks[TY.EventManager.EVENTS.HEART];
          callback&&callback(data);
        });
        socket.on('disconnect', function (resp) {
          console.log(resp, "消息通道断开");
          var callback = TY.EventManager.callbacks[TY.EventManager.EVENTS.DISCONNECT];
          callback&&callback();
          // this.disconnectCallBack&&disconnectCallBack();
        });
      });
    },
    disconnect(){//断开socket连接
      if (self._socket) {
        self._socket.disconnect();
        self._socket = null;
        L.Logger.info("通道断开连接");
      }
      this._login=false;
    },
    isLogin(){
      return this._login;
    },
    _login:false,
    _socket: null,
    token: null,
    uid: null
  };
  /**
   * @description 登录的方法
   * @param {String} params 

   */
  function test1(params){

  }
  /**
   * 登录
   * @function login -登录
   * @param {String} params 
   * @param {Function} resolve -成功的回调
   * @param {Function} reject -失败的回调
   */
  TY.login = function (params, resolve, reject) {//登录
    reject=reject?reject:resolve;
    if(!params.serverUrl){
      resolve&&reject(TY.RcCode.returnMsg(TY.RcCode.EMPTY_SERVER));
      return;
    }
    if(TY.USER.isLogin()){//已登录
      resolve&&reject(TY.RcCode.returnMsg(TY.RcCode.LOGINED));
      return;
    }
    TY.URL.initServerUrl(params.serverUrl);
    TY.reqPost(params, TY.URL.login, function (resp) {
      TY.USER.setUser(resp.token, resp.id);//保存用户信息
      resolve&&resolve(resp);
      if (params.init) {
        TY.USER.initSocket(function () {
          resolve&&resolve(resp);
          //成功
        },reject);
      }
    }, reject);
  };

  /**
   * 退出登录
   * @typedef RcCodeMsg
   * @property {Array.<code>} status -返回码
   * @property {String} msg -返回的信息
  */

   /**
   * 退出
   * @function logout -退出
   * @return {Obj.<RcCodeMsg>} 
   */

  TY.logout = function () {
    TY.USER.token = null;
    TY.USER.disconnect();
    return TY.RcCode.returnMsg(TY.RcCode.RC_CODE_S_OK);
  };
  TY.validation = function validation(options,fn1,fn2,fn) {
    if (!options || typeof options != 'object') {
      return TY.RcCode.PARAMETERS_ABNORMAL;
    }else if(!TY.USER.token){
      TY.USER.checkTokenInvalidEvent&&TY.USER.checkTokenInvalidEvent(data.status);
      return TY.RcCode.EMPTY_TOKEN;
    }
    return 0;
  };
  TY.compositePut = function (url,options,fn,fn1,fn2){
    if(TY.EventManager.callbacks[TY.EventManager.EVENTS.BEFORE_REQUEST]&&TY.EventManager.callbacks[TY.EventManager.EVENTS.BEFORE_REQUEST]()){
      return;
    }
    var RcCode;
    if(RcCode=TY.validation(options,fn1,fn2)){
      fn2(TY.RcCode.returnMsg(RcCode));
      return;
    }
    var msg;
    if(fn && (msg = fn(options))){
      if(typeof(msg)=='string' ){
        typeof fn2 == 'function'&&fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL,msg));
      }else{
        typeof fn2 == 'function'&&fn2(TY.RcCode.returnMsg(msg));
      }
      return;
    }
    TY.reqPut(options, url, fn1, fn2);
  };
  TY.compositeGet = function (url,options,fn,fn1,fn2){
    if(TY.EventManager.callbacks[TY.EventManager.EVENTS.BEFORE_REQUEST]&&TY.EventManager.callbacks[TY.EventManager.EVENTS.BEFORE_REQUEST]()){
      return;
    }
    var RcCode;
    if(RcCode=TY.validation(options,fn1,fn2)){
      fn2(TY.RcCode.returnMsg(RcCode));
      return;
    }
    var msg;
    if(fn && (msg = fn(options))){
      if(typeof(msg)=='string' ){
        typeof fn2 == 'function'&&fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL,msg));
      }else{
        typeof fn2 == 'function'&&fn2(TY.RcCode.returnMsg(msg));
      }
      return;
    }
    TY.reqGet(options, url, fn1, fn2);
  };
  TY.compositePost = function (url,options,fn,fn1,fn2){
    if(TY.EventManager.callbacks[TY.EventManager.EVENTS.BEFORE_REQUEST]&&TY.EventManager.callbacks[TY.EventManager.EVENTS.BEFORE_REQUEST]()){
      return;
    }
    fn2=fn2?fn2:fn1;
    if(TY.validation(options,fn1,fn2,fn)){
      typeof fn2 == 'function'&&fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL));
      return;
    }
    var msg;
    if(fn && (msg = fn(options))){
      if(typeof(msg)== 'object'){
        typeof fn2 == 'function'&&fn2(TY.RcCode.returnMsg(msg));
      }else{
        typeof fn2 == 'function'&&fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL,msg));
      }
      return;
    }
    TY.reqPost(options, url, fn1, fn2);
  };
  TY.compositeDown = function (url,options,fn,fn1,fn2,fn3){
    if(TY.EventManager.callbacks[TY.EventManager.EVENTS.BEFORE_REQUEST]&&TY.EventManager.callbacks[TY.EventManager.EVENTS.BEFORE_REQUEST]()){
      return;
    }
    fn2=fn2?fn2:fn1;
    if(TY.validation(options,fn1,fn2,fn)){
      typeof fn2 == 'function'&&fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL));
      return;
    }
    var msg;
    if(fn && (msg = fn(options))){
      typeof fn2 == 'function'&&fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL,msg));
      return;
    }
    TY.reqDown(options, url, fn1, fn2, fn3);
  };
  TY.compositeUpload = function (url,options,fn,fn1,fn2,fn3){
    if(TY.EventManager.callbacks[TY.EventManager.EVENTS.BEFORE_REQUEST]&&TY.EventManager.callbacks[TY.EventManager.EVENTS.BEFORE_REQUEST]()){
      return;
    }
    if(TY.validation(options,fn1,fn2,fn)){
      typeof fn2 == 'function'&&fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL));
      return;
    }
    var msg;
    if(fn && (msg = fn(options))){
      typeof fn2 == 'function'&&fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL,msg));
      return;
    }
    return TY.reqUpload(options, url, fn1, fn2, fn3);
  };
  
  TY.BroadcastManager=(function () {

    /**
   * @typedef params_sendMsg
   * @property {Array} personnel -必选 广播人员id集合
   * @property {String} msg -发送信息
  */

     /**
      * 发送广播消息
      * @function sendMsg 发送广播消息
      * @param {Array.<params_sendMsg>} params -广播人员id集合 / 发送内容
      * @param {Function} resolve -成功的回调
      * @param {Function} reject -失败的回调
      */
    return {
      getBrocastList(params,resolve, reject){
        TY.compositePost(TY.URL.getBrocastList,params,null,resolve, reject);
      },
      sendMsg(params,resolve, reject){
        TY.compositePost(TY.URL.brocastMsg,params,function (params) {
          if(!params.personnel||params.personnel.length==0){
            return  "device id";
          }
          if(!params.msg){
            return  "msg";
          }
        },resolve, reject);
      }
    };
  })();
  TY.RTCManager=function(){
    var methods={
      //根据条件获取群组列表
      getService(resolve, reject){
        TY.compositeGet(TY.URL.service,{},null,resolve, reject);
      },
      getLicense(resolve, reject){
        TY.compositeGet(TY.URL.license,{},null,resolve, reject);
      },
      setService(params,resolve, reject){
        TY.compositePut(TY.URL.service,params,null,resolve, reject);
      },
      setLicense(params,resolve, reject){
        TY.compositePut(TY.URL.license,params,null,resolve, reject);
      },
      setActivationKey(params,resolve, reject){
        TY.compositePut(TY.URL.activate,params,null,resolve, reject);
      },
      activateOnline(params,resolve, reject){
        TY.compositePut(TY.URL.activate,params,null,resolve, reject);
      },
      rtcStats(resolve, reject){
        TY.compositeGet(TY.URL.rtcStats,{},null,resolve, reject);
      },
    };
    return methods;
  }();
  TY.MoniterManager=function(){//监控类
    var OPEN=1;
    var CLOSE=0;
    var RECORD={PICTURE:0,VIDEO:1,AUDIO:2};
    var user2StreamMap={};//所有rtc room里的用户
    var clients={};//房间id==>conference
    var userMap={};//监控的用户
    var recordFiles={};
    var stid2uid={};
    var methods= {
      userMap:userMap,
      //设置监控中,设备的媒体状态

      /**
       * @typedef Data
       * @property {Number} recordAudio -本地录音,0关闭,1开启
       * @property {Number} recordVideo -本地录像0关闭,1开启
       * @property {Number} serverRecordAudio -远程录音,0关闭,1开启
       * @property {Number} serverRecordAudio -远程录像,0关闭,1开启
       */

      /**
       * 设置监控中,设备的媒体状态
       * @function setMediaStatus 设置监控中,设备的媒体状态
       * @param {string} uid 人员id
       * @param {Array.<Data>} data 
       */
      setMediaStatus:function(uid,data){
        if (!userMap[uid]) {
          return;
        }
        if(data.recordAudio){
          userMap[uid].deviceRecordAudio=data.recordAudio;
        }
        if(data.recordVideo){
          userMap[uid].deviceRecordVideo=data.recordVideo;
        }
        if(data.serverRecordAudio){
          userMap[uid].serverRecordAudio=data.serverRecordAudio;
        }
        if(data.serverRecordVideo){
          userMap[uid].serverRecordVideo=data.serverRecordVideo;
        }
      },
      reEnterMoniter() {
        for (var room in clients) {
          clients[room].leave();
          self._socket.emit("fill", room, function (token) {
            console.log("重新获取的房间token", token);
            var uids=[];
            for (var uid in userMap) {
              uids.push(parseInt(uid));
            }
            methods.changeRoom({id:uids,token:token});
          });
        }
      },
      saveRecordFile(id, file, fileName, type, name, time,rid){
        recordFiles[id+"_"+type] = {id: id, file: file, fileName: fileName, type: type, name: name, time: time,rid:rid};
      },
      removeRecordFile(id,type){
        delete recordFiles[id+"_"+type];
      },
      rtspPublish(params){
        var externalStream;
        var conference = Object.values(clients)[0];
        params.conference =conference;
        var options ={
          url: params.url,
          video: true,
          audio: true
        };
        //如果无声摄像头,则禁用声音
        if (params.isExternal == 1) {
          options ={
            url: params.url,
              video: true,
            audio: false
          };
        }
        console.log(params.isExternal,"===================发布类型",options);
        PureRTC.ExternalStream.create(options, function (err, stream) {
          if (err) {
            return console.log('create ExternalStream failed:', err);
          }
          externalStream = stream;
          conference.publish(externalStream, {}, function(st) {
            // st.attr("uid",params.id);
            stid2uid[st.id()]=parseInt(params.id);
            L.Logger.info('stream published:', st.id());
          }, function(err) {
            L.Logger.error('publish failed:', err);
          });
        });
      },
      startRtspMoniter(uids){
        for (let i = 0; i < uids.length; i++) {
          var user = userMap[uids[i]];
          if (user.url) {
            // user.url="rtsp://192.168.1.201:554/user=admin&password=admin123&channel=channel1&stream=0.sdp?";
            methods.rtspPublish(user);
          }
        }
      },
      //开始监控
      //{params={id:1,el:''};  "personnel":[553]}==》{id:1,el:'moniter'}


      /**
       * 返回状态码code
       * @typedef code
       * @property {int} 0 -初始化成功
       * @property {int} 200 -请求成功
       * @property {int} 202 -存在绑定关系,操作失败
       * @property {int} 203 -名称不能重复
       * @property {int} 204 -已存在, 不能重复
       * @property {int} 205 -围栏绑定了设备, 请解绑后删除
       * @property {int} 206 -班次绑定了设备,请解绑后删除
       * @property {int} 207 -该角色已经被用户绑定,请解绑后删除
       * @property {int} 208 -群主绑定了设备,请解绑后删除
       * @property {int} 209 -该部门下有人员, 请删除后再次操作
       * @property {int} 210 -人员编号冲突
       * @property {int} 211 -该设备识别号已注册
       * @property {int} 212 -app下已经发布了版本, 请删除后再次操作
       * @property {int} 300 -token为空,可能未登陆
       * @property {int} 213 -参数不能包括特殊字符
       * @property {int} 401 -token无效
       * @property {int} 406 -设备不在线
       * @property {int} 407 -创建调度的人在调度
       * @property {int} 460 -服务器地址为空
       * @property {int} 461 -参数不能为空
       * @property {int} 462 -参数异常
       * @property {int} 463 -已经登陆过
       * @property {int} 464 -不能删除设备文件
       * @property {int} 465 麦克风不能用
       * @property {int} 466 -RTC异常
       * @property {int} 467 -已经有人说话
       * @property {int} 468 -该设备不在监控房间
       * @property {int} 469 -该设备不是被监控的状态
       * @property {int} 470 -已经邀请改设备进入监控
       * @property {int} 471 -结束失败 ,录制时间太短
       * @property {int} 500 -系统错误
       * @property {int} 410 -账号密码错误
       * @property {int} 411 -用户不存在
       * @property {int} 403 -没有权限
       * @property {int} 999 -其他异常
       */


      /**
       * @typedef params_startMoniter
       * @property {Number} id -监控成员id
       * @property {String} el -渲染的div id
       */

       /**
        * 实时视音频数据统计
        * @callback Function_stats 
        * @example
        * [{
          "type": "VideoBWE",
          "id": "",
          "stats": {
            "available_send_bandwidth": "0",
            "available_receive_bandwidth": "762808",
            "transmit_bitrate": "0",
            "retransmit_bitrate": "0"
          }
        }, {
          "type": "ssrc_audio_recv",
          "id": "ssrc_3672166965_recv",
          "stats": {
            "bytes_rcvd": "44753",
            "delay_estimated_ms": "190",
            "packets_rcvd": "449",
            "packets_lost": "0",
            "codec_name": "opus"
          }
        }, {
          "type": "ssrc_video_recv",
          "id": "ssrc_3867188859_recv",
          "stats": {
            "bytes_rcvd": "721034",//收到字节大小
            "packets_rcvd": "576",
            "packets_lost": "0",//丢失字节大小
            "firs_sent": "0",
            "nacks_sent": "1",
            "plis_sent": "0",
            "frame_width": "1280",//分辨率宽
            "frame_height": "720",//分辨高
            "framerate_rcvd": "21",//收到的帧率
            "framerate_output": "21",//帧率输出
            "current_delay_ms": "92",
            "codec_name": "H264"
          }
        }]
        */
      
      /**
       * 回调返回的参数
       * @callback function
       * @property {Array.<code>} status -返回码
       * @property {String} msg -返回的信息
       */

      /**
       * 开启监控
       * @function startMoniter 开启监控
       * @param {Obj.<params_startMoniter>} params -B必选 传入的参数
       * @param {function} resolve -必选 成功回调
       * @param {function} reject -不是必选 失败回调
       * @param {Function_stats} statCallback -不是必选 实时视音频数据统计
       */
      startMoniter(params,resolve, reject,statCallback){//开始监控
        var options = params;
        userMap[options.id] = options;
        if(!params.el){
          return  "div id";
        }
        params={'personnel':[params.id]};
        TY.compositePost(TY.URL.createMoniter,params,function (params) {
          if(!params.personnel||params.personnel.length==0){
            return  "device id";
          }
        },function (resp) {//如果未进入则进入监控房间
          TY.UserManager.getUserInfo({id:options.id},function (resp) {
            userMap[options.id].name = resp.user.name;
          });
          userMap[options.id].resolve=resolve;
          userMap[options.id].statCallback=statCallback;
          if(resp.array&&resp.array.length>0){
            var data = resp.array[0];
            if(!clients[data.room]){//已在监控房间,则不再次进入
              methods.changeRoom(data,resolve);
              return;
            }
          }
          userMap[options.id].conference = clients[data.room];
          var inter = setInterval(function () {
            if (userMap[options.id].conference.readySub) {
              window.clearInterval(inter);
              methods.startRtspMoniter([options.id]);
              methods.subscribe(options.id);
            }
          },10);

        }, reject);
      },
      //todo
      //批量监控



      /**
       * @function startMoniters 开始批量监控
       * @param {obj.<params>} params 参数
       * @param {Function} resolve -成功的回调
       * @param {Function} reject -失败的回调
       */
      startMoniters(params,resolve, reject){//开始批量监控
        TY.compositePost(TY.URL.removeMoniter,params,function (params) {
          if(!params.personnel||params.personnel.length==0){
            return  "device id";
          }
        },function (resp) {//如果未进入则进入监控房间
          resolve&&resolve(resp);
        }, reject);
      },
      //{id:1}

    
      /**
       * 结束单个监控
       * @function stopMoniter 结束单个监控
       * @param {number} id -必选 结束监控成员id
       * @param {function} resolve -成功回调
       * @param {function} reject -失败回调
       * @param {Function_stats} statCallback 实时视音频数据统计
       */


      stopMoniter(params,resolve, reject){//关闭一个监控
        var id = params.id;
        params={'personnel':[params.id]};
        TY.compositePost(TY.URL.removeMoniter,params,function (params) {
          if(!params.personnel||params.personnel.length==0){
            return  "device id";
          }
        },function (resp) {//如果未进入则进入监控房间
          console.log("============================取消订阅",id);
          methods.unsubscribe(id,resolve, reject);
        }, reject);
      },
      //关闭所有的监控
      /**
       * 结束全部监控
       * @function stopAllMoniter 结束全部监控
       * @param {Function} resolve -成功的回调
       * @param {Function} reject -失败的回调
       */

      stopAllMoniter(resolve, reject){
        methods.uploadRecordFiles();
        var result = TY.RcCode.returnMsg(TY.RcCode.RC_CODE_S_OK);
        result.uids=window.Object.keys(userMap);
        for (let i = 0; i <result.uids.length ; i++) {
          methods.stopMoniter({id:result.uids[i]});
        }
        resolve&&resolve(result);
        methods.leaveRoom();
      },
      leaveRoom(){
        for (var room in clients) {
          clients[room]& clients[room].leave();
        }
        clients={};
      },
      /**
       * @param {*} params
       * @param {Function} resolve
       * @param {Function} reject
       */
      changeRoom(params,resolve,reject) {
        var token = params.token;
        var moniterUids=params.id;
        var conference = PureRTC.ConferenceClient.create({});
        clients[params.room] =conference;
        conference.join(token, function (resp) {
          console.log("======================加入监控成功",conference.conferenceId);
          Object.keys(conference.remoteStreams).forEach(function (key) {
            var s = conference.remoteStreams[key];
            console.log(s.id());
            var uid = parseInt(s.attributes().uid);
            if (uid) {
              console.log(uid, "============提前进来" + s.id());
              user2StreamMap[uid] = s;
              if (moniterUids.indexOf(uid) != -1) {
                console.log("============人家早早就进来了" + s.id());
                //开始监控
                userMap[uid].conference=conference;
                methods.subscribe(uid);
              }
            }
          });
          conference.readySub=true;
          conference.streams = resp.streams;
          methods.startRtspMoniter(moniterUids);
        }, function (err) {
          reject&&reject(TY.RcCode.returnMsg(TY.RcCode.RTC_ABNOMAL));
          L.Logger.error('server connection failed:', err);
        });
        conference.on('stream-added', function (event) {
          if (event.target.myId != conference.myId) {
            return;
          }
          var stream = event.stream;
          console.log(stream.attributes().uid, "============刚刚有人来" + event.stream.id());
          L.Logger.info('stream added:', stream.id());
          var uid = parseInt(stream.attributes().uid);
          //开始监控
          if (uid) {
            console.log("==============有人被监控啦======" + stream.attributes().uid);
            //开始监控
            user2StreamMap[uid] = stream;
            if (userMap[uid]) {
              console.log(userMap[uid].statsTime);
              if (userMap[uid].statsTime) {
                window.clearInterval(userMap[uid].statsTime);
              }
              userMap[uid].conference=conference;
              methods.subscribe(uid);
            }
          }else{
            uid = stid2uid[stream.id()];
            user2StreamMap[uid] = stream;
            methods.subscribe(uid);
          }
        });
        conference.on('stream-removed', function (event) {
          if (event.target.myId != conference.myId) {
            return;
          }
          var stream = event.stream;
          console.log("========================================有人离开啦" + stream.attributes().uid);
          var uid = parseInt(stream.attributes().uid);
          let user = userMap[uid];
          if(user){
            user.serverRecordVideo=CLOSE;//离开设置本地录像状态为关闭
          }
          methods.uploadRecordFile(recordFiles[uid+"_"+RECORD.AUDIO]);
          methods.uploadRecordFile(recordFiles[uid+"_"+RECORD.VIDEO]);
          var callback = TY.EventManager.callbacks[TY.EventManager.EVENTS.MEDIA];
          callback&&callback({id:uid,serverRecordAudio:CLOSE,serverRecordVideo:CLOSE});
          delete user2StreamMap[uid];
        });
        return conference;
      },
      /**
       * 订阅
       * @param {String} uid
       */
      subscribe(uid) {
        if (!uid) {
          return;
        }
        var self = this;
        var stream = user2StreamMap[uid];
        if (!stream) {
          return;
        }
        var user =  userMap[uid];
        var showEl = document.getElementById(userMap[uid].el);
        var childs = showEl.childNodes;
        for (let i = 0; i < childs.length; i++) {
          showEl.removeChild(childs[i]);
        }
        console.log(stream.id(), user.conference.myId);
        user.conference.subscribe(stream, function () {
          console.log("=========订阅===================================================",userMap[uid].el);
          self.getStats(uid);
          stream.show(userMap[uid].el);
          user.stream = stream;
          user.resolve&&user.resolve(TY.RcCode.returnMsg(TY.RcCode.RC_CODE_S_OK));
        })
      },


      unsubscribe(uid,resolve, reject) {
        var user = userMap[uid];
        if(!user){
          reject&&reject(TY.RcCode.returnMsg(TY.RcCode.NOT_SUBSCRIBE_DEVICE));
          return;
        }
        if (user.stream) {
          stid2uid[user.stream.id()]&&delete stid2uid[user.stream.id()];
          user.conference.unsubscribe(user.stream, function () {
            console.log("=========取消订阅成功");
          });
        }
        resolve&&resolve(TY.RcCode.returnMsg(TY.RcCode.RC_CODE_S_OK));
        window.clearInterval(user.statsTime);
        delete userMap[uid];
      },
      getStats(uid) {//定时渲染帧率
        // console.log("==========又开始订阅数据信息啦===================**************************************************========");
        var user = userMap[uid];
        var statsTime = setInterval(function () {
          user.conference.getConnectionStats(user.stream, function (stats) {
              // console.log("================数据信息",stats);
              user.statCallback&&user.statCallback(stats);
            }, function (err) {
              L.Logger.error('Get statistic information failed:', err);
            }
          );
        }, 1000);
        user.statsTime=statsTime;
      },
      uploadRecordFiles() {//退出后,上传未主动上传的服务器音视频
        if (recordFiles&&window.Object.keys(recordFiles).length>0) {
            $.each(recordFiles, function (index, file) {
                methods.uploadRecordFile(file);
            });
            recordFiles = {};
        }
      },
      uploadRecordFile(file){
        if (!file) {
          return;
        }
        setTimeout(function () {
            methods.uploadFile(parseInt(file.id), "", file.fileName, file.type, file.name, file.time, function () {
        });
        }, 1000);
        },
      //远程录音

        /**
       * @typedef server_params
       * @property {Number} uid -监控成员id
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject - 可不选 失败的回调
       */

  /**
   * 服务器录音状态切换
   * @function  serverRecordAudio 服务器录音状态切换
   * @param {Obj.<server_params>} uid -必选 
   * @param {Function} resolve - 必选 成功的回调
   * @param {Function} reject - 可不选 失败的回调
   */



      serverRecordAudio(uid,resolve, reject){
        var user = userMap[uid];
        if (user.serverRecordAudio != OPEN) {//
          methods.startServerRecordAudio(uid,resolve, reject);
        }else{
          methods.stopServerRecordAudio(uid,resolve, reject)
        }
      },
      //开始服务器录像
      startServerRecordAudio(uid,resolve, reject){
        var user = userMap[uid];
        var options =
          {
            audioStreamId: user.stream.id(),
            audioCodec: 'aac'
          };
        user.conference.startRecorder(options,
          function (resp) {
            methods.beginUploadFile(uid, resp.recorderId + ".mp4", RECORD.AUDIO, user.name, Date.now());
            methods.saveRecordFile(uid, "", resp.recorderId + ".mp4", RECORD.AUDIO,  user.name, Date.now(),resp.recorderId);
            user.serverRecordAudio=OPEN;
            user.time = Date.now();
            resolve&&resolve(methods.returnMsg(user,TY.RcCode.RC_CODE_S_OK));
            L.Logger.info('开始录像: ', resp);
          },
          function (err) {
            reject&&reject(user);
          }
        );
      },
      //结束服务器录像
   
      stopServerRecordAudio:function (uid, resolve, reject) {
        var user = userMap[uid];
        if(Date.now()-user.time<5000){
          reject&&reject(TY.RcCode.returnMsg(TY.RcCode.RECORD_LESS));
          return;
        }
        user.conference.stopRecorder({recorderId: recordFiles[uid+"_"+RECORD.AUDIO].rid}, function (file, v) {
            methods.uploadFile(uid,"",recordFiles[uid+"_"+RECORD.AUDIO].rid+".mp4",RECORD.AUDIO,user.name,recordFiles[uid+"_"+RECORD.AUDIO].time,function () {
              user.serverRecordAudio=CLOSE;
              resolve&&resolve(methods.returnMsg(user,TY.RcCode.RC_CODE_S_OK));
            },function (resp) {
              reject&&reject(methods.returnMsg(user,TY.RcCode.getRcCode(resp.status)));
            });
            L.Logger.info('结束录像: ', file);
          }, function (err) {
            //vue.alert.error("设备"+name+"结束录像失败");
            var result = TY.RcCode.returnMsg(TY.RcCode.OTHER_ABNORMAL);
            result.user = user;
          reject&&reject(result);
            // vue.alert.error("设备" + name + "结束录像失败");
            L.Logger.error('Media recorder cannot stop with failure: ', err, user.rid);
          }
        );
      },
      returnMsg(user,code){
        var result = TY.RcCode.returnMsg(code);
        result.user = user;
        return result;
      },
      //远程录音
/**
 * 服务器录像状态切换
 * @function  serverRecordVideo 服务器录像状态切换
 * @param {Obj.<server_params>} uid -必选 
 * @param {Function} resolve - 必选 成功的回调
 * @param {Function} reject - 可不选 失败的回调
 */
      serverRecordVideo(uid,resolve, reject){
        var user = userMap[uid];
        if (user.serverRecordVideo != OPEN) {//
          methods.startServerRecordVideo(uid,resolve, reject);
        }else{
          methods.stopServerRecordVideo(uid,resolve, reject)
        }
      },
      //开始服务器录像
      startServerRecordVideo(uid,resolve, reject){
        var user = userMap[uid];
        var options =
          {
            videoStreamId: user.stream.id(),
            audioStreamId: user.stream.id(),
            videoCodec: 'h264',
            audioCodec: 'aac'
          };
        var suffix=".mp4";
        //无声摄像头没有没有音频
        if (user.isExternal==1) {
          options =
            {
              videoStreamId: user.stream.id(),
              videoCodec: 'h264',
            };
          suffix=".mkv";
        }
        user.conference.startRecorder(options,
          function (resp) {
          console.log(resp.recorderId);
            methods.beginUploadFile(uid,resp.recorderId +suffix, RECORD.VIDEO, user.name, Date.now());
            methods.saveRecordFile(uid, "", resp.recorderId + suffix, RECORD.VIDEO,  user.name, Date.now(),resp.recorderId);
            user.time=Date.now();
            user.serverRecordVideo=OPEN;
            resolve&&resolve(methods.returnMsg(user,TY.RcCode.RC_CODE_S_OK));
            L.Logger.info('开始录像: ', resp);
          },
          function (err) {
            console.log(err);
            reject&&reject(user);
          }
        );
      },
      //结束服务器录像
      stopServerRecordVideo:function (uid, resolve, reject) {
        var user = userMap[uid];
        if(Date.now()-user.time<3000){
          reject&&reject(TY.RcCode.returnMsg(TY.RcCode.RECORD_LESS));
          return;
        }
        var suffix=".mp4";
        //无声摄像,格式为mkv
        if(user.isExternal==1){
          suffix=".mkv";
        }
        console.log( recordFiles[uid+"_"+RECORD.VIDEO].rid);
        user.conference.stopRecorder({recorderId: recordFiles[uid+"_"+RECORD.VIDEO].rid}, function (file, v) {
            methods.uploadFile(uid,"",recordFiles[uid+"_"+RECORD.VIDEO].rid+suffix,RECORD.VIDEO,user.name,recordFiles[uid+"_"+RECORD.VIDEO].time,function () {
              user.serverRecordVideo=CLOSE;
              resolve&&resolve(methods.returnMsg(user,TY.RcCode.RC_CODE_S_OK));
            },function (resp) {
              reject&&reject(methods.returnMsg(user,TY.RcCode.getRcCode(resp.status)));
            });
            L.Logger.info('结束录像: ', file);
          }, function (err) {
            //vue.alert.error("设备"+name+"结束录像失败");
            var result = TY.RcCode.returnMsg(TY.RcCode.OTHER_ABNORMAL);
            result.user = user;
          reject&&reject(result);
            // vue.alert.error("设备" + name + "结束录像失败");
            L.Logger.error('Media recorder cannot stop with failure: ', err,user.rid);
          }
        );
      },
      beginUploadFile(id, fileName, type, name, startTime) {//开始录音/录像,保存到服务器
        TY.compositePost(TY.URL.beginUploadFile, {
          "id": id,
          "name": fileName,
          "type": type,
          "begintime": startTime,
        });
      },

      /**
       * 服务器抓拍
       * @function  screenshot 服务器抓拍
       * @param el
       * @param {Obj.<server_params>} uid -必选 
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject - 可不选 失败的回调
      */


        screenshot (uid,resolve, reject) {
        var user = userMap[uid];
        var el=document.getElementById("stream"+user.stream.id());
        var canvas = document.createElement("canvas");
        var video = el;
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        var canvasCtx = canvas.getContext("2d");
        canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, video.videoWidth, video.videoHeight);
        var dataUrl = canvas.toDataURL("image/png");
        var fileName =  ".jpg";
        // var time = moment().format("YYYY-MM-DD HH:mm:ss");
        methods.uploadFile(uid, dataUrl, fileName, RECORD.PICTURE, user.name, Date.now(),function () {
          resolve&&resolve(methods.returnMsg(user,TY.RcCode.RC_CODE_S_OK));
        },function (resp) {
          reject&&reject(TY.RcCode.returnMsg(TY.RcCode.getRcCode(resp.status)));
        });
      },
      uploadFile (uid, file, fileName, type, name, startTime, callback,reject) {
        TY.compositePost(TY.URL.uploadImg, {
          "id": uid,
          "file": file,
          "name": fileName,
          "type": type,
          "begintime": startTime,
          "endtime": Date.now()
        }, null,function (resp, success) {
          methods.removeRecordFile(uid,type);
          callback&&callback();
        },reject);
      },
      //设备录音

     /**
      * serverRecordAudio|serverRecordVideo(本地录音/录像状态切换,本地拍照)
      * 输入参数
 * @typedef device_params
 * @property {Number} id -必选 监控成员id
 * @property {Function} resolve - 必选成功的回调
 * @property {Function} reject - 可不选 失败的回调
 */

     /**
   * 远程录音状态切换
   * @function deviceRecordAudio 远程录音状态切换
   * @param {Obj.<device_params>} options -必选 
   * @param {Function} resolve - 必选 成功的回调
   * @param {Function} reject - 可不选 失败的回调
   */






      deviceRecordAudio(options,resolve, reject){
        reject=reject?reject:resolve;
        var user = userMap[options.id];
        TY.DeviceManager.remoteControl({id:[options.id],recordAudio:user.deviceRecordAudio!=OPEN?OPEN:CLOSE},function (resp) {
          user.deviceRecordAudio =user.deviceRecordAudio==OPEN?CLOSE:OPEN;
          resolve&&resolve(methods.returnMsg(user,TY.RcCode.RC_CODE_S_OK));
        },function (resp) {
          reject&&reject(methods.returnMsg(user,TY.RcCode.OTHER_ABNORMAL));
        });
      },
      //设备录像

    /**
   * 远程录像状态切换
   * @function  deviceRecordVideo 远程录像状态切换
   * @param {Obj.<device_params>} options -必选 
   * @param {Function} resolve - 必选 成功的回调
   * @param {Function} reject - 可不选 失败的回调
   */

      deviceRecordVideo(options,resolve, reject){
        reject=reject?reject:resolve;
        var user = userMap[options.id];
        TY.DeviceManager.remoteControl({id:[options.id],recordVideo:user.deviceRecordVideo!=OPEN?OPEN:CLOSE},function () {
          user.deviceRecordVideo =user.deviceRecordVideo==OPEN?CLOSE:OPEN;
          resolve&&resolve(methods.returnMsg(user,TY.RcCode.RC_CODE_S_OK));
        },function (resp) {
          resp.user = user;
          reject&&reject(resp);
        });
      },
      //设备抓拍

     /**
   * 远程录音抓拍
   * @function devicePicture 远程录音抓拍
   * @param {Obj.<device_params>} options -必选 
   * @param {Function} resolve - 必选 成功的回调
   * @param {Function} reject - 可不选 失败的回调
   */


      devicePicture(options,resolve, reject){
        reject=reject?reject:resolve;
        var user = userMap[options.id];
        TY.DeviceManager.remoteControl({id:[options.id],capture:OPEN},function (resp) {
          console.log(resp);
          resolve&&resolve(methods.returnMsg(user,TY.RcCode.RC_CODE_S_OK));
        },function (resp) {
          console.log(resp);
          reject&&reject(methods.returnMsg(user,TY.RcCode.OTHER_ABNORMAL));
        });
      }
    };
    return methods;
  };

  


  TY.TalkManager=function(){//对讲类
    var el="voice_talk";//对讲渲染的div id
    var body = document.getElementsByTagName("body");
    var div = document.createElement("div");
    div.style.display='none';
    div.setAttribute("id",el);
    body[0].appendChild(div);
    var curGid=null;//当前组
    var lastGid=null;
    var  createOptions = {
      video: false,
      audio: true
    };
    var dispatched=false;//被调度
    var dispatcher=false;//主动调度
    var talking=false;//是否讲话中
    var talkingUid=null;//讲话者id
    var init=false;
    var conference=null;
    var groupList=[];
    var groupsObj ={};
    var methods= {
      /**
       * 获取当前对讲人id
       * @function getTalkingUid 获取当前对讲人id
       * @return {Number} -获取当前对讲人id
       */
      getTalkingUid(){//获取当前对讲人id
        return talkingUid;
      },

       /**
       * 是否是对讲中
       * @function isTalking 是否是对讲中
       * @return {Boolean}  -是否是对讲中
       */

      isTalking(){//是否是对讲中
        return talking;
      },

       /**
       * 是否被调度
       * @function isDispached 是否被调度
       * @return {Boolean}  -是否被调度 true被调度, false没有被调度
       */

      isDispached(){//是否被调度
        return dispatched;
      },

       /**
       * 上一个群组的id
       * @function getLastGid 上一个群组id
       * @return {Boolean}  -上一个群组的id
       */

      getLastGid () {//上一个群组id
        return lastGid;
      },

       /**
       * 获取当前群组id
       * @function getCurGid 获取当前群组id
       * @return {Boolean}  -获取当前群组id
       */

      getCurGid () {//获取当前群组id
        return curGid;
      },

      /**
       * 是否正在调度别人
       * @function isDispatcher 是否正在调度别人
       * @return {Boolean}  -是否正在调度别人
       */

      isDispatcher(){//是否正在调度别人
        return dispatcher;
      },
      getGroupInfo(params){//获取群组信息
        return groupsObj[params.id];
      },
      getGroupList(){
        return groupList;
      },

    /**
     * 查询群组参数
     * @typedef params
     * @property {Number} pageSize -每页条数 必选
     * @property {Number} pageNumber -当前页数 必选
     * @property {string} groupName -群主名称 可不选
     * @property {Number} type -0普通群组 可不选
     * @property {string} name -群主名称 可不选
     */

    /**
     * 群组列表集合
     * @typedef groups
     * @property {int} id -返回码
     * @property {string} name -返回信息
     * @property {int} unallocate -1为未分配成员集合(不能切换到这个组,00为普通群组,可以切换到这个组)
     * @property {int} type -群组类型(0为普通群组)
     */

 
   
    /**
      * @callback Function_getGroups 
      * @param {int} status -返回码
      * @param {string} msg -返回信息
      * @param {obj.<groups>} -群组列表集合
      */

    /**
     * 获取群组列表
     * @function getGroups -获取群组列表
     * @param {Function_getGroups} resolve -成功回调
     * @param {function} reject -失败回调
     */

      getGroups (params,resolve, reject) {
        var main = this;
        TY.compositeGet(TY.URL.loadTalkGroup,params,null,function (resp) {
          var isDispatch=true;
          groupsObj={};
          if(resp.intercom.length>1){
            lastGid = resp.intercom[1].id;
            for(var index in resp.intercom){//当前组
              var group = resp.intercom[index];
              groupsObj[group.id]=group;
              if(group.id==resp.id){
                isDispatch=false;
                // break;
              }
            }
          }
          if(isDispatch&&resp.id!=0){//调度状态
            dispatched=true;
            if (resp.intercom.length > 1) {//调度的话,当前组为普通组
              lastGid=resp.intercom[1].id;
            }
          }
          curGid=resp.id;
          if(params.immediateEnterGroup){
            main.switchGroup({id:curGid});
          }
          groupList=resp.intercom;
          resolve({status:TY.RcCode.RC_CODE_S_OK.code,groups:resp.intercom});
        }, reject);
        return methods;
      },
      //取消调度 {id:talkManager.getCurGid(),type:TY.GroupManager.TYPE.DISPATCH}

    /**
     * 取消调度的参数
    * @typedef params_inactiveDispatchGroup
    * @property {int} id -必选 对讲组id
    */
  
     /**
       * 取消调度
       * @function inactiveDispatchGroup 结束调度
       * @param {Obj.<params_inactiveDispatchGroup>} params 对讲组的id
       * @param {function} resolve -成功回调
       * @param {function} reject -失败回调
       */

      inactiveDispatchGroup(params,resolve, reject){
        if (params) {
          params.type = TY.GroupManager.TYPE.DISPATCH;
        }
        TY.GroupManager.deleteGroup(params,function(resp){
          curGid = null;
          dispatcher=false;
          resolve&&resolve(TY.RcCode.returnMsg(TY.RcCode.RC_CODE_S_OK));
        },function(resp){
          reject&&reject(TY.RcCode.returnMsg(TY.RcCode.getRcCode(resp.status)));
        });
      },
      //{"type":"dispatch","name":"调度",'personnel':[1]}
      //激活调度

/**
 * @typedef params_activeDispatchGroup
 * @property {array} personnel -调度成员id集合
 */

/**
 * 激活调度
 * @function activeDispatchGroup 	激活调度
 * @param {Obj.<params_activeDispatchGroup>} params 对讲组的id
 * @param {function} resolve -成功回调
 * @param {function} reject -失败回调
 */
      activeDispatchGroup (params,resolve, reject) {//激活调度组
        if (params) {
          params.type = TY.GroupManager.TYPE.DISPATCH;
          params.name="调度";
        }
        TY.GroupManager.addGroup(params,function(res){
          methods.joinRoom(res.token, function(resp){
            dispatcher=true;
            curGid = res.id;
            resp.id = res.id;
            resolve&&resolve(resp);
          },reject);
        },function(resp){
          reject&&reject(resp);
        });
      },
      //{id:id}
      switchDispatchedGroup(params,resolve, reject){//切换到被调度组
        if (!dispatcher) {//非主动调度
          lastGid=curGid;
        }
        dispatched=true;
        methods.switchGroup(params,resolve, reject);
      },
      //切换群组

       /**
        * 切换群组
        * @typedef params_switchGroup
        * @property {Number} id -必选 群组id
       */

       /**
        * @typedef RcCodeMsg
        * @property {array.<code>} status -返回状态码
        * @property {string} msg -返回信息
       */

       /**
        * @typedef rights
        * @property {string} title -标题名称
        * @property {string} url -权限路径
        * @property {int} pid -父级权限id
        * @property {int} id -权限id
       */

       /**
        * @typedef switchGroup_user
        * @property {int} id -用户id
        * @property {string} name -用户名称
        * @property {string} rtcHost -视频通讯连接地址
        * @property {Obj.<rights>} right -当前用户的权限集合
        * @property {Obj.<RcCodeMsg>} status -返回状态
       */

        /**
         * @callback Function_resolveObj
         * @param {int.<code>} status 返回码
         * @param {string} msg 返回信息
         * @param {obj.<switchGroup_user>} user 用户信息
         */

       /**
       * 切换群主
       * @function switchGroup 切换群组
       * @param {Obj.<params_switchGroup>} params -群组的id
       * @param {Function_resolveObj} resolve -成功回调
       * @param {function} reject -失败回调
       */

      switchGroup(params,resolve, reject) {//切换组
        TY.compositePost(TY.URL.changeTalkGroup,{id:params.id},function (params) {
          if(!params.id){
            return  "group id";
          }
        },function (resp) {
          methods.joinRoom(resp.token, function(resp){
            curGid = params.id;
            if(!dispatched&&!dispatcher&&curGid){//普通组的话,则上一个组
              lastGid=curGid;
            }
            dispatcher=false;
            resolve&&resolve(resp);
          },reject);
        }, reject);
      },
      //对讲状态切换

 /**
  * 对讲状态切换
  * @function changeTalkStatus 切换对讲状态(对讲申请/对讲结束)
  * @param {function} resolve -成功回调
  * @param {function} reject -失败回调
  */
      changeTalkStatus(resolve, reject){
        if (talking) {
          methods.stopTalk({id:curGid},resolve, reject);
        }else{
          methods.startTalk({id:curGid},resolve, reject);
        }
      },
      startTalk(params,resolve, reject){
        reject=reject?reject:resolve;
        TY.compositePost(TY.URL.startTalk,params,function (params) {
          if(!params.id){
            return  "group id";
          }
        },function (resp) {
          methods.publish(resolve, reject);
        }, reject);

      },
      stopTalk(params,resolve, reject){
        reject=reject?reject:resolve;
        TY.compositePost(TY.URL.stopTalk,params,function (params) {
          if(!params.id){
            return  "group id";
          }
        },function (resp) {
          methods.unPublish(resolve, reject);
        }, reject);
      },
      joinRoom(token,resolve, reject){//获得服务器许可后,进入对讲组房间内
        this.leaveRoom();
        conference =  PureRTC.ConferenceClient.create({});
        conference.streamMap={};
        conference.join(token, function (resp) {
          console.log("======================加入組成功",conference.conferenceId);
          PureRTC.LocalStream.create(
            createOptions, function (err, stream) {
              if (err) {
                console.log("麦克风不可用");
                reject&&reject(TY.RcCode.returnMsg(TY.RcCode.MICROPHONE_LIMIT));
                return;
              }
              conference.localStream = stream;
              conference.localStream.attr("uid", TY.USER.uid);
              //保存已经在线的用户信息
              Object.keys(conference.remoteStreams).forEach(function (key) {
                var s = conference.remoteStreams[key];
                console.log('============================有人进来啦',s.attributes().uid);
                conference.streamMap[parseInt(s.attributes().uid)]= s;
              });
            }
          )
          ;
          conference.streams = resp.streams;
          resolve(TY.RcCode.returnMsg(TY.RcCode.RC_CODE_S_OK));
        }, function (err) {
          reject&&reject(TY.RcCode.returnMsg(TY.RcCode.RTC_ABNOMAL));
          L.Logger.error('server connection failed:', err);
        });
        conference.on('stream-added', function (event) {
          if (event.target.myId != conference.myId) {
            return;
          }
          var stream = event.stream;
          L.Logger.info('stream added:', stream.id());
          var fromMe = false;
          for (var i in conference.localStreams) {
            if (conference.localStreams.hasOwnProperty(i)) {
              if (conference.localStreams[i].id() === stream.id()) {
                fromMe = true;
                init=true;
                break;
              }
            }
          }
          if (fromMe) {
            L.Logger.info('stream', stream.id(),
              'is from me; will not be subscribed.');
            return;
          } else {
            console.log('============================有人进来啦',stream.attributes().uid, talkingUid);
            conference.streamMap[parseInt(stream.attributes().uid)]= stream;
            if (stream.attributes().uid == talkingUid) {
              methods.subscribe(stream);
            }
          }
        });
        conference.on('stream-removed', function (event, cid) {
          if (event.target.myId != conference.myId) {
            return;
          }
          var stream = event.stream;
          delete  conference.streamMap[parseInt(stream.attributes().uid)];
        });
        conference.on('user-joined', function (event, cid) {
          if (event.target.myId != conference.myId) {
            return;
          }
          L.Logger.info('user joined:', event.user);
        });
      },
      publish:function(resolve,reject){//发布自身的流
        if (!conference||!conference.localStream) {
          reject&&reject(TY.RcCode.returnMsg(TY.RcCode.RTC_ABNOMAL));
          return;
        }
        console.log("========准备发布====",conference.localStream.id());
        conference.publish(conference.localStream, {}, function (st) {
          talking=true;
          talkingUid=st.attributes().uid;
          resolve(TY.RcCode.returnMsg(TY.RcCode.RC_CODE_S_OK));
          console.log("===============================================发布成功==" + st.id());
        }, function (err) {
          reject&&reject(TY.RcCode.returnMsg(TY.RcCode.RTC_ABNOMAL));
          L.Logger.error('publish failed:', err);
        });
      },
      unPublish:function(resolve,reject){//取消发布自身的流
        console.log("========准备取消====",conference.localStream.id());
        conference.unpublish(conference.localStream, function () {
          talking=false;
          resolve(TY.RcCode.returnMsg(TY.RcCode.RC_CODE_S_OK));
          console.log("===============================================取消发布成功==");
        }, function (err) {
          L.Logger.error('unpublish failed:', err);
          reject(TY.RcCode.returnMsg(TY.RcCode.RTC_ABNOMAL));
        });
        talkingUid=null;
      },
      listenTalk:function(uid){//听取声音
        talkingUid = uid;
        var stream = conference.streamMap[uid];
        if(stream){
          methods.subscribe(stream);
        }
      },
      subscribe:function(stream){//订阅语音流,听声音
        var options = {video: false, audio: true};
        conference.subscribe(stream, options,function () {
          stream.show(el);
          console.log("==========================================订阅成功",stream.id());
        }, function (err) {
          console.log(err);
        });
        talkingUid=null;
      },
      leaveGroup(){
        curGid=null;
        dispatched=false;
        methods.leaveRoom();
      },
      leaveRoom:function(){
        if (conference&&conference.myId) {
          conference.leave();
        }
      },
      callback:null,//对象事件注册
      addListEvent(event){
        methods.callback=event;
      },
    };
    TY.talkManager = methods;
    return methods;
  };
  TY.UserManager={
    //根据条件获取群组列表
    //{"pageSize":10,"name":"","polNo":"","pageNumber":1}


 /**
   * 人员信息 / 修改人员信息
   * @typedef User
   * @property {Number} roleld -必选 角色id
   * @property {String} departmentName -必选 部门名称
   * @property {Number} departmentId - 不是必选 部门id
   * @property {Number} id - 不是必选 人员id
   * @property {String} password -不是必选 密码
   * @property {String} name -不是必选 人员名称
   * @property {String} account -必选 人员编号(也是登录账号)
   * @property {String} describe -不是必选 描述
   * @property {String} phone -不是必选 手机号码
   * @property {String} email -不是必选 邮箱
   */

   /**
    * @callback Function_obj_result_getUserByParams
    * @param {Number} status -必选 200成功,其他失败
    * @param {obj.<User>} rows -用户信息 人脸库信息
    * @param {Number} total -必选 总条数
    */

  /**
   * @typedef params_getUserByParams 
   * @property {Number} pageSize -必选 每页条数
   * @property {Number} pageNumber -必选 当前页数(从1开始)
   * @property {string} polNo -不是必选 人员编号
   * @property {string} name -不是必选 人员名称
   */


    /**
     * 获取用户列表
     * @function  getUserByParams 获取用户列表
     * @param {Obj.<params_getUserByParams>} params -必选
     * @param {Function_obj_result_getUserByParams} resolve -成功的回调
     * @param {Function} reject -失败的回调
     */

    getUserByParams(params,resolve, reject){ //获取用户列表
      TY.compositePost(TY.URL.loadAllUser,params,null,resolve, reject);
    },
    //添加用户
    // {  roleId: "", departmentName: "", departmentId: null, id: null, name: "", account: "", describe: "", phone: '', email: ''}


     /**
   * 人员添加
   * @typedef params_addUser
   * @property {Number} id -必选 群组id
   * @property {array} personnel -必选 人员列表
   * @property {Number} type - 不是必选 0普通组,2领导组
   */

    /**
     *  人员添加 
     * @function addUser -添加群组人员
     * @param {Obj.<params_addUser>} params -必选
     * @param {Function} resolve --必选 成功的回调
     * @param {Function} reject - -可不选 失败的回调
     */

    
    addUser(params,resolve, reject){
      TY.compositePost(TY.URL.addUser,params,function (params) {
        if(!params.departmentId){
          return  "departmentId";
        }
        if(!params.account){
          return  "account";
        }
        if(!params.name){
          return  "name";
        }
      },resolve, reject);
    },


     /**
     * 人员修改 查询用户参数
     * @function modifyUser -人员修改
     * @param {Obj.<User>} params -必选
     * @param {Function} resolve -成功的回调
     * @param {Function} reject -失败的回调
     */
    modifyUser(params,resolve, reject){
      TY.compositePost(TY.URL.modifyUser,params,function (params) {
        if(!params.id){
          return  "user id";
        }
      },resolve, reject);
    },
    //删除用户


    /**
    * 人员删除
    * @typedef params_deleteUser -人员删除
    * @property {Number} id -必选 群组id
    * @property {array} personnel -必选 人员列表
    * @property {Number} type - 不是必选 0普通组,2领导组
    */

     /**
     * @function deleteUser -移除群组成员
     * @param {Obj.<params_deleteUser>} params -必选
     * @param {Function} resolve - 必选 成功的回调
     * @param {Function} reject - 可不选 失败的回调
     */

    deleteUser(params,resolve, reject){
      TY.compositePost(TY.URL.deleteUser,params,function (params) {
        if(!params.id){
          return  "id";
        }
      },resolve, reject);
    },
    //获取用户信息

       /**
   * 人员添加
   * @typedef params_getUserInfo
   * @property {Number} roleld -必选 角色id
   * @property {String} departmentName -必选 部门名称
   * @property {Number} departmentId - 不是必选 部门id
   * @property {String} name -不是必选 人员名称
   * @property {String} account -必选 人员编号(也是登录账号
   * @property {String} describe -不是必选 描述
   * @property {String} phone -不是必选 手机号码
   * @property {String} email -不是必选 邮箱
   */

/**
 * @callback Function_User  -人员信息
 * @property {Number} roleld -必选 角色id
   * @property {String} departmentName -必选 部门名称
   * @property {Number} departmentId - 不是必选 部门id
   * @property {String} name -不是必选 人员名称
   * @property {String} account -必选 人员编号(也是登录账号
   * @property {String} describe -不是必选 描述
   * @property {String} phone -不是必选 手机号码
   * @property {String} email -不是必选 邮箱
 */

    /**
     *  人员添加 查询用户参数
     * @function getUserInfo -获取人员信息
     * @param {number} id -必选 人员id
     * @param {Function_User} resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */

    getUserInfo(params,resolve, reject){
      TY.compositePost(TY.URL.getUserInfo,params,function (params) {
        if(!params.id){
          return  "id";
        }
      },resolve, reject);
    },
    //转移部门

    /**
     * params_transferUnit 转移部门的参数
     * @typedef params_transferUnit
     * @property {Number} id -必选 部门id
     * @property {array} personnel -必选 转移id列表。如[3,4,5]
 */

     /**
     * 转移部门
     * @function transferUnit -转移部门
     * @param {Obj.<params_transferUnit>} params -必选
     * @param {Function} resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */

    transferUnit(params,resolve, reject){
      TY.compositePost(TY.URL.transferDepartment,params,function (params) {
        if(!params.personnel||params.personnel.length==0){
          return  reject(TY.RcCode.returnMsg(TY.RcCode.EMPTY_PROPS,"users"));
        }
        if(!params.id){
          return  "departmentId";
        }
      },resolve, reject);
    },
    //修改密码
    modifyPwd(params,resolve, reject){
      TY.compositePost(TY.URL.modifyPwd,params,function (params) {
        if(!params.id){
          return  "user id";
        }
      },resolve, reject);
    }
  };
  //文件检索管理类
  TY.FileManager={
    TYPE:{PLATFORM:0,DEVICE:1,DOCUMENT:2},
    //根据条件获取群组列表
    //{"pageSize":10,"name":"","type":"","pageNumber":1,"begintime',endtime:'',personnel:[1,2]}
    //todo personnel 数组不好

        /**
   * 获取文件参数
   * @typedef params_getFilesByParams
   * @property {Number} pageSize -必选 每页条数
   * @property {Number} pageNumber -必选 当前页码 (从1开始)
   * @property {Array} personnel -必选 人员id;列表
   * @property {Number} storageType -必选 存储平台(0平台,1设备,2我的文档)
   * @property {String} type -必选 文件类型(0视频,1图片,2音频,,3设备日志,
   * @property {String} begintime -不是必选 开始时间
   * @property {String} endtime -不是必选 结束时间
   */

    /**
     * 获取文件列表
     * @function transferUnit -获取文件列表
     * @param {Obj.<params_getFilesByParams>} params -必选
     * @param {Function} resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */

    getFilesByParams(params,resolve, reject){
      reject=reject?reject:resolve;
      TY.compositePost(TY.URL.loadFile,params,function (params) {
        if(!params.personnel){
          return  "device id";
        }
        params.type=params.type?params.type:9;
        params.personnel=[params.personnel];
      },resolve, reject);
    },
    //删除文件
    // { {id: 1 array: [{id: 2, storageType: row.storageType, type: row.type}]}



     /**
   * file
   * @typedef file
   * @property {Number} id -必选 人员id
   * @property {Number} storageType -必选 存储平台(0平台,1设备,2我的文档)
   * @property {Number} type -必选 文件类型(0视频,1图片,2音频,3设备日志,9全部)
   */


  /**
   * 删除文件参数
   * @typedef params_deleteFile
   * @property {Number} id - 必选 人员id
   * @property {Array.<file>} array -必选 删除文件集合
   */


     /**
     * 删除文件
     * @function deleteFile -删除文件
     * @param {array.<params_deleteFile>} params -必选
     * @param {Function} resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */

    deleteFile(params,resolve, reject){
      TY.compositePost(TY.URL.deleteFile,params,function (params) {

        // if(!params.id){
        //     return  "device id";
        // }
        if(!params.array||params.array.length==0){
          return  "files";
        }
        for(var file in params.array){
          if(file.storageType==TY.FileManager.TYPE.DEVICE){
            return TY.RcCode.DEVICE_FILE_DELETE_LIMIT;
          }
        }
      },resolve, reject);
    },
    //{"id":1281,"did":535,"type":2,"storageType":0}

     /**
   * 下载文件 输入参数
   * @typedef params_downloadFile
   * @property {Number} id - 必选 人员id
   * @property {Number} storageType -必选 存储平台(0平台,1设备,2我的文档
   * @property {Number} type -必选 文件类型(0视频,1图片,2音频,3设备日志,9全部)
   * @property {Number} did - 必选 人员id
   */


     /**
     * 下载文件
     * @function downloadFile -下载文件
     * @param {array.<params_downloadFile>} params -必选
     * @param {Function} resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */

    downloadFile(params,resolve, reject){
      // // {"id":null,"array":[{"id":1282,"storageType":0,"type":0}]}
      // var options={};
      // options.id = params.puid;
      // options.array = [{id:params.id,storageType:params.storageType,type:params.type}];
      // params=options;
      // var paramscompositeDown = {"id":1281,"did":535,"type":2,"storageType":0};
      TY.compositeDown(TY.URL.downloadFile,params,function (params) {
        if(!params.id){
          return  "file id";
        }
        if(!params.did){
          return  "device id";
        }
        if(params.storageType==null){
          return  "storageType id";
        }
        // if(!params.array||params.array.length==0){
        //     return  "files";
        // }
      },resolve, reject);
    },

     /**
      * impFileObj
   * @typedef impFileObj
   * @property {Number} id - 文件id
   * @property {Number} type -文件类型(0视频,1图片,2音频,3设备日志)
   */

      /**
   * impFile
   * @typedef impFile
   * @property {Number} type - 必选 1日志文件,0其他文件
   * @property {Array.<impFileObj>} files -批量导入文件集合
   */

     /**
   * 导入文件参数
   * @typedef params_importFile
   * @property {Number} id - 必选 人员id
   * @property {Array.<impFile>} files -批量导入文件集合
   */


    /**
     * 导入文件
     * @function importFile -导入文件
     * @param {array.<params_importFile>} options -必选
     * @param {Function}  fn1 - 必选 成功的回调
     * @param {Function} fn2 -可不选 失败的回调
     */

    importFile: function (options, fn1, fn2) {
      TY.compositePost(TY.URL.importFile, options, (options) => {
        if(!options.id){
          return 'id';
        }
        if(!options.files){
          return 'files';
        }
        if(options.files && typeof options.files == 'string'){
          options.files = JSON.parse(options.files);
        }
      }, fn1, fn2);
    }
  };
  //群组类
  TY.GroupManager={
    TYPE:{NORMAL:0,LEADER:1,DISPATCH:2},//普通组,领导组,调度组
    //添加群组

  /**
   * 人员添加
   * @typedef params_addGroup_Or_ModifyGroup -添加或修改群组参数
   * @property {String} id - 可不选 群组id(空为添加,否则为修改)
   * @property {String} name -可不选 群组名称
   * @property {Number} type -必选 0是普通组
   * @property {String} describe -必选 描述
   */

    /**
     * 群组添加
     * @function addGroup -群组添加
     * @param {array.<params_addGroup_Or_ModifyGroup>} params -必选
     * @param {Function}  resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */

    addGroup(params,resolve, reject){
      var url =TY.URL.modifyGroup,params;
      if(params.type==TY.GroupManager.TYPE.DISPATCH){
        url= TY.URL.createDispatch;
        params.personnel=params.personnel&&params.personnel.length>0?params.personnel:[TY.USER.uid];
      }
      TY.compositePost(url,params,function (params) {
        if(params.type==null){
          return  "group type";
        }
        if(!params.name){
          return  "name";
        }
      },resolve, reject);
    },
    //删除群组

    /**
   * 删除群组
   * @typedef params_deleteGroup 
   * @property {Number} id -必选 群主id
   */

    /**
     * 删除群组
     * @function deleteGroup -删除群组
     * @param {array.<params_deleteGroup>} params -必选
     * @param {Function}  resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */

    deleteGroup(params,resolve, reject){
      var url =TY.URL.deleteGroup,params;
      if(params.type==TY.GroupManager.TYPE.DISPATCH){
        url= TY.URL.stopDispatch;
      }
      TY.compositePost(url,params,function (params) {
        if(!params.id){
          return  "group id";
        }
      },resolve, reject);
    },
    //修改群组信息



    /**
     * 修改群组信息
     * @function modifyGroup -修改群组信息
     * @param {array.<params_addGroup_Or_ModifyGroup>} params -必选
     * @param {Function}  resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */
    modifyGroup(params,resolve, reject){
      TY.compositePost(TY.URL.modifyGroup,params,function (params) {
        if(!params.id){
          return  "group id";
        }
        if(params.type==null){
          return "group type";
        }
        if(!params.name){
          return  "name";
        }
      },resolve, reject);
    },
    //群组信息

    /**
     * @callback Function_group_obj
     * @param {Number} id -必选 群组id
     * @param {String} name -可不选 群组名称
     * @param {Number} type -可不选 0普通组
     * @param {Number} leaderld -可不选 创建人员id
     * @param {Number} leader -可不选 创建人员名称
     * @param {String} time -可不选 创建时间
     * @param {String} desc -可不选 描述
     * @param {Number} status - 可不选 200成功,具体查看TY.RcCode
     */

     /**
     * 获取群组信息
     * @function getGroupInfo -获取群组信息
     * @param {Number} id -必选 群组信息
     * @param {Function_group_obj}  resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */

    getGroupInfo(params,resolve, reject){
      TY.compositePost(TY.URL.groupInfo,params,function (params) {
        if(!params.id){
          return  "group id";
        }
      },resolve, reject);
    },
    // (群组列表)

    /**
    * 群组信息
    * @typedef Group 群组信息
    * @property {Number} id - 必选 人员id
    * @property {String} name - 不是必选 群组名称
    * @property {Number} type - 不是必选 0普通组
    * @property {Number} leaderId - 不是必选 创建人员id
    * @property {Number} leader - 不是必选 创建人员名称
    * @property {String} time - 不是必选 创建时间
    * @property {String} desc - 不是必选 描述
    */

    /**
     * @callback Function_obj_group_list
     * @param {Number} status - 必选 200成功,其他失败
     * @param {Obj.<Group>} rows -必选 群组列表数据
     * @param {Number} total -必选 总条数
     */


    /**
     * 查询群组列表
     * @function getGroupsByParams -查询群组列表
     * @param {array.<params>} params -必选
     * @param {Function_obj_group_list}  resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */


    getGroupsByParams(params,resolve, reject){
      TY.compositePost(TY.URL.loadGroupList,params,null,resolve, reject);
    },
    //分配群组成员

      /**
       * 分配群组参数
    * @typedef params_allocateGroup
    * @property {Number} id - 必选 人员id
    * @property {Array} devicelds - 不是必选 人员id列表(疑问)
    * @property {Number} type - 不是必选 0普通组
    */

    /**
     * 分配群组成员
     * @function allocateGroup -分配群组成员
     * @param {array.<params_allocateGroup>} params -必选
     * @param {Function}  resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */
    allocateGroup(params,resolve, reject){
      TY.compositePost(TY.URL.loadGroupUsers,params,function (params) {
        if(!params.id){
          return  "group id";
        }
      },resolve, reject);
    },
    //添加群组成员


    addUser(params,resolve, reject){
      var url =TY.URL.removeGroupUser;
      if(params.type==TY.GroupManager.TYPE.DISPATCH){
        url= TY.URL.addDispatchUser;
      }
      TY.compositePost(url,params,function (params) {
        if(!params.personnel||params.personnel.length==0){
          return  "users";
        }
        if(!params.id){
          return  "group id";
        }
      },resolve, reject);
    },
    //移除群组成员
    deleteUser(params,resolve, reject){
      var url =TY.URL.removeGroupUser;
      if(params.type==TY.GroupManager.TYPE.DISPATCH){
        url= TY.URL.removeDispatchUser;
      }
      TY.compositePost(url,params,function (params) {
        if(!params.personnel||params.personnel.length==0){
          return  "users";
        }
        if(!params.id){
          return  "group id";
        }
        if(params.type==TY.GroupManager.TYPE.DISPATCH){
          params.room = params.id;
          delete params['id'];
        }

      },resolve, reject);
    },
    //群组成员列表

     

     /**
     * 群组成员列表  返回值:组内群组成员列表
     * @function getAllocateUsersByParams -群组成员列表
     * @param {Number} id -必选 群组id
     * @param {Function_obj_groupUser} resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */



    getAllocateUsersByParams(params,resolve, reject){
      TY.compositePost(TY.URL.loadGroupUser,params,function (params) {
        if(!params.id){
          return  "group id";
        }
      },resolve, reject);
    },
    //组内未分配到的群组成员列表

    /**
    * members -组内成员列表
    * @typedef Members
    * @property {String} name - 所绑定的人员名称
    * @property {String} puid -设备编号
    * @property {Number} id -人员id
    */

    /**
     * 分配群组参数
     * @callback Function_obj_groupUser
     * @param {Number} status -200成功,具体查看TY.RcCode
     * @param {Obj.<Members>} members -组内成员列表
     */

     /**
     * 组内未分配到的群组成员列表
     * @function getUnAllocateUsers -组内未分配到的群组成员列表
     * @param {Number} id -必选 群组id
     * @param {Function_obj_groupUser} resolve - 必选 成功的回调
     * @param {Function} reject -可不选 失败的回调
     */


    getUnAllocateUsers(params,resolve, reject){
      TY.compositePost(TY.URL.loadUnGroupUser,params,function (params) {
        if(!params.id){
          return  "group id";
        }
      },resolve, reject);
    }
  };
  TY.EventManager={
    EVENTS:{
      SOS:'sos',
      SUBSCRIBE_GPS:'gps',
      BEFORE_REQUEST:'beforeRequest',
      HEART:"heart",
      MEDIA:'mediaStatus',
      UPDATE_CERT: 'updateCert',
      FILE_PROGRESS: 'downProgress',
      FILE_IMPORT: 'importProgress',
      SOMEONE_LOGIN: 'someoneLogin',
      SOMEONE_TALKING:"talked",//某人在对讲
      SOMEONE_STOP_TALKING:"stopTalked",//某人在对讲
      START_DISPATCHED:"dispatched",//某人在对讲
      STOP_DISPATCHED:"stopDispatched",//某人在对讲
      SOCKET_INIT:"socketInit",//socket初始化
      DISCONNECT:"disconnect",//某人在对讲
      OFFLINE:"offline",
      ONLINE:"online",
    },
    callbacks:{},
    addEventListener(event,callback){
      this.callbacks[event] = callback;
    },
    removeEventListener(event){
      delete this.callbacks[event];
    },
  },
    TY.DeviceManager = (function () {
      var validation = function (options,fn1,fn2) {
        if (!options || typeof options != 'object') {
          return 1;
        } else if (fn1 && typeof fn1 != 'function') {
          return 2;
        } else if (fn2 && typeof fn2 != 'function') {
          return 3;
        }
        return 0;
      };
      var composite_post = function (options,url,fn1,fn2,fn){
        if(validation(options,fn1,fn2)){
          fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL));
        }
        var msg;
        if(fn && (msg = fn(options))){
          fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL,msg));
        }
        TY.reqPost(options, url, fn1, fn2);
      };
      var composite_get = function (options,url,fn1,fn2,fn){
        if(validation(options,fn1,fn2)){
          fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL));
        }
        var msg;
        if(fn && (msg = fn(options))){
          fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL,msg));
        }
        TY.reqGet(options, url, fn1, fn2);
      };
      function send(id,comm,fn){
        var socket = self._socket;
        socket.emit("config", {
          personnel: [id],
          command: comm
        }, fn);
      }
      var composite_socket = function (options,data,fn1,fn2,fn){
        if(validation(options,fn1,fn2)){
          fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL));
          return command;
        }
        var msg;
        if(fn && (msg = fn(options))){
          fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL,msg));
          return command;
        }
        send(options.id,data,function(r){
          if(r == false){
            fn2&&fn2(TY.RcCode.returnMsg({code:204,msg:"设备不在线"}));
          }else if(r == 1){
            fn1&&fn1(TY.RcCode.returnMsg({code:200,msg:"操作成功"}));
          }else if(r == 2){
            fn2&&fn2(TY.RcCode.returnMsg(TY.RcCode.RECORD_LESS));
          }else if(r == 0){
            fn1&&fn1(TY.RcCode.returnMsg({code:500,msg:"操作失败"}));
          }else if(r == -1){
            fn1&&fn1(TY.RcCode.returnMsg({code:204,msg:"警号已存在"}));
          }else if(typeof (r)=='object'){
            var result = TY.RcCode.returnMsg({code:200,msg:"操作成功"});
            result.data = r;
            fn1&&fn1(result);
          }else{
            fn1&&fn1({code:200,data:r});
          }
        });
      };
      var command = {
        callback:null,
        addListEvent(event){//添加通知事件事件
          command.callback=event;
        },

     /**
     * 获取设备信息 返回参数
     * @typedef getDeviceInfo -获取设备信息
     * @property {String} DevNo -设备编号
     * @property {String} name -  名称
     * @property {int} type - 设备类型
     * @property {String} desc -备注
     * @property {int} imei -唯一imei
     * @property {String} MAC -网卡地址
     * @example
     *   {
      DevNo:'dv_2312',          //设备编号
      name :'小明',             //名称
      type :1,                  //设备类型
      desc :'前台摄像头',        //备注
      imei :589523658521662,    //唯一imei
      MAC  :'7c-E9-f5-56-48-95' //网卡地址
  }
     */

        /**
     * 获取设备信息 输入参数
     * @function getDeviceInfo -获取设备信息
     * @param {int} id -必选 设备id
     * @example
     *  {id:1, 设备id }; 
     * @param {Function__obj} resolve -成功的回调
     * @param {Function_RcCode} reject -失败的回调
     * @param {obj.<getDeviceInfo>} ... -返回参数
     */

        getDeviceInfo(options,fn1,fn2){
          TY.compositePost(TY.URL.DeviceManager.deviceInfo,options,function (params) {
            if(!params.id){
              return  "device id";
            }
          },fn1, fn2);
        },

       

    /**
    * options 修改设备 
    * @typedef options_modifyDevice
    * @property {int} id -设备id 
    * @property {String} name -设备信息
    * @property {int} type -设备类型
    * @property {String} model -形容
    * @property {String} adress -设备地址
    * @property {String} desc -备注
    * @example
    *   {
      id:1,         //设备id
      name:'小明',  //设备名称
      type:1,       //设备类型
      model:'T5',   //形容
      adress:'xxx', //设备地址
      desc:'备注'   //备注
  }
    */

 /**
     * @callback Function__obj
     * @param {array.<code>} status -返回码
     * @param {string} msg -返回信息
     * @param {object} ... 其他(不一定有)
     */


     /**
     * 修改设备 输入参数
     * @function modifyDevice -修改设备 
     * @param {Obj.<options_modifyDevice>} options 
    * @param {Function__obj} resolve -成功的回调
     * @param {Function_RcCode} reject -失败的回调
     * @return {Obj.<code>} status - 返回参数 返回码
     * {
      "status": 200
    }
     */

    
    
     
        modifyDevice(options,fn1,fn2){
          TY.compositePost(TY.URL.DeviceManager.modifyDevice,options,function (params) {
            if(!params.id){
              return  "device id";
            }
            if(options.type==0&&(!options.imei || options.imei.length != 15)){
              return 'imei not found';
            }
          },fn1, fn2);
        },

           /**
    * options 设备注册 
    * @typedef options_addDevice
    * @property {int} imei -唯一imei
    * @property {String} password -密码
    * @property {String} desc -备注
    * @property {String} adress -地址
    * @property {int} type -设备类型
    * @property {String} name -设备名称
    * @property {String} model -设备描述
    * @example
    *    {
      imei:589658446554, //唯一imei
      password:'123',    //密码
      desc:'备注',       //备注 
      address:'23132',   //地址
      type:1,            //设备类型
      name:'设备1',      //设备名称
      model:'T5'         //设备描述
  }
    */

     /**
     * 设备注册 输入参数
     * @function addDevice -设备注册 输入参数
     * @param {Obj.<options_addDevice>} options 
     * @param {Function__obj} resolve -成功的回调
     * @param {Function_RcCode} reject -失败的回调
     * @return {Obj.<code>} status - 返回参数 返回码
     * {
      "status": 200
    }
     */

      

        addDevice:function(options,fn1,fn2){//设备注册
          composite_post(options,TY.URL.DeviceManager.addDevice,fn1,fn2,function(options){
            if(options.type==0&&(!options.imei || options.imei.length != 15)){
              return 'imei not found';
            }
            if(!options.password){
              return 'password not found';
            }
            return 0;
          });
          return command;
        },

         /**
     * 设备删除 输入参数
     * @function deleteDevice -设备删除 
     * @param {int} options -id设备id
     * @example
     * {
      id:1, //设备id
      }
     * @param {Function__obj} resolve -成功的回调
     * @param {Function_RcCode} reject -失败的回调
     * @return {Obj.<code>} status - 返回参数 返回码
     * {
      "status": 200
    }
     */

        deleteDevice:function(options,fn1,fn2){//设备删除
          composite_post(options,TY.URL.DeviceManager.deleteDevice,fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
            return 0;
          });
          return command;
        },



    /**
    * options 条件搜索设备列表 
    * line,puid,name,enalbe,sortable它们是and的关系,如果不传表示不加
    * @typedef options_getDevicesByParams 
    * @property {int} line -在线状态
    * @property {String} puid -设备编号
    * @property {String} name -设备名称
    * @property {int} enable -是否启用
    * @property {int} sortable -0平台/1设备/2本地
    * @property {int} pageSize -页面大小
    * @property {int} pageNumber -当前页
    * @property {String} deviceName -名称
    * @example
    *     {
      line:1,            //在线状态
      puid:'pu_2312',    //设备编号
      name:'小明',       //设备名称
      enable:1,          //是否启用
      sortable:1,        //0 平台/ 1 设备/ 2 本地
      pageSize:10,       //页面大小
      pageNumber:1,      //当前页
      deviceName:'设备2' //名称
  }
    */


     /**
    * 返回的参数
    * @typedef getDevicesByParams 
    * @property {int} total -数据总条数
    * @property {object} rows -数据对象
    * @property {int} rows.pid -设备id
    * @property {int} rows.id -用户id
    * @property {String} rows.name -用户名称
    * @property {String} rows.userSn -用户编号
    * @property {int} rows.type -设备类型
    * @property {String} rows.puid -设备编号
    * @property {String} rows.status -设备状态 0离线,1在线,2禁止
    * @property {String} rows.deviceName -设备名称
    * @property {String} rows.SN -设备产品序号
    * @property {String} rows.MAC -网卡地址
    * @property {String} rows.language -语言
    * @property {String} rows.line 在线状态
    * @property {String} rows.groupName -当前组名称
    * @property {String} rows.departmentId -部门id
    * @property {String} rows.departmentName -部门名称
    * @property {String} rows.password -设备密码
    * @property {String} rows.longitude -当前Ing
    * @property {String} rows.latitude -当前lat
    * @example
    *     {
      rows:[
          {
              pid:1,                   //设备id
              id:1,                    //用户主键id
              name:'小明',             //用户名称
              userSn:'oopac',          //用户编号
              type:1,                  //设备类型
              puid:'pu_75682',         //设备编号
              status:1,                //设备状态 0 离线,1 在线,2 禁止
              deviceName:'设备1',      //设备名称
              SN:'adlliw',             //设备产品序号
              MAC:'7c-E9-f5-56-48-95', //网卡地址
              language:'cn',           //语言
              line:1,                  //在线状态
              groupName:'对讲1',       //当前组名称
              departmentId:1,          //部门id
              departmentName:'主部门', //部门名称
              password:'111111',       //设备密码
              longitude:153.202301,    //当前lng
              latitude:23.165247       //当前lat
          }
      ],
      total:230
  }
    */

      /**
     * 条件搜索设备列表 输入参数
     * @function getDevicesByParams -条件搜索设备列表 输入参数
     * @param {Obj.<options_getDevicesByParams>} options -id设备id
    * @param {Function__obj} resolve -成功的回调
     * @param {Function_RcCode} reject -失败的回调
     * @param {Obj.<getDevicesByParams>}  -返回参数
     */

        getDevicesByParams:function(options,fn1,fn2){//条件搜索设备列表 options=>line,puid,name,pageSize,pageNumber,enable,sortable
          composite_post(options,TY.URL.DeviceManager.getDevicesByParams,fn1,fn2);
          return command;
        },

    /**
     * 获取设备扫码换服务器IP地址 返回值
    * @typedef getScanUrl 返回值
    * @property {int.<code>} status -在线状态
    * @property {String} msg -返回信息
    * @property {object} ... -其他(不一定有)
    
    */

       /**
     * 获取设备扫码换服务器IP地址
     * @function getScanUrl - 获取设备扫码换服务器IP地址
     * @param options -参数无
    * @param {Function__obj} resolve -成功的回调
     * @param {Function_RcCode} reject -失败的回调
     * @param {Obj.<getScanUrl>}  -返回参数
     */

        getScanUrl:function(options,fn1,fn2){//获取设备扫码换服务器IP地址
          if(typeof options == 'function' && !fn2){ fn2 = fn1;fn1 = options;options = {}}
          composite_get(options,TY.URL.DeviceManager.getScanUrl,fn1,fn2);
          return command;
        },

        /**
     * 获取绑定设备的二维码地址 返回参数
    * @typedef getBindedScanUrl 返回值
    * @property {String} url -二维码地址
    * @example
    * {
      url:'https://baidu:3005' //二维码地址
  }
    
    */

     /**
     * 获取绑定设备的二维码地址 输入参数
     * @function getBindedScanUrl - 获取绑定设备的二维码地址
     * @param {int} id -设备id
     * @example
     *  {
      id:1 //设备id
     }
     * @param {Function__obj} resolve -成功的回调
     * @param {Function_RcCode} reject -失败的回调
     * @param {Obj.<getBindedScanUrl>} ... -返回参数
     */
        getBindedScanUrl:function(options,fn1,fn2){//获取绑定设备的二维码地址
          composite_post(options,TY.URL.DeviceManager.getBindedScanUrl,fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
            return 0;
          });
          return command;
        },

       /**
    * 未绑定设备列表 
    * @typedef options_loadBindUser 
    * @property {int} id -设备id
    * @property {String} PolNo -人员编号
    * @property {String} DevNo -设备编号
    * @property {int} line -在线状态
    * @property {int} bound -绑定状态
    * @property {String} name -人员名称
    * @property {int} pageSize -行数
    * @property {int} pageNumber -页数
    * @property {String} sortable -排序(参数列值)
    * @example
    *  {
      id:1,            //设备id
      PolNo:'pu_2312', //人员编号
      DevNo:'au_2312', //设备编号
      line:1,          //在线状态
      bound:1,         //绑定状态
      name:'小明',     //人员名称
      pageSize:10,     //行数
      pageNumber:1,    //页数
      sortable:'name'  //按照名称排序
      }
    */
      
         /**
    * 未绑定设备列表 返回值
    * @typedef loadBindUser 返回值
    * @property {int} id -人员id
    * @property {String} polNo -人员编号
    * @property {String} devNo -设备编号
    * @property {String} phone -电话
    * @property {String} email -邮箱
    * @property {int} departmentId -部门id
    * @property {String} department -部门名称
    * @property {int} admin -是否管理
    * @property {int} line -在线状态
    * @property {String} name -人员名称
    * @property {String} sn -产品序列
    * @property {int} roleld -角色id
    * @property {String} roleName -角色名称
    * @example
    *     [
      {
          id:1,                      //人员id
          devNo:'pu_1654656',        //设备编号
          polNo:'ac1cq12',           //人员编号
          phone:15612354892,         //电话
          email:'192.365@163.com',   //邮箱
          departmentId:1,            //部门id
          department:'主部门',       //部门名称
          admin:1,                   //是否管理
          line:1,                    //在线状态
          name:'小明',               //人员名称
          sn:'121213328345655',      //人员编号
          roleId:1,                  //角色id
          roleName:'调度员'          //角色名称           
      },
      ...
  ]
    */

      /**
     * 未绑定设备列表 输入参数
     * @function loadBindUser - 未绑定设备列表
     * @param {Obj.<options_loadBindUser>} options -输入参数
     * @param {Function__obj} resolve -成功的回调
     * @param {Function_RcCode} reject -失败的回调
     * @param {Obj.<loadBindUser>} ... -返回参数
     */

        loadBindUser:function(options,fn1,fn2){//未绑定设备列表 =>name: "",pageNumber: 1,pageSize: 10,puid: ""
          composite_post(options,TY.URL.DeviceManager.loadBindUser,fn1,fn2);
          return command;
        },

    /**
    * 未绑定设备列表 返回值
    * @typedef getUnBindedDevice 返回值
    * @property {String} name -名称
    * @property {int} id -用户id
    * @property {String} puid -设备编号
    * @example
    *    {
      name:'pu_设备1', //名称 
      id:1, 
      puid:'pu_2312' 
  }
    */
      
      /**
     * 未绑定设备列表 输入参数
     * @function getUnBindedDevice - 未绑定设备列表
     * @param {String} name -设备名称
     * @param {String} puid -设备编号
     * @param {int} pageSize -行数
     * @param {int} pageNumber 页数
     * @param {String} sortable -排序(参数返回的列名)
     * @param {Function__obj} resolve -成功的回调
     * @param {Function_RcCode} reject -失败的回调
     * @example
     *   {
      name:'pu_设备1', //设备名称
      puid:'pu_2312',  //设备编号
      pageSize:1,      //行数
      pageNumber:1,    //页数
      sortable:'name'  //排序
  }
     * @param {Obj.<getUnBindedDevice>} ... -返回参数
     */

        getUnBindedDevice:function(options,fn1,fn2){//未绑定设备列表 =>name: "",pageNumber: 1,pageSize: 10,puid: ""
          composite_post(options,TY.URL.DeviceManager.getUnBindedDevice,fn1,fn2);
          return command;
        },

     /**
    * 未分配的设备列表 返回参数
    * @typedef getUnAllocatedDevice 
    * @property {String} name -人员名称
    * @property {int} id -用户id
    * @example
    *    {
      name:'小明', //人员名称 
      id:1,        //设备id
  }
    */

        /**
     * 未分配的设备列表 输入参数
     * @function getUnAllocatedDevice - 未分配的设备列表
     * @param {int} id -设备id
     * @example
     *  {
      id:1 //设备id
     }
    *@param {Function__obj} resolve -成功的回调
     * @param {Function_RcCode} reject -失败的回调
     * @param {Obj.<getUnAllocatedDevice>} ... -返回参数
     */

        getUnAllocatedDevice:function(options,fn1,fn2){//未分配的设备列表 => id: 411
          composite_post(options,TY.URL.DeviceManager.getUnAllocatedDevice,fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
            return 0;
          });
          return command;
        },

     /**
    * 已分配的设备列表 返回参数
    * @typedef getAllocatedDevice  返回参数
    * @property {String} name -人员名称
    * @property {int} id -人员id
    * @example
    *    {
      name:'小明', //人员名称 
      id:1,        //设备id
  }
    */

      /**
     * 已分配的设备列表 输入参数
     * @function getAllocatedDevice - 已分配的设备列表
     * @param {int} options -设备id
     * @example
     *  {
      id:1 //设备id
     }
     * @param {Function__obj} resolve -成功的回调
     * @param {Function_RcCode} reject -失败的回调
     * @param {Obj.<getAllocatedDevice>} ... -返回参数
     */

        getAllocatedDevice:function(options,fn1,fn2){//已分配的设备列表 => id: 411
          composite_post(options,TY.URL.DeviceManager.getAllocatedDevice,fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
            return 0;
          });
          return command;
        },

    /**
    * 绑定设备 返回参数
    * @typedef bindDevice 返回参数
    * @property {int.<code>} status -返回码
    */

     /**
    * 绑定设备 
    * @typedef options_bindDevice  
    * @property {int} deviceld -待解绑的用户id
    * @property {int} userid -待绑定的用户id
    * @example
    * {
      deviceId:1, //待解绑的用户id
      userId:1    //待绑定的用户id
      }
    */

     /**
     * 绑定设备 输入参数
     * @function bindDevice - 绑定设备
     * @param {obj.<options_bindDevice>} options -设备id
     * @param {Function__obj} resolve -成功的回调
     * @param {obj.<Function_RcCode>} reject -失败的回调
     * @param {Obj.<bindDevice>} ... -返回参数
     */

        bindDevice:function(options,fn1,fn2){//绑定设备 => deviceId: 568,userId: 569
          composite_post(options,TY.URL.DeviceManager.bindDevice,fn1,fn2,function(options){
            if(!options.deviceId){
              return 'deviceId not found';
            }
            if(!options.userId){
              return 'userId not found';
            }
            return 0;
          });
          return command;
        },
/**
 * @typedef Function_RcCode
 * @property {} SOCKET_INIT_SUCCESS    -{code:0,msg:"初始化成功"}, //socket初始化成功
 * @property {} RC_CODE_S_OK -{code:200,msg:"请求成功"}, //请求成功
 * @property {}  ACTION_BY_BIND -{code:202,msg:"存在绑定关系,操作失败"},
 * @property {}  GROUP_NAME_SINGLE -{code:203,msg:"名称不能重复"},
 * @property {}  USER_EXIST -{code:204,msg:"已存在,不能重复"}, //请求成功
 * @property {} FENCE_BY_BIND -{code:205,msg:"围栏绑定了设备,请解绑后删除"},
 * @property {} WORK_SHFIT_BY_BIND -{code:206,msg:"班次绑定了设备,请解绑后删除"},
 * @property {}  ROLE_BY_BIND -{code:207,msg:"该角色已被用户绑定,请解绑后删除"},
 * @property {}  GROUP_BY_BIND -{code:208,msg:"群组绑定了设备,请解绑后删除"},
 * @property {}  DEPT_BY_BIND -{code:209,msg:"该部门下有人员,请删除后再次操作"},
 * @property {}  USER_CODE_EXIST -{code:210,msg:"人员编号冲突"},
 * @property {}  DEV_CODE_EXIST -{code:211,msg:"该设备识别号已注册"},
 * @property {}  APP_VERSION_EXIST -{code:212,msg:"app下已经发布了版本,请删除后再次操作"},
 * @property {}  EMPTY_TOKEN -{code:300,msg:"token为空,可能未登录"}, //请求头没有token
 * @property {}  SPECIAL_PARAMS -{code:213,msg:"参数不能包括特殊字符"}, //请求头没有token
 * @property {}  INVALID_TOKEN -{code:401,msg:"token无效"}, //请求头没有token
 * @property {}  DEV_OUTLINE -{code:406,msg:"设备不在线"},
 * @property {}  DISPATCHER_DISPATCHER -{code:407,msg:"创建调度的人在调度"},
 * @property {}  EMPTY_SERVER -{code:460,msg:"服务器地址为空"}, //请求头没有token
 * @property {}  EMPTY_PROPS -{code:461,msg:"参数不能为空"}, //请求头没有token
 * @property {}  PARAMETERS_ABNORMAL -{code:462,msg:"参数异常"},//参数异常
 * @property {}  LOGINED -{code:463,msg:"已经登录过"}, //请求头没有token
 * @property {}  DEVICE_FILE_DELETE_LIMIT -{code:463,msg:"不能删除设备文件"}, //请求头没有token
 * @property {}  MICROPHONE_LIMIT -{code:465,msg:"麦克风不可用"}, //请求头没有token
 * @property {}  RTC_ABNOMAL -{code:466,msg:"RTC异常"}, //请求头没有token
 * @property {}  OTHER_TALK -{code:467,msg:"已有人在说话"}, //请求头没有tokenNOT_SUBSCRIBE_DEVICE
 * @property {}  NOT_STREAM -{code:468,msg:"该设备不在监控房间"}, //请求头没有token
 * @property {}  NOT_SUBSCRIBE_DEVICE -{code:469,msg:"该设备不是被监控状态"}, //请求头没有token
 * @property {}  DEVICE_MONITER_EXIST -{code:470,msg:"已经邀请该设备进入监控"}, //请求头没有token
 * @property {}  RECORD_LESS -{code:471,msg:"结束失败,录制时间太短"}, //请求头没有token
 * @property {}  SYSTEM_ERROR -{code:500,msg:"系统错误"},
 * @property {}  LOGIN_FAIL -{code:410,msg:"账号密码错误"},//登录失败==》账号密码错误
 * @property {}  LOGIN_USER_FAIL -{code:411,msg:"用户不存在"},//登录失败==》用户不存在
 * @property {}  LIMIT_AUTHORITY -{code:403,msg:"没有权限"}, //权限不够,
 * @property {}  OTHER_ABNORMAL -{code:999,msg:"其他异常"}//参数
 */

 /**
     * 成功的回调函数 返回的参数
     * @callback Function_obj
     * @param {array.<code>} status -返回码
     * @param {String} msg -返回信息
     * @param {Obj.<groups>} groups -返回列表集合说
     */
    /**
    * 绑定设备 返回参数
    * @typedef unBindDevice 返回参数
    * @property {int.<code>} status -返回码
    * @example
    * {
      "status": 200
    }
    */

         /**
     * 解绑设备 输入参数
     * @function unBindDevice - 解绑设备
     * @param {int} options -待解绑的用户id
     * @example
     * {
       userId:1    //待绑定的用户id
       }
     * @param {Function__obj} resolve -成功的回调
     * @param {obj.<Function_RcCode>} reject -失败的回调
     * @param {Obj.<unBindDevice>} ... -返回参数
     */

        unBindDevice:function(options,fn1,fn2){//解绑设备
          composite_post(options,TY.URL.DeviceManager.unBindDevice,fn1,fn2,function(options){
            if(!options.userId){
              return 'userId not found';
            }
            return 0;
          });
          return command;
        },


    /**
    *  设备分配 返回参数
    * @typedef allocateDevice 返回参数
    * @property {int.<code>} status -返回码
    * @example
    * {
      "status": 200
    }
    */

             /**
     * 设备分配 输入参数
     * @function allocateDevice - 设备分配
     * @param {int} id -待移除的用户id
     * @param {Array} devicelds -移除的设备集合
     * @example
     *  {
      userId:1,               //待移除的用户id
      deviceIds:[1,2,3,4,5]   //移除的设备集合
      }
    * @param {Function__obj} resolve -成功的回调
    * @param {obj.<Function_RcCode>} reject -失败的回调
    * @param {Obj.<allocateDevice>} ... 返回参数
    */

        allocateDevice:function(options,fn1,fn2){//设备分配
          composite_post(options,TY.URL.DeviceManager.allocateDevice,fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
            if(!options.deviceIds){
              return 'deviceIds not found';
            }
            return 0;
          });
          return command;
        },
        AllocateDevice:function(options,fn1,fn2){//添加分配设备
          composite_post(options,TY.URL.DeviceManager.AllocateDevice,fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
            if(!options.deviceIds){
              return 'deviceIds not found';
            }
            return 0;
          });
          return command;
        },
        unAllocateDevice:function(options,fn1,fn2){//移除分配设备
          composite_post(options,TY.URL.DeviceManager.unAllocateDevice,fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
            if(!options.targetId){
              return 'targetId not found';
            }
            return 0;
          });
          return command;
        },

      /**
    *  设备停用 返回参数
    * @typedef disableDevice 返回参数
    * @property {int.<code>} status -返回码
    * @example
    * {
      "status": 200
    }
    */

     /**
     * 设备停用 输入参数
     * @function disableDevice - 设备停用
     * @param {int} id -待禁用的设备id
     * @example
     *  {
      id:1           //待禁用的设备id
       }
    * @param {Function__obj} resolve -成功的回调
    * @param {obj.<Function_RcCode>} reject -失败的回调
    * @param {Obj.<allocateDevice>} ... 返回参数
    */

        disableDevice:function(options,fn1,fn2){//设备停用
          composite_post(options,TY.URL.DeviceManager.disableDevice,fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
            options.status = 2;
            return 0;
          });
          return command;
        },

          /**
    *  设备启用 返回参数
    * @typedef enableDevice 返回参数
    * @property {int.<code>} status -返回码
    * @example
    * {
      "status": 200
    }
    */

     /**
     * 设备启用 输入参数
     * @function enableDevice - 设备启用
     * @param {int} id -待启用的设备id
     * @example
     *  {
      id:1           //待启用的设备id
       }
    * @param {Function__obj} resolve -成功的回调
    * @param {obj.<Function_RcCode>} reject -失败的回调
    * @param {Obj.<enableDevice>} ... 返回参数
    */
        enableDevice:function(options,fn1,fn2){//设备启用
          composite_post(options,TY.URL.DeviceManager.disableDevice,fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
            options.status = 1;
            return 0;
          });
          return command;
        },
     /**
    *  设备调拨 返回参数
    * @typedef transferDevice 返回参数
    * @property {int.<code>} status -返回码
    * @example
    * {
      "status": 200
    }
    */

     /**
     * 设备调拨 输入参数
     * @function transferDevice - 设备调拨
     * @param {int} sourceld -待转移的设备id
     * @param {int} targetld -待接受的用户id
     * @param {int[]} devicelds -设备id集合
     * @example
     *     {
      sourceId:1,           //待转移的用户id
      targetId:1,           //待接受的用户id
      deviceIds:[1,2,3]     //设备集合
        }
    * @param {Function__obj} resolve -成功的回调
    * @param {obj.<Function_RcCode>} reject -失败的回调
    * @param {Obj.<transferDevice>} ... 返回参数
    */

        transferDevice:function(options,fn1,fn2){//设备调拔
          composite_post(options,TY.URL.DeviceManager.transferDevice,fn1,fn2,function(options){
            if(!options.sourceId){
              return 'sourceId not found';
            }
            if(!options.targetId){
              return 'targetId not found';
            }
            if(!options.deviceIds){
              return 'deviceIds not found';
            }
            if(!Array.isArray(options.deviceIds)){
              return 'deviceIds not array';
            }
            return 0;
          });
          return command;
        },

            /**
    *  轨迹 返回参数
    * @typedef loadTrackGps 返回参数
    * @property {object[]} user --用户轨迹集合
    * @property {object[]} trajectorys -轨迹文件集合
    * @example
    *    {
      users:'[
          [
              {
                  uid:1,                       //用户id
                  lat:23.562688                //纬度
                  lng:127.1513513              //经度
                  time:'2019-05-52 23:50:59' //时间
              },
              {
                  uid:1,                       //用户id
                  lat:23.562688                //纬度
                  lng:127.1513513              //经度
                  time:'2019-05-52 23:51:20' //时间
              }
          ],
          [
              {...},
              {...}
          ]
      ], 
      trajectorys:[
          {
              id:1,                            //文件id  
              name:'2312',                     //人员名称 
              begintime:'2018-05-06 20:60:30', //开始时间
              endtime:'2018-05-07 20:60:30',   //结束时间
              url:'http://xxx/xxx/a.aac',      //地址
              duration:200.3,                  //视音频文件
          }
      ],        
  }
    */



     /**
     * 轨迹 输入参数
     * @function loadTrackGps - 轨迹
     * @param {int[]} personnel -用户id的集合
     * @param {String} begin -待接受的用户id
     * @param {String} end -设备id集合
     * @param {int} distance -地图距离比例
     * @example
     * {
      personnel:[1,2,3,5],          //人员id集合
      begin:'2015-06-15 06:50:20',  //待转移的用户id
      end:'2016-05-23 20:50:30',    //待接受的用户id
      distance:500                  //设备集合
      }
    * @param {Function__obj} resolve -成功的回调
    * @param {obj.<Function_RcCode>} reject -失败的回调
    * @param {Obj.<loadTrackGps>} ... 返回参数
    */

        loadTrackGps:function(options,fn1,fn2) { //轨迹
          composite_post(options, TY.URL.loadTrackGps, fn1, fn2, function (options) {
            if (!options.personnel) {
              return 'personnel not found';
            }
            if (!options.begin) {
              return 'begin not found';
            }
            if (!options.end) {
              return 'end not found';
            }
          });
          return command;
        },

    /**
    *  获取设备音视频状态 返回参数
    * @typedef getMediaInfo 返回参数
    * @property {int} id -人员id
    * @property {int} recordAudio -录音状态
    * @property {int} recordVideo -录像状态
    * @example
    *  {
      id:1,                  //人员id
      recordAudio:1,   //录音状态
      recordVideo:0    //录像状态
     }
    */

     /**
     * 获取设备音视频状态 输入参数
     * @function getMediaInfo - 获取设备音视频状态
     * @param {int} id -用户id
     * @example
     *      {
      id:212
        }
    * @param {Function__obj} resolve -成功的回调
    * @param {obj.<Function_RcCode>} reject -失败的回调
    * @param {Obj.<getMediaInfo>} ... 返回参数
    */

        getMediaInfo:function(options,fn1,fn2){//获取设备音视频状态
          composite_socket(options,{
            cmd:9
          },fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
          });
          return command;
        },

         /**
    *  修改设备密码 返回参数
    * @typedef modifyDevicePwd 返回参数
    * @property {int.<code>} status -返回码
    * @example
    * {
      "status": 200
    }
    */

     /**
     * 修改设备密码 输入参数
     * @function modifyDevicePwd - 修改设备密码
     * @param {int} id -用户id
     * @param {String} password -密码
     * @example
     *        {
      id:212,          //用户id
      password:'2312'  //密码
  }
    * @param {Function__obj} resolve -成功的回调
    * @param {obj.<Function_RcCode>} reject -失败的回调
    * @param {Obj.<modifyDevicePwd>} ... 返回参数
    */

        modifyDevicePwd:function(options,fn1,fn2){//修改设备密码
          options.id=[options.id];
          composite_socket(options,{
            cmd:18,
            setting:options
          },fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
            if(!options.password){
              return 'password not found';
            }
          });
          return command;
        },
        remoteControl:function(options,fn1,fn2){ //向设备发送远程命令
          composite_socket(options,{
            cmd:7,
            setting:options
          },fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
          });
          return command;
        },

     /**
    *  设置设备本地配置 返回参数
    * @typedef setLocal 返回参数
    * @property {int.<code>} status -返回码
    * @example
    * {
      "status": 200
    }
    */

 /**
* 设置设备本地配置
* @typedef options_setLocal -设置设备本地配置
* @property {int} id -人员id
* @property {int} autoInfrared -红外线自动
* @property {String} login -登录的ip和端口号
* @property {bool} autoLogin -自动登录
* @property {int} bluetooth 蓝牙开启
* @property {String} capture -自动抓拍名称
* @property {int} fileName -录像文件名称(0默认文件, 1设备识别号)
* @property {int} language -语言(0中文,1英文)
* @property {int} wifi -wifi开启
* @property {int} autoRecord -自动录像
* @property {int} loopSave -循环存储
* @property {int} screen -屏幕常亮
* @property {int} volume -音量大小
* @property {int} baseGps -基站定位
* @property {int} gps 开启定位
* @property {int} lte -4g开启
* @property {int} launch 开机自启动开启
* @property {int} onlineTime -设备在线时间,单位小时
* @property {int} time -0不同步 ,1同步
* @example
* {
  id:1 //人员id
  autoInfrared: 480, //红外线自动
  login: "", //登录的ip和端口号
  autoLogin: true, //自动登录
  bluetooth: 0, //自动抓拍名称 
  capture: "", //自动抓拍名称
  fileName: 0, //录像文件名称(0默认文件,1设备识别号) 
  language: 0, //语言(0中文,1英文)
  wifi: 1, //wifi开启
  loopSave: 0, //循环存储 
  screen: 1, //屏幕常亮 
  volume: 100, //音量大小 
  autoRecord: 0, //自动录像 
  baseGps: 1, //基站定位 
  gps: 1, //定位开启 
  lte: 1, //4g开启 
  launch: 1, //开机自启动开启 
  onlineTime:10, //设备在线时间,单位小时 
  time:0 //0不同步,1同步 
  } 
  */
  


     /**
     * 设置设备本地配置 输入参数
     * @function setLocal - 设置设备本地配置
    * @param {obj.<options_setLocal>} options -参数
    * @param {Function_obj} resolve -成功的回调
    * @param {obj.<Function_RcCode>} reject -失败的回调
    * @param {Obj.<setLocal>} ... 返回参数
    */
       

        setLocal:function(options,fn1,fn2){ //设置设备本地配置
          composite_socket(options,{
            cmd:0,
            setting:options.local
          },fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
          });
          return command;
        },


        /**
         * 获取设备本地配置 返回参数
         * @typedef getLocal -获取设备本地配置
         * @property {int} autoInfrared -红外线自动
         * @property {String} login -登录的ip和端口号
         * @property {bool} autoLogin -自动登录
         * @property {int} bluetooth 蓝牙开启
         * @property {String} capture -自动抓拍名称
         * @property {int} fileName -录像文件名称(0默认文件, 1设备识别号)
         * @property {int} language -语言(0中文,1英文)
         * @property {int} wifi -wifi开启
         * @property {int} autoRecord -自动录像
         * @property {int} loopSave -循环存储
         * @property {int} screen -屏幕常亮
         * @property {int} volume -音量大小
         * @property {int} baseGps -基站定位
         * @property {int} gps 开启定位
         * @property {int} lte -4g开启
         * @property {int} launch 开机自启动开启
         * @property {int} onlineTime -设备在线时间,单位小时
         * @property {int} time -0不同步 ,1同步
         * @example
         *    {
          autoInfrared: 480,  //红外线自动
          login: "",          //登录的ip和端口号
          autoLogin: true,    //自动登录
          bluetooth: 0,       //自动抓拍名称 
          capture: "",        //自动抓拍名称
          fileName: 0,        //录像文件名称(0默认文件,1设备识别号) 
          language: 0,        //语言(0中文,1英文)
          wifi: 1,            //wifi开启
          loopSave: 0,        //循环存储 
          screen: 1,          //屏幕常亮 
          volume: 100,        //音量大小 
          autoRecord: 0,      //自动录像 
          baseGps: 1,         //基站定位 
          gps: 1,             //定位开启 
          lte: 1,             //4g开启 
          launch: 1,          //开机自启动开启 
              onlineTime:10,      //设备在线时间,单位小时    
              time:0              //0不同步,1同步  
          }
         */

        /**
    * 获取设备本地配置 输入参数
    * @function getLocal - 获取设备本地配置
    * @param {int} id -人员id
    * @example
    *     {
      id:1                //人员id
  }
    * @param {Function__obj} resolve -成功的回调
    * @param {obj.<Function_RcCode>} reject -失败的回调
    * @param {Obj.<getLocal>} ... 返回参数
    */

        getLocal:function(options,fn1,fn2){ //获取设备本地配置
          composite_socket(options,{
            cmd:15
          },fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
          });
          return command;
        },

    /**
    *  设置设备媒体配置 返回参数
    * @typedef setMedia 返回参数
    * @property {int.<code>} status -返回码
    * @example
    * {
      "status": 200
    }
    */

      /**
       * 设置设备媒体配置
       * @typedef options_setMedia -设置设备媒体配置
       * @property {int} id -人员id
       * @property {int} resolution -分辨率(480为720x480,720为1280x720,1080为1920x1080)
       * @property {bool} time -是否显示时间(true显示,false不显示)
       * @property {int} text -文本信息(空串表示不显示文本信息)
       * @property {int} positionType -位置显示类型(0位置,1地址)
       * @property {int} position -叠加位置(0左上,1左下,2右上,3右下,4四周)
       * @property {int} prerecordDelay -预录时间长度(单位秒)
       * @property {int} delayRecordDelay -延录时间长度(单位秒)
       * @property {int} recordDelay -录像时间长度(单位分钟)
       * @example
       *  {
        id:1,                //人员id
		time: false,         //是否显示时间(true显示,false不显示)
		text: "",            //文本信息(空串表示不显示文本信息)
		resolution: 480,     //分辨率(480为720x480,720为1280x720,1080为1920x1080)
		positionType: 1,     //位置显示类型(0位置,1地址)
		position: 1,         //叠加位置(0左上,1左下,2右上,3右下,4四周)
		prerecordDelay: 16,  //预录时间长度(单位秒)
		delayRecordDelay: 16,//延录时间长度(单位秒)
		recordDelay: 16      //录像时间长度(单位分钟)
    }
       */

      /**
       * 设置设备媒体配置 输入参数
       * @function setMedia - 设置设备媒体配置
       * @param {obj.<options_setMedia>} options -参数
       * @param {Function__obj} resolve -成功的回调
       * @param {obj.<Function_RcCode>} reject -失败的回调
       * @param {Obj.<setMedia>} ... 返回参数
       */

        setMedia:function(options,fn1,fn2){ //设置设备媒体配置
          composite_socket(options,{
            cmd:2,
            setting:options.videoAudio
          },fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
          });
          return command;
        },

        /**
       * 获取设备媒体配置 返回参数
       * @typedef getMedia 获取设备媒体配置 返回参数
       * @property {int} resolution -分辨率(480为720x480,720为1280x720,1080为1920x1080)
       * @property {bool} time -是否显示时间(true显示,false不显示)
       * @property {int} text -文本信息(空串表示不显示文本信息)
       * @property {int} positionType -位置显示类型(0位置,1地址)
       * @property {int} position -叠加位置(0左上,1左下,2右上,3右下,4四周)
       * @property {int} prerecordDelay -预录时间长度(单位秒)
       * @property {int} delayRecordDelay -延录时间长度(单位秒)
       * @property {int} recordDelay -录像时间长度(单位分钟)
         */

        /**
       * 获取设备媒体配置 输入参数
       * @function getMedia - 获取设备媒体配置
       * @param {int} id -人员id
       * @example
       *  {
        id:1                //人员id
         }
       * @param {Function__obj} resolve -成功的回调
       * @param {obj.<Function_RcCode>} reject -失败的回调
       * @param {Obj.<getMedia>} ... 返回参数
       */

        getMedia:function(options,fn1,fn2){ //获取设备媒体配置
          composite_socket(options,{
            cmd:17
          },fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
          });
          return command;
        },

        
    /**
    *  设置设备拍照配置 返回参数
    * @typedef setPicture 返回参数
    * @property {int.<code>} status -返回码
    * @example
    * {
      "status": 200
    }
    */

    /**
       * 设置设备拍照配置 输入参数
       * @function setPicture - 设置设备拍照配置
       * @param {int} id -人员id
       * @param {int} resolution -分辨率(13为48002700,18为56323168,23为64003600,34为78084392)
       * @param {int} shots -连拍张数
       * @example
       *  {
        id:1,               //人员id
		    resolution: 13,     //分辨率(13为4800*2700,18为5632*3168,23为6400*3600,34为7808*4392)
		    shots: 16           //连拍张数
    }
       * @param {Function__obj} resolve -成功的回调
       * @param {obj.<Function_RcCode>} reject -失败的回调
       * @param {Obj.<setPicture>} ... 返回参数
       */


        setPicture:function(options,fn1,fn2){ //设置设备拍照配置
          composite_socket(options,{
            cmd:1,
            setting:options.picture
          },fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
          });
          return command;
        },


     /**
    *  获取设备拍照配置 返回参数
    * @typedef getPicture 返回参数
    * @property {int} resolution -分辨率(13为48002700,18为56323168,23为64003600,34为78084392)
    * @property {int} shots -连拍张数
    * @example
    *   {
      resolution: 13,     //分辨率(13为4800*2700,18为5632*3168,23为6400*3600,34为7808*4392)
      shots: 16           //连拍张数
      }
    */

        

        /**
       * 获取设备拍照配置 输入参数
       * @function getPicture - 获取设备拍照配置
       * @param {int} id -人员id
       * @example
       *     {
        id:1                //人员id
          }
       * @param {Function__obj} resolve -成功的回调
       * @param {obj.<Function_RcCode>} reject -失败的回调
       * @param {Obj.<getPicture>} ... 返回参数
       */


        getPicture:function(options,fn1,fn2){ //获取设备拍照配置
          composite_socket(options,{
            cmd:16
          },fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
          });
          return command;
        },

     /**
    *  获取设备剩余空间 返回参数
    * @typedef getSpace 返回参数
    * @property {int} space -内存大小(单位m)
    * @example
    *  {
      space:100  //内存大小(单位m)
      }
    */

         /**
       * 获取设备剩余空间 输入参数
       * @function getSpace - 获取设备剩余空间
       * @param {int} id -人员id
       * @example
       *     {
             id:1   //人员id
          }
       * @param {Function__obj} resolve -成功的回调
       * @param {obj.<Function_RcCode>} reject -失败的回调
       * @param {Obj.<getSpace>} ... 返回参数
       */

        getSpace:function(options,fn1,fn2){ //获取设备剩余空间
          composite_socket(options,{
            cmd:8
          },fn1,fn2,function(options){
            if(!options.id){
              return 'id not found';
            }
          });
          return command;
        },

        /**
       * 获取部门和设备成员 输入参数
       * @function loadDepartentDevice - 获取部门和设备成员
       * @param {Object} options -参数
       * @param {Function__obj} resolve -成功的回调
       * @param {obj.<Function_RcCode>} reject -失败的回调
       */

        loadDepartentDevice: function (options, fn1, fn2) {//获取部门和设备成员
          TY.compositePost(TY.URL.loadDepartentDevice, options, null, fn1, fn2);
          return command;
        }
      };
      return command;
    })();

  TY.SystemSetManager = (function () {
    var command = {
      
      /**
       * 获取系统参数
       * logo路径需要添加服务端地址前缀
       * 输入参数 params传空{}
       * @function querySystemConfig 获取系统参数
       * @param {Obj} params -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       */
      querySystemConfig: function (options, fn1, fn2) {//查询系统参数设置
        TY.reqPost(options, TY.URL.querySystemConfig, (result) => {
          //转化数据
          let fmtResult = {
            status: result.status,
            data: {
              subsystemLogo: result.data.logo,
              loginLogo: result.data.logoTop,
              loginPoster: result.data.logoLeft,
              systemNameLogo: result.data.logoRightCenter,
              copyright: result.data.copyright,
              title: result.data.title,
              scheduleNameZh: result.data.zhName_schedule,
              scheduleNameEn: result.data.enName_schedule,
              managerNameZh: result.data.zhName_manager,
              managerNameEn: result.data.enName_manager,
              platformFileIsShare: result.data.fileIsShare,
              shareFileSavePath: result.data.fileShareAfterIsRemove,
              deletePlatformFileForSharing: result.data.fileShareSavePath,
            }
          };
          if (fn1) fn1(fmtResult);
        }, fn2);
        return command;
      },

      /**
      * @typedef params_setSystemConfig
      * @property {file} logo -不是必需 子系统LOGO
      * @property {file} logoTop -不是必需 登录页LOGO
      * @property {file} logoLeft -不是必需 登录页海报
      * @property {file} logoRightCenter -不是必需 登录页平台名称
      * @property {String} copyright -不是必需 版权信息
      * @property {String} zhName_schedule -不是必需 指控子系统中文标题
      * @property {String} enName_schedule -不是必需 指控子系统英文标题
      * @property {String} zhName_manager -不是必需 管理子系统中文标题
      * @property {String} enName_manager -不是必需 管理子系统英文标题
      * @property {String} fileIsShare -不是必需 文件共享 0:否 1:是
      * @property {String} fileShareAfterIsRemove -不是必需 文件共享后立即删除 0:否 1:是
      * @property {String} fileShareSavePath -不是必需 共享文件存储位置
      */

        /**
       * 设置系统参数
       * 修改参数会先清除全部参数再重新添加,所以需要保持不变的参数也需要提交
       * 如果不修改已经上传的file, 传现有文件路径即可
       * @function setSystemConfig 设置系统参数
       * @param {Obj.<params_setSystemConfig>} params -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       */

      setSystemConfig: function (options, fn1, fn2, fn3) {//设置系统参数
        //转化数据
        let fmtOptions = {
          logo: options.subsystemLogo,
          logoTop: options.loginLogo,
          logoLeft: options.loginPoster,
          title: options.title,
          logoRightCenter: options.systemNameLogo,
          copyright: options.copyright,
          zhName_schedule: options.scheduleNameZh,
          enName_schedule: options.scheduleNameEn,
          zhName_manager: options.managerNameZh,
          enName_manager: options.managerNameEn,
          fileIsShare: options.platformFileIsShare,
          fileShareAfterIsRemove: options.shareFileSavePath,
          fileShareSavePath: options.deletePlatformFileForSharing,
        };
        TY.compositeUpload(TY.URL.setSystemConfig, fmtOptions, null, fn1, fn2, fn3);
        return command;
      },


      /**
      * @typedef params_uploadUpPackage
      * @property {file} file -上传压缩包文件流(当前只支持zip格式压缩包) file(文件流)
      */

       /**
       * 上传升级安装包
       * @function uploadUpPackage 上传升级安装包
       * @param {Obj.<params_uploadUpPackage>} params 参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       */

      uploadUpPackage: function (options, fn1, fn2, fn3) {//上传平台升级包,进行升级
        TY.compositeUpload(TY.URL.uploadUpPackage, options, null, fn1, fn2, fn3);
        return command;
      }
    };
    return command;
  })();

  
  TY.FenceManager = (function () {
    var command = {

      /**
       * 查询围栏列表 参数
      * @typedef params_getFencesByParams
      * @property {int} type -围栏类型
      * @property {String} name -围栏名称
      * @property {String} dept_name -部门名称
      * @property {int} pageNumber -当前页
      * @property {int} pageSize -行数
      * @property {int} sortable -排序的列值
      * @example
      *  {
        type:1,              //围栏类型
        name:'午饭集合',     //围栏名称
        dept_name:'研发部',  //部门名称
        pageNumber:1,        //当前页
        pageSize:10,         //行数
        sortable:'name'      //排序的列值
         }
      */

       /**
      * rows返回值
      * @typedef getFencesByParams 返回值
      * @property {array.<code>} type -返回码
      * @property {String} msg -返回信息
      * @property {int} total -总数
      * @property {object[]} rows -数据集合
      * @example
      *      {
        rows:[
            {
                id:1,                               //主键id                   
                coordinates:
                [
                    {
                        "lat": 22.56636466490955,
                        "lng": 113.94664075447638
                    },
                    ...
                ],                                  //坐标集合
                name:'麦克',                        //围栏名称
                type:1,                             //围栏类型
                create_time:'2015-05-05 20:00:10',  //创建时间
                update_time:'2015-06-05 20:00:10',  //修改时间
                dept_name:'主部门'                   //部门
            }
        ],
        total:1,
      }
      */


      /**
       * 查询围栏列表
       * @function getFencesByParams 查询围栏列表
       * @param {Obj.<params_getFencesByParams>} params 参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {Obj.<getFencesByParams>} ... row返回值
       */

      getFencesByParams: function (options, fn1, fn2) {//查询围栏列表
        TY.reqPost(options, TY.URL.getFencesByParams, fn1, fn2);
        return command;
      },
      getFenceRecordsByParams: function (options, fn1, fn2) {//查询围栏列表
        TY.reqPost(options, TY.URL.getFenceRecordsByParams, fn1, fn2);
        return command;
      },

      /**
      * object返回值
      * @typedef getFenceInfo 围栏详情 返回值
      * @property {array.<code>} type -返回码
      * @property {String} msg -返回信息
      * @property {object} ... -其他
      * @example
      *  {
        id:1,                   //主键id
        coordinates:
        [
            {
                "lat": 22.56636466490955,
                "lng": 113.94664075447638
            },
            ...
        ],                      //坐标集合
        name:'集合',            //围栏名称
        type:1,                 //围栏类型
        opid:2312,              //发起人id
       }
      */

       /**
       * 围栏详情
       * @function getFenceInfo 围栏详情
       * @param {int} id -围栏id
       * @example
       *  {
            id:1,    //围栏id
          }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {Obj.<getFenceInfo>} ... object返回值
       */

      getFenceInfo: function (options, fn1, fn2) {//围栏详情
        TY.compositePost(TY.URL.getFenceInfo, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

        /**
      * 状态返回
      * @typedef State_return 状态返回
      * @property {array.<code>} int -返回码
      * @property {String} msg -返回信息
      */

       /**
      * 设置围栏 参数
      * @typedef param_setFence 设置围栏 返回值
      * @property {String} coordinates -围栏详情
      * @property {String} name -围栏名称
      * @property {int} type -围栏类型
      * @example
      *  {
        coordinates:
        [
            {
                "lat": 22.56636466490955,
                "lng": 113.94664075447638
            },
            ...
        ], //坐标集合
        name:'集合',            //围栏名称
        type:1                 //围栏类型  1圆形 2多边形
        }
      */

        /**
       * 设置围栏
       * @function setFence 设置围栏
       * @param {obj.<param_setFence>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {Obj.<State_return>} ... 状态返回
       */

      setFence: function (options, fn1, fn2) {//设置围栏
        TY.compositePost(TY.URL.setFence, options, (options) => {
          if(!options.name){
            return 'name';
          }
          if(!options.type){
            return 'type';
          }
          if(!options.coordinates){
            return 'coordinates';
          }
        }, fn1, fn2);
        return command;
      },

      /**
       * 删除围栏
       * @function deleteFence 删除围栏
       * @param {int} id -围栏id
       * @example
       *  {
        id:1,              //围栏id
          }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {Obj.<State_return>} ... 状态返回
       */


      deleteFence: function (options, fn1, fn2) {//删除围栏
        TY.compositePost(TY.URL.deleteFence, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

       /**
       * 设别绑定到围栏
       * @function deviceBindFence 设别绑定到围栏
       * @param {int} fid -围栏id
       * @param {int[]} uids -人员id集合
       * @example
       *    {
        fid:1,              //围栏id
        uids:[1,2,3]        //人员id集合
           }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {Obj.<State_return>} ... 状态返回
       */

      deviceBindFence: function (options, fn1, fn2) {//设备绑定到围栏
        TY.compositePost(TY.URL.deviceBindFence, options, (options) => {
          if(!options.fid){
            return 'fid';
          }
        }, fn1, fn2);
        return command;
      },

       /**
      *  未绑定围栏的设备列表 返回值
      * @typedef datas_getUnBindFences 返回值 
      * @property {array.<code>} status -返回码
      * @property {String} msg -返回信息
      * @property {object[]} datas -其他
      * @example
      *    {
        datas:[
            {
                uid:1,                  //人员id
                name:'小明',            //人员名称
                user_sn:'sn_23121',     //人员编号
                isBound:1,              //是否绑定 0:未绑定 1:已绑定 2:被他人绑定
                puinfo_id:1,            //设备id
            }
        ]
     }
      */

        /**
       * 未绑定围栏的设备列表
       * @function getUnBindFences 未绑定围栏的设备列表
       * @param {int} fid -围栏id
       * @example
       *    {
             fid:1,          //围栏id
           }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {Obj.<datas_getUnBindFences>} ... 状态返回
       */

      getUnBindFences: function (options, fn1, fn2) {//未绑定围栏的设备列表
        TY.reqPost(options, TY.URL.getUnBindFences, fn1, fn2);
        return command;
      },

       /**
      *  已绑定围栏的设备列表 返回值
      * @typedef datas_getBindFences 返回值 
      * @property {array.<code>} status -返回码
      * @property {String} msg -返回信息
      * @property {object[]} datas -其他
      * @example
      *   {
        status: 200,
        datas:
        [
            {
              uid:1,        //用户id
              name:'小明'   //用户名称
            },
            {
             ...
            }
        ]
       }
      */

       /**
       * 已绑定围栏的设备列表
       * @function getBindFences 已绑定围栏的设备列表
       * @param {int} fid -围栏id
       * @example
       *    {
        fid:1,          //围栏id
           }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {Obj.<datas_getBindFences>} ... 状态返回
      */

      getBindFences: function (options, fn1, fn2) {//已绑定围栏的设备列表
        TY.compositePost(TY.URL.getBindFences, options, (options) => {
          if(!options.fid){
            return 'fid';
          }
        }, fn1, fn2);
        return command;
      }
    };
    return command;
  })();
  TY.SignManager = (function () {
    var command = {

        /**
         *  rows参数说明
      * @typedef rows  rows参数说明
      * @property {int} bid  -班次id
      * @property {String} name -班次名称
      * @property {string} time -创建时间
      * @property {string} dept_name -所属部门名称
      * @example 
      * {
        "total": 3,
        "rows": [{
          "bid": 14,
          "name": "东方半岛",
          “dept_name”: "所属部门名称",
          "time": "2018-09-03 01:25:27",
        }, {
          "bid": 15,
          "name": "景芬路口",
          "time": "2018-08-29 05:01:38",
        }, {
          "bid": 17,
          "name": "俄文",
          "time": "2018-09-03 01:28:06",
        }],
        status:200
      }
      */

      /**
       * 返回参数
      * @typedef getWorkShiftsByParams 返回参数
      * @property {Array.<code>} status  -返回状态码
      * @property {String} msg -返回信息
      * @property {int} total -数据总条数
      * @property {obj.<rows>} ... 其他
      */

      /**
      *  RcCodeMsg 返回状态
      * @typedef Function_RcCodeMsg
      * @property {Array.<code>} status  -返回状态码
      * @property {String} msg -返回信息
      */

      /**
      *  根据参数查询签到班次列表 
      * @typedef params_getWorkShiftsByParams 请求参数
      * @property {String} name -不是必选 班次名称,支持模糊查询
      * @property {String} dept_name -不是必选 部门名称,支持模糊查询
      * @property {int} pageNumber -页码
      * @property {int} pageSize -每页数据数量
      * @property {object} sortable -排序对象
      * @example
      *   {
        name:1,                 //班次名称
        dept_name:'一级部门',   //部门名称
        pageNumber:1,    //页码
        pageSize:10,         //每页数据数量
        sortable:{
            prop: 'name',//排序字段
            order: 'desc'//排序方式desc或者asc
        },
       }
      */

      /**
       * 根据参数查询签到班次列表 检索参数 返回值:签到班次列表
       * @function getWorkShiftsByParams 根据参数查询签到班次列表
       * @param {obj.<params_getWorkShiftsByParams>} params -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<getWorkShiftsByParams>} ... 返回参数
      */

      getWorkShiftsByParams: function (options, fn1, fn2) {//签到班次列表
        TY.reqPost(options, TY.URL.getWorkShiftsByParams, fn1, fn2);
        return command;
      },

      /**
       * 签到班次详情 bid 返回值:班次详情
       * @function workShiftInfo 签到班次
       * @param {int} bid -必选 班次id
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @example     返回示例
       * {
        name: 谷歌,                       //string 班次名称
        bid: 18,                          //int    班次id
        signtimesList:[{                  //object 班次需要签到的时间段列表
          "beginTime": -25200000,         //int    开始时间
          "endTime": -21600000,           //int    结束时间
          "week": 1,                      //int    星期
          "name": "早",                   //string 时间段名称
          "stid": "1",                    //int    时间段标识(早班,午班等标识)
          "spid": "23",                   //int    关联签到点id
          "place": "东方半岛"              //string 关联签到点名称
        }, {
          "beginTime": -25200000,
          "endTime": -21600000,
          "week": 2,
          "name": "早",
          "stid": "1",
          "spid": "23",
          "place": "东方半岛"
        }, {
          "beginTime": -25200000,
          "endTime": -21600000,
          "week": 3,
          "name": "早",
          "stid": "1",
          "spid": "23",
          "place": "东方半岛"
        },
        ...
        ]
    }
      */


      workShiftInfo: function (options, fn1, fn2) {//签到班次详情
        TY.compositePost(TY.URL.workShiftInfo, options, (options) => {
          if(!options.bid){
            return 'bid';
          }
        }, fn1, fn2);
        return command;
      },

      /**
       * 添加修改班次 班次参数
       * @function setWorkShift  添加,修改班次
       * 不传bid为创建 , 传bid为修改
       * @param {obj | {}} options -必选 
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @example     请求示例 不传bid为创建 , 传bid为修改
       * {
         name: 谷歌,                       //string 班次名称
         bid: 18,                          //int    班次id
        signtimesList:[{                  //object 班次需要签到的时间段列表
          "beginTime": -25200000,         //int    开始时间
          "endTime": -21600000,           //int    结束时间
          "week": 1,                      //int    星期
          "name": "早",                   //string 时间段名称
          "stid": "1",                    //int    时间段标识(早班,午班等标识)
          "spid": "23",                   //int    关联签到点id
          "place": "东方半岛"              //string 关联签到点名称
        }, {
          "beginTime": -25200000,
          "endTime": -21600000,
          "week": 2,
          "name": "早",
          "stid": "1",
          "spid": "23",
          "place": "东方半岛"
        },
        ...
        ]
    }
      *@param {int.<code>} ... -status返回码
      */

      setWorkShift: function (options, fn1, fn2) {//设置签到班次
        if(options.signtimesList && typeof options.signtimesList == 'string'){
            options.signtimesList = JSON.parse(options.signtimesList);
        }
        TY.compositePost(TY.URL.setWorkShift, options, (options) => {
          if(!options.name){
            return 'name';
          }
        }, fn1, fn2);
        return command;
      },

        /**
      *  删除签到班次 请求参数
      * @typedef params_deviceBindWorkShift
      * @property {Array} uids  -必选 绑定的设备用户id列表
      * @property {int} bid -必选 班次id
      */

      /**
       * 删除签到班次 
       * @function deleteWorkShift  删除签到班次
       * @param {int} id -必选 班次id
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {int.<code>} ... -status返回码
       * @example 返回示例 { status:200 //状态码 }
     
   */

      deleteWorkShift: function (options, fn1, fn2) {//删除签到班次
        TY.compositePost(TY.URL.deleteWorkShift, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

       /**
      *  设备绑定到签到班次 请求参数
      * @typedef params_deviceBindWorkShift
      * @property {Array} uids  -必选 绑定的设备用户id列表
      * @property {int} bid -必选 班次id
      * @example 请求参数示例
       * {
          uids: [610,16],
          bid: 14
         }
      */

      /**
       * 设备绑定到签到班次 
       * @function deviceBindWorkShift  设备绑定到签到班次
       * @param {obj.<params_deviceBindWorkShift>} options -必选 
      
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {int.<code>} ... -status返回码
       * @example 返回示例 { status:200 //状态码 }
       
     */

      deviceBindWorkShift: function (options, fn1, fn2) {//设备绑定到签到班次
        TY.compositePost(TY.URL.deviceBindWorkShift, options, (options) => {
          if(!options.bid){
            return 'bid';
          }
        }, fn1, fn2);
        return command;
      },

      
       /**
       * 查询未分配签到班次的设备列表 bid 返回值:用户列表
       * @function unBindWorkShiftDevices  查询未分配签到班次的设备列表
       * @param {int} bid -必选  班次id
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {int.<code>} ... -status返回码
       * @example 返回参数示例 
       * {
        "status": 200,              //int 返回码
        "datas": [                  //array 设备用户列表
          {
            "uid": 404,             //int 用户id
            "name": "Wilson233",    //string 用户名称
            "isBound": 2            //int  0:未被分配 1:已分配 2:被其他班次分配
          }
        ]
      }
     
   */

      unBindWorkShiftDevices: function (options, fn1, fn2) {//未绑定签到班次的设备列表
        TY.reqPost(options, TY.URL.unBindWorkShiftDevices, fn1, fn2);
        return command;
      },

           /**
       * 查询分配签到班次的设备列表 bid 返回值:用户列表
       * @function bindWorkShiftDevices  查询分配签到班次的设备列表
       * @param {int} bid -必选  班次id
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {int.<code>} ... -status返回码
       * @example 返回参数示例 
       * {
        "status": 200,               //int 返回码
        "datas": [                   //array 设备用户列表
          {
            "uid": 404,              //int 用户id
            "name": "Wilson233",     //string 用户名称
          }
        ]
      }
     
   */

      bindWorkShiftDevices: function (options, fn1, fn2) {//绑定签到班次的设备列表
        TY.compositePost(TY.URL.bindWorkShiftDevices, options, null, fn1, fn2);
        return command;
      },

        /**
      *  设备绑定到签到班次 请求参数
      * @typedef params_getSignPlacesByParams 参数
      * @property {integer} pageNUmber  -必选 页码1
      * @property {integer} pageSize -必选  一条页数10
      * @property {string} name -不是必选 签到点名称,支持模糊查询
      * @property {string} dept_name -不是必选 部门名称,支持模糊查询
      * @property {object} sortable -不是必选 排序对象 sortable
      * @example 排序对象
      *   {
        prop: 'xx', //排序字段
        order:'desc', //排序方式 desc 或者 asc
      }
      */

         /**
       * 查询签到点列表 检索参数  返回值:签到点列表
       * @function getSignPlacesByParams  查询签到点列表
       * @param {obj.<params_getSignPlacesByParams>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {int.<code>} ... -status返回码
       * @example 返回参数示例 
       *{
    "rows": [                                //array 分页数据集合
        {
            "spid": 4,                       //number 签到点id
            "lat": 12,                       //number 经度
            "lng": 23,                       //number 纬度
            "scope": 34,                     //number 签到范围
            "name": "55",                    //number 签到点名称
            "create_time": "1538039462726",  //number 签到点创建时间
            "update_time": "1538046507447",  //number 签到点修改时间
            "dept_name": "主部门"
        }
    ],
    "total": 3,                              //number 分页总数量
    "status": 200                            //number 返回码
}
   */

      getSignPlacesByParams: function (options, fn1, fn2) {//签到点列表
        TY.reqPost(options, TY.URL.getSignPlacesByParams, fn1, fn2);
        return command;
      },

       /**
      *  设备绑定到签到班次 请求参数
      * @typedef params_allSignPlaces 参数
      * @property {string} name  -不是必选 签到点名称,模糊查询
      * @property {string} bid -必选 
      */

        /**
       * 查询全部签到点列表  返回值:签到点列表
       * @function allSignPlaces  查询全部签到点列表
       * @param {obj.<params_allSignPlaces>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {int.<code>} ... -status返回码
       * @example 返回参数示例 
       *{
         "rows": [{                            //array 数据集合
           "spid": 4,                          //number 签到点id
           "lat": 12,                          //number 经度
           "lng": 23,                          //number 纬度
           "scope": 34,                        //number 签到范围
           "name": "55",                       //number 签到点名称
           "create_time": "1538039462726",     //number 签到点创建时间
           "update_time": "1538046507447"      //number 签到点修改时间
         }],
         "status": 200                         //number 返回码
       }
   */

      allSignPlaces: function (options, fn1, fn2) {//全部签到点列表
        TY.reqPost(options, TY.URL.allSignPlaces, fn1, fn2);
        return command;
      },
/**
       * 查询签到点详情 spid 返回值:签到点详情
       * @function signPlaceInfo  查询签到点详情
       * @param {string} spid -必需 签到点id
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {int.<code>} ... -status返回码
       * @example 返回参数示例 
       *{
    "spid": 1,                      //number 签到点id
    "name": "康和盛大厦1",           //string 签到点名称
    "lat": 22.565752936830386,      //number 经度
    "lng": 113.94782850347625,      //number 纬度
    "scope": 200,                   //number 签到范围
    "status": 200                   //number 返回码
}
   */

      signPlaceInfo: function (options, fn1, fn2) {//签到点详情
        TY.compositePost(TY.URL.signPlaceInfo, options, (options) => {
          if(!options.spid){
            return 'spid';
          }
        }, fn1, fn2);
        return command;
      },

        /**
      *  设置签到点 请求参数
      * @typedef params_setSignPlace 
      * @property {string} lat  -必选 经度 示例:113.15454645
      * @property {string} Ing -必选  纬度 示例:23.155115616
      * @property {integer} scope -必选 签到范围 示例:200
      * @property {string} name -必选 签到点名称 示例:狼山路中
      * @property {integer} spid  -不是必须 签到点id,为空则添加 , 有id则修改
      */

      /**
       * 设置签到点 签到点参数 返回值:返回码
       * @function setSignPlace  添加,修改签到点
       * @param {obj.<params_setSignPlace>} options -必需 
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {int.<code>} ... -status返回码
   */

      setSignPlace: function (options, fn1, fn2) {//设置签到点
        TY.compositePost(TY.URL.setSignPlace, options, (options) => {
          if(!options.name){
            return 'name';
          }
          if(!options.lat || !options.lng){
            return 'position';
          }
        }, fn1, fn2);
        return command;
      },

      /**
       * 删除签到点 id 返回值:返回码
       * @function deleteSignPlace  删除签到点
       * @param {integer} id -必需  签到点id
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {int.<code>} ... -status返回码
   */

      deleteSignPlace: function (options, fn1, fn2) {//删除签到点
        TY.compositePost(TY.URL.deleteSignPlace, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

      /**
      * @typedef getSignsByParams_rows
      * @property {array} rows  -分页数据集合
      * @property {number} rows.sid -考勤id
      * @property {number} rows.uid 设备ID
      * @property {number} rows.stid -签到时间段标识
      * @property {number} rows.date  -打卡的日期时间戳,具体变现为那一天的0点
      * @property {number} rows.opid -所属部门id
      * @property {string} rows.time -打卡时间(如果没有打卡,则为空)
      * @property {string} rows.uname -设备名称
      * @property {number} rows.mark -是否打卡(0未打卡,1正常)
      * @property {string} rows.banciName -班次名称
      * @property {string} rows.timesName -时间段名称
      * @property {string} rows.period -正常打卡时间范围(start--end)
      * @property {number} total -分页总数量
      * @property {array.<code>} status -返回码
      *  @example 返回示例
       * {
        "rows": [                                   //array 分页数据集合
            {
                "sid": 2,                           //number 考勤id
                "uid": 1,                           //number 设备id
                "stid": 1,                          //number 签到时间段标识
                "date": 1538064000000,              //number 打卡的日期时间戳,具体变现为那一天的0点
                "time": null,                       //number 所属部门id
                "uname": "小明",                     //string 打卡时间 (如果没有打开,则为空)
                "mark": "0",                         //number 是否打卡(0未打卡,1正常)
                "banciName": "谷歌",                 //string 班次名称
                "timesName": "早",                   //string 时间段名称
                "period": "-25200000—-21600000",
                "dept_name": "主部门"
            }
        ],
        "total": 2,                                  //number 分页总数量
        "status": 200                                //number 返回码
    }
      */

      /**
      *  查询考勤记录列表 参数
      * @typedef param_getSignsByParams
      * @property {integer} pageNumber  -必需 分页页码 eg:1
      * @property {integer} pageSize -必需  分页条数 eg:10
      * @property {integer} quick -不是必需 1为快速搜索 , 0为实时搜索
      * @property {string} name -不是必需 设备关联的人员名称
      * @property {string} mark  -打卡状态(0未打卡,1正常)
      * @property {string} beginDate -不是必需 开始时间 eg:2018-08-23 19:16:50
      * @property {string} endDate -不是必需 结束时间  eg:2018-08-23 19:16:50
      */

     /**
       * 查询考勤记录列表 检索参数 返回值:考勤记录列表
       * @function getSignsByParams  查询考勤记录列表
       * @param {obj.<param_getSignsByParams>} options -必需  
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {int.<getSignsByParams_rows>} ... -返回参数
       *
   */

      getSignsByParams: function (options, fn1, fn2) {//考勤记录
        if(options.beginDate && typeof options.beginDate == 'string'){
          options.beginDate = new Date(options.beginDate).getTime();
        }
        if(options.endDate && typeof options.endDate == 'string'){
          options.endDate = new Date(options.endDate).getTime();
        }
        TY.reqPost(options, TY.URL.getSignsByParams, fn1, fn2);
        return command;
      },

       /**
      *  导出考勤记录 参数
      * @typedef param_exportSigns
      * @property {string} name -不是必需 设备关联的人员名称
      * @property {string} mark  -不是必需打卡状态(0未打卡,1正常)
      * @property {string} beginDate -不是必需 开始时间 eg:2018-08-23 19:16:50
      * @property {string} endDate -不是必需 结束时间  eg:2018-08-23 19:16:50
      * @property {object} sortable -不是必选 排序对象 sortable
      * @example 排序对象
      *   {
        prop: 'xx', //排序字段
        order:'desc', //排序方式 desc 或者 asc
      }
      */

     /**
       * 导出考勤记录 检索参数 返回值:文件流
       * @function exportSigns  导出考勤记录
       * @param {obj.<param_exportSigns>} options -必需  
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {int.<code>} ... -status返回码
   */

      exportSigns: function (options, fn1) {//导出考勤记录
        TY.reqDown(options, TY.URL.exportSigns, fn1);
        return command;
      }
    };
    return command;
  })();
  TY.PermissionManager = (function () {
    var command = {


       /**
      *  添加&修改权限 返回值 操作状态 
      * @typedef modifyRights 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      */

       /**
      *  添加&修改权限 参数
      * @typedef param_modifyRights 
      * @property {int} rights_type -权限类型
      * @property {string} rights_url  -地址路径
      * @property {string} rights_desc -备注
      * @property {string} menu_title -标题
      * @property {string} parent_rights -上级权限id
      * @property {int} rights_id -主键id(修改时使用)
      * @example 
      *    {
        rights_type:1,
        rights_url:'/a/c',
        rights_desc:'添加',
        menu_title:'人员添加',
        parent_rights:1,
        rights_id:2,
    }
      */

      /**
       *添加&修改权限
       * @function modifyRights  添加&修改权限
       * @param {obj.<param_modifyRights>} options -必需  
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<modifyRights>} ... -操作状态
   */

      modifyRights: function (options, fn1, fn2) {//添加&修改权限
        TY.compositePost(TY.URL.modifyRights, options, (options) => {
          if(!options.menu_title){
            return 'menu_title';
          }
          if(!options.rights_url){
            return 'rights_url';
          }
          if(!options.rights_type){
            return 'rights_type';
          }
        }, fn1, fn2);
        return command;
      },

       /**
      *  获得权限详情  权限信息
      * @typedef getRightsInfo 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      * @example
      *     {
        rights_id:2,            //主键id
        rights_type:'1',        //权限类型
        rights_url:'/a/b',      //权限地址
        rights_desc:'查询列表', //权限备注
        menu_title:'查询',      //权限标题
        parent_rights:1,        //上级主键
        parent_title:'检索模块' //上级标题
    }
      */

        /**
       *获得权限详情
       * @function getRightsInfo  获得权限详情
       * @param {int} id -主键id 
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<getRightsInfo>} ... -权限信息
   */



      getRightsInfo: function (options, fn1, fn2) {//获取权限详情
        TY.compositePost(TY.URL.getRightsInfo, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

       /**
      *  删除权限 返回值 操作状态 
      * @typedef deleteRights 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      */

        /**
       *删除权限
       * @function deleteRights  删除权限
       * @param {int} ids -主键id集合
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<deleteRights>} ... -操作状态
       * @example
       * {
        ids:[1,2,3]          //主键id集合
      }
      */

      deleteRights: function (options, fn1, fn2) {//删除权限
        TY.compositePost(TY.URL.deleteRights, options, (options) => {
          if(!options.ids){
            return 'ids';
          }
          if(options.ids && typeof options.ids == 'string'){
            options.ids = JSON.parse(options.ids);
          }
        }, fn1, fn2);
        return command;
      },

       /**
      *  获取所有权限列表 返回值 权限列表
      * @typedef getRightsList 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      * @property {object[]} data -权限集合
      * @example     {
        data:[                  //权限集合
            {
                id:2,               //主键id
                title:'按钮',       //权限标题      
                pid:1,             //父级id
                rights_type:1,      //权限类型
                url:'/a/c',         //权限地址
                have:20             //角色关联id
            }
        ]             
    }
      */


        /**
       *获取所有权限列表
       * @function getRightsList  获取所有权限列表
       * @param {int} id -主键id
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<getRightsList>} ... -权限列表
       * @example
       * {
        id:1          //主键id
          }
        */

      getRightsList: function (options, fn1, fn2) {//获取所有权限列表
        options = options || {}, options.id = 0;
        TY.compositePost(TY.URL.getSubRights, options, null, fn1, fn2);
        return command;
      },

       /**
      *  获得下级权限列表 返回值 操权限列表
      * @typedef getChildRightsList 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      * @example
      *     {
        title:'',       //权限标题
        rights_type:1,  //权限类型
        url:'/a/b',     //权限地址
        id:1,           //主键id
        pid:2,          //上级id
        sort:1,         //上级id,无显示自己
        child_num:5,    //下级权限的数目
        desc:''         //备注
    }
      */

       /**
      *  获得下级权限列表 参数
      * @typedef param_getChildRightsList 
      * @property {int} id -主键id
      * @property {string} name  -权限名称(模糊匹配)
      * @property {int} type -权限类型
      * @example
      * {
        id:3,
        name:'删除',
        type:1
      }
      */

       /**
       *获得下级权限列表
       * @function getChildRightsList  获得下级权限列表
       * @param {obj.<param_getChildRightsList>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<getChildRightsList>} ... -权限列表
        */


      getChildRightsList: function (options, fn1, fn2) {//获取下级权限列表
        TY.compositePost(TY.URL.getChildRightsList, options, null, fn1, fn2);
        return command;
      },

       /**
      *  获得所有下级权限 
      * @typedef getSubRights 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      * @property {object[]} data -权限集合
      * @example
      *  {
        data:[                  //权限集合
            {
                id:2,               //主键id
                title:'按钮',       //权限标题      
                pid:1,             //父级id
                rights_type:1,      //权限类型
                url:'/a/c',         //权限地址
                have:20             //角色关联id
            }
        ]             
    }
      */

      /**
       *获得所有下级权限
       * @function getSubRights  获得所有下级权限
       * @param {int} id -主键id
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<getSubRights>} ... -权限列表
        */

      getSubRights: function (options, fn1, fn2) {//获取所有下级权限
        TY.compositePost(TY.URL.getSubRights, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

      /**
      *  添加或修改角色,更改绑定的权限 返回值 操作状态 
      * @typedef modifyRole 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      */

      /**
      *  添加或修改角色,更改绑定的权限
      * @typedef param_modifyRole 
      * @property {string} role_name -角色名称
      * @property {string} role_desc  -备注
      * @property {string} role_id -角色id(修改时使用)
      * @example
      *  {
        role_name:'添加',   //角色名称
        role_desc:'人员',   //备注
        role_id:1           //角色id (修改时使用)
        }
      */

         /**
       *添加或修改角色,更改绑定的权限
       * @function modifyRole  添加&修改角色,更改绑定的权限
       * @param {obj.<param_modifyRole>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<modifyRole>} ... -权限列表
        */


      modifyRole: function (options, fn1, fn2) {//添加&修改角色,更改绑定的权限
        TY.compositePost(TY.URL.modifyRole, options, (options) => {
          if(!options.role_name){
            return 'role_name';
          }
          if(options.role_rights && typeof options.role_rights == 'string'){
            options.role_rights = JSON.parse(options.role_rights);
          }
        }, fn1, fn2);
        return command;
      },

       /**
      *  获取角色详情 
      * @typedef getRoleInfo 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      * @example
      *    {
        data:{
            role:[
                {
                    role_id:1,          //角色主键
                    role_name:'管理',   //角色名称
                    role_desc:'备注',   //备注
                    def:0              
                }
            ],
            roleRights:[
                {
                    id:2,           //关联表主键
                    title:'',       //权限标题
                    pid:3,          //父级外键
                    rights_type:1,  //权限类型
                    url:'/a/b',     //地址
                    have:2          //上级id
                }
            ]
        }
    }
      */

         /**
       *获取角色详情
       * @function getRoleInfo  获取角色详情
       * @param {int} id -角色id
       * @example
       *  {
        id:1                //角色id
    }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<getRoleInfo>} ... -权限列表
        */

      getRoleInfo: function (options, fn1, fn2) {//获取角色详情
        TY.compositePost(TY.URL.getRoleInfo, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

         /**
      *  删除角色,清除权限绑定关系 
      * @typedef deleteRole 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      */

       /**
       *删除角色,清除权限绑定关系
       * @function deleteRole  删除角色,清除权限绑定关系
       * @param {int[]} ids -id集合
       * @example
       *   {
        ids:[1,2,3]         //id集合
        }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<deleteRole>} ... -权限列表
        */

      deleteRole: function (options, fn1, fn2) {//删除角色,清除权限绑定关系
        TY.compositePost(TY.URL.deleteRole, options, (options) => {
          if(!options.ids){
            return 'ids';
          }
          if(options.ids && typeof options.ids == 'string'){
            options.ids = JSON.parse(options.ids);
          }
        }, fn1, fn2);
        return command;
      },

        /**
      *  获取角色列表 
      * @typedef getRoleList 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      * @example
      *  {
        role_id:1,              //主键id
        role_name:'管理员',     //角色名称
        role_desc:'备注',      //备注
    }
      */

       /**
       *获取角色列表
       * @function getRoleList  获取角色列表
       * @param {} option -参数无
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<getRoleList>} ... -权限列表
        */

      getRoleList: function (options, fn1, fn2) {//获取角色列表
        TY.reqPost(options, TY.URL.getRoleList, fn1, fn2);
        return command;
      }
    };
    return command;
  })();
  TY.LogManager = (function () {
    var command = {

        /**
      *  用户登录日志 
      * @typedef getUserLoginLogsByParams 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      * @example
      *   {
        puid:'abc',                         //人员编号
        name:'',                            //人员名称
        ip:'192.168.xxx',                   //登录ip
        port:8080,                          //登录端口
        onlineTime:'2018-05-06 20:03:06',   //上线时间
        offlineTime:'2018-05-06 20:03:06',  //下线时间
        curTime:2032,                       //本次时长
        totalTime:5622556                   //总共时长
    }
      */

       /**
      *  用户登录日志 
      * @typedef param_getUserLoginLogsByParams 
      * @property {string} beginDate -开始时间
      * @property {string} endDate  -结束时间
      * @property {string} name -人员名称
      * @property {int} pageSize -行数
      * @property {int} pageNumber -当前页
      * @property {object} sortable -排序(参照返回列)
      * @example
      *     {
        {
            beginDate:'2018-02-01 20:30:56',//开始时间
            endDate:'2018-02-02 20:30:56',  //结束时间
            name:'',                        //人员名称
            pageSize:10,                    //行数
            pageNumber:1,                   //页数
            sortable:{
                prop:'',                    //排序列(根据返回列)        
                order:'desc'                //排序方式
            }
        }
    }
      */


       /**
       *用户登录日志
       * @function getUserLoginLogsByParams  用户登录日志
       * @param {obj.<param_getUserLoginLogsByParams>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<getUserLoginLogsByParams>} ... -result返回值
        */

      getUserLoginLogsByParams: function (options, fn1, fn2) {//用户登录日志
        TY.reqPost(options, TY.URL.getUserLoginLogsByParams, fn1, fn2);
        return command;
      },

  /**
      *  设备登录日志 
      * @typedef getDeviceLoginLogsByParams 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      * @example
      *  {
        puid:'abc',                         //设备编号
        name:'',                            //人员名称
        ip:'192.168.xxx',                   //登录ip
        port:8080,                          //登录端口
        onlineTime:'2018-05-06 20:03:06',   //上线时间
        offlineTime:'2018-05-06 20:03:06',  //下线时间
        curTime:2032,                       //本次时长
        totalTime:5622556                   //总共时长
    }
      */

       /**
      *  设备登录日志 
      * @typedef param_getDeviceLoginLogsByParams
      * @property {string} puid 设备编号
      * @property {string} beginDate -开始时间
      * @property {string} endDate  -结束时间
      * @property {string} name -人员名称
      * @property {int} pageSize -行数
      * @property {int} pageNumber -当前页
      * @property {object} sortable -排序(参照返回列)
      * @example
      *      {
        {
            puid:'',                        //设备编号
            beginDate:'2018-02-01 20:30:56',//开始时间
            endDate:'2018-02-02 20:30:56',  //结束时间
            name:'',                        //人员名称
            pageSize:10,                    //行数
            pageNumber:1,                   //页数
            sortable:{
                prop:'',                    //排序列(根据返回列)        
                order:'desc'                //排序方式
            }
        }
    }
      */

         /**
       *设备登录日志
       * @function getDeviceLoginLogsByParams  设备登录日志
       * @param {obj.<param_getDeviceLoginLogsByParams>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<getDeviceLoginLogsByParams>} ... -result返回值
        */


      getDeviceLoginLogsByParams: function (options, fn1, fn2) {//设备登录日志
        TY.reqPost(options, TY.URL.getDeviceLoginLogsByParams, fn1, fn2);
        return command;
      },

       /**
      *  操作日志
      * @typedef getUserOperateLogsByParams 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      * @example
      *   {
        name:'小明'                 //人员名称             
        time:'2018-06-09 20:20:23'  //操作时间
        operate:'查询'              //操作内容
        result:1                    //操作结果                   
        description:'aa'            //备注
    }
      */
      
       /**
      *  操作日志 
      * @typedef param_getUserOperateLogsByParams
      * @property {string} beginDate -开始时间
      * @property {string} endDate  -结束时间
      * @property {string} name -人员名称
      * @property {int} pageSize -行数
      * @property {int} pageNumber -当前页
      * @property {object} sortable -排序(参照返回列)
      * @property {string} operate -操作内容(模糊匹配)
      * @example
      *    {
        {
            beginDate:'2018-02-01 20:30:56',//开始时间
            endDate:'2018-02-02 20:30:56',  //结束时间
            name:'',                        //人员名称
            pageSize:10,                    //行数
            pageNumber:1,                   //页数
            operate:'拍照',                 //操作内容(模糊匹配)
            sortable:{
                prop:'',                    //排序列(根据返回列)        
                order:'desc'                //排序方式
            }
        }
    }
      */

        /**
       *操作日志
       * @function getUserOperateLogsByParams  操作日志
       * @param {obj.<param_getUserOperateLogsByParams>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<getUserOperateLogsByParams>} ... -result返回值
        */

      getUserOperateLogsByParams: function (options, fn1, fn2) {//操作日志
        TY.reqPost(options, TY.URL.getUserOperateLogsByParams, fn1, fn2);
        return command;
      },


       /**
      *  设备操作日志文件检索
      * @typedef getDeviceOperateLogsByParams 
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      * @example
      *    {
        id:1,           //文件id
        name:'',        //文件名称        
        startTime:'',   //开始时间(视频)    
        endTime:'',     //结束时间(视频)
        size:1313254,   //文件大小
        duration:200,   //文件长度
        type:1,         //文件类型
        desc:'aa'       //文件备注
        storageType:1   //存储类型 (平台/设备/本地)
    }
      */

       /**
      *  设备操作日志文件检索 
      * @typedef param_getDeviceOperateLogsByParams
      * @property {int[]} personnel -人员id集合
      * @property {int} storageType  -平台类型
      * @example
      *   {
        personnel;[
            1,2,3
        ],
        storageType:1
        }
      */

      /**
       *设备操作日志文件检索
       * @function getDeviceOperateLogsByParams  设备操作日志文件检索
       * @param {obj.<param_getDeviceOperateLogsByParams>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<getDeviceOperateLogsByParams>} ... -result返回值
        */

      getDeviceOperateLogsByParams: function (options, fn1, fn2) {//设备操作日志
        options.type = 3;
        TY.compositePost(TY.URL.loadFile, options, (options) => {
          if(!options.personnel){
            return 'personnel';
          }
          if(!options.storageType){
            return 'storageType';
          }
          if(options.personnel && typeof options.personnel == 'string'){
            options.personnel = JSON.parse(options.personnel);
          }
        }, fn1, fn2);
        return command;
      },

      
       /**
      *  导入设备日志 
      * @typedef importDeviceOperateLog
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      */

         /**
      *  导入设备日志
      * @typedef param_importDeviceOperateLog
      * @property {int} id -人员id
      * @property {int[]} array  -文件id集合
      * @property {int} type -文件类型
      * @example
      *   {
        id:1,               //人员id
        array:[1,2,3],      //文件id集合
        type:1              //文件类型
      }
      */
        /**
       *导入设备日志
       * @function importDeviceOperateLog  导入设备日志
       * @param {obj.<param_importDeviceOperateLog>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<importDeviceOperateLog>} ... -状态返回
        */

      importDeviceOperateLog: function (options, fn1, fn2) {//导入设备日志
        TY.compositePost(TY.URL.importFile, options, (options) => {
          if(!options.id){
            return 'id';
          }
          if(!options.files){
            return 'files';
          }
          if(options.files && typeof options.files == 'string'){
            options.files = JSON.parse(options.files);
          }
        }, fn1, fn2);
        return command;
      },
      getDeviceLogByParams:function(options, fn1, fn2){
        options.type=3;
        TY.FileManager.getFilesByParams(options, fn1, fn2);
      },

      /**
      *  下载设备日志 
      * @typedef downloadDeviceOperateLog
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      */

       /**
      *  下载设备日志
      * @typedef param_downloadDeviceOperateLog
      * @property {int} id -人员id
      * @property {int} storageType  -存储类型
      * @property {int} type -文件类型
      * @property {int} did -人员id
      * @example
      *    {
        id:1,           //文件id
        storageType:1,  //储存类型
        type:2,         //文件类型
        did:2312        //人员id
       }
      */

       /**
       *下载设备日志
       * @function downloadDeviceOperateLog  下载设备日志
       * @param {obj.<param_downloadDeviceOperateLog>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<downloadDeviceOperateLog>} ... -状态返回
        */

      downloadDeviceOperateLog: function (options, fn1, fn2) {//下载设备日志
        options.type = 3;
        TY.compositeDown(TY.URL.downloadFile, options, (options) => {
          if(!options.did){
            return 'did';
          }
          if(!options.id && options.id != 0){
            return 'id';
          }
          if(!options.storageType && options.storageType != 0){
            return 'storageType';
          }
        }, fn1, fn2);
        return command;
      },

       /**
      *  删除设备日志 
      * @typedef deleteDeviceOperateLog
      * @property {int.<code>} status -返回码
      * @property {string} msg  -返回信息
      */

        /**
      *  删除设备日志
      * @typedef param_deleteDeviceOperateLog
      * @property {int} id -人员id
      * @property {int[]} array -文件id集合
      * @example
      *  {
        id:1,               //人员id
        array:[1,2,3]       //文件id集合
        }
      */

         /**
       *删除设备日志
       * @function deleteDeviceOperateLog  删除设备日志
       * @param {obj.<param_deleteDeviceOperateLog>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function_RcCodeMsg} reject -不是必选 失败的回调
       * @param {obj.<deleteDeviceOperateLog>} ... -状态返回
        */

      deleteDeviceOperateLog: function (options, fn1, fn2) {//删除设备日志
        TY.compositePost(TY.URL.deleteFile, options, (options) => {
          if(!options.id){
            return 'id';
          }

        }, fn1, fn2);
        return command;
      }
    };
    return command;
  })();
  TY.AI = (function () {
    var command = {


/**
   * rect[]矩形区域
   * @typedef rect -矩形区域
   * @property {Number} x -必选 x坐标
   * @property {Number} y -必选 y坐标
   * @property {Number} h -必选 高度
   * @property {Number} w -必选 宽度
   * @property {Number} id -必选 200成功,其他失败
   * @property {strint} name -必选 识别到的人员名称
    */

  /**
   * Result 人脸记录
   * @typedef Result -人脸记录
   * @property {Number} id -必选 人脸记录id
   * @property {float} lat -必选 纬度
   * @property {float} lng -必选 经度
   * @property {String} time -必选 上传时间(格式yyyy-MM-dd hh:mm:ss)
   * @property {String} name -必选 上传人员名称
   * @property {Number} devNo -必选 设备编号
   * @property {Number} polNo -必选 人员便号
   * @property {Number} img -必选 图片id
   * @property {Array.<rect>} rect -必选 总条数
    */

/**
 * @callback Function_obj_result
 * @param {Number} status -必选 200成功,其他失败
 * @param {Array.<Result>} rows -必选 人脸库信息
 * @param {Number} total -必选 总条数
 */

    /**
   * @typedef params_getResultsByParams
   * @property {Number} pageSize -是
   * @property {Number} pageNumber -是
   * @property {String} begintime -是
   * @property {String} endtime -是
   * @property {Number} name -是
   * @property {Number} devNo -是
   * @property {Number} polNo -是
    */


       /**
       * 查询识别记录列表
       * @function getResultsByParams 查询识别记录列表
       * @param {Obj.<params_getResultsByParams>} params -必选 没有返回值
       * @param {Function_obj_result} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       */



      getResultsByParams: function (options, fn1, fn2) {//识别结果列表
        TY.reqPost(options, TY.URL.getResultsByParams, fn1, fn2);
        return command;
      },



      /**
       * 批量删除识别记录
      * @typedef obj_result
      * @property {Array} ids -是
      */

       /**
       * 批量删除识别记录
       * @function deleteResult 批量删除识别记录
       * @param {Array.<obj_result>} params -必选 没有返回值
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       */


      deleteResult: function (options, fn1, fn2) {//删除人脸记录
        TY.compositePost(TY.URL.deleteResult, options, (options) => {
          if(!options.id){
            return 'id';
          }
          options.ids = [parseInt(options.id)];
        }, fn1, fn2);
        return command;
      },
      deleteResults: function (options, fn1, fn2) {//批量删除人脸记录
        TY.compositePost(TY.URL.deleteResult, options, (options) => {
          if(!options.ids){
            return 'ids';
          }
          if(options.ids && typeof options.ids == 'string'){
            options.ids = JSON.parse(options.ids);
          }
        }, fn1, fn2);
        return command;
      },

        /**
      * @typedef Face
      * @property {Nnmber} id -必选 人脸库id
      * @property {String} uname -必选 识别到的名称
      * @property {String} time -必选 上传时间(格式 yyyy-MM-dd hh:mm:ss)
      * @property {String} name -必选 上传人员名称
      * @property {Nnmber} polNo -必选 上传人员编号
      * @property {Nnmber} imgs -必选 人脸库图片id集合
      */
      
  /**
   * @callback Function_obj_faces
   * @param {Number} status -必选 200成功,其他失败
   * @param {Obj.<Face>} rows -必选 人脸库信息
   * @param {Number} total -必选 总条数
 */
      
      /**
       * 查询人脸库列表
      * @typedef obj_getFacesByParams
      * @property {Nnmber} pageSize -是
      * @property {Nnmber} pageNumber -是
      * @property {Nnmber} begintime -是
      * @property {Nnmber} endtime -是
      * @property {Nnmber} name -是
      * @property {Nnmber} devNo -是
      * @property {Nnmber} polNo -是
      */

       /**
       * 查询人脸库列表
       * @function getFacesByParams 查询人脸库列表
       * @param {Obj.<obj_getFacesByParams>} options -必选 没有返回值
       * @param {Function_obj_faces} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       */

      getFacesByParams: function (options, fn1, fn2) {//人脸库列表
        TY.reqPost(options, TY.URL.getFacesByParams, fn1, fn2);
        return command;
      },

      /**
       * 批量删除人脸库
      * @typedef obj_face_result
      * @property {Array} ids -人脸库id
      */

      /**
       * 批量删除人脸库
       * @function deleteFace -批量删除人脸库
       * @param {Obj.<obj_face_result>} options -必选 没有返回值
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       */

      deleteFace: function (options, fn1, fn2) {//删除人脸库
        TY.compositePost(TY.URL.deleteFace, options, (options) => {
          if(!options.id){
            return 'id';
          }
          options.ids = [parseInt(options.id)];
        }, fn1, fn2);
        return command;
      },
      deleteFaces: function (options, fn1, fn2) {//批量删除人脸库
        TY.compositePost(TY.URL.deleteFace, options, (options) => {
          if(!options.ids){
            return 'ids';
          }
          if(options.ids && typeof options.ids == 'string'){
            options.ids = JSON.parse(options.ids);
          }
        }, fn1, fn2);
        return command;
      }
    };
    return command;
  })();
  TY.GpsManager = (function () {
    var validation = function (options,fn1,fn2) {
      if (!options || typeof options != 'object') {
        return 1;
      } else if (fn1 && typeof fn1 != 'function') {
        return 2;
      } else if (fn2 && typeof fn2 != 'function') {
        return 3;
      }
      return 0;
    };
    var composite_post = function (options,url,fn1,fn2,fn){
      if(validation(options,fn1,fn2)){
        fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL));
      }
      var msg;
      if(fn && (msg = fn(options))){
        fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL,msg));
      }
      TY.reqPost(options, url, fn1, fn2);
    };
    var composite_get = function (options,url,fn1,fn2,fn){
      if(validation(options,fn1,fn2)){
        fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL));
      }
      var msg;
      if(fn && (msg = fn(options))){
        fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL,msg));
      }
      TY.reqGet(options, url, fn1, fn2);
    };
    var socket = self._socket;
    function send(id,comm,fn){
      socket.emit("config", {
        personnel:[id],
        command:comm
      }, fn);
    }
    var composite_socket = function (options,data,fn1,fn2,fn){
      if(validation(options,fn1,fn2)){
        fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL));
        return command;
      }
      var msg;
      if(fn && (msg = fn(options))){
        fn2(TY.RcCode.returnMsg(TY.RcCode.PARAMETERS_ABNORMAL,msg));
        return command;
      }
      send(options.id,data,function(r){
        if(r == false){
          fn2&&fn2(TY.RcCode.returnMsg({code:204,msg:"设备不在线"}));
        }else if(r == 1){
          fn1&&fn1(TY.RcCode.returnMsg({code:200,msg:"操作成功"}));
        }else if(r == 0){
          fn1&&fn1(TY.RcCode.returnMsg({code:500,msg:"操作失败"}));
        }else if(r == -1){
          fn1&&fn1(TY.RcCode.returnMsg({code:204,msg:"警号已存在"}));
        }
        fn1&&fn1(TY.RcCode.returnMsg({code:204,data:r}));
      });
    };
    var command = {

        /**
       * 返回值
      * @typedef status
      * @property {Array.<code>} status -返回码
      * @property {String} msg -返回的信息
      */

       /**
       * 订阅设备的gps 
       * @function subscribeGpsByDevice -订阅设备的gps
       * @param {int[]} options -personnel 人员id集合
       * @example  {
        personnel:[1,2,3,4,5]
    }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<status>} ... -状态返回
       */

      subscribeGpsByDevice:function(options, fn1, fn2){//=>personnel []
        composite_post(options,TY.URL.subscribeGpsByDevice,fn1,fn2,function(options){
          if(!options.personnel){
            return 'personnel not found';
          }
          return 0;
        });
        return command;
      },

          /**
       * 订阅一个组的gps 
       * @function subscribeGpsByGroup -订阅一个组的gps
       * @param {int} room -组id
       * @example {
        room:2312
       }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<status>} ... -状态返回
       */

      subscribeGpsByGroup:function(options, fn1, fn2){//=>personnel []
        options.room = options.id;
        composite_post(options,TY.URL.subscribeGpsByGroup,fn1,fn2,function(options){
          if(!options.room){
            return 'room not found';
          }
          return 0;
        });
        return command;
      },

      /**
       * @function unsubscribeAll 取消所有的订阅
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       */
      unsubscribeAll:function(fn1, fn2){
        var options={};
        composite_post(options,TY.URL.unsubscribeGps,fn1,fn2);
        return command;
      },

        /**
       * 取消单个或所有的订阅 
       * @function unsubscribeGps -取消单个或所有的订阅
       * @param {Array} personnel -人员id集合
       * @example {
        personnel:[1,2,3,4,5]
    }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       */

      unsubscribeGps:function(options, fn1, fn2){
        if(typeof options == 'function'){
          fn2 = fn1;
          fn1 = options;
          options = {};
        }
        composite_post(options,TY.URL.unsubscribeGps,fn1,fn2);
        return command;
      },
    };
    return command;
  })();
  TY.UnitManager = (function () {
    var command = {

      /**
       * addUnit 添加部门 参数
      * @typedef param_addUnit 添加部门
      * @property {int} parent_id -父级部门id
      * @property {String} name -部门名称
      * @property {String} department_code -部门编号
      * @example
      *  {
        parent_id:'2',          //父级部门id
        name:'研发部门',        //部门名称
        department_code:'abc'   //部门编号
        }
      */

        /**
       * 添加部门 
       * @function addUnit -添加部门
       * @param {obj.<param_addUnit>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<status>} ... -状态返回
       */

      addUnit: function (options, fn1, fn2) {//添加部门
        TY.compositePost(TY.URL.addUnit, options, (options) => {
          if(options.parent_id==null){
            return 'parent_id';
          }
          if(!options.name){
            return 'name';
          }
        }, fn1, fn2);
        return command;
      },

      /**
       * 删除部门 
       * @function deleteUnit -删除部门
       * @param {int} id -部门id
       * @example
       *  {
        id:1                    //部门id
    }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<status>} ... -状态返回
       */

      deleteUnit: function (options, fn1, fn2) {//删除部门
        TY.compositePost(TY.URL.deleteUnit, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

        /**
       * 修改部门 参数 
      * @typedef param_modifyUnit 修改部门
      * @property {int} id -部门id
      * @property {int} parent_id -父级部门id
      * @property {String} name -部门编名称
      * @property {String} department_code -部门编号
      * @example
      *     {
        id:1,                   //部门id
        parent_id:'2',          //父级部门id
        name:'研发部门',        //部门名称
        department_code:'abc'   //部门编号
    }
      */

         /**
       * 修改部门 
       * @function modifyUnit -修改部门
       * @param {obj.<param_modifyUnit>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<status>} ... -状态返回
       */

      modifyUnit: function (options, fn1, fn2) {//修改部门
        TY.compositePost(TY.URL.modifyUnit, options, (options) => {
          if(!options.id){
            return 'id';
          }
          if(options.parent_id==null){
            return 'parent_id';
          }
          if(!options.name){
            return 'name';
          }
        }, fn1, fn2);
        return command;
      },

      /**
       * 部门列表  
      * @typedef param_getUnitsByParams 部门列表
      * @property {int.<code>} status -返回码
      * @property {String} name -返回信息
      * @property {object[]} department -部门集合
      * @example
      *     {
        department:[
            {
                id:1,           //主键id
                name:'主部门',  //部门名称
                type:1,         //部门类型
                pId:0,          //父级id
                code:'abc'      //部门编号
            }
        ]                    
    }
      */

       /**
       * 部门列表 返回值:部门列表
       * @function getUnitsByParams -部门列表
       * @param {} ... -参数无
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<param_getUnitsByParams>} ... -部门列表
       */

      getUnitsByParams: function (options, fn1, fn2) {//部门列表
        TY.compositePost(TY.URL.getUnitsByParams, options, null, fn1, fn2);
        return command;
      },

      /**
       * 人员列表
      * @typedef getUserByUnit 人员列表
      * @property {int.<code>} status -返回码
      * @property {String} msg -返回信息
      * @property {int} total -数据总数量
      * @property {object[]} row -部门成员集合
      * @property {int} rows.id -人员id
      * @property {string} rows.account -人员账号
      * @property {string} rows.name -人员名称
      * @property {string} rows.phone -phone
      * @property {int} rows.roleId -角色id
      * @property {string} rows.email -邮箱
      * @property {string} rows.departmentName -所属部门吗v
      * @property {int} rows.departmentId -所属部门id
      * @property {string} rows.describe -备注信息
      * @property {int} rows.line 是否在线 0:离线 1:在线
      * @property {string} rows.roleName 角色名称
      * @example
      * {
        "status": 200,
        "rows": [
          {
            "id": 708,
            "account": "zhuxiao",
            "name": "zhuxiao",
            "phone": "",
            "roleId": 61,
            "email": "",
            "departmentName": "主部门",
            "departmentId": 8,
            "describe": "",
            "line": 0,
            "roleName": "zhuxiao"
          },
          ...
        ],
        "total": 82
      }
      */

        /**
       * 获取部门下的人员列表  
      * @typedef param_getUserByUnit 获取部门下的人员列表
      * @property {int} id -部门id
      * @property {int} line -在线状态
      * @property {string} name -人员名称
      * @property {string} account -人员编号
      * @property {int} pageSize -行数
      * @property {int} pageNumber -页数
      * @property {string} sortable -排序列
      * @example
      *     {
        id:1,                   //部门id
        line:1,                 //在线状态
        name:'',                //人员名称
        account:'',             //人员编号
        sortable:{
            prop:'',           //排序列(根据返回列)        
            order:'desc'       //排序方式
        }
    }
      */

       /**
       * 获取部门下的人员列表 返回值:人员列表
       * @function getUserByUnit -获取部门下的人员列表
       * @param {obj.<param_getUserByUnit>} options -参数 
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<getUserByUnit>} ... -返回值:人员列表
       */

      getUserByUnit: function (options, fn1, fn2) {//获取部门下的人员列表
        TY.compositePost(TY.URL.getUserByUnit, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

            /**
       * 获取部门和部门成员  
      * @typedef loadDepartmentCough 获取部门和部门成员
      * @property {int.<code>} status -返回码
      * @property {string} msg -返回信息
      * @example
      *     {
        department:[
            {
                id:1,               //主键id
                name:'',            //名称    
                type:'',            //类型    
                pId:1,              //父级id    
                isParent:1,         //是否是部门
                status:,            //设备状态    
                line:,              //在线状态
                DevNo:'',           //设备编号
                sn:'ccc'            //产品序号
                isBound:0           //是否绑定
            }
        ]
    }
      */

      /**
       * 获取部门和部门成员 返回值:部门和部门成员
       * @function loadDepartmentCough -获取部门和部门成员
       * @param {int} id -部门id
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<loadDepartmentCough>} ... -获取部门和部门成员
       */


      loadDepartmentCough: function (options, fn1, fn2) {//获取主部门
        TY.compositePost(TY.URL.loadDepartmentCough, options, null, fn1, fn2);
        return command;
      }
    };
    return command;
  })();
  TY.VersionManager = (function () {
    var command = {

      /**
      * 添加或修改app  
      * @typedef param_modifyApp 添加或修改app
      * @property {int} id -id(添加时不需要)
      * @property {int} instruction -备注
      * @property {int} name -app名称
      * @example
      * {
        id:1,                         //id
        instruction:'详细信息',       //详细信息
        name:'版本名称',              //版本名称
    }
      */

       /**
       * 添加或者修改app 返回值:操作状态
       * @function modifyApp -添加或者修改app
       * @param {obj.<param_modifyApp>} id -部门id
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<code>} ... -返回参数
       * @example
       * {
        "status": 200
      }
       */

      modifyApp: function (options, fn1, fn2) {//添加&修改App
        TY.compositePost(TY.URL.modifyApp, options, (options) => {
          if(!options.name){
            return 'name';
          }
        }, fn1, fn2);
        return command;
      },

        /** 
      * @typedef getAppInfo 
      * @property {array.<code>} status -返回码
      * @property {int} id -id
      * @property {int} instruction -备注
      * @property {int} name -APP名称
      * @example
      *{
        "status": 200,                //返回码
        id:1,                         //id
        instruction:'详细信息',       //备注
        name:'版本名称',              //APP名称
     }
      */


         /**
       * 获取app详情  返回值:APP详情
       * @function getAppInfo -获取app详情
       * @param {int} id -id
       * @example{ id:1                 //id}
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<getAppInfo>} ... -返回参数
       * 
       */

      getAppInfo: function (options, fn1, fn2) {//获取App详情
        TY.compositePost(TY.URL.getAppInfo, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

         /**
       * 删除aapp 返回值:操作状态
       * @function deleteApp -删除App
       * @param {int} id -id
       * @example
       *  {
        id:1                 //APPid
        }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<code>} ... -返回参数
       * @example{"status": 200}
       */

      deleteApp: function (options, fn1, fn2) {//删除App
        TY.compositePost(TY.URL.deleteApp, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

       /** 
      * @typedef getAppList 
      * @property {array.<code>} status -返回码
      * @property {int} total -数据总条数
      * @property {array} rows -数据集合
      * @property {int} rows.id -id
      * @property {string} rows.app_id -APP ID唯一标识
      * @property {string} rows.createtime -创建时间
      * @property {string} rows.instruction -备注
      * @property {string} rows.name -APP名称
      * @example
      * {
        "total": 3,
        "rows": [
          {
            "id": 32,
            "app_id": "e165421110ba03099a1c0393373c5b43",
            "createtime": "2019-02-16 11:14:22",
            "instruction": "1",
            "name": "233"
          },
          ...
        ],
        "status": 200
      }
      */

        /**
      * 获取app列表  
      * @typedef param_getAppList 获取app列表
      * @property {int} pageNumber -页数
      * @property {int} pageSize -行数
      * @property {String} sortable -排序列(参数返回列)
      * @example
      *  {
        pageNumber:1,         //页数
        pageSize:10,          //行数
        sortable:'name',      //排序列(参考返回列)
    }
      */

       /**
       * 获取app列表 返回值:APP列表
       * @function getAppList -获取app列表
       * @param {obj.<param_getAppList>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<getAppList>} ... -返回参数
       */

      getAppList: function (options, fn1, fn2) {//获取App列表
        options = options || {};
        TY.compositePost(TY.URL.getAppList, options, null, fn1, fn2);
        return command;
      },

         /**
      * 添加或者修改APP版本
      * @typedef param_modifyAppVersion 添加或者修改APP版本
      * @property {file} file -文件流(FormData(file))
      * @property {int} activite -状态(0 不可用 / 1 可用)
      * @property {String} filename -文件名称
      * @property {String} instruction -备注
      * @property {String} os -系统 (Android or ios)
      * @property {String} title -标题
      * @property {int} type -类型(0 不强制更新 / 1 强制)
      * @property {String} version -版本名
      * @property {String} version_code -版本编号
      * @property {file} id -版本id (添加非必要)
      * @example
      *    {
        activite:1,                         //状态(0 不可用 / 1 可用)
        filename:'1.2.1.zip',               //文件名称
        instruction:'解决了..,添加了...',   //描述
        os:'android',                       //系统
        title:'节日定制',                   //标题
        type:1,                            //类型(0 不强制更新 / 1 强制)
        version:'1.2.1',                    //版本名
        version_code:'abc',                 //版本编号
        id:1                                //版本id (添加非必要)
    }
      */

         /**
       * 添加或修改APP版本  返回值:操作状态
       * @function modifyAppVersion -添加&修改App版本
       * @param {obj.<param_modifyAppVersion>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<code>} ... -返回参数
       */


      modifyAppVersion: function (options, fn1, fn2, fn3) {//添加&修改App版本
        TY.compositeUpload(TY.URL.modifyAppVersion, options, (options) => {
          if(!options.filename){
            return 'filename';
          }
          if(!options.os){
            return 'os';
          }
          if(!options.version){
            return 'version';
          }
          if(!options.version_code){
            return 'version_code';
          }
          if(!options.type){
            return 'type';
          }
        }, fn1, fn2, fn3);
        return command;
      },

          /**
       * 获取APP版本详情 返回值:APP版本详情
       * @function getAppVersionInfo -获取APP版本详情
       * @param {int} id -版本id
       * @example {id:1             //版本id }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @example  返回参数
       * {
        "id": 71,                                              //int 版本id (添加非必要)
        "activite": 1,                                         //int 状态(0 不可用 / 1 可用)
        "filename": "ivmcc_telyes_release_1.7.24_993.apk",     //string 文件名称
        "instruction": "",                                     //string 备注
        "os": "Android",                                       //os 系统(Android or ios)
        "type": 0,                                             //int 类型(0 不强制更新 / 1 强制)
        "version": "ivmcc_telyes_release_1.7.24_993",          //string 版本名
        "version_code": 993,                                   //string 版本编号
        "status": 200                               
      }
       */


      getAppVersionInfo: function (options, fn1, fn2) {//获取App版本详情
        TY.compositePost(TY.URL.getAppVersionInfo, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

        /**
       * 删除App版本 返回值:操作状态
       * @function deleteAppVersion -删除app版本
       * @param {int} id -版本id
       * @example {   id:1       //版本id }
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<code>} ... -返回参数
       * @example 返回参数 { "status": 200}
       */

      deleteAppVersion: function (options, fn1, fn2) {//删除App版本
        TY.compositePost(TY.URL.deleteAppVersion, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },

       /**
        * 获取App版本列表
      * @typedef getAppVersionList 
      * @property {array.<code>} status -返回码
      * @property {int} total -数据总条数
      * @property {array} rows -数据集合
      * @property {int} rows.activite -状态(0不可用/可用)
      * @property {String} rows.filename -文件名称
      * @property {String} rows.instruction -备注
      * @property {string} rows.os -系统 (Android or ios)
      * @property {String} rows.title -标题
      * @property {int} rows.type	 -类型(0 不强制更新 / 1 强制)
      * @property {string} rows.version -版本名
      * @property {string} rows.version_code -版本编号
      * @property {int} rows.id 版本id(添加非必要)
      * @example
      * {
        "total": 1,
        "rows": [
          {
            "id": 71,
            "activite": 1,
            "filename": "ivmcc_telyes_release_1.7.24_993.apk",
            "instruction": "",
            "os": "Android",
            "type": 0,
            "version": "ivmcc_telyes_release_1.7.24_993",
            "version_code": 993,
          },
          ...
        ],
        "status": 200
      }
      */

       /**
      * 获取App版本列表  
      * @typedef param_getAppVersionList 获取App版本列表
      * @property {string} app_id -APP 的 app_id字段 (必需)(VersionManager.getAppList.row.id)
      * @property {int} pageNumber -当前页
      * @property {int} pageSize -行数
      * @property {object} sortable -排序(参照返回列)
      * @example 返回参数
      *  {
        app_id:'1E2D3365E',           //版本id
        pageNumber:1,                 //当前页    
        pageSize:10,                  //行数    
        sortable:{
            prop:'',                  //排序列(参照返回列)
            order:'desc'              //排序方式    
        }
    }
      */

         /**
       * 获取App版本列表  返回值:APP版本列表
       * @function getAppVersionList -获取App版本列表
       * @param {obj.<param_getAppVersionList>} options -参数
       * @param {Function} resolve - 必选 成功的回调
       * @param {Function} reject -不是必选 失败的回调
       * @param {obj.<getAppVersionList>} ... -返回参数
       */

      getAppVersionList: function (options, fn1, fn2) {//获取App版本列表
        TY.reqPost(options, TY.URL.getAppVersionList, fn1, fn2);
        return command;
      }
    }
    return command;
  })();
  TY.EvidenceManager = (function () {
    var command = {
      getDeptList: function (options, fn1, fn2) {//获取部门列表
        TY.reqPost(options, TY.URL.evidence.deptList, fn1, fn2);
        return command;
      },
      getSortList: function (options, fn1, fn2) {//获取文件类型列表
        TY.reqPost(options, TY.URL.evidence.sortList, fn1, fn2);
        return command;
      },
      getCaseTopicList: function (options, fn1, fn2) {//获取案件类型列表
        TY.reqPost(options, TY.URL.evidence.caseTopicList, fn1, fn2);
        return command;
      },
      getFileList: function (options, fn1, fn2) {//获取文件列表
        TY.reqPost(options, TY.URL.evidence.fileList, fn1, fn2);
        return command;
      },
      getDevList: function (options, fn1, fn2) {//获取设备列表
        TY.reqPost(options, TY.URL.evidence.devList, fn1, fn2);
        return command;
      },
      getFileDetail: function (options, fn1, fn2) {//获取文件信息
        TY.compositePost(TY.URL.evidence.fileDetail, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },
      delFile: function (options, fn1, fn2) {//删除文件
        TY.compositePost(TY.URL.evidence.delFile, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },
      saveFile: function (options, fn1, fn2) {//编辑文件信息
        TY.compositePost(TY.URL.evidence.saveFile, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2);
        return command;
      },
      downFile: function (options, fn1, fn2, fn3) {//下载文件
        return TY.compositeDown(TY.URL.evidence.downFile, options, (options) => {
          if(!options.id){
            return 'id';
          }
        }, fn1, fn2, fn3);
      },
      exportCsv: function (options, fn1, fn2, fn3) {//导出文件
        return TY.compositeDown(TY.URL.evidence.exportCsv, options, null, fn1, fn2, fn3);
      },
      openN: function (options, fn1, fn2) {//记录播放次数
        TY.reqPost(options, TY.URL.evidence.openN, fn1, fn2);
        return command;
      },
      uploadFile: function (options, fn1, fn2, fn3) {//上传文件
        return TY.compositeUpload(TY.URL.evidence.uploadFile, options, null, fn1, fn2, fn3);
      },
      getMajorCount: function (options, fn1, fn2) {//获取文档重要级别统计
        TY.compositePost(TY.URL.evidence.majorCount, options, null, fn1, fn2);
        return command;
      },
      getImportCount: function (options, fn1, fn2) {//获取文档导入时段统计
        TY.compositePost(TY.URL.evidence.importCount, options, null, fn1, fn2);
        return command;
      },
      getPersonnelCount: function (options, fn1, fn2) {//获取人员资料统计
        TY.compositePost(TY.URL.evidence.personnelCount, options, null, fn1, fn2);
        return command;
      },
      getAssessCount: function (options, fn1, fn2) {//获取人员考核统计
        TY.compositePost(TY.URL.evidence.assessCount, options, null, fn1, fn2);
        return command;
      },
    };
    return command;
  })();
  TY.SOS = (function () {
    var callback, data;
    var addListenEvent = function (callback) {//注册信息通知回调事件
      callback = callback;
    };
    var getInfo = function () {//获取sos信息
      return data;
    };
    return this;
  })();
  TY.reqUpload = function (params, url, successCallback, failCallback, progressCallback) { //post上传请求
    console.log("请求参数", params, url);
    var xmlhttp;
    if (window.XMLHttpRequest) {
      // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
      xmlhttp = new XMLHttpRequest();
    } else {
      // IE6, IE5 浏览器执行代码
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.onreadystatechange = function () {
      if (xmlhttp.readyState == 4) {
        if (xmlhttp.status == TY.RcCode.RC_CODE_S_OK.code) {
          var data = eval('(' + ('response' in xmlhttp ? xmlhttp.response : xmlhttp.responseText || xmlhttp.responseXML) + ')');
          console.log("返回结果", data);
          if (typeof data == 'object' && data.status == TY.RcCode.RC_CODE_S_OK.code && successCallback) {
            successCallback(data);
            return;
          }
        }
        TY.USER.checkTokenInvalidEvent&&TY.USER.checkTokenInvalidEvent(data && data.status || '');
        if (failCallback) {
          failCallback(TY.RcCode.returnMsg(TY.RcCode.getRcCode(data && data.status || '')));
        }
      }
    };
    xmlhttp.open("POST", url, true);
    if (progressCallback) xmlhttp.upload.onprogress = progressCallback;
    if (TY.USER.token) {
      xmlhttp.setRequestHeader("x-access-token", TY.USER.token);
    }
    let fd = new FormData();
    for (let key in params) {
      fd.append(key, params[key]);
    }
    xmlhttp.send(fd);
    return xmlhttp;
  };
  TY.reqDown = function (params, url, resolve, reject, progressCallback) { //下载文件
    var req = new XMLHttpRequest();
    req.open("POST", url, true);
    //监听进度事件

    /**
     * 添加事件监听事件
     * @function addEventListener -添加事件监听事件
     * @param {} event
     * @param {} eventCallback(data)
     */
    req.addEventListener("progress", function (evt) {
      // console.log(evt.loaded / evt.total);
      progressCallback && progressCallback(evt);
    }, false);
    req.responseType = "blob";
    req.setRequestHeader("x-access-token",TY.USER.token);
    req.setRequestHeader("Content-Type","application/json");
    req.onreadystatechange = function () {
      var status = req.getResponseHeader("status");
      if (req.readyState === 4 && req.status === TY.RcCode.RC_CODE_S_OK.code&&status==TY.RcCode.RC_CODE_S_OK.code) {
        if (typeof window.chrome != 'undefined') {
          // Chrome version
          var link = document.createElement('a');
          link.href = window.URL.createObjectURL(req.response);
          link.download = decodeURIComponent(req.getResponseHeader("filename"));
          link.click();
          resolve&&resolve(TY.RcCode.returnMsg(TY.RcCode.getRcCode(req.getResponseHeader("status"))));
          return;
        }
        // else if (typeof window.navigator.msSaveBlob !== 'undefined') {
        //     // IE version
        //     var blob = new Blob([req.response], { type:req.getResponseHeader("content-type") });
        //     window.navigator.msSaveBlob(blob, filename);
        // } else {
        //     // Firefox version
        //     var file = new File([req.response], filename, { type: 'application/force-download' });
        //     window.open(URL.createObjectURL(file));
        // }
        TY.USER.checkTokenInvalidEvent&&TY.USER.checkTokenInvalidEvent(data && data.status);
        reject&&reject(TY.RcCode.returnMsg(TY.RcCode.ABORT_ERR));
      }
    };
    req.send(JSON.stringify(params));
    return req;
  };
  TY.reqPost = function (params, url, successCallback, failCallback) { //所有的post请求
    console.log("请求参数", params, url);
    var xmlhttp;
    if (window.XMLHttpRequest) {
      // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
      xmlhttp = new XMLHttpRequest();
    } else {
      // IE6, IE5 浏览器执行代码
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.onreadystatechange = function () {
      if (xmlhttp.readyState == 4) {
        if( xmlhttp.status == TY.RcCode.RC_CODE_S_OK.code){
          var data = eval('(' + ('response' in xmlhttp ? xmlhttp.response : xmlhttp.responseText || xmlhttp.responseXML) + ')');
          console.log("返回结果", data);
          if (typeof data == 'object' && data.status == TY.RcCode.RC_CODE_S_OK.code && successCallback) {
            successCallback(data);
            return;
          }
        }
        TY.USER.checkTokenInvalidEvent&&TY.USER.checkTokenInvalidEvent(data.status);
        if (failCallback) {
          failCallback(TY.RcCode.returnMsg(TY.RcCode.getRcCode(data.status)));
        }
      }
    };
    xmlhttp.open("POST", url, true);
    if (TY.USER.token) {
      xmlhttp.setRequestHeader("x-access-token", TY.USER.token);
    }
    xmlhttp.setRequestHeader("content-type", "application/json");
    xmlhttp.send(JSON.stringify(params));
  };
  TY.reqGet = function (params, url, successCallback, failCallback) { //所有的get请求
    console.log("请求参数", params, url);
    var xmlhttp;
    if (window.XMLHttpRequest) {
      // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
      xmlhttp = new XMLHttpRequest();
    } else {
      // IE6, IE5 浏览器执行代码
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.onreadystatechange = function () {
      if (xmlhttp.readyState == 4) {
        if (xmlhttp.status == TY.RcCode.RC_CODE_S_OK.code) {
          var data = eval('(' + ('response' in xmlhttp ? xmlhttp.response : xmlhttp.responseText || xmlhttp.responseXML) + ')');
          console.log("返回结果", data);
          if (typeof data == 'object' && data.status == TY.RcCode.RC_CODE_S_OK.code) {
            if (successCallback) {
              successCallback(data);
            }
            return;
          }
        }
        TY.USER.checkTokenInvalidEvent&&TY.USER.checkTokenInvalidEvent(data.status);
        if (failCallback) {
          failCallback(data);
        }
      }
    };
    xmlhttp.open("GET", url, true);
    if (TY.USER.token) {
      xmlhttp.setRequestHeader("x-access-token", TY.USER.token);
    }
    xmlhttp.setRequestHeader("content-type", "application/json");
    xmlhttp.send(JSON.stringify(params));
  };
  TY.reqPut = function (params, url, successCallback, failCallback) { //所有的get请求
    console.log("请求参数", params, url);
    var xmlhttp;
    if (window.XMLHttpRequest) {
      // IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
      xmlhttp = new XMLHttpRequest();
    } else {
      // IE6, IE5 浏览器执行代码
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.onreadystatechange = function () {
      if (xmlhttp.readyState == 4) {
        if (xmlhttp.status == TY.RcCode.RC_CODE_S_OK.code) {
          var data = eval('(' + ('response' in xmlhttp ? xmlhttp.response : xmlhttp.responseText || xmlhttp.responseXML) + ')');
          console.log("返回结果", data);
          if (typeof data == 'object' && data.status == TY.RcCode.RC_CODE_S_OK.code) {
            if (successCallback) {
              successCallback(data);
            }
            return;
          }
        }
        TY.USER.checkTokenInvalidEvent&&TY.USER.checkTokenInvalidEvent(data.status);
        if (failCallback) {
          failCallback(data);
        }
      }
    };
    xmlhttp.open("PUT", url, true);
    if (TY.USER.token) {
      xmlhttp.setRequestHeader("x-access-token", TY.USER.token);
    }
    xmlhttp.setRequestHeader("content-type", "application/json");
    xmlhttp.send(JSON.stringify(params));
  };

  TY.RcCode = { //返回码
    returnMsg(RC_CODE,msg){
      if (!RC_CODE) {
        RC_CODE=  this.OTHER_ABNORMAL;
      }
      msg=msg?msg:"";
      return {status:RC_CODE.code,msg:msg+RC_CODE.msg};
    },
    getRcCode(code){
      for(var p in this){
        var obj = this[p];
        if(!typeof(obj)!='function'&&obj.code==code){
          return obj;
        }
      }
      return null;
    },
    SOCKET_INIT_SUCCESS: {code:0,msg:"初始化成功"}, //socket初始化成功
    RC_CODE_S_OK: {code:200,msg:"请求成功"}, //请求成功
    ACTION_BY_BIND: {code:202,msg:"存在绑定关系,操作失败"},
    GROUP_NAME_SINGLE: {code:203,msg:"名称不能重复"},
    USER_EXIST: {code:204,msg:"已存在,不能重复"}, //请求成功
    FENCE_BY_BIND: {code:205,msg:"围栏绑定了设备,请解绑后删除"},
    WORK_SHFIT_BY_BIND: {code:206,msg:"班次绑定了设备,请解绑后删除"},
    ROLE_BY_BIND: {code:207,msg:"该角色已被用户绑定,请解绑后删除"},
    GROUP_BY_BIND: {code:208,msg:"群组绑定了设备,请解绑后删除"},
    DEPT_BY_BIND: {code:209,msg:"该部门下有人员,请删除后再次操作"},
    USER_CODE_EXIST: {code:210,msg:"人员编号冲突"},
    DEV_CODE_EXIST: {code:211,msg:"该设备识别号已注册"},
    APP_VERSION_EXIST: {code:212,msg:"app下已经发布了版本,请删除后再次操作"},
    EMPTY_TOKEN:  {code:300,msg:"token为空,可能未登录"}, //请求头没有token
    SPECIAL_PARAMS:  {code:213,msg:"参数不能包括特殊字符"}, //请求头没有token
    INVALID_TOKEN:  {code:401,msg:"token无效"}, //请求头没有token
    DEV_OUTLINE:  {code:406,msg:"设备不在线"},
    DISPATCHER_DISPATCHER:  {code:407,msg:"创建调度的人在调度"},
    EMPTY_SERVER:  {code:460,msg:"服务器地址为空"}, //请求头没有token
    EMPTY_PROPS:  {code:461,msg:"参数不能为空"}, //请求头没有token
    PARAMETERS_ABNORMAL: {code:462,msg:"参数异常"},//参数异常
    LOGINED:  {code:463,msg:"已经登录过"}, //请求头没有token
    DEVICE_FILE_DELETE_LIMIT:  {code:463,msg:"不能删除设备文件"}, //请求头没有token
    MICROPHONE_LIMIT:  {code:465,msg:"麦克风不可用"}, //请求头没有token
    RTC_ABNOMAL:  {code:466,msg:"RTC异常"}, //请求头没有token
    OTHER_TALK:  {code:467,msg:"已有人在说话"}, //请求头没有tokenNOT_SUBSCRIBE_DEVICE
    NOT_STREAM:  {code:468,msg:"该设备不在监控房间"}, //请求头没有token
    NOT_SUBSCRIBE_DEVICE:  {code:469,msg:"该设备不是被监控状态"}, //请求头没有token
    DEVICE_MONITER_EXIST:  {code:470,msg:"已经邀请该设备进入监控"}, //请求头没有token
    RECORD_LESS:  {code:471,msg:"结束失败,录制时间太短"}, //请求头没有token
    SYSTEM_ERROR: {code:500,msg:"系统错误"},
    LOGIN_FAIL:{code:410,msg:"账号密码错误"},//登录失败==》账号密码错误
    LOGIN_USER_FAIL:{code:411,msg:"用户不存在"},//登录失败==》用户不存在
    LIMIT_AUTHORITY: {code:403,msg:"没有权限"}, //权限不够,
    OTHER_ABNORMAL: {code:999,msg:"其他异常"}//参数异常
  }
})(TY);
function getmatrix(a, b, c, d, e, f) {
  var aa = Math.round(180 * Math.asin(a) / Math.PI);
  var bb = Math.round(180 * Math.acos(b) / Math.PI);
  var cc = Math.round(180 * Math.asin(c) / Math.PI);
  var dd = Math.round(180 * Math.acos(d) / Math.PI);
  var deg = 0;
  if (aa == bb || -aa == bb) {
    deg = dd;
  } else if (-aa + bb == 180) {
    deg = 180 + cc;
  } else if (aa + bb == 180) {
    deg = 360 - cc || 360 - dd;
  }
  return deg >= 360 ? 0 : deg;
  //return (aa+','+bb+','+cc+','+dd);
};
(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.io=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<r.length;o++)s(r[o]);return s}({1:[function(_dereq_,module,exports){module.exports=_dereq_("./lib/")},{"./lib/":2}],2:[function(_dereq_,module,exports){module.exports=_dereq_("./socket");module.exports.parser=_dereq_("engine.io-parser")},{"./socket":3,"engine.io-parser":19}],3:[function(_dereq_,module,exports){(function(global){var transports=_dereq_("./transports");var Emitter=_dereq_("component-emitter");var debug=_dereq_("debug")("engine.io-client:socket");var index=_dereq_("indexof");var parser=_dereq_("engine.io-parser");var parseuri=_dereq_("parseuri");var parsejson=_dereq_("parsejson");var parseqs=_dereq_("parseqs");module.exports=Socket;function noop(){}function Socket(uri,opts){if(!(this instanceof Socket))return new Socket(uri,opts);opts=opts||{};if(uri&&"object"==typeof uri){opts=uri;uri=null}if(uri){uri=parseuri(uri);opts.hostname=uri.host;opts.secure=uri.protocol=="https"||uri.protocol=="wss";opts.port=uri.port;if(uri.query)opts.query=uri.query}else if(opts.host){opts.hostname=parseuri(opts.host).host}this.secure=null!=opts.secure?opts.secure:global.location&&"https:"==location.protocol;if(opts.hostname&&!opts.port){opts.port=this.secure?"443":"80"}this.agent=opts.agent||false;this.hostname=opts.hostname||(global.location?location.hostname:"localhost");this.port=opts.port||(global.location&&location.port?location.port:this.secure?443:80);this.query=opts.query||{};if("string"==typeof this.query)this.query=parseqs.decode(this.query);this.upgrade=false!==opts.upgrade;this.path=(opts.path||"/engine.io").replace(/\/$/,"")+"/";this.forceJSONP=!!opts.forceJSONP;this.jsonp=false!==opts.jsonp;this.forceBase64=!!opts.forceBase64;this.enablesXDR=!!opts.enablesXDR;this.timestampParam=opts.timestampParam||"t";this.timestampRequests=opts.timestampRequests;this.transports=opts.transports||["polling","websocket"];this.readyState="";this.writeBuffer=[];this.policyPort=opts.policyPort||843;this.rememberUpgrade=opts.rememberUpgrade||false;this.binaryType=null;this.onlyBinaryUpgrades=opts.onlyBinaryUpgrades;this.perMessageDeflate=false!==opts.perMessageDeflate?opts.perMessageDeflate||{}:false;if(true===this.perMessageDeflate)this.perMessageDeflate={};if(this.perMessageDeflate&&null==this.perMessageDeflate.threshold){this.perMessageDeflate.threshold=1024}this.pfx=opts.pfx||null;this.key=opts.key||null;this.passphrase=opts.passphrase||null;this.cert=opts.cert||null;this.ca=opts.ca||null;this.ciphers=opts.ciphers||null;this.rejectUnauthorized=opts.rejectUnauthorized===undefined?null:opts.rejectUnauthorized;var freeGlobal=typeof global=="object"&&global;if(freeGlobal.global===freeGlobal){if(opts.extraHeaders&&Object.keys(opts.extraHeaders).length>0){this.extraHeaders=opts.extraHeaders}}this.open()}Socket.priorWebsocketSuccess=false;Emitter(Socket.prototype);Socket.protocol=parser.protocol;Socket.Socket=Socket;Socket.Transport=_dereq_("./transport");Socket.transports=_dereq_("./transports");Socket.parser=_dereq_("engine.io-parser");Socket.prototype.createTransport=function(name){debug('creating transport "%s"',name);var query=clone(this.query);query.EIO=parser.protocol;query.transport=name;if(this.id)query.sid=this.id;var transport=new transports[name]({agent:this.agent,hostname:this.hostname,port:this.port,secure:this.secure,path:this.path,query:query,forceJSONP:this.forceJSONP,jsonp:this.jsonp,forceBase64:this.forceBase64,enablesXDR:this.enablesXDR,timestampRequests:this.timestampRequests,timestampParam:this.timestampParam,policyPort:this.policyPort,socket:this,pfx:this.pfx,key:this.key,passphrase:this.passphrase,cert:this.cert,ca:this.ca,ciphers:this.ciphers,rejectUnauthorized:this.rejectUnauthorized,perMessageDeflate:this.perMessageDeflate,extraHeaders:this.extraHeaders});return transport};function clone(obj){var o={};for(var i in obj){if(obj.hasOwnProperty(i)){o[i]=obj[i]}}return o}Socket.prototype.open=function(){var transport;if(this.rememberUpgrade&&Socket.priorWebsocketSuccess&&this.transports.indexOf("websocket")!=-1){transport="websocket"}else if(0===this.transports.length){var self=this;setTimeout(function(){self.emit("error","No transports available")},0);return}else{transport=this.transports[0]}this.readyState="opening";try{transport=this.createTransport(transport)}catch(e){this.transports.shift();this.open();return}transport.open();this.setTransport(transport)};Socket.prototype.setTransport=function(transport){debug("setting transport %s",transport.name);var self=this;if(this.transport){debug("clearing existing transport %s",this.transport.name);this.transport.removeAllListeners()}this.transport=transport;transport.on("drain",function(){self.onDrain()}).on("packet",function(packet){self.onPacket(packet)}).on("error",function(e){self.onError(e)}).on("close",function(){self.onClose("transport close")})};Socket.prototype.probe=function(name){debug('probing transport "%s"',name);var transport=this.createTransport(name,{probe:1}),failed=false,self=this;Socket.priorWebsocketSuccess=false;function onTransportOpen(){if(self.onlyBinaryUpgrades){var upgradeLosesBinary=!this.supportsBinary&&self.transport.supportsBinary;failed=failed||upgradeLosesBinary}if(failed)return;debug('probe transport "%s" opened',name);transport.send([{type:"ping",data:"probe"}]);transport.once("packet",function(msg){if(failed)return;if("pong"==msg.type&&"probe"==msg.data){debug('probe transport "%s" pong',name);self.upgrading=true;self.emit("upgrading",transport);if(!transport)return;Socket.priorWebsocketSuccess="websocket"==transport.name;debug('pausing current transport "%s"',self.transport.name);self.transport.pause(function(){if(failed)return;if("closed"==self.readyState)return;debug("changing transport and sending upgrade packet");cleanup();self.setTransport(transport);transport.send([{type:"upgrade"}]);self.emit("upgrade",transport);transport=null;self.upgrading=false;self.flush()})}else{debug('probe transport "%s" failed',name);var err=new Error("probe error");err.transport=transport.name;self.emit("upgradeError",err)}})}function freezeTransport(){if(failed)return;failed=true;cleanup();transport.close();transport=null}function onerror(err){var error=new Error("probe error: "+err);error.transport=transport.name;freezeTransport();debug('probe transport "%s" failed because of error: %s',name,err);self.emit("upgradeError",error)}function onTransportClose(){onerror("transport closed")}function onclose(){onerror("socket closed")}function onupgrade(to){if(transport&&to.name!=transport.name){debug('"%s" works - aborting "%s"',to.name,transport.name);freezeTransport()}}function cleanup(){transport.removeListener("open",onTransportOpen);transport.removeListener("error",onerror);transport.removeListener("close",onTransportClose);self.removeListener("close",onclose);self.removeListener("upgrading",onupgrade)}transport.once("open",onTransportOpen);transport.once("error",onerror);transport.once("close",onTransportClose);this.once("close",onclose);this.once("upgrading",onupgrade);transport.open()};Socket.prototype.onOpen=function(){debug("socket open");this.readyState="open";Socket.priorWebsocketSuccess="websocket"==this.transport.name;this.emit("open");this.flush();if("open"==this.readyState&&this.upgrade&&this.transport.pause){debug("starting upgrade probes");for(var i=0,l=this.upgrades.length;i<l;i++){this.probe(this.upgrades[i])}}};Socket.prototype.onPacket=function(packet){if("opening"==this.readyState||"open"==this.readyState){debug('socket receive: type "%s", data "%s"',packet.type,packet.data);this.emit("packet",packet);this.emit("heartbeat");switch(packet.type){case"open":this.onHandshake(parsejson(packet.data));break;case"pong":this.setPing();this.emit("pong");break;case"error":var err=new Error("server error");err.code=packet.data;this.onError(err);break;case"message":this.emit("data",packet.data);this.emit("message",packet.data);break}}else{debug('packet received with socket readyState "%s"',this.readyState)}};Socket.prototype.onHandshake=function(data){this.emit("handshake",data);this.id=data.sid;this.transport.query.sid=data.sid;this.upgrades=this.filterUpgrades(data.upgrades);this.pingInterval=data.pingInterval;this.pingTimeout=data.pingTimeout;this.onOpen();if("closed"==this.readyState)return;this.setPing();this.removeListener("heartbeat",this.onHeartbeat);this.on("heartbeat",this.onHeartbeat)};Socket.prototype.onHeartbeat=function(timeout){clearTimeout(this.pingTimeoutTimer);var self=this;self.pingTimeoutTimer=setTimeout(function(){if("closed"==self.readyState)return;self.onClose("ping timeout")},timeout||self.pingInterval+self.pingTimeout)};Socket.prototype.setPing=function(){var self=this;clearTimeout(self.pingIntervalTimer);self.pingIntervalTimer=setTimeout(function(){debug("writing ping packet - expecting pong within %sms",self.pingTimeout);self.ping();self.onHeartbeat(self.pingTimeout)},self.pingInterval)};Socket.prototype.ping=function(){var self=this;this.sendPacket("ping",function(){self.emit("ping")})};Socket.prototype.onDrain=function(){this.writeBuffer.splice(0,this.prevBufferLen);this.prevBufferLen=0;if(0===this.writeBuffer.length){this.emit("drain")}else{this.flush()}};Socket.prototype.flush=function(){if("closed"!=this.readyState&&this.transport.writable&&!this.upgrading&&this.writeBuffer.length){debug("flushing %d packets in socket",this.writeBuffer.length);this.transport.send(this.writeBuffer);this.prevBufferLen=this.writeBuffer.length;this.emit("flush")}};Socket.prototype.write=Socket.prototype.send=function(msg,options,fn){this.sendPacket("message",msg,options,fn);return this};Socket.prototype.sendPacket=function(type,data,options,fn){if("function"==typeof data){fn=data;data=undefined}if("function"==typeof options){fn=options;options=null}if("closing"==this.readyState||"closed"==this.readyState){return}options=options||{};options.compress=false!==options.compress;var packet={type:type,data:data,options:options};this.emit("packetCreate",packet);this.writeBuffer.push(packet);if(fn)this.once("flush",fn);this.flush()};Socket.prototype.close=function(){if("opening"==this.readyState||"open"==this.readyState){this.readyState="closing";var self=this;if(this.writeBuffer.length){this.once("drain",function(){if(this.upgrading){waitForUpgrade()}else{close()}})}else if(this.upgrading){waitForUpgrade()}else{close()}}function close(){self.onClose("forced close");debug("socket closing - telling transport to close");self.transport.close()}function cleanupAndClose(){self.removeListener("upgrade",cleanupAndClose);self.removeListener("upgradeError",cleanupAndClose);close()}function waitForUpgrade(){self.once("upgrade",cleanupAndClose);self.once("upgradeError",cleanupAndClose)}return this};Socket.prototype.onError=function(err){debug("socket error %j",err);Socket.priorWebsocketSuccess=false;this.emit("error",err);this.onClose("transport error",err)};Socket.prototype.onClose=function(reason,desc){if("opening"==this.readyState||"open"==this.readyState||"closing"==this.readyState){debug('socket close with reason: "%s"',reason);var self=this;clearTimeout(this.pingIntervalTimer);clearTimeout(this.pingTimeoutTimer);this.transport.removeAllListeners("close");this.transport.close();this.transport.removeAllListeners();this.readyState="closed";this.id=null;this.emit("close",reason,desc);self.writeBuffer=[];self.prevBufferLen=0}};Socket.prototype.filterUpgrades=function(upgrades){var filteredUpgrades=[];for(var i=0,j=upgrades.length;i<j;i++){if(~index(this.transports,upgrades[i]))filteredUpgrades.push(upgrades[i])}return filteredUpgrades}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./transport":4,"./transports":5,"component-emitter":15,debug:17,"engine.io-parser":19,indexof:23,parsejson:26,parseqs:27,parseuri:28}],4:[function(_dereq_,module,exports){var parser=_dereq_("engine.io-parser");var Emitter=_dereq_("component-emitter");module.exports=Transport;function Transport(opts){this.path=opts.path;this.hostname=opts.hostname;this.port=opts.port;this.secure=opts.secure;this.query=opts.query;this.timestampParam=opts.timestampParam;this.timestampRequests=opts.timestampRequests;this.readyState="";this.agent=opts.agent||false;this.socket=opts.socket;this.enablesXDR=opts.enablesXDR;this.pfx=opts.pfx;this.key=opts.key;this.passphrase=opts.passphrase;this.cert=opts.cert;this.ca=opts.ca;this.ciphers=opts.ciphers;this.rejectUnauthorized=opts.rejectUnauthorized;this.extraHeaders=opts.extraHeaders}Emitter(Transport.prototype);Transport.prototype.onError=function(msg,desc){var err=new Error(msg);err.type="TransportError";err.description=desc;this.emit("error",err);return this};Transport.prototype.open=function(){if("closed"==this.readyState||""==this.readyState){this.readyState="opening";this.doOpen()}return this};Transport.prototype.close=function(){if("opening"==this.readyState||"open"==this.readyState){this.doClose();this.onClose()}return this};Transport.prototype.send=function(packets){if("open"==this.readyState){this.write(packets)}else{throw new Error("Transport not open")}};Transport.prototype.onOpen=function(){this.readyState="open";this.writable=true;this.emit("open")};Transport.prototype.onData=function(data){var packet=parser.decodePacket(data,this.socket.binaryType);this.onPacket(packet)};Transport.prototype.onPacket=function(packet){this.emit("packet",packet)};Transport.prototype.onClose=function(){this.readyState="closed";this.emit("close")}},{"component-emitter":15,"engine.io-parser":19}],5:[function(_dereq_,module,exports){(function(global){var XMLHttpRequest=_dereq_("xmlhttprequest-ssl");var XHR=_dereq_("./polling-xhr");var JSONP=_dereq_("./polling-jsonp");var websocket=_dereq_("./websocket");exports.polling=polling;exports.websocket=websocket;function polling(opts){var xhr;var xd=false;var xs=false;var jsonp=false!==opts.jsonp;if(global.location){var isSSL="https:"==location.protocol;var port=location.port;if(!port){port=isSSL?443:80}xd=opts.hostname!=location.hostname||port!=opts.port;xs=opts.secure!=isSSL}opts.xdomain=xd;opts.xscheme=xs;xhr=new XMLHttpRequest(opts);if("open"in xhr&&!opts.forceJSONP){return new XHR(opts)}else{if(!jsonp)throw new Error("JSONP disabled");return new JSONP(opts)}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./polling-jsonp":6,"./polling-xhr":7,"./websocket":9,"xmlhttprequest-ssl":10}],6:[function(_dereq_,module,exports){(function(global){var Polling=_dereq_("./polling");var inherit=_dereq_("component-inherit");module.exports=JSONPPolling;var rNewline=/\n/g;var rEscapedNewline=/\\n/g;var callbacks;var index=0;function empty(){}function JSONPPolling(opts){Polling.call(this,opts);this.query=this.query||{};if(!callbacks){if(!global.___eio)global.___eio=[];callbacks=global.___eio}this.index=callbacks.length;var self=this;callbacks.push(function(msg){self.onData(msg)});this.query.j=this.index;if(global.document&&global.addEventListener){global.addEventListener("beforeunload",function(){if(self.script)self.script.onerror=empty},false)}}inherit(JSONPPolling,Polling);JSONPPolling.prototype.supportsBinary=false;JSONPPolling.prototype.doClose=function(){if(this.script){this.script.parentNode.removeChild(this.script);this.script=null}if(this.form){this.form.parentNode.removeChild(this.form);this.form=null;this.iframe=null}Polling.prototype.doClose.call(this)};JSONPPolling.prototype.doPoll=function(){var self=this;var script=document.createElement("script");if(this.script){this.script.parentNode.removeChild(this.script);this.script=null}script.async=true;script.src=this.uri();script.onerror=function(e){self.onError("jsonp poll error",e)};var insertAt=document.getElementsByTagName("script")[0];if(insertAt){insertAt.parentNode.insertBefore(script,insertAt)}else{(document.head||document.body).appendChild(script)}this.script=script;var isUAgecko="undefined"!=typeof navigator&&/gecko/i.test(navigator.userAgent);if(isUAgecko){setTimeout(function(){var iframe=document.createElement("iframe");document.body.appendChild(iframe);document.body.removeChild(iframe)},100)}};JSONPPolling.prototype.doWrite=function(data,fn){var self=this;if(!this.form){var form=document.createElement("form");var area=document.createElement("textarea");var id=this.iframeId="eio_iframe_"+this.index;var iframe;form.className="socketio";form.style.position="absolute";form.style.top="-1000px";form.style.left="-1000px";form.target=id;form.method="POST";form.setAttribute("accept-charset","utf-8");area.name="d";form.appendChild(area);document.body.appendChild(form);this.form=form;this.area=area}this.form.action=this.uri();function complete(){initIframe();fn()}function initIframe(){if(self.iframe){try{self.form.removeChild(self.iframe)}catch(e){self.onError("jsonp polling iframe removal error",e)}}try{var html='<iframe src="javascript:0" name="'+self.iframeId+'">';iframe=document.createElement(html)}catch(e){iframe=document.createElement("iframe");iframe.name=self.iframeId;iframe.src="javascript:0"}iframe.id=self.iframeId;self.form.appendChild(iframe);self.iframe=iframe}initIframe();data=data.replace(rEscapedNewline,"\\\n");this.area.value=data.replace(rNewline,"\\n");try{this.form.submit()}catch(e){}if(this.iframe.attachEvent){this.iframe.onreadystatechange=function(){if(self.iframe.readyState=="complete"){complete()}}}else{this.iframe.onload=complete}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./polling":8,"component-inherit":16}],7:[function(_dereq_,module,exports){(function(global){var XMLHttpRequest=_dereq_("xmlhttprequest-ssl");var Polling=_dereq_("./polling");var Emitter=_dereq_("component-emitter");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:polling-xhr");module.exports=XHR;module.exports.Request=Request;function empty(){}function XHR(opts){Polling.call(this,opts);if(global.location){var isSSL="https:"==location.protocol;var port=location.port;if(!port){port=isSSL?443:80}this.xd=opts.hostname!=global.location.hostname||port!=opts.port;this.xs=opts.secure!=isSSL}else{this.extraHeaders=opts.extraHeaders}}inherit(XHR,Polling);XHR.prototype.supportsBinary=true;XHR.prototype.request=function(opts){opts=opts||{};opts.uri=this.uri();opts.xd=this.xd;opts.xs=this.xs;opts.agent=this.agent||false;opts.supportsBinary=this.supportsBinary;opts.enablesXDR=this.enablesXDR;opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;opts.extraHeaders=this.extraHeaders;return new Request(opts)};XHR.prototype.doWrite=function(data,fn){var isBinary=typeof data!=="string"&&data!==undefined;var req=this.request({method:"POST",data:data,isBinary:isBinary});var self=this;req.on("success",fn);req.on("error",function(err){self.onError("xhr post error",err)});this.sendXhr=req};XHR.prototype.doPoll=function(){debug("xhr poll");var req=this.request();var self=this;req.on("data",function(data){self.onData(data)});req.on("error",function(err){self.onError("xhr poll error",err)});this.pollXhr=req};function Request(opts){this.method=opts.method||"GET";this.uri=opts.uri;this.xd=!!opts.xd;this.xs=!!opts.xs;this.async=false!==opts.async;this.data=undefined!=opts.data?opts.data:null;this.agent=opts.agent;this.isBinary=opts.isBinary;this.supportsBinary=opts.supportsBinary;this.enablesXDR=opts.enablesXDR;this.pfx=opts.pfx;this.key=opts.key;this.passphrase=opts.passphrase;this.cert=opts.cert;this.ca=opts.ca;this.ciphers=opts.ciphers;this.rejectUnauthorized=opts.rejectUnauthorized;this.extraHeaders=opts.extraHeaders;this.create()}Emitter(Request.prototype);Request.prototype.create=function(){var opts={agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR};opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;var xhr=this.xhr=new XMLHttpRequest(opts);var self=this;try{debug("xhr open %s: %s",this.method,this.uri);xhr.open(this.method,this.uri,this.async);try{if(this.extraHeaders){xhr.setDisableHeaderCheck(true);for(var i in this.extraHeaders){if(this.extraHeaders.hasOwnProperty(i)){xhr.setRequestHeader(i,this.extraHeaders[i])}}}}catch(e){}if(this.supportsBinary){xhr.responseType="arraybuffer"}if("POST"==this.method){try{if(this.isBinary){xhr.setRequestHeader("Content-type","application/octet-stream")}else{xhr.setRequestHeader("Content-type","text/plain;charset=UTF-8")}}catch(e){}}if("withCredentials"in xhr){xhr.withCredentials=true}if(this.hasXDR()){xhr.onload=function(){self.onLoad()};xhr.onerror=function(){self.onError(xhr.responseText)}}else{xhr.onreadystatechange=function(){if(4!=xhr.readyState)return;if(200==xhr.status||1223==xhr.status){self.onLoad()}else{setTimeout(function(){self.onError(xhr.status)},0)}}}debug("xhr data %s",this.data);xhr.send(this.data)}catch(e){setTimeout(function(){self.onError(e)},0);return}if(global.document){this.index=Request.requestsCount++;Request.requests[this.index]=this}};Request.prototype.onSuccess=function(){this.emit("success");this.cleanup()};Request.prototype.onData=function(data){this.emit("data",data);this.onSuccess()};Request.prototype.onError=function(err){this.emit("error",err);this.cleanup(true)};Request.prototype.cleanup=function(fromError){if("undefined"==typeof this.xhr||null===this.xhr){return}if(this.hasXDR()){this.xhr.onload=this.xhr.onerror=empty}else{this.xhr.onreadystatechange=empty}if(fromError){try{this.xhr.abort()}catch(e){}}if(global.document){delete Request.requests[this.index]}this.xhr=null};Request.prototype.onLoad=function(){var data;try{var contentType;try{contentType=this.xhr.getResponseHeader("Content-Type").split(";")[0]}catch(e){}if(contentType==="application/octet-stream"){data=this.xhr.response}else{if(!this.supportsBinary){data=this.xhr.responseText}else{try{data=String.fromCharCode.apply(null,new Uint8Array(this.xhr.response))}catch(e){var ui8Arr=new Uint8Array(this.xhr.response);var dataArray=[];for(var idx=0,length=ui8Arr.length;idx<length;idx++){dataArray.push(ui8Arr[idx])}data=String.fromCharCode.apply(null,dataArray)}}}}catch(e){this.onError(e)}if(null!=data){this.onData(data)}};Request.prototype.hasXDR=function(){return"undefined"!==typeof global.XDomainRequest&&!this.xs&&this.enablesXDR};Request.prototype.abort=function(){this.cleanup()};if(global.document){Request.requestsCount=0;Request.requests={};if(global.attachEvent){global.attachEvent("onunload",unloadHandler)}else if(global.addEventListener){global.addEventListener("beforeunload",unloadHandler,false)}}function unloadHandler(){for(var i in Request.requests){if(Request.requests.hasOwnProperty(i)){Request.requests[i].abort()}}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./polling":8,"component-emitter":15,"component-inherit":16,debug:17,"xmlhttprequest-ssl":10}],8:[function(_dereq_,module,exports){var Transport=_dereq_("../transport");var parseqs=_dereq_("parseqs");var parser=_dereq_("engine.io-parser");var inherit=_dereq_("component-inherit");var yeast=_dereq_("yeast");var debug=_dereq_("debug")("engine.io-client:polling");module.exports=Polling;var hasXHR2=function(){var XMLHttpRequest=_dereq_("xmlhttprequest-ssl");var xhr=new XMLHttpRequest({xdomain:false});return null!=xhr.responseType}();function Polling(opts){var forceBase64=opts&&opts.forceBase64;if(!hasXHR2||forceBase64){this.supportsBinary=false}Transport.call(this,opts)}inherit(Polling,Transport);Polling.prototype.name="polling";Polling.prototype.doOpen=function(){this.poll()};Polling.prototype.pause=function(onPause){var pending=0;var self=this;this.readyState="pausing";function pause(){debug("paused");self.readyState="paused";onPause()}if(this.polling||!this.writable){var total=0;if(this.polling){debug("we are currently polling - waiting to pause");total++;this.once("pollComplete",function(){debug("pre-pause polling complete");--total||pause()})}if(!this.writable){debug("we are currently writing - waiting to pause");total++;this.once("drain",function(){debug("pre-pause writing complete");--total||pause()})}}else{pause()}};Polling.prototype.poll=function(){debug("polling");this.polling=true;this.doPoll();this.emit("poll")};Polling.prototype.onData=function(data){var self=this;debug("polling got data %s",data);var callback=function(packet,index,total){if("opening"==self.readyState){self.onOpen()}if("close"==packet.type){self.onClose();return false}self.onPacket(packet)};parser.decodePayload(data,this.socket.binaryType,callback);if("closed"!=this.readyState){this.polling=false;this.emit("pollComplete");if("open"==this.readyState){this.poll()}else{debug('ignoring poll - transport state "%s"',this.readyState)}}};Polling.prototype.doClose=function(){var self=this;function close(){debug("writing close packet");self.write([{type:"close"}])}if("open"==this.readyState){debug("transport open - closing");close()}else{debug("transport not open - deferring close");this.once("open",close)}};Polling.prototype.write=function(packets){var self=this;this.writable=false;var callbackfn=function(){self.writable=true;self.emit("drain")};var self=this;parser.encodePayload(packets,this.supportsBinary,function(data){self.doWrite(data,callbackfn)})};Polling.prototype.uri=function(){var query=this.query||{};var schema=this.secure?"https":"http";var port="";if(false!==this.timestampRequests){query[this.timestampParam]=yeast()}if(!this.supportsBinary&&!query.sid){query.b64=1}query=parseqs.encode(query);if(this.port&&("https"==schema&&this.port!=443||"http"==schema&&this.port!=80)){port=":"+this.port}if(query.length){query="?"+query}var ipv6=this.hostname.indexOf(":")!==-1;return schema+"://"+(ipv6?"["+this.hostname+"]":this.hostname)+port+this.path+query}},{"../transport":4,"component-inherit":16,debug:17,"engine.io-parser":19,parseqs:27,"xmlhttprequest-ssl":10,yeast:30}],9:[function(_dereq_,module,exports){(function(global){var Transport=_dereq_("../transport");var parser=_dereq_("engine.io-parser");var parseqs=_dereq_("parseqs");var inherit=_dereq_("component-inherit");var yeast=_dereq_("yeast");var debug=_dereq_("debug")("engine.io-client:websocket");var BrowserWebSocket=global.WebSocket||global.MozWebSocket;var WebSocket=BrowserWebSocket;if(!WebSocket&&typeof window==="undefined"){try{WebSocket=_dereq_("ws")}catch(e){}}module.exports=WS;function WS(opts){var forceBase64=opts&&opts.forceBase64;if(forceBase64){this.supportsBinary=false}this.perMessageDeflate=opts.perMessageDeflate;Transport.call(this,opts)}inherit(WS,Transport);WS.prototype.name="websocket";WS.prototype.supportsBinary=true;WS.prototype.doOpen=function(){if(!this.check()){return}var self=this;var uri=this.uri();var protocols=void 0;var opts={agent:this.agent,perMessageDeflate:this.perMessageDeflate};opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;if(this.extraHeaders){opts.headers=this.extraHeaders}this.ws=BrowserWebSocket?new WebSocket(uri):new WebSocket(uri,protocols,opts);if(this.ws.binaryType===undefined){this.supportsBinary=false}if(this.ws.supports&&this.ws.supports.binary){this.supportsBinary=true;this.ws.binaryType="buffer"}else{this.ws.binaryType="arraybuffer"}this.addEventListeners()};WS.prototype.addEventListeners=function(){var self=this;this.ws.onopen=function(){self.onOpen()};this.ws.onclose=function(){self.onClose()};this.ws.onmessage=function(ev){self.onData(ev.data)};this.ws.onerror=function(e){self.onError("websocket error",e)}};if("undefined"!=typeof navigator&&/iPad|iPhone|iPod/i.test(navigator.userAgent)){WS.prototype.onData=function(data){var self=this;setTimeout(function(){Transport.prototype.onData.call(self,data)},0)}}WS.prototype.write=function(packets){var self=this;this.writable=false;var total=packets.length;for(var i=0,l=total;i<l;i++){(function(packet){parser.encodePacket(packet,self.supportsBinary,function(data){if(!BrowserWebSocket){var opts={};if(packet.options){opts.compress=packet.options.compress}if(self.perMessageDeflate){var len="string"==typeof data?global.Buffer.byteLength(data):data.length;if(len<self.perMessageDeflate.threshold){opts.compress=false}}}try{if(BrowserWebSocket){self.ws.send(data)}else{self.ws.send(data,opts)}}catch(e){debug("websocket closed before onclose event")}--total||done()})})(packets[i])}function done(){self.emit("flush");setTimeout(function(){self.writable=true;self.emit("drain")},0)}};WS.prototype.onClose=function(){Transport.prototype.onClose.call(this)};WS.prototype.doClose=function(){if(typeof this.ws!=="undefined"){this.ws.close()}};WS.prototype.uri=function(){var query=this.query||{};var schema=this.secure?"wss":"ws";var port="";if(this.port&&("wss"==schema&&this.port!=443||"ws"==schema&&this.port!=80)){port=":"+this.port}if(this.timestampRequests){query[this.timestampParam]=yeast()}if(!this.supportsBinary){query.b64=1}query=parseqs.encode(query);if(query.length){query="?"+query}var ipv6=this.hostname.indexOf(":")!==-1;return schema+"://"+(ipv6?"["+this.hostname+"]":this.hostname)+port+this.path+query};WS.prototype.check=function(){return!!WebSocket&&!("__initialize"in WebSocket&&this.name===WS.prototype.name)}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"../transport":4,"component-inherit":16,debug:17,"engine.io-parser":19,parseqs:27,ws:undefined,yeast:30}],10:[function(_dereq_,module,exports){var hasCORS=_dereq_("has-cors");module.exports=function(opts){var xdomain=opts.xdomain;var xscheme=opts.xscheme;var enablesXDR=opts.enablesXDR;try{if("undefined"!=typeof XMLHttpRequest&&(!xdomain||hasCORS)){return new XMLHttpRequest}}catch(e){}try{if("undefined"!=typeof XDomainRequest&&!xscheme&&enablesXDR){return new XDomainRequest}}catch(e){}if(!xdomain){try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(e){}}}},{"has-cors":22}],11:[function(_dereq_,module,exports){module.exports=after;function after(count,callback,err_cb){var bail=false;err_cb=err_cb||noop;proxy.count=count;return count===0?callback():proxy;function proxy(err,result){if(proxy.count<=0){throw new Error("after called too many times")}--proxy.count;if(err){bail=true;callback(err);callback=err_cb}else if(proxy.count===0&&!bail){callback(null,result)}}}function noop(){}},{}],12:[function(_dereq_,module,exports){module.exports=function(arraybuffer,start,end){var bytes=arraybuffer.byteLength;start=start||0;end=end||bytes;if(arraybuffer.slice){return arraybuffer.slice(start,end)}if(start<0){start+=bytes}if(end<0){end+=bytes}if(end>bytes){end=bytes}if(start>=bytes||start>=end||bytes===0){return new ArrayBuffer(0)}var abv=new Uint8Array(arraybuffer);var result=new Uint8Array(end-start);for(var i=start,ii=0;i<end;i++,ii++){result[ii]=abv[i]}return result.buffer}},{}],13:[function(_dereq_,module,exports){(function(chars){"use strict";exports.encode=function(arraybuffer){var bytes=new Uint8Array(arraybuffer),i,len=bytes.length,base64="";for(i=0;i<len;i+=3){base64+=chars[bytes[i]>>2];
    base64+=chars[(bytes[i]&3)<<4|bytes[i+1]>>4];base64+=chars[(bytes[i+1]&15)<<2|bytes[i+2]>>6];base64+=chars[bytes[i+2]&63]}if(len%3===2){base64=base64.substring(0,base64.length-1)+"="}else if(len%3===1){base64=base64.substring(0,base64.length-2)+"=="}return base64};exports.decode=function(base64){var bufferLength=base64.length*.75,len=base64.length,i,p=0,encoded1,encoded2,encoded3,encoded4;if(base64[base64.length-1]==="="){bufferLength--;if(base64[base64.length-2]==="="){bufferLength--}}var arraybuffer=new ArrayBuffer(bufferLength),bytes=new Uint8Array(arraybuffer);for(i=0;i<len;i+=4){encoded1=chars.indexOf(base64[i]);encoded2=chars.indexOf(base64[i+1]);encoded3=chars.indexOf(base64[i+2]);encoded4=chars.indexOf(base64[i+3]);bytes[p++]=encoded1<<2|encoded2>>4;bytes[p++]=(encoded2&15)<<4|encoded3>>2;bytes[p++]=(encoded3&3)<<6|encoded4&63}return arraybuffer}})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")},{}],14:[function(_dereq_,module,exports){(function(global){var BlobBuilder=global.BlobBuilder||global.WebKitBlobBuilder||global.MSBlobBuilder||global.MozBlobBuilder;var blobSupported=function(){try{var a=new Blob(["hi"]);return a.size===2}catch(e){return false}}();var blobSupportsArrayBufferView=blobSupported&&function(){try{var b=new Blob([new Uint8Array([1,2])]);return b.size===2}catch(e){return false}}();var blobBuilderSupported=BlobBuilder&&BlobBuilder.prototype.append&&BlobBuilder.prototype.getBlob;function mapArrayBufferViews(ary){for(var i=0;i<ary.length;i++){var chunk=ary[i];if(chunk.buffer instanceof ArrayBuffer){var buf=chunk.buffer;if(chunk.byteLength!==buf.byteLength){var copy=new Uint8Array(chunk.byteLength);copy.set(new Uint8Array(buf,chunk.byteOffset,chunk.byteLength));buf=copy.buffer}ary[i]=buf}}}function BlobBuilderConstructor(ary,options){options=options||{};var bb=new BlobBuilder;mapArrayBufferViews(ary);for(var i=0;i<ary.length;i++){bb.append(ary[i])}return options.type?bb.getBlob(options.type):bb.getBlob()}function BlobConstructor(ary,options){mapArrayBufferViews(ary);return new Blob(ary,options||{})}module.exports=function(){if(blobSupported){return blobSupportsArrayBufferView?global.Blob:BlobConstructor}else if(blobBuilderSupported){return BlobBuilderConstructor}else{return undefined}}()}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],15:[function(_dereq_,module,exports){module.exports=Emitter;function Emitter(obj){if(obj)return mixin(obj)}function mixin(obj){for(var key in Emitter.prototype){obj[key]=Emitter.prototype[key]}return obj}Emitter.prototype.on=Emitter.prototype.addEventListener=function(event,fn){this._callbacks=this._callbacks||{};(this._callbacks[event]=this._callbacks[event]||[]).push(fn);return this};Emitter.prototype.once=function(event,fn){var self=this;this._callbacks=this._callbacks||{};function on(){self.off(event,on);fn.apply(this,arguments)}on.fn=fn;this.on(event,on);return this};Emitter.prototype.off=Emitter.prototype.removeListener=Emitter.prototype.removeAllListeners=Emitter.prototype.removeEventListener=function(event,fn){this._callbacks=this._callbacks||{};if(0==arguments.length){this._callbacks={};return this}var callbacks=this._callbacks[event];if(!callbacks)return this;if(1==arguments.length){delete this._callbacks[event];return this}var cb;for(var i=0;i<callbacks.length;i++){cb=callbacks[i];if(cb===fn||cb.fn===fn){callbacks.splice(i,1);break}}return this};Emitter.prototype.emit=function(event){this._callbacks=this._callbacks||{};var args=[].slice.call(arguments,1),callbacks=this._callbacks[event];if(callbacks){callbacks=callbacks.slice(0);for(var i=0,len=callbacks.length;i<len;++i){callbacks[i].apply(this,args)}}return this};Emitter.prototype.listeners=function(event){this._callbacks=this._callbacks||{};return this._callbacks[event]||[]};Emitter.prototype.hasListeners=function(event){return!!this.listeners(event).length}},{}],16:[function(_dereq_,module,exports){module.exports=function(a,b){var fn=function(){};fn.prototype=b.prototype;a.prototype=new fn;a.prototype.constructor=a}},{}],17:[function(_dereq_,module,exports){exports=module.exports=_dereq_("./debug");exports.log=log;exports.formatArgs=formatArgs;exports.save=save;exports.load=load;exports.useColors=useColors;exports.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:localstorage();exports.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"];function useColors(){return"WebkitAppearance"in document.documentElement.style||window.console&&(console.firebug||console.exception&&console.table)||navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31}exports.formatters.j=function(v){return JSON.stringify(v)};function formatArgs(){var args=arguments;var useColors=this.useColors;args[0]=(useColors?"%c":"")+this.namespace+(useColors?" %c":" ")+args[0]+(useColors?"%c ":" ")+"+"+exports.humanize(this.diff);if(!useColors)return args;var c="color: "+this.color;args=[args[0],c,"color: inherit"].concat(Array.prototype.slice.call(args,1));var index=0;var lastC=0;args[0].replace(/%[a-z%]/g,function(match){if("%%"===match)return;index++;if("%c"===match){lastC=index}});args.splice(lastC,0,c);return args}function log(){return"object"===typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function save(namespaces){try{if(null==namespaces){exports.storage.removeItem("debug")}else{exports.storage.debug=namespaces}}catch(e){}}function load(){var r;try{r=exports.storage.debug}catch(e){}return r}exports.enable(load());function localstorage(){try{return window.localStorage}catch(e){}}},{"./debug":18}],18:[function(_dereq_,module,exports){exports=module.exports=debug;exports.coerce=coerce;exports.disable=disable;exports.enable=enable;exports.enabled=enabled;exports.humanize=_dereq_("ms");exports.names=[];exports.skips=[];exports.formatters={};var prevColor=0;var prevTime;function selectColor(){return exports.colors[prevColor++%exports.colors.length]}function debug(namespace){function disabled(){}disabled.enabled=false;function enabled(){var self=enabled;var curr=+new Date;var ms=curr-(prevTime||curr);self.diff=ms;self.prev=prevTime;self.curr=curr;prevTime=curr;if(null==self.useColors)self.useColors=exports.useColors();if(null==self.color&&self.useColors)self.color=selectColor();var args=Array.prototype.slice.call(arguments);args[0]=exports.coerce(args[0]);if("string"!==typeof args[0]){args=["%o"].concat(args)}var index=0;args[0]=args[0].replace(/%([a-z%])/g,function(match,format){if(match==="%%")return match;index++;var formatter=exports.formatters[format];if("function"===typeof formatter){var val=args[index];match=formatter.call(self,val);args.splice(index,1);index--}return match});if("function"===typeof exports.formatArgs){args=exports.formatArgs.apply(self,args)}var logFn=enabled.log||exports.log||console.log.bind(console);logFn.apply(self,args)}enabled.enabled=true;var fn=exports.enabled(namespace)?enabled:disabled;fn.namespace=namespace;return fn}function enable(namespaces){exports.save(namespaces);var split=(namespaces||"").split(/[\s,]+/);var len=split.length;for(var i=0;i<len;i++){if(!split[i])continue;namespaces=split[i].replace(/\*/g,".*?");if(namespaces[0]==="-"){exports.skips.push(new RegExp("^"+namespaces.substr(1)+"$"))}else{exports.names.push(new RegExp("^"+namespaces+"$"))}}}function disable(){exports.enable("")}function enabled(name){var i,len;for(i=0,len=exports.skips.length;i<len;i++){if(exports.skips[i].test(name)){return false}}for(i=0,len=exports.names.length;i<len;i++){if(exports.names[i].test(name)){return true}}return false}function coerce(val){if(val instanceof Error)return val.stack||val.message;return val}},{ms:25}],19:[function(_dereq_,module,exports){(function(global){var keys=_dereq_("./keys");var hasBinary=_dereq_("has-binary");var sliceBuffer=_dereq_("arraybuffer.slice");var base64encoder=_dereq_("base64-arraybuffer");var after=_dereq_("after");var utf8=_dereq_("utf8");var isAndroid=navigator.userAgent.match(/Android/i);var isPhantomJS=/PhantomJS/i.test(navigator.userAgent);var dontSendBlobs=isAndroid||isPhantomJS;exports.protocol=3;var packets=exports.packets={open:0,close:1,ping:2,pong:3,message:4,upgrade:5,noop:6};var packetslist=keys(packets);var err={type:"error",data:"parser error"};var Blob=_dereq_("blob");exports.encodePacket=function(packet,supportsBinary,utf8encode,callback){if("function"==typeof supportsBinary){callback=supportsBinary;supportsBinary=false}if("function"==typeof utf8encode){callback=utf8encode;utf8encode=null}var data=packet.data===undefined?undefined:packet.data.buffer||packet.data;if(global.ArrayBuffer&&data instanceof ArrayBuffer){return encodeArrayBuffer(packet,supportsBinary,callback)}else if(Blob&&data instanceof global.Blob){return encodeBlob(packet,supportsBinary,callback)}if(data&&data.base64){return encodeBase64Object(packet,callback)}var encoded=packets[packet.type];if(undefined!==packet.data){encoded+=utf8encode?utf8.encode(String(packet.data)):String(packet.data)}return callback(""+encoded)};function encodeBase64Object(packet,callback){var message="b"+exports.packets[packet.type]+packet.data.data;return callback(message)}function encodeArrayBuffer(packet,supportsBinary,callback){if(!supportsBinary){return exports.encodeBase64Packet(packet,callback)}var data=packet.data;var contentArray=new Uint8Array(data);var resultBuffer=new Uint8Array(1+data.byteLength);resultBuffer[0]=packets[packet.type];for(var i=0;i<contentArray.length;i++){resultBuffer[i+1]=contentArray[i]}return callback(resultBuffer.buffer)}function encodeBlobAsArrayBuffer(packet,supportsBinary,callback){if(!supportsBinary){return exports.encodeBase64Packet(packet,callback)}var fr=new FileReader;fr.onload=function(){packet.data=fr.result;exports.encodePacket(packet,supportsBinary,true,callback)};return fr.readAsArrayBuffer(packet.data)}function encodeBlob(packet,supportsBinary,callback){if(!supportsBinary){return exports.encodeBase64Packet(packet,callback)}if(dontSendBlobs){return encodeBlobAsArrayBuffer(packet,supportsBinary,callback)}var length=new Uint8Array(1);length[0]=packets[packet.type];var blob=new Blob([length.buffer,packet.data]);return callback(blob)}exports.encodeBase64Packet=function(packet,callback){var message="b"+exports.packets[packet.type];if(Blob&&packet.data instanceof global.Blob){var fr=new FileReader;fr.onload=function(){var b64=fr.result.split(",")[1];callback(message+b64)};return fr.readAsDataURL(packet.data)}var b64data;try{b64data=String.fromCharCode.apply(null,new Uint8Array(packet.data))}catch(e){var typed=new Uint8Array(packet.data);var basic=new Array(typed.length);for(var i=0;i<typed.length;i++){basic[i]=typed[i]}b64data=String.fromCharCode.apply(null,basic)}message+=global.btoa(b64data);return callback(message)};exports.decodePacket=function(data,binaryType,utf8decode){if(typeof data=="string"||data===undefined){if(data.charAt(0)=="b"){return exports.decodeBase64Packet(data.substr(1),binaryType)}if(utf8decode){try{data=utf8.decode(data)}catch(e){return err}}var type=data.charAt(0);if(Number(type)!=type||!packetslist[type]){return err}if(data.length>1){return{type:packetslist[type],data:data.substring(1)}}else{return{type:packetslist[type]}}}var asArray=new Uint8Array(data);var type=asArray[0];var rest=sliceBuffer(data,1);if(Blob&&binaryType==="blob"){rest=new Blob([rest])}return{type:packetslist[type],data:rest}};exports.decodeBase64Packet=function(msg,binaryType){var type=packetslist[msg.charAt(0)];if(!global.ArrayBuffer){return{type:type,data:{base64:true,data:msg.substr(1)}}}var data=base64encoder.decode(msg.substr(1));if(binaryType==="blob"&&Blob){data=new Blob([data])}return{type:type,data:data}};exports.encodePayload=function(packets,supportsBinary,callback){if(typeof supportsBinary=="function"){callback=supportsBinary;supportsBinary=null}var isBinary=hasBinary(packets);if(supportsBinary&&isBinary){if(Blob&&!dontSendBlobs){return exports.encodePayloadAsBlob(packets,callback)}return exports.encodePayloadAsArrayBuffer(packets,callback)}if(!packets.length){return callback("0:")}function setLengthHeader(message){return message.length+":"+message}function encodeOne(packet,doneCallback){exports.encodePacket(packet,!isBinary?false:supportsBinary,true,function(message){doneCallback(null,setLengthHeader(message))})}map(packets,encodeOne,function(err,results){return callback(results.join(""))})};function map(ary,each,done){var result=new Array(ary.length);var next=after(ary.length,done);var eachWithIndex=function(i,el,cb){each(el,function(error,msg){result[i]=msg;cb(error,result)})};for(var i=0;i<ary.length;i++){eachWithIndex(i,ary[i],next)}}exports.decodePayload=function(data,binaryType,callback){if(typeof data!="string"){return exports.decodePayloadAsBinary(data,binaryType,callback)}if(typeof binaryType==="function"){callback=binaryType;binaryType=null}var packet;if(data==""){return callback(err,0,1)}var length="",n,msg;for(var i=0,l=data.length;i<l;i++){var chr=data.charAt(i);if(":"!=chr){length+=chr}else{if(""==length||length!=(n=Number(length))){return callback(err,0,1)}msg=data.substr(i+1,n);if(length!=msg.length){return callback(err,0,1)}if(msg.length){packet=exports.decodePacket(msg,binaryType,true);if(err.type==packet.type&&err.data==packet.data){return callback(err,0,1)}var ret=callback(packet,i+n,l);if(false===ret)return}i+=n;length=""}}if(length!=""){return callback(err,0,1)}};exports.encodePayloadAsArrayBuffer=function(packets,callback){if(!packets.length){return callback(new ArrayBuffer(0))}function encodeOne(packet,doneCallback){exports.encodePacket(packet,true,true,function(data){return doneCallback(null,data)})}map(packets,encodeOne,function(err,encodedPackets){var totalLength=encodedPackets.reduce(function(acc,p){var len;if(typeof p==="string"){len=p.length}else{len=p.byteLength}return acc+len.toString().length+len+2},0);var resultArray=new Uint8Array(totalLength);var bufferIndex=0;encodedPackets.forEach(function(p){var isString=typeof p==="string";var ab=p;if(isString){var view=new Uint8Array(p.length);for(var i=0;i<p.length;i++){view[i]=p.charCodeAt(i)}ab=view.buffer}if(isString){resultArray[bufferIndex++]=0}else{resultArray[bufferIndex++]=1}var lenStr=ab.byteLength.toString();for(var i=0;i<lenStr.length;i++){resultArray[bufferIndex++]=parseInt(lenStr[i])}resultArray[bufferIndex++]=255;var view=new Uint8Array(ab);for(var i=0;i<view.length;i++){resultArray[bufferIndex++]=view[i]}});return callback(resultArray.buffer)})};exports.encodePayloadAsBlob=function(packets,callback){function encodeOne(packet,doneCallback){exports.encodePacket(packet,true,true,function(encoded){var binaryIdentifier=new Uint8Array(1);binaryIdentifier[0]=1;if(typeof encoded==="string"){var view=new Uint8Array(encoded.length);for(var i=0;i<encoded.length;i++){view[i]=encoded.charCodeAt(i)}encoded=view.buffer;binaryIdentifier[0]=0}var len=encoded instanceof ArrayBuffer?encoded.byteLength:encoded.size;var lenStr=len.toString();var lengthAry=new Uint8Array(lenStr.length+1);for(var i=0;i<lenStr.length;i++){lengthAry[i]=parseInt(lenStr[i])}lengthAry[lenStr.length]=255;if(Blob){var blob=new Blob([binaryIdentifier.buffer,lengthAry.buffer,encoded]);doneCallback(null,blob)}})}map(packets,encodeOne,function(err,results){return callback(new Blob(results))})};exports.decodePayloadAsBinary=function(data,binaryType,callback){if(typeof binaryType==="function"){callback=binaryType;binaryType=null}var bufferTail=data;var buffers=[];var numberTooLong=false;while(bufferTail.byteLength>0){var tailArray=new Uint8Array(bufferTail);var isString=tailArray[0]===0;var msgLength="";for(var i=1;;i++){if(tailArray[i]==255)break;if(msgLength.length>310){numberTooLong=true;break}msgLength+=tailArray[i]}if(numberTooLong)return callback(err,0,1);bufferTail=sliceBuffer(bufferTail,2+msgLength.length);msgLength=parseInt(msgLength);var msg=sliceBuffer(bufferTail,0,msgLength);if(isString){try{msg=String.fromCharCode.apply(null,new Uint8Array(msg))}catch(e){var typed=new Uint8Array(msg);msg="";for(var i=0;i<typed.length;i++){msg+=String.fromCharCode(typed[i])}}}buffers.push(msg);bufferTail=sliceBuffer(bufferTail,msgLength)}var total=buffers.length;buffers.forEach(function(buffer,i){callback(exports.decodePacket(buffer,binaryType,true),i,total)})}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./keys":20,after:11,"arraybuffer.slice":12,"base64-arraybuffer":13,blob:14,"has-binary":21,utf8:29}],20:[function(_dereq_,module,exports){module.exports=Object.keys||function keys(obj){var arr=[];var has=Object.prototype.hasOwnProperty;for(var i in obj){if(has.call(obj,i)){arr.push(i)}}return arr}},{}],21:[function(_dereq_,module,exports){(function(global){var isArray=_dereq_("isarray");module.exports=hasBinary;function hasBinary(data){function _hasBinary(obj){if(!obj)return false;if(global.Buffer&&global.Buffer.isBuffer(obj)||global.ArrayBuffer&&obj instanceof ArrayBuffer||global.Blob&&obj instanceof Blob||global.File&&obj instanceof File){return true}if(isArray(obj)){for(var i=0;i<obj.length;i++){if(_hasBinary(obj[i])){return true}}}else if(obj&&"object"==typeof obj){if(obj.toJSON){obj=obj.toJSON()}for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key)&&_hasBinary(obj[key])){return true}}}return false}return _hasBinary(data)}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{isarray:24}],22:[function(_dereq_,module,exports){try{module.exports=typeof XMLHttpRequest!=="undefined"&&"withCredentials"in new XMLHttpRequest}catch(err){module.exports=false}},{}],23:[function(_dereq_,module,exports){var indexOf=[].indexOf;module.exports=function(arr,obj){if(indexOf)return arr.indexOf(obj);for(var i=0;i<arr.length;++i){if(arr[i]===obj)return i}return-1}},{}],24:[function(_dereq_,module,exports){module.exports=Array.isArray||function(arr){return Object.prototype.toString.call(arr)=="[object Array]"}},{}],25:[function(_dereq_,module,exports){var s=1e3;var m=s*60;var h=m*60;var d=h*24;var y=d*365.25;module.exports=function(val,options){options=options||{};if("string"==typeof val)return parse(val);return options.long?long(val):short(val)};function parse(str){str=""+str;if(str.length>1e4)return;var match=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str);if(!match)return;var n=parseFloat(match[1]);var type=(match[2]||"ms").toLowerCase();switch(type){case"years":case"year":case"yrs":case"yr":case"y":return n*y;case"days":case"day":case"d":return n*d;case"hours":case"hour":case"hrs":case"hr":case"h":return n*h;case"minutes":case"minute":case"mins":case"min":case"m":return n*m;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n}}function short(ms){if(ms>=d)return Math.round(ms/d)+"d";if(ms>=h)return Math.round(ms/h)+"h";if(ms>=m)return Math.round(ms/m)+"m";if(ms>=s)return Math.round(ms/s)+"s";return ms+"ms"}function long(ms){return plural(ms,d,"day")||plural(ms,h,"hour")||plural(ms,m,"minute")||plural(ms,s,"second")||ms+" ms"}function plural(ms,n,name){if(ms<n)return;if(ms<n*1.5)return Math.floor(ms/n)+" "+name;return Math.ceil(ms/n)+" "+name+"s"}},{}],26:[function(_dereq_,module,exports){(function(global){var rvalidchars=/^[\],:{}\s]*$/;var rvalidescape=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;var rvalidtokens=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;var rvalidbraces=/(?:^|:|,)(?:\s*\[)+/g;var rtrimLeft=/^\s+/;var rtrimRight=/\s+$/;module.exports=function parsejson(data){if("string"!=typeof data||!data){return null}data=data.replace(rtrimLeft,"").replace(rtrimRight,"");if(global.JSON&&JSON.parse){return JSON.parse(data)}if(rvalidchars.test(data.replace(rvalidescape,"@").replace(rvalidtokens,"]").replace(rvalidbraces,""))){return new Function("return "+data)()}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],27:[function(_dereq_,module,exports){exports.encode=function(obj){var str="";for(var i in obj){if(obj.hasOwnProperty(i)){if(str.length)str+="&";str+=encodeURIComponent(i)+"="+encodeURIComponent(obj[i])}}return str};exports.decode=function(qs){var qry={};var pairs=qs.split("&");for(var i=0,l=pairs.length;i<l;i++){var pair=pairs[i].split("=");qry[decodeURIComponent(pair[0])]=decodeURIComponent(pair[1])}return qry}},{}],28:[function(_dereq_,module,exports){var re=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;var parts=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];module.exports=function parseuri(str){var src=str,b=str.indexOf("["),e=str.indexOf("]");if(b!=-1&&e!=-1){str=str.substring(0,b)+str.substring(b,e).replace(/:/g,";")+str.substring(e,str.length)}var m=re.exec(str||""),uri={},i=14;while(i--){uri[parts[i]]=m[i]||""}if(b!=-1&&e!=-1){uri.source=src;uri.host=uri.host.substring(1,uri.host.length-1).replace(/;/g,":");uri.authority=uri.authority.replace("[","").replace("]","").replace(/;/g,":");uri.ipv6uri=true}return uri}},{}],29:[function(_dereq_,module,exports){(function(global){(function(root){var freeExports=typeof exports=="object"&&exports;var freeModule=typeof module=="object"&&module&&module.exports==freeExports&&module;var freeGlobal=typeof global=="object"&&global;if(freeGlobal.global===freeGlobal||freeGlobal.window===freeGlobal){root=freeGlobal}var stringFromCharCode=String.fromCharCode;function ucs2decode(string){var output=[];var counter=0;var length=string.length;var value;var extra;while(counter<length){value=string.charCodeAt(counter++);if(value>=55296&&value<=56319&&counter<length){extra=string.charCodeAt(counter++);if((extra&64512)==56320){output.push(((value&1023)<<10)+(extra&1023)+65536)}else{output.push(value);counter--}}else{output.push(value)}}return output}function ucs2encode(array){var length=array.length;var index=-1;var value;var output="";while(++index<length){value=array[index];if(value>65535){value-=65536;output+=stringFromCharCode(value>>>10&1023|55296);value=56320|value&1023}output+=stringFromCharCode(value)}return output}function checkScalarValue(codePoint){if(codePoint>=55296&&codePoint<=57343){throw Error("Lone surrogate U+"+codePoint.toString(16).toUpperCase()+" is not a scalar value")}}function createByte(codePoint,shift){return stringFromCharCode(codePoint>>shift&63|128)}function encodeCodePoint(codePoint){if((codePoint&4294967168)==0){return stringFromCharCode(codePoint)}var symbol="";if((codePoint&4294965248)==0){symbol=stringFromCharCode(codePoint>>6&31|192)}else if((codePoint&4294901760)==0){checkScalarValue(codePoint);symbol=stringFromCharCode(codePoint>>12&15|224);symbol+=createByte(codePoint,6)}else if((codePoint&4292870144)==0){symbol=stringFromCharCode(codePoint>>18&7|240);symbol+=createByte(codePoint,12);symbol+=createByte(codePoint,6)}symbol+=stringFromCharCode(codePoint&63|128);return symbol}function utf8encode(string){var codePoints=ucs2decode(string);var length=codePoints.length;var index=-1;var codePoint;var byteString="";while(++index<length){codePoint=codePoints[index];byteString+=encodeCodePoint(codePoint)}return byteString}function readContinuationByte(){if(byteIndex>=byteCount){throw Error("Invalid byte index")}var continuationByte=byteArray[byteIndex]&255;byteIndex++;if((continuationByte&192)==128){return continuationByte&63}throw Error("Invalid continuation byte")}function decodeSymbol(){var byte1;var byte2;var byte3;var byte4;var codePoint;if(byteIndex>byteCount){throw Error("Invalid byte index")}if(byteIndex==byteCount){return false}byte1=byteArray[byteIndex]&255;byteIndex++;if((byte1&128)==0){return byte1}if((byte1&224)==192){var byte2=readContinuationByte();codePoint=(byte1&31)<<6|byte2;if(codePoint>=128){return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&240)==224){byte2=readContinuationByte();byte3=readContinuationByte();codePoint=(byte1&15)<<12|byte2<<6|byte3;if(codePoint>=2048){checkScalarValue(codePoint);return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&248)==240){byte2=readContinuationByte();byte3=readContinuationByte();byte4=readContinuationByte();codePoint=(byte1&15)<<18|byte2<<12|byte3<<6|byte4;if(codePoint>=65536&&codePoint<=1114111){return codePoint}}throw Error("Invalid UTF-8 detected")}var byteArray;var byteCount;var byteIndex;function utf8decode(byteString){byteArray=ucs2decode(byteString);byteCount=byteArray.length;byteIndex=0;var codePoints=[];var tmp;while((tmp=decodeSymbol())!==false){codePoints.push(tmp)}return ucs2encode(codePoints)}var utf8={version:"2.0.0",encode:utf8encode,decode:utf8decode};if(typeof define=="function"&&typeof define.amd=="object"&&define.amd){define(function(){return utf8})}else if(freeExports&&!freeExports.nodeType){if(freeModule){freeModule.exports=utf8}else{var object={};var hasOwnProperty=object.hasOwnProperty;for(var key in utf8){hasOwnProperty.call(utf8,key)&&(freeExports[key]=utf8[key])}}}else{root.utf8=utf8}})(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],30:[function(_dereq_,module,exports){"use strict";var alphabet="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),length=64,map={},seed=0,i=0,prev;function encode(num){var encoded="";do{encoded=alphabet[num%length]+encoded;num=Math.floor(num/length)}while(num>0);return encoded}function decode(str){var decoded=0;for(i=0;i<str.length;i++){decoded=decoded*length+map[str.charAt(i)]}return decoded}function yeast(){var now=encode(+new Date);if(now!==prev)return seed=0,prev=now;return now+"."+encode(seed++)}for(;i<length;i++)map[alphabet[i]]=i;yeast.encode=encode;yeast.decode=decode;module.exports=yeast},{}],31:[function(_dereq_,module,exports){var url=_dereq_("./url");var parser=_dereq_("socket.io-parser");var Manager=_dereq_("./manager");var debug=_dereq_("debug")("socket.io-client");module.exports=exports=lookup;var cache=exports.managers={};function lookup(uri,opts){if(typeof uri=="object"){opts=uri;uri=undefined}opts=opts||{};var parsed=url(uri);var source=parsed.source;var id=parsed.id;var path=parsed.path;var sameNamespace=cache[id]&&path in cache[id].nsps;var newConnection=opts.forceNew||opts["force new connection"]||false===opts.multiplex||sameNamespace;var io;if(newConnection){debug("ignoring socket cache for %s",source);io=Manager(source,opts)}else{if(!cache[id]){debug("new io instance for %s",source);cache[id]=Manager(source,opts)}io=cache[id]}return io.socket(parsed.path)}exports.protocol=parser.protocol;exports.connect=lookup;exports.Manager=_dereq_("./manager");exports.Socket=_dereq_("./socket")},{"./manager":32,"./socket":34,"./url":35,debug:39,"socket.io-parser":47}],32:[function(_dereq_,module,exports){var eio=_dereq_("engine.io-client");var Socket=_dereq_("./socket");var Emitter=_dereq_("component-emitter");var parser=_dereq_("socket.io-parser");var on=_dereq_("./on");var bind=_dereq_("component-bind");var debug=_dereq_("debug")("socket.io-client:manager");var indexOf=_dereq_("indexof");var Backoff=_dereq_("backo2");var has=Object.prototype.hasOwnProperty;module.exports=Manager;function Manager(uri,opts){if(!(this instanceof Manager))return new Manager(uri,opts);if(uri&&"object"==typeof uri){opts=uri;uri=undefined}opts=opts||{};opts.path=opts.path||"/socket.io";this.nsps={};this.subs=[];this.opts=opts;this.reconnection(opts.reconnection!==false);this.reconnectionAttempts(opts.reconnectionAttempts||Infinity);this.reconnectionDelay(opts.reconnectionDelay||1e3);this.reconnectionDelayMax(opts.reconnectionDelayMax||5e3);this.randomizationFactor(opts.randomizationFactor||.5);this.backoff=new Backoff({min:this.reconnectionDelay(),max:this.reconnectionDelayMax(),jitter:this.randomizationFactor()});this.timeout(null==opts.timeout?2e4:opts.timeout);this.readyState="closed";this.uri=uri;this.connecting=[];this.lastPing=null;this.encoding=false;this.packetBuffer=[];this.encoder=new parser.Encoder;this.decoder=new parser.Decoder;this.autoConnect=opts.autoConnect!==false;if(this.autoConnect)this.open()}Manager.prototype.emitAll=function(){this.emit.apply(this,arguments);for(var nsp in this.nsps){if(has.call(this.nsps,nsp)){this.nsps[nsp].emit.apply(this.nsps[nsp],arguments)}}};Manager.prototype.updateSocketIds=function(){for(var nsp in this.nsps){if(has.call(this.nsps,nsp)){this.nsps[nsp].id=this.engine.id}}};Emitter(Manager.prototype);Manager.prototype.reconnection=function(v){if(!arguments.length)return this._reconnection;this._reconnection=!!v;return this};Manager.prototype.reconnectionAttempts=function(v){if(!arguments.length)return this._reconnectionAttempts;this._reconnectionAttempts=v;return this};Manager.prototype.reconnectionDelay=function(v){if(!arguments.length)return this._reconnectionDelay;this._reconnectionDelay=v;this.backoff&&this.backoff.setMin(v);return this};Manager.prototype.randomizationFactor=function(v){if(!arguments.length)return this._randomizationFactor;this._randomizationFactor=v;this.backoff&&this.backoff.setJitter(v);return this};Manager.prototype.reconnectionDelayMax=function(v){if(!arguments.length)return this._reconnectionDelayMax;this._reconnectionDelayMax=v;this.backoff&&this.backoff.setMax(v);return this};Manager.prototype.timeout=function(v){if(!arguments.length)return this._timeout;this._timeout=v;return this};Manager.prototype.maybeReconnectOnOpen=function(){if(!this.reconnecting&&this._reconnection&&this.backoff.attempts===0){this.reconnect()}};Manager.prototype.open=Manager.prototype.connect=function(fn){debug("readyState %s",this.readyState);if(~this.readyState.indexOf("open"))return this;debug("opening %s",this.uri);this.engine=eio(this.uri,this.opts);var socket=this.engine;var self=this;this.readyState="opening";this.skipReconnect=false;var openSub=on(socket,"open",function(){self.onopen();fn&&fn()});var errorSub=on(socket,"error",function(data){debug("connect_error");self.cleanup();self.readyState="closed";self.emitAll("connect_error",data);if(fn){var err=new Error("Connection error");err.data=data;fn(err)}else{self.maybeReconnectOnOpen()}});if(false!==this._timeout){var timeout=this._timeout;debug("connect attempt will timeout after %d",timeout);var timer=setTimeout(function(){debug("connect attempt timed out after %d",timeout);openSub.destroy();socket.close();socket.emit("error","timeout");self.emitAll("connect_timeout",timeout)},timeout);this.subs.push({destroy:function(){clearTimeout(timer)}})}this.subs.push(openSub);this.subs.push(errorSub);return this};Manager.prototype.onopen=function(){debug("open");this.cleanup();this.readyState="open";this.emit("open");var socket=this.engine;this.subs.push(on(socket,"data",bind(this,"ondata")));this.subs.push(on(socket,"ping",bind(this,"onping")));this.subs.push(on(socket,"pong",bind(this,"onpong")));this.subs.push(on(socket,"error",bind(this,"onerror")));this.subs.push(on(socket,"close",bind(this,"onclose")));this.subs.push(on(this.decoder,"decoded",bind(this,"ondecoded")))};Manager.prototype.onping=function(){this.lastPing=new Date;this.emitAll("ping")};Manager.prototype.onpong=function(){this.emitAll("pong",new Date-this.lastPing)};Manager.prototype.ondata=function(data){this.decoder.add(data)};Manager.prototype.ondecoded=function(packet){this.emit("packet",packet)};Manager.prototype.onerror=function(err){debug("error",err);this.emitAll("error",err)};Manager.prototype.socket=function(nsp){var socket=this.nsps[nsp];if(!socket){socket=new Socket(this,nsp);this.nsps[nsp]=socket;var self=this;socket.on("connecting",onConnecting);
    socket.on("connect",function(){socket.id=self.engine.id});if(this.autoConnect){onConnecting()}}function onConnecting(){if(!~indexOf(self.connecting,socket)){self.connecting.push(socket)}}return socket};Manager.prototype.destroy=function(socket){var index=indexOf(this.connecting,socket);if(~index)this.connecting.splice(index,1);if(this.connecting.length)return;this.close()};Manager.prototype.packet=function(packet){debug("writing packet %j",packet);var self=this;if(!self.encoding){self.encoding=true;this.encoder.encode(packet,function(encodedPackets){for(var i=0;i<encodedPackets.length;i++){self.engine.write(encodedPackets[i],packet.options)}self.encoding=false;self.processPacketQueue()})}else{self.packetBuffer.push(packet)}};Manager.prototype.processPacketQueue=function(){if(this.packetBuffer.length>0&&!this.encoding){var pack=this.packetBuffer.shift();this.packet(pack)}};Manager.prototype.cleanup=function(){debug("cleanup");var sub;while(sub=this.subs.shift())sub.destroy();this.packetBuffer=[];this.encoding=false;this.lastPing=null;this.decoder.destroy()};Manager.prototype.close=Manager.prototype.disconnect=function(){debug("disconnect");this.skipReconnect=true;this.reconnecting=false;if("opening"==this.readyState){this.cleanup()}this.backoff.reset();this.readyState="closed";if(this.engine)this.engine.close()};Manager.prototype.onclose=function(reason){debug("onclose");this.cleanup();this.backoff.reset();this.readyState="closed";this.emit("close",reason);if(this._reconnection&&!this.skipReconnect){this.reconnect()}};Manager.prototype.reconnect=function(){if(this.reconnecting||this.skipReconnect)return this;var self=this;if(this.backoff.attempts>=this._reconnectionAttempts){debug("reconnect failed");this.backoff.reset();this.emitAll("reconnect_failed");this.reconnecting=false}else{var delay=this.backoff.duration();debug("will wait %dms before reconnect attempt",delay);this.reconnecting=true;var timer=setTimeout(function(){if(self.skipReconnect)return;debug("attempting reconnect");self.emitAll("reconnect_attempt",self.backoff.attempts);self.emitAll("reconnecting",self.backoff.attempts);if(self.skipReconnect)return;self.open(function(err){if(err){debug("reconnect attempt error");self.reconnecting=false;self.reconnect();self.emitAll("reconnect_error",err.data)}else{debug("reconnect success");self.onreconnect()}})},delay);this.subs.push({destroy:function(){clearTimeout(timer)}})}};Manager.prototype.onreconnect=function(){var attempt=this.backoff.attempts;this.reconnecting=false;this.backoff.reset();this.updateSocketIds();this.emitAll("reconnect",attempt)}},{"./on":33,"./socket":34,backo2:36,"component-bind":37,"component-emitter":38,debug:39,"engine.io-client":1,indexof:42,"socket.io-parser":47}],33:[function(_dereq_,module,exports){module.exports=on;function on(obj,ev,fn){obj.on(ev,fn);return{destroy:function(){obj.removeListener(ev,fn)}}}},{}],34:[function(_dereq_,module,exports){var parser=_dereq_("socket.io-parser");var Emitter=_dereq_("component-emitter");var toArray=_dereq_("to-array");var on=_dereq_("./on");var bind=_dereq_("component-bind");var debug=_dereq_("debug")("socket.io-client:socket");var hasBin=_dereq_("has-binary");module.exports=exports=Socket;var events={connect:1,connect_error:1,connect_timeout:1,connecting:1,disconnect:1,error:1,reconnect:1,reconnect_attempt:1,reconnect_failed:1,reconnect_error:1,reconnecting:1,ping:1,pong:1};var emit=Emitter.prototype.emit;function Socket(io,nsp){this.io=io;this.nsp=nsp;this.json=this;this.ids=0;this.acks={};this.receiveBuffer=[];this.sendBuffer=[];this.connected=false;this.disconnected=true;if(this.io.autoConnect)this.open()}Emitter(Socket.prototype);Socket.prototype.subEvents=function(){if(this.subs)return;var io=this.io;this.subs=[on(io,"open",bind(this,"onopen")),on(io,"packet",bind(this,"onpacket")),on(io,"close",bind(this,"onclose"))]};Socket.prototype.open=Socket.prototype.connect=function(){if(this.connected)return this;this.subEvents();this.io.open();if("open"==this.io.readyState)this.onopen();this.emit("connecting");return this};Socket.prototype.send=function(){var args=toArray(arguments);args.unshift("message");this.emit.apply(this,args);return this};Socket.prototype.emit=function(ev){if(events.hasOwnProperty(ev)){emit.apply(this,arguments);return this}var args=toArray(arguments);var parserType=parser.EVENT;if(hasBin(args)){parserType=parser.BINARY_EVENT}var packet={type:parserType,data:args};packet.options={};packet.options.compress=!this.flags||false!==this.flags.compress;if("function"==typeof args[args.length-1]){debug("emitting packet with ack id %d",this.ids);this.acks[this.ids]=args.pop();packet.id=this.ids++}if(this.connected){this.packet(packet)}else{this.sendBuffer.push(packet)}delete this.flags;return this};Socket.prototype.packet=function(packet){packet.nsp=this.nsp;this.io.packet(packet)};Socket.prototype.onopen=function(){debug("transport is open - connecting");if("/"!=this.nsp){this.packet({type:parser.CONNECT})}};Socket.prototype.onclose=function(reason){debug("close (%s)",reason);this.connected=false;this.disconnected=true;delete this.id;this.emit("disconnect",reason)};Socket.prototype.onpacket=function(packet){if(packet.nsp!=this.nsp)return;switch(packet.type){case parser.CONNECT:this.onconnect();break;case parser.EVENT:this.onevent(packet);break;case parser.BINARY_EVENT:this.onevent(packet);break;case parser.ACK:this.onack(packet);break;case parser.BINARY_ACK:this.onack(packet);break;case parser.DISCONNECT:this.ondisconnect();break;case parser.ERROR:this.emit("error",packet.data);break}};Socket.prototype.onevent=function(packet){var args=packet.data||[];debug("emitting event %j",args);if(null!=packet.id){debug("attaching ack callback to event");args.push(this.ack(packet.id))}if(this.connected){emit.apply(this,args)}else{this.receiveBuffer.push(args)}};Socket.prototype.ack=function(id){var self=this;var sent=false;return function(){if(sent)return;sent=true;var args=toArray(arguments);debug("sending ack %j",args);var type=hasBin(args)?parser.BINARY_ACK:parser.ACK;self.packet({type:type,id:id,data:args})}};Socket.prototype.onack=function(packet){var ack=this.acks[packet.id];if("function"==typeof ack){debug("calling ack %s with %j",packet.id,packet.data);ack.apply(this,packet.data);delete this.acks[packet.id]}else{debug("bad ack %s",packet.id)}};Socket.prototype.onconnect=function(){this.connected=true;this.disconnected=false;this.emit("connect");this.emitBuffered()};Socket.prototype.emitBuffered=function(){var i;for(i=0;i<this.receiveBuffer.length;i++){emit.apply(this,this.receiveBuffer[i])}this.receiveBuffer=[];for(i=0;i<this.sendBuffer.length;i++){this.packet(this.sendBuffer[i])}this.sendBuffer=[]};Socket.prototype.ondisconnect=function(){debug("server disconnect (%s)",this.nsp);this.destroy();this.onclose("io server disconnect")};Socket.prototype.destroy=function(){if(this.subs){for(var i=0;i<this.subs.length;i++){this.subs[i].destroy()}this.subs=null}this.io.destroy(this)};Socket.prototype.close=Socket.prototype.disconnect=function(){if(this.connected){debug("performing disconnect (%s)",this.nsp);this.packet({type:parser.DISCONNECT})}this.destroy();if(this.connected){this.onclose("io client disconnect")}return this};Socket.prototype.compress=function(compress){this.flags=this.flags||{};this.flags.compress=compress;return this}},{"./on":33,"component-bind":37,"component-emitter":38,debug:39,"has-binary":41,"socket.io-parser":47,"to-array":51}],35:[function(_dereq_,module,exports){(function(global){var parseuri=_dereq_("parseuri");var debug=_dereq_("debug")("socket.io-client:url");module.exports=url;function url(uri,loc){var obj=uri;var loc=loc||global.location;if(null==uri)uri=loc.protocol+"//"+loc.host;if("string"==typeof uri){if("/"==uri.charAt(0)){if("/"==uri.charAt(1)){uri=loc.protocol+uri}else{uri=loc.host+uri}}if(!/^(https?|wss?):\/\//.test(uri)){debug("protocol-less url %s",uri);if("undefined"!=typeof loc){uri=loc.protocol+"//"+uri}else{uri="https://"+uri}}debug("parse %s",uri);obj=parseuri(uri)}if(!obj.port){if(/^(http|ws)$/.test(obj.protocol)){obj.port="80"}else if(/^(http|ws)s$/.test(obj.protocol)){obj.port="443"}}obj.path=obj.path||"/";var ipv6=obj.host.indexOf(":")!==-1;var host=ipv6?"["+obj.host+"]":obj.host;obj.id=obj.protocol+"://"+host+":"+obj.port;obj.href=obj.protocol+"://"+host+(loc&&loc.port==obj.port?"":":"+obj.port);return obj}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{debug:39,parseuri:45}],36:[function(_dereq_,module,exports){module.exports=Backoff;function Backoff(opts){opts=opts||{};this.ms=opts.min||100;this.max=opts.max||1e4;this.factor=opts.factor||2;this.jitter=opts.jitter>0&&opts.jitter<=1?opts.jitter:0;this.attempts=0}Backoff.prototype.duration=function(){var ms=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var rand=Math.random();var deviation=Math.floor(rand*this.jitter*ms);ms=(Math.floor(rand*10)&1)==0?ms-deviation:ms+deviation}return Math.min(ms,this.max)|0};Backoff.prototype.reset=function(){this.attempts=0};Backoff.prototype.setMin=function(min){this.ms=min};Backoff.prototype.setMax=function(max){this.max=max};Backoff.prototype.setJitter=function(jitter){this.jitter=jitter}},{}],37:[function(_dereq_,module,exports){var slice=[].slice;module.exports=function(obj,fn){if("string"==typeof fn)fn=obj[fn];if("function"!=typeof fn)throw new Error("bind() requires a function");var args=slice.call(arguments,2);return function(){return fn.apply(obj,args.concat(slice.call(arguments)))}}},{}],38:[function(_dereq_,module,exports){module.exports=Emitter;function Emitter(obj){if(obj)return mixin(obj)}function mixin(obj){for(var key in Emitter.prototype){obj[key]=Emitter.prototype[key]}return obj}Emitter.prototype.on=Emitter.prototype.addEventListener=function(event,fn){this._callbacks=this._callbacks||{};(this._callbacks["$"+event]=this._callbacks["$"+event]||[]).push(fn);return this};Emitter.prototype.once=function(event,fn){function on(){this.off(event,on);fn.apply(this,arguments)}on.fn=fn;this.on(event,on);return this};Emitter.prototype.off=Emitter.prototype.removeListener=Emitter.prototype.removeAllListeners=Emitter.prototype.removeEventListener=function(event,fn){this._callbacks=this._callbacks||{};if(0==arguments.length){this._callbacks={};return this}var callbacks=this._callbacks["$"+event];if(!callbacks)return this;if(1==arguments.length){delete this._callbacks["$"+event];return this}var cb;for(var i=0;i<callbacks.length;i++){cb=callbacks[i];if(cb===fn||cb.fn===fn){callbacks.splice(i,1);break}}return this};Emitter.prototype.emit=function(event){this._callbacks=this._callbacks||{};var args=[].slice.call(arguments,1),callbacks=this._callbacks["$"+event];if(callbacks){callbacks=callbacks.slice(0);for(var i=0,len=callbacks.length;i<len;++i){callbacks[i].apply(this,args)}}return this};Emitter.prototype.listeners=function(event){this._callbacks=this._callbacks||{};return this._callbacks["$"+event]||[]};Emitter.prototype.hasListeners=function(event){return!!this.listeners(event).length}},{}],39:[function(_dereq_,module,exports){arguments[4][17][0].apply(exports,arguments)},{"./debug":40,dup:17}],40:[function(_dereq_,module,exports){arguments[4][18][0].apply(exports,arguments)},{dup:18,ms:44}],41:[function(_dereq_,module,exports){(function(global){var isArray=_dereq_("isarray");module.exports=hasBinary;function hasBinary(data){function _hasBinary(obj){if(!obj)return false;if(global.Buffer&&global.Buffer.isBuffer&&global.Buffer.isBuffer(obj)||global.ArrayBuffer&&obj instanceof ArrayBuffer||global.Blob&&obj instanceof Blob||global.File&&obj instanceof File){return true}if(isArray(obj)){for(var i=0;i<obj.length;i++){if(_hasBinary(obj[i])){return true}}}else if(obj&&"object"==typeof obj){if(obj.toJSON&&"function"==typeof obj.toJSON){obj=obj.toJSON()}for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key)&&_hasBinary(obj[key])){return true}}}return false}return _hasBinary(data)}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{isarray:43}],42:[function(_dereq_,module,exports){arguments[4][23][0].apply(exports,arguments)},{dup:23}],43:[function(_dereq_,module,exports){arguments[4][24][0].apply(exports,arguments)},{dup:24}],44:[function(_dereq_,module,exports){arguments[4][25][0].apply(exports,arguments)},{dup:25}],45:[function(_dereq_,module,exports){arguments[4][28][0].apply(exports,arguments)},{dup:28}],46:[function(_dereq_,module,exports){(function(global){var isArray=_dereq_("isarray");var isBuf=_dereq_("./is-buffer");exports.deconstructPacket=function(packet){var buffers=[];var packetData=packet.data;function _deconstructPacket(data){if(!data)return data;if(isBuf(data)){var placeholder={_placeholder:true,num:buffers.length};buffers.push(data);return placeholder}else if(isArray(data)){var newData=new Array(data.length);for(var i=0;i<data.length;i++){newData[i]=_deconstructPacket(data[i])}return newData}else if("object"==typeof data&&!(data instanceof Date)){var newData={};for(var key in data){newData[key]=_deconstructPacket(data[key])}return newData}return data}var pack=packet;pack.data=_deconstructPacket(packetData);pack.attachments=buffers.length;return{packet:pack,buffers:buffers}};exports.reconstructPacket=function(packet,buffers){var curPlaceHolder=0;function _reconstructPacket(data){if(data&&data._placeholder){var buf=buffers[data.num];return buf}else if(isArray(data)){for(var i=0;i<data.length;i++){data[i]=_reconstructPacket(data[i])}return data}else if(data&&"object"==typeof data){for(var key in data){data[key]=_reconstructPacket(data[key])}return data}return data}packet.data=_reconstructPacket(packet.data);packet.attachments=undefined;return packet};exports.removeBlobs=function(data,callback){function _removeBlobs(obj,curKey,containingObject){if(!obj)return obj;if(global.Blob&&obj instanceof Blob||global.File&&obj instanceof File){pendingBlobs++;var fileReader=new FileReader;fileReader.onload=function(){if(containingObject){containingObject[curKey]=this.result}else{bloblessData=this.result}if(!--pendingBlobs){callback(bloblessData)}};fileReader.readAsArrayBuffer(obj)}else if(isArray(obj)){for(var i=0;i<obj.length;i++){_removeBlobs(obj[i],i,obj)}}else if(obj&&"object"==typeof obj&&!isBuf(obj)){for(var key in obj){_removeBlobs(obj[key],key,obj)}}}var pendingBlobs=0;var bloblessData=data;_removeBlobs(bloblessData);if(!pendingBlobs){callback(bloblessData)}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./is-buffer":48,isarray:43}],47:[function(_dereq_,module,exports){var debug=_dereq_("debug")("socket.io-parser");var json=_dereq_("json3");var isArray=_dereq_("isarray");var Emitter=_dereq_("component-emitter");var binary=_dereq_("./binary");var isBuf=_dereq_("./is-buffer");exports.protocol=4;exports.types=["CONNECT","DISCONNECT","EVENT","BINARY_EVENT","ACK","BINARY_ACK","ERROR"];exports.CONNECT=0;exports.DISCONNECT=1;exports.EVENT=2;exports.ACK=3;exports.ERROR=4;exports.BINARY_EVENT=5;exports.BINARY_ACK=6;exports.Encoder=Encoder;exports.Decoder=Decoder;function Encoder(){}Encoder.prototype.encode=function(obj,callback){debug("encoding packet %j",obj);if(exports.BINARY_EVENT==obj.type||exports.BINARY_ACK==obj.type){encodeAsBinary(obj,callback)}else{var encoding=encodeAsString(obj);callback([encoding])}};function encodeAsString(obj){var str="";var nsp=false;str+=obj.type;if(exports.BINARY_EVENT==obj.type||exports.BINARY_ACK==obj.type){str+=obj.attachments;str+="-"}if(obj.nsp&&"/"!=obj.nsp){nsp=true;str+=obj.nsp}if(null!=obj.id){if(nsp){str+=",";nsp=false}str+=obj.id}if(null!=obj.data){if(nsp)str+=",";str+=json.stringify(obj.data)}debug("encoded %j as %s",obj,str);return str}function encodeAsBinary(obj,callback){function writeEncoding(bloblessData){var deconstruction=binary.deconstructPacket(bloblessData);var pack=encodeAsString(deconstruction.packet);var buffers=deconstruction.buffers;buffers.unshift(pack);callback(buffers)}binary.removeBlobs(obj,writeEncoding)}function Decoder(){this.reconstructor=null}Emitter(Decoder.prototype);Decoder.prototype.add=function(obj){var packet;if("string"==typeof obj){packet=decodeString(obj);if(exports.BINARY_EVENT==packet.type||exports.BINARY_ACK==packet.type){this.reconstructor=new BinaryReconstructor(packet);if(this.reconstructor.reconPack.attachments===0){this.emit("decoded",packet)}}else{this.emit("decoded",packet)}}else if(isBuf(obj)||obj.base64){if(!this.reconstructor){throw new Error("got binary data when not reconstructing a packet")}else{packet=this.reconstructor.takeBinaryData(obj);if(packet){this.reconstructor=null;this.emit("decoded",packet)}}}else{throw new Error("Unknown type: "+obj)}};function decodeString(str){var p={};var i=0;p.type=Number(str.charAt(0));if(null==exports.types[p.type])return error();if(exports.BINARY_EVENT==p.type||exports.BINARY_ACK==p.type){var buf="";while(str.charAt(++i)!="-"){buf+=str.charAt(i);if(i==str.length)break}if(buf!=Number(buf)||str.charAt(i)!="-"){throw new Error("Illegal attachments")}p.attachments=Number(buf)}if("/"==str.charAt(i+1)){p.nsp="";while(++i){var c=str.charAt(i);if(","==c)break;p.nsp+=c;if(i==str.length)break}}else{p.nsp="/"}var next=str.charAt(i+1);if(""!==next&&Number(next)==next){p.id="";while(++i){var c=str.charAt(i);if(null==c||Number(c)!=c){--i;break}p.id+=str.charAt(i);if(i==str.length)break}p.id=Number(p.id)}if(str.charAt(++i)){try{p.data=json.parse(str.substr(i))}catch(e){return error()}}debug("decoded %s as %j",str,p);return p}Decoder.prototype.destroy=function(){if(this.reconstructor){this.reconstructor.finishedReconstruction()}};function BinaryReconstructor(packet){this.reconPack=packet;this.buffers=[]}BinaryReconstructor.prototype.takeBinaryData=function(binData){this.buffers.push(binData);if(this.buffers.length==this.reconPack.attachments){var packet=binary.reconstructPacket(this.reconPack,this.buffers);this.finishedReconstruction();return packet}return null};BinaryReconstructor.prototype.finishedReconstruction=function(){this.reconPack=null;this.buffers=[]};function error(data){return{type:exports.ERROR,data:"parser error"}}},{"./binary":46,"./is-buffer":48,"component-emitter":49,debug:39,isarray:43,json3:50}],48:[function(_dereq_,module,exports){(function(global){module.exports=isBuf;function isBuf(obj){return global.Buffer&&global.Buffer.isBuffer(obj)||global.ArrayBuffer&&obj instanceof ArrayBuffer}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],49:[function(_dereq_,module,exports){arguments[4][15][0].apply(exports,arguments)},{dup:15}],50:[function(_dereq_,module,exports){(function(global){(function(){var isLoader=typeof define==="function"&&define.amd;var objectTypes={"function":true,object:true};var freeExports=objectTypes[typeof exports]&&exports&&!exports.nodeType&&exports;var root=objectTypes[typeof window]&&window||this,freeGlobal=freeExports&&objectTypes[typeof module]&&module&&!module.nodeType&&typeof global=="object"&&global;if(freeGlobal&&(freeGlobal["global"]===freeGlobal||freeGlobal["window"]===freeGlobal||freeGlobal["self"]===freeGlobal)){root=freeGlobal}function runInContext(context,exports){context||(context=root["Object"]());exports||(exports=root["Object"]());var Number=context["Number"]||root["Number"],String=context["String"]||root["String"],Object=context["Object"]||root["Object"],Date=context["Date"]||root["Date"],SyntaxError=context["SyntaxError"]||root["SyntaxError"],TypeError=context["TypeError"]||root["TypeError"],Math=context["Math"]||root["Math"],nativeJSON=context["JSON"]||root["JSON"];if(typeof nativeJSON=="object"&&nativeJSON){exports.stringify=nativeJSON.stringify;exports.parse=nativeJSON.parse}var objectProto=Object.prototype,getClass=objectProto.toString,isProperty,forEach,undef;var isExtended=new Date(-0xc782b5b800cec);try{isExtended=isExtended.getUTCFullYear()==-109252&&isExtended.getUTCMonth()===0&&isExtended.getUTCDate()===1&&isExtended.getUTCHours()==10&&isExtended.getUTCMinutes()==37&&isExtended.getUTCSeconds()==6&&isExtended.getUTCMilliseconds()==708}catch(exception){}function has(name){if(has[name]!==undef){return has[name]}var isSupported;if(name=="bug-string-char-index"){isSupported="a"[0]!="a"}else if(name=="json"){isSupported=has("json-stringify")&&has("json-parse")}else{var value,serialized='{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';if(name=="json-stringify"){var stringify=exports.stringify,stringifySupported=typeof stringify=="function"&&isExtended;if(stringifySupported){(value=function(){return 1}).toJSON=value;try{stringifySupported=stringify(0)==="0"&&stringify(new Number)==="0"&&stringify(new String)=='""'&&stringify(getClass)===undef&&stringify(undef)===undef&&stringify()===undef&&stringify(value)==="1"&&stringify([value])=="[1]"&&stringify([undef])=="[null]"&&stringify(null)=="null"&&stringify([undef,getClass,null])=="[null,null,null]"&&stringify({a:[value,true,false,null,"\x00\b\n\f\r	"]})==serialized&&stringify(null,value)==="1"&&stringify([1,2],null,1)=="[\n 1,\n 2\n]"&&stringify(new Date(-864e13))=='"-271821-04-20T00:00:00.000Z"'&&stringify(new Date(864e13))=='"+275760-09-13T00:00:00.000Z"'&&stringify(new Date(-621987552e5))=='"-000001-01-01T00:00:00.000Z"'&&stringify(new Date(-1))=='"1969-12-31T23:59:59.999Z"'}catch(exception){stringifySupported=false}}isSupported=stringifySupported}if(name=="json-parse"){var parse=exports.parse;if(typeof parse=="function"){try{if(parse("0")===0&&!parse(false)){value=parse(serialized);var parseSupported=value["a"].length==5&&value["a"][0]===1;if(parseSupported){try{parseSupported=!parse('"	"')}catch(exception){}if(parseSupported){try{parseSupported=parse("01")!==1}catch(exception){}}if(parseSupported){try{parseSupported=parse("1.")!==1}catch(exception){}}}}}catch(exception){parseSupported=false}}isSupported=parseSupported}}return has[name]=!!isSupported}if(!has("json")){var functionClass="[object Function]",dateClass="[object Date]",numberClass="[object Number]",stringClass="[object String]",arrayClass="[object Array]",booleanClass="[object Boolean]";var charIndexBuggy=has("bug-string-char-index");if(!isExtended){var floor=Math.floor;var Months=[0,31,59,90,120,151,181,212,243,273,304,334];var getDay=function(year,month){return Months[month]+365*(year-1970)+floor((year-1969+(month=+(month>1)))/4)-floor((year-1901+month)/100)+floor((year-1601+month)/400)}}if(!(isProperty=objectProto.hasOwnProperty)){isProperty=function(property){var members={},constructor;if((members.__proto__=null,members.__proto__={toString:1},members).toString!=getClass){isProperty=function(property){var original=this.__proto__,result=property in(this.__proto__=null,this);this.__proto__=original;return result}}else{constructor=members.constructor;isProperty=function(property){var parent=(this.constructor||constructor).prototype;return property in this&&!(property in parent&&this[property]===parent[property])}}members=null;return isProperty.call(this,property)}}forEach=function(object,callback){var size=0,Properties,members,property;(Properties=function(){this.valueOf=0}).prototype.valueOf=0;members=new Properties;for(property in members){if(isProperty.call(members,property)){size++}}Properties=members=null;if(!size){members=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,length;var hasProperty=!isFunction&&typeof object.constructor!="function"&&objectTypes[typeof object.hasOwnProperty]&&object.hasOwnProperty||isProperty;for(property in object){if(!(isFunction&&property=="prototype")&&hasProperty.call(object,property)){callback(property)}}for(length=members.length;property=members[--length];hasProperty.call(object,property)&&callback(property));}}else if(size==2){forEach=function(object,callback){var members={},isFunction=getClass.call(object)==functionClass,property;for(property in object){if(!(isFunction&&property=="prototype")&&!isProperty.call(members,property)&&(members[property]=1)&&isProperty.call(object,property)){callback(property)}}}}else{forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,isConstructor;for(property in object){if(!(isFunction&&property=="prototype")&&isProperty.call(object,property)&&!(isConstructor=property==="constructor")){callback(property)}}if(isConstructor||isProperty.call(object,property="constructor")){callback(property)}}}return forEach(object,callback)};if(!has("json-stringify")){var Escapes={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};var leadingZeroes="000000";var toPaddedString=function(width,value){return(leadingZeroes+(value||0)).slice(-width)};var unicodePrefix="\\u00";var quote=function(value){var result='"',index=0,length=value.length,useCharIndex=!charIndexBuggy||length>10;var symbols=useCharIndex&&(charIndexBuggy?value.split(""):value);for(;index<length;index++){var charCode=value.charCodeAt(index);switch(charCode){case 8:case 9:case 10:case 12:case 13:case 34:case 92:result+=Escapes[charCode];break;default:if(charCode<32){result+=unicodePrefix+toPaddedString(2,charCode.toString(16));break}result+=useCharIndex?symbols[index]:value.charAt(index)}}return result+'"'};var serialize=function(property,object,callback,properties,whitespace,indentation,stack){var value,className,year,month,date,time,hours,minutes,seconds,milliseconds,results,element,index,length,prefix,result;try{value=object[property]}catch(exception){}if(typeof value=="object"&&value){className=getClass.call(value);if(className==dateClass&&!isProperty.call(value,"toJSON")){if(value>-1/0&&value<1/0){if(getDay){date=floor(value/864e5);for(year=floor(date/365.2425)+1970-1;getDay(year+1,0)<=date;year++);for(month=floor((date-getDay(year,0))/30.42);getDay(year,month+1)<=date;month++);date=1+date-getDay(year,month);time=(value%864e5+864e5)%864e5;hours=floor(time/36e5)%24;minutes=floor(time/6e4)%60;seconds=floor(time/1e3)%60;milliseconds=time%1e3}else{year=value.getUTCFullYear();month=value.getUTCMonth();date=value.getUTCDate();hours=value.getUTCHours();minutes=value.getUTCMinutes();seconds=value.getUTCSeconds();milliseconds=value.getUTCMilliseconds()}value=(year<=0||year>=1e4?(year<0?"-":"+")+toPaddedString(6,year<0?-year:year):toPaddedString(4,year))+"-"+toPaddedString(2,month+1)+"-"+toPaddedString(2,date)+"T"+toPaddedString(2,hours)+":"+toPaddedString(2,minutes)+":"+toPaddedString(2,seconds)+"."+toPaddedString(3,milliseconds)+"Z"}else{value=null}}else if(typeof value.toJSON=="function"&&(className!=numberClass&&className!=stringClass&&className!=arrayClass||isProperty.call(value,"toJSON"))){value=value.toJSON(property)}}if(callback){value=callback.call(object,property,value)}if(value===null){return"null"}className=getClass.call(value);if(className==booleanClass){return""+value}else if(className==numberClass){return value>-1/0&&value<1/0?""+value:"null"}else if(className==stringClass){return quote(""+value)}if(typeof value=="object"){for(length=stack.length;length--;){if(stack[length]===value){throw TypeError()}}stack.push(value);results=[];prefix=indentation;indentation+=whitespace;if(className==arrayClass){for(index=0,length=value.length;index<length;index++){element=serialize(index,value,callback,properties,whitespace,indentation,stack);results.push(element===undef?"null":element)}result=results.length?whitespace?"[\n"+indentation+results.join(",\n"+indentation)+"\n"+prefix+"]":"["+results.join(",")+"]":"[]"}else{forEach(properties||value,function(property){var element=serialize(property,value,callback,properties,whitespace,indentation,stack);if(element!==undef){results.push(quote(property)+":"+(whitespace?" ":"")+element)}});result=results.length?whitespace?"{\n"+indentation+results.join(",\n"+indentation)+"\n"+prefix+"}":"{"+results.join(",")+"}":"{}"}stack.pop();return result}};exports.stringify=function(source,filter,width){var whitespace,callback,properties,className;if(objectTypes[typeof filter]&&filter){if((className=getClass.call(filter))==functionClass){callback=filter}else if(className==arrayClass){properties={};for(var index=0,length=filter.length,value;index<length;value=filter[index++],(className=getClass.call(value),className==stringClass||className==numberClass)&&(properties[value]=1));}}if(width){if((className=getClass.call(width))==numberClass){if((width-=width%1)>0){for(whitespace="",width>10&&(width=10);whitespace.length<width;whitespace+=" ");}}else if(className==stringClass){whitespace=width.length<=10?width:width.slice(0,10)}}return serialize("",(value={},value[""]=source,value),callback,properties,whitespace,"",[])}}if(!has("json-parse")){var fromCharCode=String.fromCharCode;var Unescapes={92:"\\",34:'"',47:"/",98:"\b",116:"	",110:"\n",102:"\f",114:"\r"};var Index,Source;var abort=function(){Index=Source=null;throw SyntaxError()};var lex=function(){var source=Source,length=source.length,value,begin,position,isSigned,charCode;while(Index<length){charCode=source.charCodeAt(Index);switch(charCode){case 9:case 10:case 13:case 32:Index++;break;case 123:case 125:case 91:case 93:case 58:case 44:value=charIndexBuggy?source.charAt(Index):source[Index];Index++;return value;case 34:for(value="@",Index++;Index<length;){charCode=source.charCodeAt(Index);if(charCode<32){abort()}else if(charCode==92){charCode=source.charCodeAt(++Index);switch(charCode){case 92:case 34:case 47:case 98:case 116:case 110:case 102:case 114:value+=Unescapes[charCode];Index++;break;case 117:begin=++Index;for(position=Index+4;Index<position;Index++){charCode=source.charCodeAt(Index);if(!(charCode>=48&&charCode<=57||charCode>=97&&charCode<=102||charCode>=65&&charCode<=70)){abort()}}value+=fromCharCode("0x"+source.slice(begin,Index));break;default:abort()}}else{if(charCode==34){break}charCode=source.charCodeAt(Index);begin=Index;while(charCode>=32&&charCode!=92&&charCode!=34){charCode=source.charCodeAt(++Index)}value+=source.slice(begin,Index)}}if(source.charCodeAt(Index)==34){Index++;return value}abort();default:begin=Index;if(charCode==45){isSigned=true;charCode=source.charCodeAt(++Index)}if(charCode>=48&&charCode<=57){if(charCode==48&&(charCode=source.charCodeAt(Index+1),charCode>=48&&charCode<=57)){abort()}isSigned=false;for(;Index<length&&(charCode=source.charCodeAt(Index),charCode>=48&&charCode<=57);Index++);if(source.charCodeAt(Index)==46){position=++Index;for(;position<length&&(charCode=source.charCodeAt(position),charCode>=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}charCode=source.charCodeAt(Index);if(charCode==101||charCode==69){charCode=source.charCodeAt(++Index);if(charCode==43||charCode==45){Index++}for(position=Index;position<length&&(charCode=source.charCodeAt(position),charCode>=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}return+source.slice(begin,Index)}if(isSigned){abort()}if(source.slice(Index,Index+4)=="true"){Index+=4;return true}else if(source.slice(Index,Index+5)=="false"){Index+=5;return false}else if(source.slice(Index,Index+4)=="null"){Index+=4;return null}abort()}}return"$"};var get=function(value){var results,hasMembers;if(value=="$"){abort()}if(typeof value=="string"){if((charIndexBuggy?value.charAt(0):value[0])=="@"){return value.slice(1)}if(value=="["){results=[];for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="]"){break}if(hasMembers){if(value==","){value=lex();if(value=="]"){abort()}}else{abort()}}if(value==","){abort()}results.push(get(value))}return results}else if(value=="{"){results={};for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="}"){break}if(hasMembers){if(value==","){value=lex();if(value=="}"){abort()}}else{abort()}}if(value==","||typeof value!="string"||(charIndexBuggy?value.charAt(0):value[0])!="@"||lex()!=":"){abort()}results[value.slice(1)]=get(lex())
  }return results}abort()}return value};var update=function(source,property,callback){var element=walk(source,property,callback);if(element===undef){delete source[property]}else{source[property]=element}};var walk=function(source,property,callback){var value=source[property],length;if(typeof value=="object"&&value){if(getClass.call(value)==arrayClass){for(length=value.length;length--;){update(value,length,callback)}}else{forEach(value,function(property){update(value,property,callback)})}}return callback.call(source,property,value)};exports.parse=function(source,callback){var result,value;Index=0;Source=""+source;result=get(lex());if(lex()!="$"){abort()}Index=Source=null;return callback&&getClass.call(callback)==functionClass?walk((value={},value[""]=result,value),"",callback):result}}}exports["runInContext"]=runInContext;return exports}if(freeExports&&!isLoader){runInContext(root,freeExports)}else{var nativeJSON=root.JSON,previousJSON=root["JSON3"],isRestored=false;var JSON3=runInContext(root,root["JSON3"]={noConflict:function(){if(!isRestored){isRestored=true;root.JSON=nativeJSON;root["JSON3"]=previousJSON;nativeJSON=previousJSON=null}return JSON3}});root.JSON={parse:JSON3.parse,stringify:JSON3.stringify}}if(isLoader){define(function(){return JSON3})}}).call(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],51:[function(_dereq_,module,exports){module.exports=toArray;function toArray(list,index){var array=[];index=index||0;for(var i=index||0;i<list.length;i++){array[i-index]=list[i]}return array}},{}]},{},[31])(31)});


(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<r.length;o++)s(r[o]);return s})({1:[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;
          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:<pt> 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';
    };

// Expose public methods.
    module.exports = SDPUtils;

  },{}],2:[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":3}],3:[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';

// Shimming starts here.
    module.exports = function(dependencies) {
      var window = dependencies && dependencies.window;

      // Utils.
      var utils = require('./utils');
      var logging = utils.log;
      var browserDetails = utils.detectBrowser(window);

      // Export to the adapter global object visible in the browser.
      var adapter = {
        browserDetails: browserDetails,
        extractVersion: utils.extractVersion,
        disableLog: utils.disableLog,
        disableWarnings: utils.disableWarnings
      };

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

      // Shim browser if found.
      switch (browserDetails.browser) {
        case 'chrome':
          if (!chromeShim || !chromeShim.shimPeerConnection) {
            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;

          chromeShim.shimGetUserMedia(window);
          chromeShim.shimMediaStream(window);
          utils.shimCreateObjectURL(window);
          chromeShim.shimSourceObject(window);
          chromeShim.shimPeerConnection(window);
          chromeShim.shimOnTrack(window);
          chromeShim.shimGetSendersWithDtmf(window);
          break;
        case 'firefox':
          if (!firefoxShim || !firefoxShim.shimPeerConnection) {
            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;

          firefoxShim.shimGetUserMedia(window);
          utils.shimCreateObjectURL(window);
          firefoxShim.shimSourceObject(window);
          firefoxShim.shimPeerConnection(window);
          firefoxShim.shimOnTrack(window);
          break;
        case 'edge':
          if (!edgeShim || !edgeShim.shimPeerConnection) {
            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;

          edgeShim.shimGetUserMedia(window);
          utils.shimCreateObjectURL(window);
          edgeShim.shimPeerConnection(window);
          edgeShim.shimReplaceTrack(window);
          break;
        case 'safari':
          if (!safariShim) {
            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;
          // shim window.URL.createObjectURL Safari (technical preview)
          utils.shimCreateObjectURL(window);
          safariShim.shimRTCIceServerUrls(window);
          safariShim.shimCallbacksAPI(window);
          safariShim.shimLocalStreamsAPI(window);
          safariShim.shimRemoteStreamsAPI(window);
          safariShim.shimGetUserMedia(window);
          break;
        default:
          logging('Unsupported browser!');
          break;
      }

      return adapter;
    };

  },{"./chrome/chrome_shim":4,"./edge/edge_shim":6,"./firefox/firefox_shim":9,"./safari/safari_shim":11,"./utils":12}],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.js');
    var logging = utils.log;

    var chromeShim = {
      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) {
              var self = this;
              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) {
                // 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 = self.getReceivers().find(function(r) {
                      return r.track.id === te.track.id;
                    });
                  } else {
                    receiver = {track: te.track};
                  }

                  var event = new Event('track');
                  event.track = te.track;
                  event.receiver = receiver;
                  event.streams = [e.stream];
                  self.dispatchEvent(event);
                });
                e.stream.getTracks().forEach(function(track) {
                  var receiver;
                  if (window.RTCPeerConnection.prototype.getReceivers) {
                    receiver = self.getReceivers().find(function(r) {
                      return r.track.id === track.id;
                    });
                  } else {
                    receiver = {track: track};
                  }
                  var event = new Event('track');
                  event.track = track;
                  event.receiver = receiver;
                  event.streams = [e.stream];
                  this.dispatchEvent(event);
                }.bind(this));
              }.bind(this));
            }
          });
        }
      },

      shimGetSendersWithDtmf: function(window) {
        if (typeof window === 'object' && window.RTCPeerConnection &&
          !('getSenders' in window.RTCPeerConnection.prototype) &&
          'createDTMFSender' in window.RTCPeerConnection.prototype) {
          window.RTCPeerConnection.prototype.getSenders = function() {
            return this._senders || [];
          };
          var origAddStream = window.RTCPeerConnection.prototype.addStream;
          var origRemoveStream = window.RTCPeerConnection.prototype.removeStream;

          if (!window.RTCPeerConnection.prototype.addTrack) {
            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');
              }

              pc._senders = pc._senders || [];
              var alreadyExists = pc._senders.find(function(t) {
                return t.track === track;
              });
              if (alreadyExists) {
                throw new DOMException('Track already exists.',
                  'InvalidAccessError');
              }

              pc._streams = pc._streams || {};
              var oldStream = pc._streams[stream.id];
              if (oldStream) {
                oldStream.addTrack(track);
                pc.removeStream(oldStream);
                pc.addStream(oldStream);
              } else {
                var newStream = new window.MediaStream([track]);
                pc._streams[stream.id] = newStream;
                pc.addStream(newStream);
              }

              var sender = {
                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._senders.push(sender);
              return sender;
            };
          }
          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({
                track: track,
                get dtmf() {
                  if (this._dtmf === undefined) {
                    if (track.kind === 'audio') {
                      this._dtmf = pc.createDTMFSender(track);
                    } else {
                      this._dtmf = null;
                    }
                  }
                  return this._dtmf;
                }
              });
            });
          };

          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);
                });
              }
            });
          }
        }
      },

      shimPeerConnection: function(window) {
        var browserDetails = utils.detectBrowser(window);

        // The RTCPeerConnection object.
        if (!window.RTCPeerConnection) {
          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')) {
                  console.warn('RTCIceServer.url is deprecated! Use urls instead.');
                  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 self = 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(self, [
              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 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]);
                    }
                  });
              };
            });
        }

        // 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 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);
            };
          });
        }

        // 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);
        };
      }
    };


// Expose public methods.
    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){
    /*
 *  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) {
        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 < 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) {
              // 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) {
            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":12}],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');
    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);
              }
            });
          }
        }
        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":12,"./getusermedia":7,"./rtcpeerconnection_shim":8}],7:[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));
        });
      };
    };

  },{}],8:[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');

// sort tracks such that they follow an a-v-a-v...
// pattern.
    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;
    }

// Edge does not like
// 1) stun:
// 2) turn: that does not have all of turn:host:port?transport=udp
// 3) turn: with ipv6 addresses
// 4) turn: occurring muliple times
    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;
      });
    }

// 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;
    }

    module.exports = function(window, edgeVersion) {
      var RTCPeerConnection = function(config) {
        var self = this;

        var _eventTarget = document.createDocumentFragment();
        ['addEventListener', 'removeEventListener', 'dispatchEvent']
          .forEach(function(method) {
            self[method] = _eventTarget[method].bind(_eventTarget);
          });

        this.needNegotiation = false;

        this.onicecandidate = null;
        this.onaddstream = null;
        this.ontrack = null;
        this.onremovestream = null;
        this.onsignalingstatechange = null;
        this.oniceconnectionstatechange = null;
        this.onicegatheringstatechange = null;
        this.onnegotiationneeded = null;
        this.ondatachannel = null;
        this.canTrickleIceCandidates = null;

        this.localStreams = [];
        this.remoteStreams = [];
        this.getLocalStreams = function() {
          return self.localStreams;
        };
        this.getRemoteStreams = function() {
          return self.remoteStreams;
        };

        this.localDescription = new window.RTCSessionDescription({
          type: '',
          sdp: ''
        });
        this.remoteDescription = new window.RTCSessionDescription({
          type: '',
          sdp: ''
        });
        this.signalingState = 'stable';
        this.iceConnectionState = 'new';
        this.iceGatheringState = 'new';

        this.iceOptions = {
          gatherPolicy: 'all',
          iceServers: []
        };
        if (config && config.iceTransportPolicy) {
          switch (config.iceTransportPolicy) {
            case 'all':
            case 'relay':
              this.iceOptions.gatherPolicy = config.iceTransportPolicy;
              break;
            default:
              // don't set iceTransportPolicy.
              break;
          }
        }
        this.usingBundle = config && config.bundlePolicy === 'max-bundle';

        if (config && config.iceServers) {
          this.iceOptions.iceServers = filterIceServers(config.iceServers,
            edgeVersion);
        }
        this._config = config || {};

        // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
        // everything that is needed to describe a SDP m-line.
        this.transceivers = [];

        // since the iceGatherer is currently created in createOffer but we
        // must not emit candidates until after setLocalDescription we buffer
        // them in this array.
        this._localIceCandidatesBuffer = [];

        this._sdpSessionId = SDPUtils.generateSessionId();
      };

      RTCPeerConnection.prototype._emitGatheringStateChange = function() {
        var event = new Event('icegatheringstatechange');
        this.dispatchEvent(event);
        if (this.onicegatheringstatechange !== null) {
          this.onicegatheringstatechange(event);
        }
      };

      RTCPeerConnection.prototype._emitBufferedCandidates = function() {
        var self = this;
        var sections = SDPUtils.splitSections(self.localDescription.sdp);
        // FIXME: need to apply ice candidates in a way which is async but
        // in-order
        this._localIceCandidatesBuffer.forEach(function(event) {
          var end = !event.candidate || Object.keys(event.candidate).length === 0;
          if (end) {
            for (var j = 1; j < sections.length; j++) {
              if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) {
                sections[j] += 'a=end-of-candidates\r\n';
              }
            }
          } else {
            sections[event.candidate.sdpMLineIndex + 1] +=
              'a=' + event.candidate.candidate + '\r\n';
          }
          self.localDescription.sdp = sections.join('');
          self.dispatchEvent(event);
          if (self.onicecandidate !== null) {
            self.onicecandidate(event);
          }
          if (!event.candidate && self.iceGatheringState !== 'complete') {
            var complete = self.transceivers.every(function(transceiver) {
              return transceiver.iceGatherer &&
                transceiver.iceGatherer.state === 'completed';
            });
            if (complete && self.iceGatheringStateChange !== 'complete') {
              self.iceGatheringState = 'complete';
              self._emitGatheringStateChange();
            }
          }
        });
        this._localIceCandidatesBuffer = [];
      };

      RTCPeerConnection.prototype.getConfiguration = function() {
        return this._config;
      };

      // 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,
          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 < 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);
        }

        transceiver.track = track;
        transceiver.stream = stream;
        transceiver.rtpSender = new window.RTCRtpSender(track,
          transceiver.dtlsTransport);

        this._maybeFireNegotiationNeeded();
        return transceiver.rtpSender;
      };

      RTCPeerConnection.prototype.addStream = function(stream) {
        var self = this;
        if (edgeVersion >= 15025) {
          this.localStreams.push(stream);
          stream.getTracks().forEach(function(track) {
            self.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) {
            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;
          });
      };

      // Create ICE gatherer and hook it up.
      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;
          // Edge emits an empty object for RTCIceCandidateComplete‥
          if (end) {
            // polyfill since RTCIceGatherer.state is not implemented in
            // Edge 10547 yet.
            if (iceGatherer.state === undefined) {
              iceGatherer.state = 'completed';
            }
          } else {
            // 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(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';
          });

          // Emit candidate if localDescription is set.
          // Also emits null candidate when all gatherers are complete.
          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':
              // should not happen... currently!
              break;
            default: // no-op.
              break;
          }
        };
        return iceGatherer;
      };

      // Create ICE transport and DTLS transport.
      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() {
          // onerror does not set state to failed by itself.
          Object.defineProperty(dtlsTransport, 'state',
            {value: 'failed', writable: true});
          self._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.ondtlssttatechange;
          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) {
          // remove RTX field in Edge 14942
          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') {
          // FIXME: What was the purpose of this empty if statement?
          // if (!this._pendingOffer) {
          // } else {
          if (this._pendingOffer) {
            // 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);
              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);
              }

              // Calculate intersection of capabilities.
              var params = getCommonCapabilities(localCapabilities,
                remoteCapabilities);

              // Start the RTCRtpSender. The RTCRtpReceiver for this
              // transceiver has already been started in setRemoteDescription.
              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 +
              '"');
        }

        // If a success callback was provided, emit ICE candidates after it
        // has been executed. Otherwise, emit callback after the Promise is
        // resolved.
        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();
            }
            // Usually candidates will be emitted earlier.
            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();

          // Reject datachannels which are not implemented yet.
          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;
          // 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' || cand.component === 1;
            });

          // Check if we can use BUNDLE and dispose transports.
          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);

            // 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 = [{
              ssrc: (2 * sdpMLineIndex + 2) * 1001
            }];

            if (direction === 'sendrecv' || direction === 'sendonly') {
              rtpReceiver = new window.RTCRtpReceiver(transceiver.dtlsTransport,
                kind);

              track = rtpReceiver.track;
              // FIXME: does not work with Plan B.
              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;

            // Start the RTCRtpReceiver now. The RTPSender is started in
            // setLocalDescription.
            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 {
              // FIXME: actually the receiver should be created later.
              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);
              }
            });
          }
        });

        // check whether addIceCandidate({}) was called within four seconds after
        // setRemoteDescription.
        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) {
          /* 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._updateSignalingState('closed');
      };

      // Update the signaling state.
      RTCPeerConnection.prototype._updateSignalingState = function(newState) {
        this.signalingState = newState;
        var event = new Event('signalingstatechange');
        this.dispatchEvent(event);
        if (this.onsignalingstatechange !== null) {
          this.onsignalingstatechange(event);
        }
      };

      // Determine whether to fire the negotiationneeded 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);
      };

      // Update the connection state.
      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]++;
        });
        // 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 !== 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;

        // Determine number of audio and video tracks we need to send/recv.
        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--;
          }
        }
        // reorder tracks
        var transceivers = sortTracks(this.transceivers);

        var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId);
        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 = self.usingBundle && sdpMLineIndex > 0 ?
              transceivers[0].iceGatherer :
              self._createIceGatherer(mid, sdpMLineIndex);
          }

          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 = [{
            ssrc: (2 * sdpMLineIndex + 1) * 1001
          }];
          if (track) {
            // add RTX
            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;
        });

        // always offer BUNDLE and dispose on return if not supported.
        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;
          }

          // 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 = {
                  ssrc: (2 * sdpMLineIndex + 2) * 1001 + 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 += 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; j < this.transceivers.length; j++) {
            this.transceivers[j].iceTransport.addRemoteCandidate({});
            if (this.usingBundle) {
              return Promise.resolve();
            }
          }
        } else {
          var mLineIndex = candidate.sdpMLineIndex;
          if (candidate.sdpMid) {
            for (var i = 0; i < this.transceivers.length; i++) {
              if (this.transceivers[i].mid === candidate.sdpMid) {
                mLineIndex = i;
                break;
              }
            }
          }
          var transceiver = this.transceivers[mLineIndex];
          if (transceiver) {
            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' || cand.component === 1)) {
              return Promise.resolve();
            }
            transceiver.iceTransport.addRemoteCandidate(cand);

            // update the remoteDescription.
            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) {
          // 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]);
              });
            });
            if (cb) {
              window.setTimeout(cb, 0, results);
            }
            resolve(results);
          });
        });
      };
      return RTCPeerConnection;
    };

  },{"sdp":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';

    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) {
        // 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);
        };
      }
    };

// Expose public methods.
    module.exports = {
      shimOnTrack: firefoxShim.shimOnTrack,
      shimSourceObject: firefoxShim.shimSourceObject,
      shimPeerConnection: firefoxShim.shimPeerConnection,
      shimGetUserMedia: require('./getusermedia')
    };

  },{"../utils":12,"./getusermedia":10}],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');
    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.
        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){
    /*
 *  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');

    var safariShim = {
      // TODO: DrAlex, should be here, double check against LayoutTests

      // TODO: once the back-end for the mac port is done, add.
      // TODO: check for webkitGTK+
      // shimPeerConnection: function() { },

      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) {
        // 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.
        Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
          get: function() {
            return OrigPeerConnection.generateCertificate;
          }
        });
      }
    };

// Expose public methods.
    module.exports = {
      shimCallbacksAPI: safariShim.shimCallbacksAPI,
      shimLocalStreamsAPI: safariShim.shimLocalStreamsAPI,
      shimRemoteStreamsAPI: safariShim.shimRemoteStreamsAPI,
      shimGetUserMedia: safariShim.shimGetUserMedia,
      shimRTCIceServerUrls: safariShim.shimRTCIceServerUrls
      // TODO
      // shimPeerConnection: safariShim.shimPeerConnection
    };

  },{"../utils":12}],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.
 */
    /* eslint-env node */
    'use strict';

    var logDisabled_ = true;
    var deprecationWarnings_ = true;

// Utility methods.
    var utils = {
      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.');
      },

      /**
       * 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.
       */
      extractVersion: function(uastring, expr, pos) {
        var match = uastring.match(expr);
        return match && match.length >= pos && parseInt(match[pos], 10);
      },

      /**
       * 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;
        }

        // Firefox.
        if (navigator.mozGetUserMedia) {
          result.browser = 'firefox';
          result.version = this.extractVersion(navigator.userAgent,
            /Firefox\/(\d+)\./, 1);
        } else if (navigator.webkitGetUserMedia) {
          // Chrome, Chromium, Webview, Opera, all use the chrome shim for now
          if (window.webkitRTCPeerConnection) {
            result.browser = 'chrome';
            result.version = this.extractVersion(navigator.userAgent,
              /Chrom(e|ium)\/(\d+)\./, 2);
          } else { // Safari (in an unpublished version) or unknown webkit-based.
            if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) {
              result.browser = 'safari';
              result.version = this.extractVersion(navigator.userAgent,
                /AppleWebKit\/(\d+)\./, 1);
            } else { // unknown webkit-based browser.
              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+)$/)) { // Edge.
          result.browser = 'edge';
          result.version = this.extractVersion(navigator.userAgent,
            /Edge\/(\d+).(\d+)$/, 2);
        } else if (navigator.mediaDevices &&
          navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) {
          // Safari, with webkitGetUserMedia removed.
          result.browser = 'safari';
          result.version = this.extractVersion(navigator.userAgent,
            /AppleWebKit\/(\d+)\./, 1);
        } else { // Default fallthrough: not supported.
          result.browser = 'Not a supported browser.';
          return result;
        }

        return result;
      },

      // 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)) {
          // 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);
        };
      }
    };

// Export.
    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]);
/*
 * Intel WebRTC SDK version 3.4.1
 * Copyright (c) 2018 Intel <http://webrtc.purertc.com>
 * Homepage: http://webrtc.purertc.com
 */


! function(a) {
  function b(a) {
    "use strict";
    this.target = a.target, this.type = a.type, this.attributes = a.attributes
  }

  function c(a) {
    "use strict";
    var b = this,
      c = new SignalingChannel;
    this.onConnected = null, this.onDisconnected = null, this.onConnectFailed = null, this.onChatInvitation = null, this.onChatDenied =
      null, this.onChatStopped = null, this.onChatAccepted = null, this.onChatSignal = null, this.onStreamType = null,
      this.onAuthenticated = null, c.onMessage = function(a, c) {
      var d = JSON.parse(a);
      switch (d.type) {
        case "chat-invitation":
          b.onChatInvitation && b.onChatInvitation(c, d.ua);
          break;
        case "chat-accepted":
          b.onChatAccepted && b.onChatAccepted(c, d.ua);
          break;
        case "chat-denied":
          b.onChatDenied && b.onChatDenied(c);
          break;
        case "chat-closed":
          b.onChatStopped && b.onChatStopped(c);
          break;
        case "stream-type":
          b.onStreamType && b.onStreamType(d.data, c);
          break;
        case "chat-signal":
          b.onChatSignal && b.onChatSignal(d.data, c);
          break;
        case "chat-negotiation-needed":
          b.onNegotiationNeeded && b.onNegotiationNeeded(c);
          break;
        case "chat-negotiation-accepted":
          b.onNegotiationAccepted && b.onNegotiationAccepted(c);
          break;
        default:
          e.Logger.error("Received unkown message")
      }
    }, c.onServerDisconnected = function() {
      b.onDisconnected && b.onDisconnected()
    }, this.sendChatInvitation = function(a, b, d, e) {
      var f = {
        type: "chat-closed"
      };
      c.sendMessage(JSON.stringify(f), a), f = {
        type: "chat-invitation",
        ua: b
      }, c.sendMessage(JSON.stringify(f), a, d, e)
    }, this.sendChatAccepted = function(a, b, d, e) {
      var f = {
        type: "chat-accepted",
        ua: b
      };
      c.sendMessage(JSON.stringify(f), a, d, e)
    }, this.sendChatDenied = function(a, b, d) {
      var e = {
        type: "chat-denied"
      };
      c.sendMessage(JSON.stringify(e), a, b, d)
    }, this.sendChatStopped = function(a, b, d) {
      var e = {
        type: "chat-closed"
      };
      c.sendMessage(JSON.stringify(e), a, b, d)
    }, this.sendStreamType = function(a, b, d, e) {
      var f = {
        type: "stream-type",
        data: b
      };
      c.sendMessage(JSON.stringify(f), a, d, e)
    }, this.sendSignalMessage = function(a, b, d, e) {
      var f = {
        type: "chat-signal",
        data: b
      };
      c.sendMessage(JSON.stringify(f), a, d, e)
    }, this.sendNegotiationNeeded = function(a, b, d) {
      var e = {
        type: "chat-negotiation-needed"
      };
      c.sendMessage(JSON.stringify(e), a, b, d)
    }, this.sendNegotiationAccepted = function(a, b, d) {
      var e = {
        type: "chat-negotiation-accepted"
      };
      c.sendMessage(JSON.stringify(e), a, b, d)
    }, this.finalize = function() {
      c.disconnect()
    }, this.connect = function(a, d, e) {
      c.connect(a, function(a) {
        b.onConnected && b.onConnected(), b.onAuthenticated && b.onAuthenticated(a), d && d(a)
      }, e)
    }
  }
  var d = function() {
      "use strict";
      var a = {};
      return Object.defineProperties(a, {
        version: {
          get: function() {
            return "3.4.1"
          }
        },
        name: {
          get: function() {
            return "Intel WebRTC SDK"
          }
        }
      }), a
    }(),
    e = {},
    f = {};
  d.EventDispatcher = function(a) {
    "use strict";
    var b = {};
    return a.dispatcher = {}, a.dispatcher.eventListeners = {}, b.addEventListener = function(b, c) {
      void 0 === a.dispatcher.eventListeners[b] && (a.dispatcher.eventListeners[b] = []), a.dispatcher.eventListeners[b]
        .push(c)
    }, b.on = b.addEventListener, b.removeEventListener = function(b, c) {
      if (a.dispatcher.eventListeners[b]) {
        var d = a.dispatcher.eventListeners[b].indexOf(c); - 1 !== d && a.dispatcher.eventListeners[b].splice(d, 1)
      }
    }, b.clearEventListener = function(b) {
      a.dispatcher.eventListeners[b] = []
    }, b.dispatchEvent = function(b) {
      a.dispatcher.eventListeners[b.type] && a.dispatcher.eventListeners[b.type].map(function(a) {
        a(b)
      })
    }, b
  }, d.StreamEvent = function(a) {
    "use strict";
    b.call(this, a), this.stream = a.stream, this.msg = a.msg
  }, d.ClientEvent = function(a) {
    "use strict";
    b.call(this, a), this.user = a.user
  }, d.MessageEvent = function(a) {
    "use strict";
    b.call(this, a), this.msg = a.msg
  }, d.ChatEvent = function(a) {
    "use strict";
    b.call(this, a), this.type = a.type, this.senderId = a.senderId, this.peerId = a.peerId
  }, d.DataEvent = function(a) {
    "use strict";
    b.call(this, a), this.type = a.type, this.senderId = a.senderId, this.data = a.data
  }, d.RecorderEvent = function(a) {
    "use strict";
    b.call(this, a), this.recorderId = a.id
  }, d.StreamEvent.prototype = Object.create(b.prototype), d.StreamEvent.prototype.constructor = d.StreamEvent, d.ClientEvent
    .prototype = Object.create(b.prototype), d.ClientEvent.prototype.constructor = d.ClientEvent, d.MessageEvent.prototype =
    Object.create(b.prototype), d.MessageEvent.prototype.constructor = d.MessageEvent, d.ChatEvent.prototype = Object.create(
    b.prototype), d.ChatEvent.prototype.constructor = d.ChatEvent, d.DataEvent.prototype = Object.create(b.prototype), d
    .DataEvent.prototype.constructor = d.DataEvent, d.RecorderEvent.prototype = Object.create(b.prototype), d.RecorderEvent
    .prototype.constructor = d.RecorderEvent, d.Common = function() {
    function a(a, c, d) {
      return b(a, 0, -1, c, d)
    }

    function b(a, b, c, d, e) {
      for (var f = -1 !== c ? c : a.length, g = b; g < f; ++g)
        if (0 === a[g].indexOf(d) && (!e || -1 !== a[g].toLowerCase().indexOf(e.toLowerCase()))) return g;
      return null
    }

    function c(b, c) {
      var e = a(b, "a=rtpmap", c);
      return e ? d(b[e]) : null
    }

    function d(a) {
      var b = new RegExp("a=rtpmap:(\\d+) [a-zA-Z0-9-]+\\/\\d+", "i"),
        c = a.match(b);
      return c && 2 === c.length ? c[1] : null
    }

    function f(a, b) {
      var c = a.split(" "),
        d = c.slice(0, 3);
      d.push(b);
      for (var e = 3; e < c.length; e++) c[e] !== b && d.push(c[e]);
      return d.join(" ")
    }

    function g(c, d, f) {
      var g = c.split("\r\n"),
        h = a(g, "m=", d);
      if (null === h) return e.Logger.debug("Failed to add bandwidth line to sdp, as no m-line found"), c;
      var i = b(g, h + 1, -1, "m=");
      null === i && (i = g.length);
      var j = b(g, h + 1, i, "c=");
      if (null === j) return e.Logger.debug("Failed to add bandwidth line to sdp, as no c-line found"), c;
      var k = b(g, j + 1, i, "b=AS");
      k && g.splice(k, 1);
      var l = "b=AS:" + f;
      return g.splice(j + 1, 0, l), c = g.join("\r\n")
    }
    return {
      parseStats: function(a) {
        "use strict";
        var b = 0,
          c = [];
        return navigator.mozGetUserMedia ? a.forEach(function(d, e) {
          var f, g = !1;
          e.indexOf("outbound_rtp_audio_") >= 0 ? (g = !0, f = {
            type: "ssrc_audio_send",
            id: d.id,
            stats: {
              bytes_sent: d.bytesSent,
              codec_name: "",
              packets_sent: d.packetsSent,
              packets_lost: a.get("outbound_rtcp_audio_" + e.slice(19)).packetsLost,
              rtt_ms: a.get("outbound_rtcp_audio_" + e.slice(19)).mozRtt
            }
          }) : e.indexOf("outbound_rtp_video_") >= 0 ? (g = !0, f = {
            type: "ssrc_video_send",
            id: d.id,
            stats: {
              bytes_sent: d.bytesSent,
              codec_name: "",
              packets_sent: d.packetsSent,
              packets_lost: a.get("outbound_rtcp_video_" + e.slice(19)).packetsLost,
              firs_rcvd: -1,
              plis_rcvd: -1,
              nacks_rcvd: -1,
              send_frame_width: -1,
              send_frame_height: -1,
              adapt_reason: -1,
              adapt_changes: -1,
              framerate_sent: d.framerateMean,
              rtt_ms: a.get("outbound_rtcp_video_" + e.slice(19)).mozRtt
            }
          }) : e.indexOf("inbound_rtp_audio_") >= 0 ? (g = !0, f = {
            type: "ssrc_audio_recv",
            id: d.id,
            stats: {
              bytes_rcvd: d.bytesReceived,
              delay_estimated_ms: -1,
              packets_rcvd: d.packetsReceived,
              packets_lost: d.packetsLost,
              codec_name: ""
            }
          }) : e.indexOf("inbound_rtp_video_") >= 0 && (g = !0, f = {
            type: "ssrc_video_recv",
            id: d.id,
            stats: {
              bytes_rcvd: d.bytesReceived,
              packets_rcvd: d.packetsReceived,
              packets_lost: d.packetsLost,
              firs_sent: -1,
              nacks_sent: -1,
              plis_sent: -1,
              frame_width: -1,
              frame_height: -1,
              framerate_rcvd: d.framerateMean,
              framerate_output: -1,
              current_delay_ms: -1,
              codec_name: ""
            }
          }), g && (c[b] = f, b++)
        }) : a.forEach(function(a) {
          var d, e = !1;
          if ("ssrc" === a.type)
            if (e = !0, a.bytesSent)
              if (a.googFrameHeightSent) {
                var f;
                f = !0 === a.googCpuLimitedResolution ? 1 : !0 === a.googBandwidthLimitedResolution ? 2 : !0 === a.googViewLimitedResolution ?
                  3 : 99, d = {
                  type: "ssrc_video_send",
                  id: a.id,
                  stats: {
                    bytes_sent: a.bytesSent,
                    codec_name: a.googCodecName,
                    packets_sent: a.packetsSent,
                    packets_lost: a.packetsLost,
                    firs_rcvd: a.googFirsReceived,
                    plis_rcvd: a.googPlisReceived,
                    nacks_rcvd: a.googNacksReceived,
                    send_frame_width: a.googFrameWidthSent,
                    send_frame_height: a.googFrameHeightSent,
                    adapt_reason: f,
                    adapt_changes: a.googAdaptationChanges,
                    framerate_sent: a.googFrameRateSent,
                    rtt_ms: a.googRtt
                  }
                }
              } else d = {
                type: "ssrc_audio_send",
                id: a.id,
                stats: {
                  bytes_sent: a.bytesSent,
                  codec_name: a.googCodecName,
                  packets_sent: a.packetsSent,
                  packets_lost: a.packetsLost,
                  rtt_ms: a.googRtt
                }
              };
            else d = a.googFrameHeightReceived ? {
              type: "ssrc_video_recv",
              id: a.id,
              stats: {
                bytes_rcvd: a.bytesReceived,
                packets_rcvd: a.packetsReceived,
                packets_lost: a.packetsLost,
                firs_sent: a.googFirsSent,
                nacks_sent: a.googNacksSent,
                plis_sent: a.googPlisSent,
                frame_width: a.googFrameWidthReceived,
                frame_height: a.googFrameHeightReceived,
                framerate_rcvd: a.googFrameRateReceived,
                framerate_output: a.googFrameRateDecoded,
                current_delay_ms: a.googCurrentDelayMs,
                codec_name: a.googCodecName
              }
            } : {
              type: "ssrc_audio_recv",
              id: a.id,
              stats: {
                bytes_rcvd: a.bytesReceived,
                delay_estimated_ms: a.googCurrentDelayMs,
                packets_rcvd: a.packetsReceived,
                packets_lost: a.packetsLost,
                codec_name: a.googCodecName
              }
            };
          else "VideoBwe" === a.type && (e = !0, d = {
            type: "VideoBWE",
            id: "",
            stats: {
              available_send_bandwidth: a.googAvailableSendBandwidth,
              available_receive_bandwidth: a.googAvailableReceiveBandwidth,
              transmit_bitrate: a.googTransmitBitrate,
              retransmit_bitrate: a.googRetransmitBitrate
            }
          });
          e && (c[b] = d, b++)
        }), c
      },
      parseAudioLevel: function(a) {
        for (var b = 0, c = 0, d = {}, e = [], f = [], g = !1, h = a.result(), i = 0; i < h.length; i++) {
          var j = h[i];
          if ("ssrc" === j.type)
            if (j.stat("bytesSent"))
              if (j.stat("googFrameHeightSent"));
              else {
                g = !0;
                var k = {};
                k.ssrc = j.id, k.level = j.stat("audioInputLevel"), e[b] = k, b++
              }
            else if (j.stat("googFrameHeightReceived"));
            else {
              g = !0;
              var k = {};
              k.ssrc = j.id, k.level = j.stat("audioOutputLevel"), f[c] = k, c++
            }
        }
        return g && (b > 0 && (d.audioInputLevels = e), c > 0 && (d.audioOutputLevels = f)), d
      },
      setPreferredCodec: function(b, d, g) {
        if (!d || !g) return e.Logger.warning("Media type or codec name is not provided."), b;
        var h = b.split("\r\n"),
          i = a(h, "m=", d);
        if (null === i) return b;
        var j = c(h, g);
        return j && (h[i] = f(h[i], j)), b = h.join("\r\n")
      },
      setPreferredBitrate: g,
      sysInfo: function() {
        var a = Object.create({});
        a.sdk = {
          version: "3.4.1",
          type: "JavaScript"
        };
        var b = navigator.userAgent,
          c = /Firefox\/([0-9\.]+)/,
          d = /Chrome\/([0-9\.]+)/,
          e = /Edge\/([0-9\.]+)/,
          f = d.exec(b);
        f ? a.runtime = {
          name: "Chrome",
          version: f[1]
        } : (f = c.exec(b)) ? a.runtime = {
          name: "FireFox",
          version: f[1]
        } : (f = e.exec(b)) ? a.runtime = {
          name: "Edge",
          version: f[1]
        } : a.runtime = {
          name: "Unknown",
          version: "Unknown"
        };
        var g = /Windows NT ([0-9\.]+)/,
          h = /Intel Mac OS X ([0-9_\.]+)/,
          i = /iPhone OS ([0-9_\.]+)/,
          j = /X11; Linux/,
          k = /Android( ([0-9\.]+))?/,
          l = /CrOS/;
        return (f = g.exec(b)) ? a.os = {
          name: "Windows NT",
          version: f[1]
        } : (f = h.exec(b)) ? a.os = {
          name: "Mac OS X",
          version: f[1].replace(/_/g, ".")
        } : (f = i.exec(b)) ? a.os = {
          name: "iPhone OS",
          version: f[1].replace(/_/g, ".")
        } : (f = j.exec(b)) ? a.os = {
          name: "Linux",
          version: "Unknown"
        } : (f = k.exec(b)) ? a.os = {
          name: "Android",
          version: f[1] || "Unknown"
        } : (f = l.exec(b)) ? a.os = {
          name: "Chrome OS",
          version: "Unknown"
        } : a.os = {
          name: "Unknown",
          version: "Unknown"
        }, a
      }
    }
  }(), e.Base64 = function() {
    "use strict";
    var a, b, c, d, e, f, g, h, i, j, k, l;
    for (a = -1, b = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
      "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q",
      "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"
    ], c = [], f = 0; f < b.length; f += 1) c[b[f]] = f;
    return g = function(a) {
      d = a, e = 0
    }, h = function() {
      var b;
      return d ? e >= d.length ? a : (b = 255 & d.charCodeAt(e), e += 1, b) : a
    }, i = function(c) {
      var d, e, f;
      for (g(c), d = "", e = new Array(3), f = !1; !f && (e[0] = h()) !== a;) e[1] = h(), e[2] = h(), d += b[e[0] >> 2],
        e[1] !== a ? (d += b[e[0] << 4 & 48 | e[1] >> 4], e[2] !== a ? (d += b[e[1] << 2 & 60 | e[2] >> 6], d += b[63 & e[
          2]]) : (d += b[e[1] << 2 & 60], d += "=", f = !0)) : (d += b[e[0] << 4 & 48], d += "=", d += "=", f = !0);
      return d
    }, j = function() {
      if (!d) return a;
      for (;;) {
        if (e >= d.length) return a;
        var b = d.charAt(e);
        if (e += 1, c[b]) return c[b];
        if ("A" === b) return 0
      }
    }, k = function(a) {
      return a = a.toString(16), 1 === a.length && (a = "0" + a), a = "%" + a, unescape(a)
    }, l = function(b) {
      var c, d, e;
      for (g(b), c = "", d = new Array(4), e = !1; !e && (d[0] = j()) !== a && (d[1] = j()) !== a;) d[2] = j(), d[3] = j(),
        c += k(d[0] << 2 & 255 | d[1] >> 4), d[2] !== a ? (c += k(d[1] << 4 & 255 | d[2] >> 2), d[3] !== a ? c += k(d[2] <<
        6 & 255 | d[3]) : e = !0) : e = !0;
      return c
    }, {
      encodeBase64: i,
      decodeBase64: l
    }
  }(), e.Logger = function() {
    "use strict";
    var b = 0,
      c = 1,
      d = 2,
      e = 3,
      f = 4,
      g = 5,
      h = function() {},
      i = {
        DEBUG: b,
        TRACE: c,
        INFO: d,
        WARNING: e,
        ERROR: f,
        NONE: g
      };
    i.log = a.console.log.bind(a.console);
    var j = function(b) {
        return "function" == typeof a.console[b] ? a.console[b].bind(a.console) : a.console.log.bind(a.console)
      },
      k = function(a) {
        i.debug = a <= b ? j("debug") : h, i.trace = a <= c ? j("trace") : h, i.info = a <= d ? j("info") : h, i.warning =
          a <= e ? j("warn") : h, i.error = a <= f ? j("error") : h
      };
    return k(b), i.setLogLevel = k, i
  }(),
    function() {
      "use strict";

      function b(a) {
        this.mediaStream = a.mediaStream, a.attributes = a.attributes || {}, this.url = function() {
          if ("string" == typeof a.url && "" !== a.url) return a.url
        }, this.hasVideo = function() {
          return !!a.video
        }, this.hasAudio = function() {
          return !!a.audio
        }, this.attributes = function() {
          return a.attributes
        }, this.attr = function(b, c) {
          return arguments.length > 1 && (a.attributes[b] = c), a.attributes[b]
        }, this.id = function() {
          return a.id || null
        }, this.isScreen = function() {
          return !!a.video && "screen" === a.video.device
        }, this.bitRate = {
          maxVideoBW: void 0,
          maxAudioBW: void 0
        }, this.toJson = function() {
          return {
            id: this.id(),
            audio: !!this.hasAudio() && a.audio,
            video: !!this.hasVideo() && a.video,
            attributes: a.attributes
          }
        }
      }

      function c(a) {
        b.call(this, a), this.hasAudio = function() {
          return this.mediaStream ? !!this.mediaStream.getAudioTracks().length : !!a.hasAudio
        }, this.hasVideo = function() {
          return this.mediaStream ? !!this.mediaStream.getVideoTracks().length : !!a.hasVideo
        }
      }

      function f(a) {
        b.call(this, a), this.isMixed = function() {
          return !1
        }, this.from = a.from;
        var c = {},
          d = this;
        Object.defineProperties(this, {
          on: {
            get: function() {
              return function(a, b) {
                return c[a] = c[a] || [], c[a].push(b), d
              }
            }
          },
          emit: {
            get: function() {
              return function(a) {
                if (c[a]) {
                  var b = [].slice.call(arguments, 1);
                  c[a].map(function(a) {
                    a.apply(d, b)
                  })
                }
                return d
              }
            }
          },
          removeListener: {
            get: function() {
              return function(a, b) {
                return void 0 === b ? c[a] = [] : c[a] && c[a].map(function(d, e) {
                  d === b && c[a].splice(e, 1)
                }), d
              }
            }
          },
          clearListeners: {
            get: function() {
              return function() {
                return c = {}, d
              }
            }
          }
        })
      }

      function g(a) {
        f.call(this, a), this.resolutions = function() {
          return a.video.resolutions instanceof Array ? a.video.resolutions.map(function(a) {
            return a
          }) : []
        }, this.isMixed = function() {
          return !0
        }, this.viewport = function() {
          return a.viewport
        }
      }

      function h(a) {
        this.url = function() {
          if ("string" == typeof a.url && "" !== a.url) return a.url
        }, this.id = function() {
          return a.id || null
        }, this.hasVideo = function() {
          return !!a.video
        }, this.hasAudio = function() {
          return !!a.audio
        }, this.toJson = function() {
          var b;
          return !0 === a.video ? b = {
            device: "camera"
          } : !1 === a.video ? b = a.video : "object" == typeof a.video && (b = a.video, b.device = a.video.device ||
            "camera"), {
            id: this.id(),
            audio: a.audio,
            video: b,
            url: this.url()
          }
        }
      }

      function i() {
        return null !== a.navigator.appVersion.match(/Chrome\/([\w\W]*?)\./) && a.navigator.appVersion.match(
          /Chrome\/([\w\W]*?)\./)[1] <= 35
      }

      function j() {
        return a.navigator.appVersion.indexOf("Trident") > -1
      }

      function k() {
        return null !== a.navigator.userAgent.match("Firefox")
      }

      function l() {
        return k() || null !== a.navigator.appVersion.match(/Chrome\/([\w\W]*?)\./) && a.navigator.appVersion.match(
          /Chrome\/([\w\W]*?)\./)[1] >= 34
      }

      function m(a, b) {
        return {
          width: a,
          height: b
        }
      }

      function n(a, b) {
        if ("object" == typeof(a = JSON.parse(JSON.stringify(a))) && null !== a && void 0 !== a.url) {
          var c = "URL for LocalStream is deprecated, please use ExternalStream instead.";
          "function" == typeof console.warn ? console.warn(c) : e.Logger.warning(c);
          var f = new d.LocalStream(a);
          return void("function" == typeof b && b(null, f))
        }
        if ("function" != typeof q && !j()) return void("function" == typeof b && b({
          code: 1100,
          msg: "webrtc support not available"
        }));
        var g = arguments[3];
        void 0 === g && (g = 2);
        var h = {};
        if (null !== a && "object" == typeof a) {
          if (!a.audio && !a.video) return void("function" == typeof b && b({
            code: 1107,
            msg: "At least one of audio and video must be requested."
          }));
          if (a.video) {
            if ("object" != typeof a.video && a.video && (a.video = Object.create({})), "string" != typeof a.video.device &&
            (a.video.device = "camera"), "screen" === a.video.device && !l() && "function" == typeof b) return void b({
              code: 1103,
              msg: "browser screen sharing not supported"
            });
            "object" == typeof a.video.resolution && void 0 !== a.video.resolution.width && void 0 !== a.video.resolution.height ?
              h.video = JSON.parse(JSON.stringify(m(a.video.resolution.width, a.video.resolution.height))) : h.video = JSON.parse(
              JSON.stringify(p[a.video.resolution] || p.unspecified)), "string" == typeof a.video.deviceId && (h.video.deviceId =
              a.video.deviceId), j() || i() || (a.video.frameRate instanceof Array && a.video.frameRate.length >= 2 ? h.video
              .frameRate = {
              min: a.video.frameRate[0],
              max: a.video.frameRate[1]
            } : "number" == typeof a.video.frameRate ? h.video.frameRate = a.video.frameRate : e.Logger.warning(
              "Invalid frame rate value, ignored."))
          }
          "object" == typeof a.audio ? h.audio = a.audio : !0 === a.audio && (h.audio = !0)
        } else if ("function" == typeof b) return void b({
          code: 1107,
          msg: "USER_INPUT_INVALID"
        });
        var o = function(c) {
            if (("object" != typeof a.video || "screen" !== a.video.device) && (a.audio && 0 === c.getAudioTracks().length ||
              a.video && 0 === c.getVideoTracks().length)) {
              for (var e = 0; e < c.getTracks().length; e++) c.getTracks()[e].stop();
              return void b({
                code: 1104,
                msg: "Not all device requests are satisfied."
              })
            }
            a.mediaStream = c, a.id = c.id;
            var f = new d.LocalStream(a);
            if (a.video && "screen" === a.video.device) {
              var g = c.getVideoTracks();
              g.length > 0 && (g[0].onended = function() {
                f.close()
              })
            }
            if (h.video) switch (h.video.width) {
              case 320:
                f.bitRate.maxVideoBW = 512;
                break;
              case 640:
                f.bitRate.maxVideoBW = 1024;
                break;
              case 1280:
                f.bitRate.maxVideoBW = 2048
            }
            "function" == typeof b && b(null, f)
          },
          r = function(c) {
            var d = {
              code: 1100,
              msg: c.name || c
            };
            switch (d.msg) {
              case "Starting video failed":
              case "TrackStartError":
                if (a.video = {
                  device: a.video.device,
                  extensionId: a.video.extensionId
                }, g > 0) return void setTimeout(function() {
                  n(a, b, g - 1)
                }, 1);
                d.msg = "MEDIA_OPTION_INVALID", d.code = 1104;
                break;
              case "DevicesNotFoundError":
                d.msg = "DEVICES_NOT_FOUND", d.code = 1102;
                break;
              case "NotSupportedError":
                d.msg = "NOT_SUPPORTED", d.code = 1105;
                break;
              case "PermissionDeniedError":
                d.msg = "PERMISSION_DENIED", d.code = 1101;
                break;
              case "PERMISSION_DENIED":
                d.code = 1101;
                break;
              case "ConstraintNotSatisfiedError":
                d.msg = "CONSTRAINT_NOT_SATISFIED", d.code = 1106;
                break;
              default:
                d.msg || (d.msg = "UNDEFINED")
            }
            "function" == typeof b && b(d)
          };
        if (a.video && "screen" === a.video.device) {
          if (k()) return void 0 !== h.video ? h.video.mediaSource = "window" : h.video = {
            mediaSource: "window"
          }, void q.apply(navigator, [h, o, r]);
          var s = a.video.extensionId || "pndohhifhheefbpeljcmnhnkphepimhe",
            t = ["screen", "window", "tab"];
          a.audio && t.push("audio");
          try {
            chrome.runtime.sendMessage(s, {
              getStream: t
            }, function(a) {
              if (void 0 === a) return void("function" == typeof b && b({
                code: 1103,
                msg: "screen sharing plugin inaccessible"
              }));
              h.audio = {
                mandatory: {
                  chromeMediaSource: "desktop",
                  chromeMediaSourceId: a.streamId
                }
              }, h.video.mandatory = h.video.mandatory || {}, h.video.mandatory.chromeMediaSource = "desktop", h.video.mandatory
                .chromeMediaSourceId = a.streamId, h.video.height && (h.video.mandatory.maxHeight = h.video.mandatory.minHeight =
                h.video.height, delete h.video.height), h.video.width && (h.video.mandatory.maxWidth = h.video.mandatory.minWidth =
                h.video.width, delete h.video.width), h.video.frameRate && ("object" == typeof h.video.frameRate ? (h.video.mandatory
                  .minFrameRate = h.video.frameRate.min, h.video.mandatory.maxFrameRate = h.video.frameRate.max) : "number" ==
                typeof h.video.frameRate ? (h.video.mandatory.minFrameRate = h.video.frameRate, h.video.mandatory.maxFrameRate =
                  h.video.frameRate) : e.Logger.warning("Invalid frame rate value for screen sharing."), delete h.video.frameRate
              ), q.apply(navigator, [h, o, r])
            })
          } catch (u) {
            "function" == typeof b && b({
              code: 1103,
              msg: "screen sharing plugin inaccessible",
              err: u
            })
          }
        } else j() ? navigator.getUserMedia(h, o, r) : q.apply(navigator, [h, o, r])
      }

      function o(a, b) {
        if ("object" != typeof a || !a.url) return void("function" == typeof b && b({
          code: 1107,
          msg: "External stream must have url property"
        }));
        if (!a.audio && !a.video) return void("function" == typeof b && b({
          code: 1107,
          msg: "External stream must have video or audio"
        }));
        var c = new d.ExternalStream(a);
        "function" == typeof b && b(null, c)
      }
      b.prototype.close = function() {
        "function" == typeof this.hide && this.hide(), this.mediaStream && this.mediaStream.getTracks().map(function(a) {
          "function" == typeof a.stop && a.stop()
        }), this.mediaStream = null, "function" == typeof this.unpublish && this.unpublish(), this.channel && "function" ==
        typeof this.channel.close && this.channel.close()
      }, b.prototype.createObjectURL = function() {
        return this.mediaStream ? (a.URL || webkitURL).createObjectURL(this.mediaStream) : ""
      }, b.prototype.disableAudio = function(a) {
        var b = this;
        if (b.hasAudio() && b.mediaStream) {
          if (void 0 === a && (a = 0), -1 === a) return b.mediaStream.getAudioTracks().map(function(a) {
            return !!a.enabled && (a.enabled = !1, !0)
          });
          var c = b.mediaStream.getAudioTracks();
          if (c && c[a] && c[a].enabled) return c[a].enabled = !1, !0
        }
        return !1
      }, b.prototype.enableAudio = function(a) {
        var b = this;
        if (b.hasAudio() && b.mediaStream) {
          if (void 0 === a && (a = 0), -1 === a) return b.mediaStream.getAudioTracks().map(function(a) {
            return !0 !== a.enabled && (a.enabled = !0, !0)
          });
          var c = b.mediaStream.getAudioTracks();
          if (c && c[a] && !0 !== c[a].enabled) return c[a].enabled = !0, !0
        }
        return !1
      }, b.prototype.disableVideo = function(a) {
        var b = this;
        if (b.hasVideo() && b.mediaStream) {
          if (void 0 === a && (a = 0), -1 === a) return b.mediaStream.getVideoTracks().map(function(a) {
            return !!a.enabled && (a.enabled = !1, !0)
          });
          var c = b.mediaStream.getVideoTracks();
          if (c && c[a] && c[a].enabled) return c[a].enabled = !1, !0
        }
        return !1
      }, b.prototype.enableVideo = function(a) {
        var b = this;
        if (b.hasVideo() && b.mediaStream) {
          if (void 0 === a && (a = 0), -1 === a) return b.mediaStream.getVideoTracks().map(function(a) {
            return !0 !== a.enabled && (a.enabled = !0, !0)
          });
          var c = b.mediaStream.getVideoTracks();
          if (c && c[a] && !0 !== c[a].enabled) return c[a].enabled = !0, !0
        }
        return !1
      }, b.prototype.updateConfiguration = function(a, b) {
        if (void 0 !== a) return this.channel ? void this.channel.updateSpec(a, b) :
          "This stream has not been published, ignoring"
      }, c.prototype = Object.create(b.prototype), f.prototype = Object.create(b.prototype), g.prototype = Object.create(
        f.prototype), h.prototype = Object.create({}), c.prototype.constructor = c, f.prototype.constructor = f, g.prototype
        .constructor = g, h.prototype.constructor = h;
      var p = {
          true: {},
          unspecified: {},
          sif: m(320, 240),
          vga: m(640, 480),
          hd720p: m(1280, 720),
          hd1080p: m(1920, 1080)
        },
        q = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
      c.create = function() {
        n.apply(this, arguments)
      }, h.create = function() {
        o.apply(this, arguments)
      }, d.Stream = b, d.LocalStream = c, d.RemoteStream = f, d.RemoteMixedStream = g, d.ExternalStream = h
    }(),
    function() {
      function b() {
        var a = arguments[0];
        if ("function" == typeof a) {
          var b = Array.prototype.slice.call(arguments, 1);
          a.apply(null, b)
        }
      }

      function c(a) {
        a.session_id = d.sessionId += 1;
        var b = {};
        if (b.browser = m(), "mozilla" === b.browser) e.Logger.debug("Firefox Stack"), b = f.FirefoxStack(a);
        else if ("bowser" === b.browser) e.Logger.debug("Bowser Stack"), b = f.BowserStack(a);
        else if ("chrome-stable" === b.browser) e.Logger.debug("Stable!"), b = f.ChromeStableStack(a);
        else {
          if ("edge" !== b.browser) throw e.Logger.debug("None!"), "WebRTC stack not available";
          e.Logger.debug("Edge Stack"), b = f.EdgeORTCStack(a)
        }
        return b.updateSpec || (b.updateSpec = function(a, b) {
          e.Logger.error("Update Configuration not implemented in this browser"), b && b("unimplemented")
        }), b
      }

      function g(a) {
        if (!a.video) return new d.RemoteStream(a);
        switch (a.video.device) {
          case "mcu":
            return new d.RemoteMixedStream(a);
          default:
            return new d.RemoteStream(a)
        }
      }

      function h(a, b, c) {
        if (!a || !a.connected) return c("socket not ready");
        try {
          a.emit(b, function(a, b) {
            return "success" === a ? c(null, b) : c(b || "response error")
          })
        } catch (d) {
          c("socket emit error")
        }
      }

      function i(a, b, c, d) {
        if (!a || !a.connected) return d("socket not ready");
        try {
          a.emit(b, c, function(a, b) {
            return "success" === a ? d(null, b) : d(b || "response error")
          })
        } catch (e) {
          d("socket emit error")
        }
      }

      function j(a, b, c, d, e) {
        if (!a || !a.connected) return e("error", "socket not ready");
        try {
          a.emit(b, c, d, function(a, b) {
            e(a, b)
          })
        } catch (f) {
          e("error", "socket emit error")
        }
      }

      function k(a, c, d, e, f) {
        i(a, "customMessage", {
          type: "control",
          payload: {
            action: c,
            streamId: d
          }
        }, function(a, c) {
          if (a) return b(f, a);
          b(e, c)
        })
      }

      function l(a, c, e, f, g, h) {
        if (!(e instanceof d.Stream || e instanceof d.ExternalStream)) return b(h, "Invalid stream");
        if (!Array.isArray(f)) return b(h, "Target streams is not a list");
        var j, k, l = [];
        for (j = 0; j < f.length; j++) {
          if (!((k = f[j]) instanceof d.RemoteMixedStream)) return b(h, "Invalid stream found in targetStreams.");
          l.push(k.id())
        }
        i(c, a, {
          streamId: e.id(),
          mixStreams: l
        }, function(a) {
          if (a) return b(h, a);
          b(g, null)
        })
      }
      d.sessionId = 103;
      var m = function() {
          var b = "none";
          return null !== a.navigator.userAgent.match("Firefox") ? b = "mozilla" : null !== a.navigator.userAgent.match(
            "Bowser") ? b = "bowser" : null !== a.navigator.userAgent.match(/Edge\/(\d+).(\d+)$/) ? b = "edge" : null !== a.navigator
            .userAgent.match("Chrome") ? a.navigator.appVersion.match(/Chrome\/([\w\W]*?)\./)[1] >= 26 && (b =
            "chrome-stable") : null !== a.navigator.userAgent.match("Safari") ? b = "bowser" : null !== a.navigator.userAgent
            .match("WebKit") && (b = "bowser"), b
        },
        n = 0,
        o = function(a) {
          this.spec = a || {}, this.remoteStreams = {}, this.localStreams = {}, this.state = n, this.conferenceId = ""
        };
      o.prototype = d.EventDispatcher({}), o.prototype.setIceServers = function() {
        var a = this.spec;
        return a.userSetIceServers = [], Array.prototype.slice.call(arguments, 0).map(function(b) {
          b instanceof Array ? b.map(function(b) {
            "object" == typeof b && null !== b ? "string" == typeof b.urls && "" !== b.urls || b.urls instanceof Array ?
              a.userSetIceServers.push(b) : "string" == typeof b.url && "" !== b.url && (b.urls = b.url, delete b.url, a.userSetIceServers
              .push(b)) : "string" == typeof b && "" !== b && a.userSetIceServers.push({
              urls: b
            })
          }) : "object" == typeof b && null !== b ? "string" == typeof b.urls && "" !== b.urls || b.urls instanceof Array ?
            a.userSetIceServers.push(b) : "string" == typeof b.url && "" !== b.url && (b.urls = b.url, delete b.url, a.userSetIceServers
            .push(b)) : "string" == typeof b && "" !== b && a.userSetIceServers.push({
            urls: b
          })
        }), a.userSetIceServers
      }, o.prototype.getIceServers = function() {
        return this.spec.userSetIceServers
      }, o.prototype.join = function(a, c, f) {
        var h;
        try {
          h = JSON.parse(e.Base64.decodeBase64(a))
        } catch (m) {
          return b(f, "invalid token")
        }
        var i = this,
          j = !0 === h.secure,
          k = h.host;
        if ("string" != typeof k) return b(f, "invalid host");
        if (-1 === k.indexOf("http") && (k = j ? "https://" + k : "http://" + k), i.state !== n) return b(f,
          "connection state invalid");
        i.state = 1, void 0 !== i.socket ? i.socket.connect() : (i.socket = io.connect(k, {
          reconnect: !1,
          secure: j,
          "force new connection": !0
        }), i.socket.on("add_stream", function(a) {
          if (void 0 !== i.remoteStreams[a.id]) return void e.Logger.warning("stream already added:", a.id);
          var b = g({
              video: a.video,
              audio: a.audio,
              id: a.id,
              from: a.from,
              attributes: a.attributes,
              viewport: a.view
            }),
            c = new d.StreamEvent({
              target: i,
              type: "stream-added",
              stream: b
            });
          i.remoteStreams[a.id] = b, i.dispatchEvent(c)
        }), i.socket.on("update_stream", function(a) {
          var b = i.remoteStreams[a.id];
          b && b.emit(a.event, a.data)
        }), i.socket.on("remove_stream", function(a) {
          var b = i.remoteStreams[a.id];
          if (b) {
            b.close(), delete i.remoteStreams[a.id];
            var c = new d.StreamEvent({
              target: i,
              type: "stream-removed",
              stream: b
            });
            i.dispatchEvent(c)
          }
        }), i.socket.on("signaling_message_erizo", function(a) {
          var b;
          (b = a.peerId ? i.remoteStreams[a.peerId] : i.localStreams[a.streamId]) && b.channel && b.channel.processSignalingMessage(
            a.mess)
        }), i.socket.on("add_recorder", function(a) {
          var b = new d.RecorderEvent({
            target: i,
            type: "recorder-added",
            id: a.id
          });
          i.dispatchEvent(b)
        }), i.socket.on("reuse_recorder", function(a) {
          var b = new d.RecorderEvent({
            target: i,
            type: "recorder-continued",
            id: a.id
          });
          i.dispatchEvent(b)
        }), i.socket.on("remove_recorder", function(a) {
          var b = new d.RecorderEvent({
            type: "recorder-removed",
            id: a.id
          });
          i.dispatchEvent(b)
        }), i.socket.on("disconnect", function() {
          var a = !1;
          i.state !== n ? (a = !0, e.Logger.info("Will trigger server-disconnect")) : e.Logger.info(
            "Will not trigger server-disconnect"), i.state = n, i.myId = null;
          var b, c;
          for (b in i.remoteStreams) i.remoteStreams.hasOwnProperty(b) && (c = i.remoteStreams[b], c.close(), delete i.remoteStreams[
            b]);
          for (b in i.localStreams) i.localStreams.hasOwnProperty(b) && (c = i.localStreams[b], c.channel && "function" ==
          typeof c.channel.close && c.channel.close(), delete i.localStreams[b]);
          try {
            i.socket.disconnect()
          } catch (m) {}
          if (a) {
            var f = new d.ClientEvent({
              target: i,
              type: "server-disconnected"
            });
            i.dispatchEvent(f)
          }
        }), i.socket.on("user_join", function(a) {
          var b = new d.ClientEvent({
            target: i,
            type: "user-joined",
            user: a.user
          });
          i.dispatchEvent(b)
        }), i.socket.on("user_leave", function(a) {
          var b = new d.ClientEvent({
            target: i,
            type: "user-left",
            user: a.user
          });
          i.dispatchEvent(b)
        }), i.socket.on("custom_message", function(a) {
          var b = new d.MessageEvent({
            target: i,
            type: "message-received",
            msg: a
          });
          i.dispatchEvent(b)
        }), i.socket.on("connect_failed", function(a) {
          b(f, a || "connection_failed")
        }), i.socket.on("error", function(a) {
          b(f, a || "connection_error")
        }), i.socket.on("connection_failed", function(a) {
          if (e.Logger.error("MCU reports connection failed for stream: " + a.streamId), void 0 !== i.localStreams[a.streamId]) {
            var b = i.localStreams[a.streamId];
            i.unpublish(b), delete i.localStreams[a.streamId]
          } else i.unsubscribe(i.remoteStreams[a.streamId]);
          if (i.state !== n) {
            var c = new d.StreamEvent({
              type: "stream-failed"
            });
            i.dispatchEvent(c)
          }
        }), i.socket.on("stream-publish", function(a) {
          var b = i.localStreams[a.id];
          b && (console.log("Stream published"), i.dispatchEvent(new d.StreamEvent({
            target: i,
            type: "stream-published",
            stream: b
          })))
        }));
        try {
          var l = {
            token: a,
            userAgent: d.Common.sysInfo()
          };
          i.socket.emit("login", l, function(a, d) {
            if ("success" === a) {
              i.myId = d.clientId, i.conferenceId = d.id, i.state = 2;
              var e = [];
              void 0 !== d.streams && (e = d.streams.map(function(a) {
                return a.viewport = a.view, i.remoteStreams[a.id] = g(a), i.remoteStreams[a.id]
              }));
              var h;
              if (void 0 !== d.users)
                for (var j = 0; j < d.users.length; j++)
                  if (d.users[j].id === d.clientId) {
                    h = d.users[j];
                    break
                  } return b(c, {
                streams: e,
                users: d.users,
                self: h
              })
            }
            return b(f, d || "response error")
          })
        } catch (o) {
          b(f, "socket emit error")
        }
      }, o.prototype.publish = function(a, f, g, h) {
        var l = this;
        if (a = a || {}, "function" == typeof f ? (h = g, g = f) : "object" == typeof f && null !== f || (f = {}), !(a instanceof d
          .LocalStream || a instanceof d.ExternalStream) || ("object" != typeof a.mediaStream || null === a.mediaStream) &&
        void 0 === a.url()) return b(h, "invalid stream");
        if (f.videoCodec = f.videoCodec || "h264", void 0 !== l.localStreams[a.id()]) return b(h, "already published");
        var m = a.toJson();
        if (!0 === f.unmix && (m.unmix = !0), void 0 !== a.url()) return m.state = "url", m.transport = f.transport, m.bufferSize =
          f.bufferSize, void j(l.socket, "publish", m, a.url(), function(c, d) {
          if ("success" !== c) return b(h, "error" === c ? d : c);
          a.id = function() {
            return d
          }, a.unpublish = function(b, c) {
            l.unpublish(a, b, c)
          }, l.localStreams[d] = a, b(g, a)
        });
        m.state = "erizo", j(l.socket, "publish", m, void 0, function(d, m) {
          if ("error" === d) return b(h, m);
          if ("timeout" === d) return b(h, d);
          a.id = function() {
            return m
          }, l.localStreams[m] = a, a.channel = c({
            callback: function(a) {
              console.log("Sending message", a), j(l.socket, "signaling_message", {
                streamId: m,
                msg: a
              }, void 0, function() {})
            },
            video: a.hasVideo(),
            audio: a.hasAudio(),
            iceServers: l.getIceServers(),
            iceTransportPolicy: f.iceTransportPolicy,
            pcConstraints: f.pcConstraints,
            maxAudioBW: f.maxAudioBW,
            maxVideoBW: f.maxVideoBW,
            audioCodec: f.audioCodec,
            videoCodec: f.videoCodec
          });
          var n = function() {
              a.signalOnPlayAudio = function(a, b) {
                k(l.socket, "audio-out-on", m, a, b)
              }, a.signalOnPauseAudio = function(a, b) {
                k(l.socket, "audio-out-off", m, a, b)
              }, a.signalOnPlayVideo = function(a, b) {
                k(l.socket, "video-out-on", m, a, b)
              }, a.signalOnPauseVideo = function(a, b) {
                k(l.socket, "video-out-off", m, a, b)
              }, a.unpublish = function(b, c) {
                l.unpublish(a, b, c)
              }, b(g, a), h = function() {}, n = function() {}
            },
            o = function() {
              i(l.socket, "unpublish", m, function() {}, function() {}), a.channel.close(), a.channel = void 0, b(h,
                "peer connection failed"), n = function() {}, o = function() {}
            };
          a.channel.oniceconnectionstatechange = function(a) {
            switch (a) {
              case "completed":
              case "connected":
                n();
                break;
              case "checking":
              case "closed":
                break;
              case "failed":
                o();
                break;
              default:
                e.Logger.warning("unknown ice connection state:", a)
            }
          }, a.channel.addStream(a.mediaStream), a.channel.createOffer(!1)
        })
      }, o.prototype.unpublish = function(a, c, e) {
        var f = this;
        if (!(a instanceof d.LocalStream || a instanceof d.ExternalStream)) return b(e, "invalid stream");
        i(f.socket, "unpublish", a.id(), function(d) {
          if (a.channel && "function" == typeof a.channel.close && (a.channel.close(), a.channel = null), delete f.localStreams[
            a.id()], a.id = function() {
            return null
          }, a.signalOnPlayAudio = void 0, a.signalOnPauseAudio = void 0, a.signalOnPlayVideo = void 0, a.signalOnPauseVideo =
            void 0, delete a.unpublish, d) return b(e, d);
          b(c, null)
        })
      }, o.prototype.subscribe = function(a, f, g, h) {
        var l = this,
          n = !1,
          o = !1;
        return "function" == typeof f ? (h = g, g = f, f = {}) : "object" == typeof f && null !== f || (f = {}), a instanceof d
          .RemoteStream ? !1 === f.audio && !1 === f.video ? b(h, "no audio or video to subscribe.") : (f.videoCodec = f.videoCodec ||
          "h264", a.isMixed() || "object" != typeof f.video || !f.video.resolution && !f.video.qualityLevel ? ("object" ==
        typeof f.video && f.video.qualityLevel && (f.video.quality_level = f.video.qualityLevel, delete f.video.qualityLevel),
          void j(l.socket, "subscribe", {
            streamId: a.id(),
            audio: a.hasAudio() && !1 !== f.audio,
            video: a.hasVideo() && f.video,
            browser: m()
          }, void 0, function(d, m) {
            if ("error" === d || "timeout" === d) return b(h, m || d);
            a.channel = c({
              callback: function(b) {
                j(l.socket, "signaling_message", {
                  streamId: a.id(),
                  msg: b,
                  browser: a.channel.browser
                }, void 0, function() {})
              },
              audio: a.hasAudio() && !1 !== f.audio,
              video: a.hasVideo() && !1 !== f.video,
              iceServers: l.getIceServers(),
              iceTransportPolicy: f.iceTransportPolicy,
              videoCodec: f.videoCodec
            }), a.channel.onaddstream = function(c) {
              a.mediaStream = c.stream, o && !1 === n ? (n = !0, b(g, a)) : n = !0
            };
            var p = function() {
                a.signalOnPlayAudio = function(b, c) {
                  k(l.socket, "audio-in-on", a.id(), b, c)
                }, a.signalOnPauseAudio = function(b, c) {
                  k(l.socket, "audio-in-off", a.id(), b, c)
                }, a.signalOnPlayVideo = function(b, c) {
                  k(l.socket, "video-in-on", a.id(), b, c)
                }, a.signalOnPauseVideo = function(b, c) {
                  k(l.socket, "video-in-off", a.id(), b, c)
                }, n && !1 === o ? (o = !0, b(g, a)) : o = !0, h = function() {}, p = function() {}
              },
              q = function() {
                i(l.socket, "unsubscribe", a.id(), function() {}, function() {}), a.close(), a.signalOnPlayAudio = void 0,
                  a.signalOnPauseAudio = void 0, a.signalOnPlayVideo = void 0, a.signalOnPauseVideo = void 0, b(h,
                  "peer connection failed"), p = function() {}, q = function() {}
              };
            a.channel.oniceconnectionstatechange = function(a) {
              switch (a) {
                case "completed":
                case "connected":
                  p();
                  break;
                case "checking":
                case "closed":
                  break;
                case "failed":
                  q();
                  break;
                default:
                  e.Logger.warning("unknown ice connection state:", a)
              }
            }, a.channel.createOffer(!0)
          })) : b(h, "Resolution and quality level settings are not available for non-mixed stream.")) : b(h,
          "invalid stream")
      }, o.prototype.unsubscribe = function(a, c, e) {
        var f = this;
        if (!(a instanceof d.RemoteStream)) return b(e, "invalid stream");
        i(f.socket, "unsubscribe", a.id(), function(d, f) {
          if (d) return b(e, d);
          a.close(), a.signalOnPlayAudio = void 0, a.signalOnPauseAudio = void 0, a.signalOnPlayVideo = void 0, a.signalOnPauseVideo =
            void 0, b(c, f)
        })
      }, o.prototype.onMessage = function(a) {
        "function" == typeof a && this.on("message-received", a)
      }, d.ConferenceClient = function() {
        "use strict";
        var a = function(a) {
          o.call(this, a), this.join = function(a, b, c) {
            o.prototype.join.call(this, a, b, c)
          }, this.leave = function() {
            h(this.socket, "logout", function(a) {
              a && e.Logger.warning("Server returns error for logout event")
            }), this.socket.disconnect()
          }, this.send = function(a, c, d, e) {
            if (void 0 === a || null === a || "function" == typeof a) return b(e, "nothing to send");
            if (void 0 === c) c = "all";
            else if ("string" == typeof c);
            else {
              if ("function" != typeof c) return b(e, "invalid receiver");
              e = d, d = c, c = "all"
            }
            i(this.socket, "customMessage", {
              type: "data",
              data: a,
              receiver: c
            }, function(a, c) {
              if (a) return b(e, a);
              b(d, c)
            })
          }, this.mix = function(a, b, c, d) {
            return l("mix", this.socket, a, b, c, d)
          }, this.unmix = function(a, b, c, d) {
            return l("unmix", this.socket, a, b, c, d)
          }, this.shareScreen = function(a, c, f) {
            e.Logger.warning(
              "shareScreen is deprecated, please create a LocalStream and publish it to specific conference.");
            var g = this;
            "function" == typeof a && (f = c, c = a, a = {}), a = a || {}, d.LocalStream.create({
              video: {
                device: "screen",
                extensionId: a.extensionId,
                resolution: a.resolution ? a.resolution : {
                  width: screen.width,
                  height: screen.height
                },
                frameRate: a.frameRate
              },
              audio: !1
            }, function(d, e) {
              if (d) return b(f, d);
              g.publish(e, {
                maxVideoBW: a.maxVideoBW,
                videoCodec: a.videoCodec
              }, function(a) {
                b(c, a)
              }, function(a) {
                b(f, a)
              })
            })
          }, this.playAudio = function(a, b, c) {
            if (a instanceof d.Stream && a.hasAudio() && "function" == typeof a.signalOnPlayAudio) return a.signalOnPlayAudio(
              b, c);
            "function" == typeof c && c("unable to call playAudio")
          }, this.pauseAudio = function(a, b, c) {
            if (a instanceof d.Stream && a.hasAudio() && "function" == typeof a.signalOnPauseAudio) return a.signalOnPauseAudio(
              b, c);
            "function" == typeof c && c("unable to call pauseAudio")
          }, this.playVideo = function(a, b, c) {
            if (a instanceof d.Stream && a.hasVideo() && "function" == typeof a.signalOnPlayVideo) return a.signalOnPlayVideo(
              b, c);
            "function" == typeof c && c("unable to call playVideo")
          }, this.pauseVideo = function(a, b, c) {
            if (a instanceof d.Stream && a.hasVideo() && "function" == typeof a.signalOnPauseVideo) return a.signalOnPauseVideo(
              b, c);
            "function" == typeof c && c("unable to call pauseVideo")
          }, this.addExternalOutput = function(a, c, d, e) {
            var f = this;
            "function" == typeof c ? (e = d, d = c, c = {}) : "object" == typeof c && null !== c || (c = {}), c.url = a, c
              .video && c.video.resolution && (c.resolution = c.video.resolution), i(f.socket, "addExternalOutput", c,
              function(a) {
                if (a) return b(e, a);
                b(d)
              })
          }, this.updateExternalOutput = function(a, c, d, e) {
            var f = this;
            "function" == typeof c ? (e = d, d = c, c = {}) : "object" == typeof c && null !== c || (c = {}), c.url = a, c
              .video && c.video.resolution && (c.resolution = c.video.resolution), i(f.socket, "updateExternalOutput", c,
              function(a) {
                if (a) return b(e, a);
                b(d)
              })
          }, this.removeExternalOutput = function(a, c, d) {
            var e = this;
            if ("string" != typeof a) return void b(d, "URL should be string.");
            i(e.socket, "removeExternalOutput", {
              url: a
            }, function(a) {
              if (a) return b(d, a);
              b(c)
            })
          }, this.startRecorder = function(a, c, d) {
            var e = this;
            "function" == typeof a ? (d = c, c = a, a = {}) : "object" == typeof a && null !== a || (a = {}), i(e.socket,
              "startRecorder", a,
              function(a, e) {
                if (a) return b(d, a);
                b(c, e)
              })
          }, this.stopRecorder = function(a, c, d) {
            var e = this;
            "function" == typeof a ? (d = c, c = a, a = {}) : "object" == typeof a && null !== a || (a = {}), i(e.socket,
              "stopRecorder", a,
              function(a, e) {
                if (a) return b(d, a);
                b(c, e)
              })
          }, this.getRegion = function(a, c, d) {
            var e = this;
            if ("object" != typeof a || null === a || "string" != typeof a.id || "" === a.id) return b(d,
              "invalid options");
            var f = {
              id: a.id,
              mixStreamId: a.mixedStreamId
            };
            i(e.socket, "getRegion", f, function(a, e) {
              if (a) return b(d, a);
              b(c, e)
            })
          }, this.setRegion = function(a, c, d) {
            var e = this;
            if ("object" != typeof a || null === a || "string" != typeof a.id || "" === a.id || "string" != typeof a.region ||
              "" === a.region) return b(d, "invalid options");
            var f = {
              id: a.id,
              region: a.region,
              mixStreamId: a.mixedStreamId
            };
            i(e.socket, "setRegion", f, function(a, e) {
              if (a) return b(d, a);
              b(c, e)
            })
          }, this.setVideoBitrate = function(a, c, d) {
            var e = this;
            "function" == typeof a ? (d = c, c = a, a = {}) : "object" == typeof a && null !== a || (a = {}), i(e.socket,
              "setVideoBitrate", a,
              function(a, e) {
                if (a) return b(d, a);
                b(c, e)
              })
          }, this.getConnectionStats = function(a, c, d) {
            a && a.channel && "function" == typeof a.channel.getConnectionStats ? a.channel.getConnectionStats(function(a) {
              b(c, a)
            }, function(a) {
              b(d, a)
            }) : b(d, "invalid stream.")
          }, this.mute = function(a, c, e, f) {
            a instanceof d.Stream || b(f, "Invalid stream"), void 0 !== c && "audio" !== c && "video" !== c && b(f,
              "Invalid track kind.");
            var g = c || "av";
            i(this.socket, "mute", {
              streamId: a.id(),
              track: g
            }, function(a) {
              if (a) return b(f, a);
              b(e)
            })
          }, this.unmute = function(a, c, e, f) {
            a instanceof d.Stream || b(f, "Invalid stream"), void 0 !== c && "audio" !== c && "video" !== c && b(f,
              "Invalid track kind.");
            var g = c || "av";
            i(this.socket, "unmute", {
              streamId: a.id(),
              track: g
            }, function(a) {
              if (a) return b(f, a);
              b(e)
            })
          }
        };
        return a.prototype = Object.create(o.prototype), a.prototype.constructor = a, a.create = function(b) {
          return new a(b)
        }, a
      }()
    }(), f.ChromeStableStack = function(a) {
    "use strict";
    var b = {},
      c = RTCPeerConnection;
    if (b.pc_config = {
      iceServers: []
    }, b.con = {
      optional: [{
        DtlsSrtpKeyAgreement: !0
      }]
    }, a.iceServers instanceof Array && (b.pc_config.iceServers = a.iceServers), a.iceTransportPolicy && (b.pc_config.iceTransportPolicy =
      a.iceTransportPolicy), void 0 !== a.pcConstraints && void 0 !== a.pcConstraints.optional && a.pcConstraints.optional instanceof Array)
      for (var f in a.pcConstraints.optional) b.con.optional.push(a.pcConstraints.optional[f]);
    void 0 === a.audio && (a.audio = !0), void 0 === a.video && (a.video = !0), b.mediaConstraints = {
      mandatory: {
        OfferToReceiveVideo: a.video,
        OfferToReceiveAudio: a.audio
      }
    };
    var g = function(a) {
      console.log("Error in Stack ", a)
    };
    b.peerConnection = new c(b.pc_config, b.con);
    var h = function(b) {
        var c, d;
        return a.video && a.maxVideoBW && (b = b.replace(/b=AS:.*\r\n/g, ""), c = b.match(/m=video.*\r\n/), null == c && (
          c = b.match(/m=video.*\n/)), c && c.length > 0 && (d = c[0] + "b=AS:" + a.maxVideoBW + "\r\n", b = b.replace(c[
          0], d))), a.audio && a.maxAudioBW && (c = b.match(/m=audio.*\r\n/), null == c && (c = b.match(/m=audio.*\n/)), c &&
        c.length > 0 && (d = c[0] + "b=AS:" + a.maxAudioBW + "\r\n", b = b.replace(c[0], d))), b
      },
      i = function(b) {
        return a.audioCodec ? d.Common.setPreferredCodec(b, "audio", a.audioCodec) : b
      },
      j = function(b) {
        return a.videoCodec ? d.Common.setPreferredCodec(b, "video", a.videoCodec) : b
      },
      k = function(a) {
        var b = i(a);
        return b = j(b)
      };
    b.close = function() {
      b.state = "closed", "closed" !== b.peerConnection.signalingState && b.peerConnection.close()
    }, a.localCandidates = [], b.peerConnection.onicecandidate = function(b) {
      if (b.candidate) {
        b.candidate.candidate.match(/a=/) || (b.candidate.candidate = "a=" + b.candidate.candidate);
        var c = {
          sdpMLineIndex: b.candidate.sdpMLineIndex,
          sdpMid: b.candidate.sdpMid,
          candidate: b.candidate.candidate
        };
        a.remoteDescriptionSet ? a.callback({
          type: "candidate",
          candidate: c
        }) : (a.localCandidates.push(c), e.Logger.info("Storing candidate: ", a.localCandidates.length, c))
      } else console.log("End of candidates.")
    }, b.peerConnection.onaddstream = function(a) {
      b.onaddstream && b.onaddstream(a)
    }, b.peerConnection.onremovestream = function(a) {
      b.onremovestream && b.onremovestream(a)
    }, b.peerConnection.oniceconnectionstatechange = function(a) {
      b.oniceconnectionstatechange && b.oniceconnectionstatechange(a.currentTarget.iceConnectionState)
    };
    var l, m, n = function(c) {
        c.sdp = h(c.sdp), c.sdp = k(c.sdp.replace(/a=ice-options:google-ice\r\n/g, "")), a.callback({
          type: c.type,
          sdp: c.sdp
        }), l = c, b.peerConnection.setLocalDescription(c)
      },
      o = function(c) {
        c.sdp = h(c.sdp), a.callback({
          type: c.type,
          sdp: c.sdp
        }), l = c, b.peerConnection.setLocalDescription(c)
      };
    return b.updateSpec = function(c, d) {
      (c.maxVideoBW || c.maxAudioBW) && (c.maxVideoBW && (console.log("Maxvideo Requested", c.maxVideoBW), a.maxVideoBW =
        c.maxVideoBW), c.maxAudioBW && (console.log("Maxaudio Requested", c.maxAudioBW), a.maxAudioBW = c.maxAudioBW), l
        .sdp = h(l.sdp), b.peerConnection.setLocalDescription(l, function() {
        m.sdp = h(m.sdp), b.peerConnection.setRemoteDescription(new RTCSessionDescription(m), function() {
          a.remoteDescriptionSet = !0, d && d("success")
        })
      }))
    }, b.createOffer = function(a) {
      !0 === a ? b.peerConnection.createOffer(n, g, b.mediaConstraints) : b.peerConnection.createOffer(n, g)
    }, b.iceRestart = function() {
      var c = {
        offerToReceiveAudio: a.audio,
        offerToReceiveVideo: a.video,
        iceRestart: !0
      };
      b.peerConnection.createOffer(n, g, c)
    }, b.addStream = function(a) {
      b.peerConnection.addStream(a)
    }, a.remoteCandidates = [], a.remoteDescriptionSet = !1, b.processSignalingMessage = function(c) {
      if ("offer" === c.type) c.sdp = h(c.sdp), b.peerConnection.setRemoteDescription(new RTCSessionDescription(c),
        function() {
          b.peerConnection.createAnswer(o, function(a) {
            e.Logger.error("Error: ", a)
          }, b.mediaConstraints), a.remoteDescriptionSet = !0
        },
        function(a) {
          e.Logger.error("Error setting Remote Description", a)
        });
      else if ("answer" === c.type) console.log("Set remote and local description", c.sdp), c.sdp = h(c.sdp), m = c, b.peerConnection
        .setRemoteDescription(new RTCSessionDescription(c), function() {
          for (a.remoteDescriptionSet = !0, console.log("Candidates to be added: ", a.remoteCandidates.length, a.remoteCandidates); a
            .remoteCandidates.length > 0;) b.peerConnection.addIceCandidate(a.remoteCandidates.shift());
          for (console.log("Local candidates to send:", a.localCandidates.length); a.localCandidates.length > 0;) a.callback({
            type: "candidate",
            candidate: a.localCandidates.shift()
          })
        });
      else if ("candidate" === c.type) try {
        var d;
        d = "object" == typeof c.candidate ? c.candidate : JSON.parse(c.candidate), d.candidate = d.candidate.replace(
          /a=/g, ""), d.sdpMLineIndex = parseInt(d.sdpMLineIndex, 10);
        var f = new RTCIceCandidate(d);
        a.remoteDescriptionSet ? b.peerConnection.addIceCandidate(f) : (a.remoteCandidates.push(f), console.log(
          "Candidates stored: ", a.remoteCandidates.length, a.remoteCandidates))
      } catch (g) {
        e.Logger.error("Error parsing candidate", c.candidate)
      }
    }, b.getConnectionStats = function(a, c) {
      b.peerConnection.getStats(null, function(b) {
        a(d.Common.parseStats(b))
      }, c)
    }, b
  }, f.FirefoxStack = function(a) {
    "use strict";

    function b(a) {
      var b = a.split("\n");
      return {
        indexAudio: b.findIndex(function(a) {
          return /^m=audio/.test(a)
        }),
        indexVideo: b.findIndex(function(a) {
          return /^m=video/.test(a)
        })
      }
    }

    function c(a) {
      var b = a.split("\n"),
        c = b.findIndex(function(a) {
          return /^m=audio/.test(a)
        }),
        d = b.findIndex(function(a) {
          return /^m=video/.test(a)
        });
      if (e.Logger.debug("indexAudio: ", c, " indexVideo: ", d), c < d) {
        var f = b.slice(c, d),
          g = b.slice(d, b.length - 1);
        a = b.slice(0, c).join("\n") + "\n" + g.join("\n") + "\n" + f.join("\n")
      }
      return a
    }
    var f = {},
      g = mozRTCPeerConnection,
      h = mozRTCSessionDescription,
      i = mozRTCIceCandidate;
    f.pc_config = {
      iceServers: []
    }, a.iceServers instanceof Array && (f.pc_config.iceServers = a.iceServers), void 0 === a.audio && (a.audio = !0),
    void 0 === a.video && (a.video = !0), f.mediaConstraints = {
      offerToReceiveAudio: a.audio,
      offerToReceiveVideo: a.video,
      mozDontOfferDataChannel: !0
    };
    var j = function(a) {
        e.Logger.error("Error in Stack ", a)
      },
      k = !1;
    f.peerConnection = new g(f.pc_config), a.localCandidates = [], f.peerConnection.onicecandidate = function(b) {
      b.candidate ? (k = !0, b.candidate.candidate.match(/a=/) || (b.candidate.candidate = "a=" + b.candidate.candidate),
          a.remoteDescriptionSet ? a.callback({
            type: "candidate",
            candidate: b.candidate
          }) : (a.localCandidates.push(b.candidate), console.log("Local Candidates stored: ", a.localCandidates.length, a.localCandidates))
      ) : console.log("End of candidates.")
    };
    var l = function(b) {
        return a.audioCodec ? d.Common.setPreferredCodec(b, "audio", a.audioCodec) : b
      },
      m = function(b) {
        return a.videoCodec ? d.Common.setPreferredCodec(b, "video", a.videoCodec) : b
      },
      n = function(a) {
        var b = l(a);
        return b = m(b)
      };
    f.peerConnection.onaddstream = function(a) {
      f.onaddstream && f.onaddstream(a)
    }, f.peerConnection.onremovestream = function(a) {
      f.onremovestream && f.onremovestream(a)
    }, f.peerConnection.oniceconnectionstatechange = function(a) {
      f.oniceconnectionstatechange && f.oniceconnectionstatechange(a.currentTarget.iceConnectionState)
    };
    var o, p = function(b) {
        var c, d;
        return a.video && a.maxVideoBW && (c = b.match(/m=video.*\r\n/), null == c && (c = b.match(/m=video.*\n/)), c && c
          .length > 0 && (d = c[0] + "b=AS:" + a.maxVideoBW + "\r\n", b = b.replace(c[0], d))), a.audio && a.maxAudioBW &&
        (c = b.match(/m=audio.*\r\n/), null == c && (c = b.match(/m=audio.*\n/)), c && c.length > 0 && (d = c[0] +
          "b=AS:" + a.maxAudioBW + "\r\n", b = b.replace(c[0], d))), b
      },
      q = function(b) {
        b.sdp = p(b.sdp), b.sdp = n(b.sdp.replace(/a=ice-options:google-ice\r\n/g, "")), a.callback(b), o = b
      },
      r = function(b) {
        b.sdp = p(b.sdp), b.sdp = b.sdp.replace(/a=ice-options:google-ice\r\n/g, ""), a.callback(b), o = b, f.peerConnection
          .setLocalDescription(o)
      };
    return f.createOffer = function(a) {
      !0 === a ? f.peerConnection.createOffer(q, j, f.mediaConstraints) : f.peerConnection.createOffer(q, j)
    }, f.addStream = function(a) {
      f.peerConnection.addStream(a)
    }, a.remoteCandidates = [], a.remoteDescriptionSet = !1, f.close = function() {
      f.state = "closed", "closed" !== f.peerConnection.signalingState && f.peerConnection.close()
    }, f.iceRestart = function() {
      var b = {
        offerToReceiveAudio: a.audio,
        offerToReceiveVideo: a.video,
        iceRestart: !0
      };
      f.peerConnection.createOffer(q, j, b)
    }, f.processSignalingMessage = function(d) {
      if ("offer" === d.type) {
        d.sdp = p(d.sdp);
        var g = b(r.sdp);
        e.Logger.debug("Offer: local sdp", g), g.indexAudio > g.indexVideo && (d.sdp = c(d.sdp)), f.peerConnection.setRemoteDescription(
          new h(d),
          function() {
            f.peerConnection.createAnswer(r, function(a) {
              e.Logger.error("Error", a)
            }, f.mediaConstraints), a.remoteDescriptionSet = !0
          },
          function(a) {
            e.Logger.error("Error setting Remote Description", a, "sdp: ", d.sdp)
          })
      } else if ("answer" === d.type) {
        console.log("Set remote and local description , remote sdp", d.sdp), d.sdp = p(d.sdp);
        var j = b(o.sdp);
        e.Logger.debug("Answer: local sdp", j), j.indexAudio > j.indexVideo && (d.sdp = c(d.sdp)), f.peerConnection.setLocalDescription(
          o,
          function() {
            f.peerConnection.setRemoteDescription(new h(d), function() {
              for (a.remoteDescriptionSet = !0, e.Logger.info("Remote Description successfully set"); a.remoteCandidates.length >
              0 && k;) e.Logger.info("Setting stored remote candidates"), f.peerConnection.addIceCandidate(a.remoteCandidates
                .shift());
              for (; a.localCandidates.length > 0;) e.Logger.info("Sending Candidate from list"), a.callback({
                type: "candidate",
                candidate: a.localCandidates.shift()
              })
            }, function(a) {
              e.Logger.error("Error Setting Remote Description", a, "sdp: ", d.sdp, "Loacal sdp: ", o)
            })
          },
          function(a) {
            e.Logger.error("Failure setting Local Description", a, "sdp: ", d.sdp, "Loacal sdp: ", o)
          })
      } else if ("candidate" === d.type) try {
        var l;
        l = "object" == typeof d.candidate ? d.candidate : JSON.parse(d.candidate), l.candidate = l.candidate.replace(
          / generation 0/g, ""), l.candidate = l.candidate.replace(/ udp /g, " UDP "), l.sdpMLineIndex = parseInt(l.sdpMLineIndex,
          10);
        var m = new i(l);
        if (a.remoteDescriptionSet && k)
          for (f.peerConnection.addIceCandidate(m); a.remoteCandidates.length > 0;) e.Logger.info(
            "Setting stored remote candidates"), f.peerConnection.addIceCandidate(a.remoteCandidates.shift());
        else a.remoteCandidates.push(m)
      } catch (n) {
        e.Logger.error("Error parsing candidate", d.candidate, n)
      }
    }, f.getConnectionStats = function(a, b) {
      f.peerConnection.getStats(null, function(b) {
        a(d.Common.parseStats(b))
      }, b)
    }, f
  }, f.EdgeORTCStack = function(b) {
    "use strict";
    var c = {};
    c.generateIdentifier = function() {
      return Math.random().toString(36).substr(2, 10)
    }, c.localCName = c.generateIdentifier(), c.splitLines = function(a) {
      return a.trim().split("\n").map(function(a) {
        return a.trim()
      })
    }, c.splitSections = function(a) {
      return a.split("\nm=").map(function(a, b) {
        return (b > 0 ? "m=" + a : a).trim() + "\r\n"
      })
    }, c.matchPrefix = function(a, b) {
      return c.splitLines(a).filter(function(a) {
        return 0 === a.indexOf(b)
      })
    }, c.parseCandidate = function(a) {
      var b;
      b = 0 === a.indexOf("a=candidate:") ? a.substring(12).split(" ") : a.substring(10).split(" ");
      for (var c = {
        foundation: b[0],
        component: b[1],
        protocol: b[2].toLowerCase(),
        priority: parseInt(b[3], 10),
        ip: b[4],
        port: parseInt(b[5], 10),
        type: b[7]
      }, d = 8; d < b.length; d += 2) switch (b[d]) {
        case "raddr":
          c.relatedAddress = b[d + 1];
          break;
        case "rport":
          c.relatedPort = parseInt(b[d + 1], 10);
          break;
        case "tcptype":
          c.tcpType = b[d + 1]
      }
      return c
    }, c.writeCandidate = function(a) {
      var b = [];
      b.push(a.foundation), b.push(a.component), b.push(a.protocol.toUpperCase()), b.push(a.priority), b.push(a.ip), b.push(
        a.port);
      var c = a.type;
      return b.push("typ"), b.push(c), "host" !== c && a.relatedAddress && a.relatedPort && (b.push("raddr"), b.push(a.relatedAddress),
        b.push("rport"), b.push(a.relatedPort)), a.tcpType && "tcp" === a.protocol.toLowerCase() && (b.push("tcptype"),
        b.push(a.tcpType)), "candidate:" + b.join(" ")
    }, c.parseRtpMap = function(a) {
      var b = a.substr(9).split(" "),
        c = {
          payloadType: parseInt(b.shift(), 10)
        };
      return b = b[0].split("/"), c.name = b[0], c.clockRate = parseInt(b[1], 10), c.numChannels = 3 === b.length ?
        parseInt(b[2], 10) : 1, c
    }, c.writeRtpMap = function(a) {
      var b = a.payloadType;
      return void 0 !== a.preferredPayloadType && (b = a.preferredPayloadType), "a=rtpmap:" + b + " " + a.name + "/" + a
        .clockRate + (1 !== a.numChannels ? "/" + a.numChannels : "") + "\r\n"
    }, c.parseExtmap = function(a) {
      var b = a.substr(9).split(" ");
      return {
        id: parseInt(b[0], 10),
        uri: b[1]
      }
    }, c.writeExtmap = function(a) {
      return "a=extmap:" + (a.id || a.preferredId) + " " + a.uri + "\r\n"
    }, c.parseFmtp = function(a) {
      for (var b, c = {}, d = a.substr(a.indexOf(" ") + 1).split(";"), e = 0; e < d.length; e++) b = d[e].trim().split(
        "="), c[b[0].trim()] = b[1];
      return c
    }, c.writeFmtp = function(a) {
      var b = "",
        c = a.payloadType;
      if (void 0 !== a.preferredPayloadType && (c = a.preferredPayloadType), a.parameters && Object.keys(a.parameters).length) {
        var d = [];
        Object.keys(a.parameters).forEach(function(b) {
          d.push(b + "=" + a.parameters[b])
        }), b += "a=fmtp:" + c + " " + d.join(";") + "\r\n"
      }
      return b
    }, c.parseRtcpFb = function(a) {
      var b = a.substr(a.indexOf(" ") + 1).split(" ");
      return {
        type: b.shift(),
        parameter: b.join(" ")
      }
    }, c.writeRtcpFb = function(a) {
      var b = "",
        c = a.payloadType;
      return void 0 !== a.preferredPayloadType && (c = a.preferredPayloadType), a.rtcpFeedback && a.rtcpFeedback.length &&
      a.rtcpFeedback.forEach(function(a) {
        b += "a=rtcp-fb:" + c + " " + a.type + " " + a.parameter + "\r\n"
      }), b
    }, c.parseSsrcMedia = function(a) {
      var b = a.indexOf(" "),
        c = {
          ssrc: parseInt(a.substr(7, b - 7), 10)
        },
        d = a.indexOf(":", b);
      return d > -1 ? (c.attribute = a.substr(b + 1, d - b - 1), c.value = a.substr(d + 1)) : c.attribute = a.substr(b +
        1), c
    }, c.getDtlsParameters = function(a, b) {
      var d = c.splitLines(a);
      d = d.concat(c.splitLines(b));
      var e = d.filter(function(a) {
        return 0 === a.indexOf("a=fingerprint:")
      })[0].substr(14);
      return {
        role: "auto",
        fingerprints: [{
          algorithm: e.split(" ")[0],
          value: e.split(" ")[1]
        }]
      }
    }, c.writeDtlsParameters = function(a, b) {
      var c = "a=setup:" + b + "\r\n";
      return a.fingerprints.forEach(function(a) {
        c += "a=fingerprint:" + a.algorithm + " " + a.value + "\r\n"
      }), c
    }, c.getIceParameters = function(a, b) {
      var d = c.splitLines(a);
      return d = d.concat(c.splitLines(b)), {
        usernameFragment: d.filter(function(a) {
          return 0 === a.indexOf("a=ice-ufrag:")
        })[0].substr(12),
        password: d.filter(function(a) {
          return 0 === a.indexOf("a=ice-pwd:")
        })[0].substr(10)
      }
    }, c.writeIceParameters = function(a) {
      return "a=ice-ufrag:" + a.usernameFragment + "\r\na=ice-pwd:" + a.password + "\r\n"
    }, c.parseRtpParameters = function(a) {
      for (var b = {
        codecs: [],
        headerExtensions: [],
        fecMechanisms: [],
        rtcp: []
      }, d = c.splitLines(a), e = d[0].split(" "), f = 3; f < e.length; f++) {
        var g = e[f],
          h = c.matchPrefix(a, "a=rtpmap:" + g + " ")[0];
        if (h) {
          var i = c.parseRtpMap(h),
            j = c.matchPrefix(a, "a=fmtp:" + g + " ");
          switch (i.parameters = j.length ? c.parseFmtp(j[0]) : {}, i.rtcpFeedback = c.matchPrefix(a, "a=rtcp-fb:" + g +
            " ").map(c.parseRtcpFb), b.codecs.push(i), i.name.toUpperCase()) {
            case "RED":
            case "ULPFEC":
              b.fecMechanisms.push(i.name.toUpperCase())
          }
        }
      }
      return c.matchPrefix(a, "a=extmap:").forEach(function(a) {
        b.headerExtensions.push(c.parseExtmap(a))
      }), b
    }, c.writeRtpDescription = function(a, b) {
      var d = "";
      return d += "m=" + a + " ", d += b.codecs.length > 0 ? "9" : "0", d += " UDP/TLS/RTP/SAVPF ", d += b.codecs.map(
        function(a) {
          return void 0 !== a.preferredPayloadType ? a.preferredPayloadType : a.payloadType
        }).join(" ") + "\r\n", d += "c=IN IP4 0.0.0.0\r\n", d += "a=rtcp:9 IN IP4 0.0.0.0\r\n", b.codecs.forEach(
        function(a) {
          d += c.writeRtpMap(a), d += c.writeFmtp(a), d += c.writeRtcpFb(a)
        }), d += "a=rtcp-mux\r\n"
    }, c.parseRtpEncodingParameters = function(a) {
      var b, d = [],
        e = c.parseRtpParameters(a),
        f = -1 !== e.fecMechanisms.indexOf("RED"),
        g = -1 !== e.fecMechanisms.indexOf("ULPFEC"),
        h = c.matchPrefix(a, "a=ssrc:").map(function(a) {
          return c.parseSsrcMedia(a)
        }).filter(function(a) {
          return "cname" === a.attribute
        }),
        i = h.length > 0 && h[0].ssrc,
        j = c.matchPrefix(a, "a=ssrc-group:FID").map(function(a) {
          var b = a.split(" ");
          return b.shift(), b.map(function(a) {
            return parseInt(a, 10)
          })
        });
      j.length > 0 && j[0].length > 1 && j[0][0] === i && (b = j[0][1]), e.codecs.forEach(function(a) {
        if ("RTX" === a.name.toUpperCase() && a.parameters.apt) {
          var c = {
            ssrc: i,
            codecPayloadType: parseInt(a.parameters.apt, 10),
            rtx: {
              payloadType: a.payloadType,
              ssrc: b
            }
          };
          d.push(c), f && (c = JSON.parse(JSON.stringify(c)), c.fec = {
            ssrc: b,
            mechanism: g ? "red+ulpfec" : "red"
          }, d.push(c))
        }
      }), 0 === d.length && i && d.push({
        ssrc: i
      });
      var k = c.matchPrefix(a, "b=");
      return k.length && (0 === k[0].indexOf("b=TIAS:") ? k = parseInt(k[0].substr(7), 10) : 0 === k[0].indexOf("b=AS:") &&
        (k = parseInt(k[0].substr(5), 10)), d.forEach(function(a) {
        a.maxBitrate = k
      })), d
    }, c.writeSessionBoilerplate = function() {
      return "v=0\r\no=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n"
    }, c.writeMediaSection = function(a, b, d, e) {
      var f = c.writeRtpDescription(a.kind, b);
      if (f += c.writeIceParameters(a.iceGatherer.getLocalParameters()), f += c.writeDtlsParameters(a.dtlsTransport.getLocalParameters(),
        "offer" === d ? "actpass" : "active"), f += "a=mid:" + a.mid + "\r\n", a.rtpSender && a.rtpReceiver ? f +=
        "a=sendrecv\r\n" : a.rtpSender ? f += "a=sendonly\r\n" : a.rtpReceiver ? f += "a=recvonly\r\n" : f +=
        "a=inactive\r\n", a.rtpSender) {
        var g = "msid:" + e.id + " " + a.rtpSender.track.id + "\r\n";
        f += "a=" + g, f += "a=ssrc:" + a.sendEncodingParameters[0].ssrc + " " + g
      }
      return f += "a=ssrc:" + a.sendEncodingParameters[0].ssrc + " cname:" + c.localCName + "\r\n"
    }, c.getDirection = function(a, b) {
      for (var d = c.splitLines(a), e = 0; e < d.length; e++) switch (d[e]) {
        case "a=sendrecv":
        case "a=sendonly":
        case "a=recvonly":
        case "a=inactive":
          return d[e].substr(2)
      }
      return b ? c.getDirection(b) : "sendrecv"
    }, a.RTCIceGatherer && (a.RTCIceCandidate || (a.RTCIceCandidate = function(a) {
      return a
    }), a.RTCSessionDescription || (a.RTCSessionDescription = function(a) {
      return a
    }));
    var d = {};
    d.pc_config = {
      iceServers: []
    }, b.iceServers instanceof Array && (d.pc_config.iceServers = b.iceServers), void 0 === b.audio && (b.audio = !0),
    void 0 === b.video && (b.video = !0), a.RTCPeerConnection = function(a) {
      var b = this,
        c = document.createDocumentFragment();
      if (["addEventListener", "removeEventListener", "dispatchEvent"].forEach(function(a) {
        b[a] = c[a].bind(c)
      }), this.onicecandidate = null, this.onaddstream = null, this.ontrack = null, this.onremovestream = null, this.onsignalingstatechange =
        null, this.oniceconnectionstatechange = null, this.onnegotiationneeded = null, this.ondatachannel = null, this.localStreams = [],
        this.remoteStreams = [], this.getLocalStreams = function() {
        return b.localStreams
      }, this.getRemoteStreams = function() {
        return b.remoteStreams
      }, this.localDescription = new RTCSessionDescription({
        type: "",
        sdp: ""
      }), this.remoteDescription = new RTCSessionDescription({
        type: "",
        sdp: ""
      }), this.signalingState = "stable", this.iceConnectionState = "new", this.iceGatheringState = "new", this.iceOptions = {
        gatherPolicy: "all",
        iceServers: []
      }, a && a.iceTransportPolicy) switch (a.iceTransportPolicy) {
        case "all":
        case "relay":
          this.iceOptions.gatherPolicy = a.iceTransportPolicy;
          break;
        case "none":
          throw new TypeError('iceTransportPolicy "none" not supported')
      }
      if (this.usingBundle = a && "max-bundle" === a.bundlePolicy, a && a.iceServers) {
        var d = JSON.parse(JSON.stringify(a.iceServers));
        this.iceOptions.iceServers = d.filter(function(a) {
          if (a && a.urls) {
            var b = a.urls;
            return "string" == typeof b && (b = [b]), !!(b = b.filter(function(a) {
              return 0 === a.indexOf("turn:") && -1 !== a.indexOf("transport=udp")
            })[0])
          }
          return !1
        })
      }
      this.transceivers = [], this._localIceCandidatesBuffer = []
    }, a.RTCPeerConnection.prototype.addStream = function(a) {
      this.localStreams.push(a.clone()), this._maybeFireNegotiationNeeded()
    }, a.RTCPeerConnection.prototype.removeStream = function(a) {
      var b = this.localStreams.indexOf(a);
      b > -1 && (this.localStreams.splice(b, 1), this._maybeFireNegotiationNeeded())
    }, a.RTCPeerConnection.prototype.getSenders = function() {
      return this.transceivers.filter(function(a) {
        return !!a.rtpSender
      }).map(function(a) {
        return a.rtpSender
      })
    }, a.RTCPeerConnection.prototype.getReceivers = function() {
      return this.transceivers.filter(function(a) {
        return !!a.rtpReceiver
      }).map(function(a) {
        return a.rtpReceiver
      })
    }, a.RTCPeerConnection.prototype._emitBufferedCandidates = function() {
      var a = this,
        b = c.splitSections(a.localDescription.sdp);
      this._localIceCandidatesBuffer.forEach(function(c) {
        if (c.candidate && 0 !== Object.keys(c.candidate).length) - 1 === c.candidate.candidate.indexOf(
          "typ endOfCandidates") && (b[c.candidate.sdpMLineIndex + 1] += "a=" + c.candidate.candidate + "\r\n");
        else
          for (var d = 1; d < b.length; d++) - 1 === b[d].indexOf("\r\na=end-of-candidates\r\n") && (b[d] +=
            "a=end-of-candidates\r\n");
        if (a.localDescription.sdp = b.join(""), null !== a.dispatchEvent && a.dispatchEvent(c), null !== a.onicecandidate &&
        a.onicecandidate(c), !c.candidate && "complete" !== a.iceGatheringState) {
          a.transceivers.every(function(a) {
            return a.iceGatherer && "completed" === a.iceGatherer.state
          }) && (a.iceGatheringState = "complete")
        }
      }), this._localIceCandidatesBuffer = []
    }, a.RTCPeerConnection.prototype._getCommonCapabilities = function(a, b) {
      var c = {
        codecs: [],
        headerExtensions: [],
        fecMechanisms: []
      };
      return a.codecs.forEach(function(a) {
        for (var d = 0; d < b.codecs.length; d++) {
          var e = b.codecs[d];
          if (a.name.toLowerCase() === e.name.toLowerCase() && a.clockRate === e.clockRate && a.numChannels === e.numChannels) {
            c.codecs.push(e);
            break
          }
        }
      }), a.headerExtensions.forEach(function(a) {
        for (var d = 0; d < b.headerExtensions.length; d++) {
          var e = b.headerExtensions[d];
          if (a.uri === e.uri) {
            c.headerExtensions.push(e);
            break
          }
        }
      }), c
    }, a.RTCPeerConnection.prototype._createIceAndDtlsTransports = function(b, d) {
      var e = this,
        f = new a.RTCIceGatherer(e.iceOptions),
        g = new a.RTCIceTransport(f);
      f.onlocalcandidate = function(a) {
        var h = new Event("icecandidate");
        h.candidate = {
          sdpMid: b,
          sdpMLineIndex: d
        };
        var i = a.candidate,
          j = !i || 0 === Object.keys(i).length;
        j ? (void 0 === f.state && (f.state = "completed"), h.candidate.candidate =
          "candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates") : (i.component = "RTCP" === g.component ? 2 : 1, h.candidate
          .candidate = c.writeCandidate(i));
        var k = c.splitSections(e.localDescription.sdp); - 1 === h.candidate.candidate.indexOf("typ endOfCandidates") ?
          k[h.candidate.sdpMLineIndex + 1] += "a=" + h.candidate.candidate + "\r\n" : k[h.candidate.sdpMLineIndex + 1] +=
            "a=end-of-candidates\r\n", e.localDescription.sdp = k.join("");
        var l = e.transceivers.every(function(a) {
          return a.iceGatherer && "completed" === a.iceGatherer.state
        });
        switch (e.iceGatheringState) {
          case "new":
            e._localIceCandidatesBuffer.push(h), j && l && e._localIceCandidatesBuffer.push(new Event("icecandidate"));
            break;
          case "gathering":
            e._emitBufferedCandidates(), null !== e.dispatchEvent && e.dispatchEvent(h), null !== e.onicecandidate && e.onicecandidate(
              h), l && (null !== e.dispatchEvent && e.dispatchEvent(new Event("icecandidate")), null !== e.onicecandidate &&
            e.onicecandidate(new Event("icecandidate")), e.iceGatheringState = "complete")
        }
      }, g.onicestatechange = function() {
        e._updateConnectionState()
      };
      var h = new a.RTCDtlsTransport(g);
      return h.ondtlsstatechange = function() {
        e._updateConnectionState()
      }, h.onerror = function() {
        e._updateConnectionState()
      }, {
        iceGatherer: f,
        iceTransport: g,
        dtlsTransport: h
      }
    }, a.RTCPeerConnection.prototype._transceive = function(a, b, d) {
      var e = this._getCommonCapabilities(a.localCapabilities, a.remoteCapabilities);
      b && a.rtpSender && (e.encodings = a.sendEncodingParameters, e.rtcp = {
        cname: c.localCName
      }, a.recvEncodingParameters.length && (e.rtcp.ssrc = a.recvEncodingParameters[0].ssrc), a.rtpSender.send(e)), d &&
      a.rtpReceiver && (e.encodings = a.recvEncodingParameters, e.rtcp = {
        cname: a.cname
      }, a.sendEncodingParameters.length && (e.rtcp.ssrc = a.sendEncodingParameters[0].ssrc), a.rtpReceiver.receive(e))
    }, a.RTCPeerConnection.prototype._updateSignalingState = function(a) {
      this.signalingState = a;
      var b = new Event("signalingstatechange");
      null !== this.dispatchEvent && this.dispatchEvent(b), null !== this.onsignalingstatechange && this.onsignalingstatechange(
        b)
    }, a.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {
      var a = new Event("negotiationneeded");
      null !== this.dispatchEvent && this.dispatchEvent(a), null !== this.onnegotiationneeded && this.onnegotiationneeded(
        a)
    }, a.RTCPeerConnection.prototype._updateConnectionState = function() {
      var a, b = this,
        c = {
          new: 0,
          closed: 0,
          connecting: 0,
          checking: 0,
          connected: 0,
          completed: 0,
          failed: 0
        };
      if (this.transceivers.forEach(function(a) {
        c[a.iceTransport.state]++, c[a.dtlsTransport.state]++
      }), c.connected += c.completed, a = "new", c.failed > 0 ? a = "failed" : c.connecting > 0 || c.checking > 0 ? a =
        "connecting" : c.disconnected > 0 ? a = "disconnected" : c.new > 0 ? a = "new" : (c.connected > 0 || c.completed >
        0) && (a = "connected"), a !== b.iceConnectionState) {
        b.iceConnectionState = a;
        var d = new Event("iceconnectionstatechange");
        null !== this.dispatchEvent && this.dispatchEvent(d), null !== this.oniceconnectionstatechange && this.oniceconnectionstatechange(
          a)
      }
    }, a.RTCPeerConnection.prototype.setLocalDescription = function(b) {
      var d, e, f = this;
      if ("offer" === b.type) this._pendingOffer && (d = c.splitSections(b.sdp), e = d.shift(), d.forEach(function(a, b) {
        var d = c.parseRtpParameters(a);
        f._pendingOffer[b].localCapabilities = d
      }), this.transceivers = this._pendingOffer, delete this._pendingOffer);
      else if ("answer" === b.type) {
        d = c.splitSections(f.remoteDescription.sdp), e = d.shift();
        var g = c.matchPrefix(e, "a=ice-lite").length > 0;
        d.forEach(function(a, b) {
          var d = f.transceivers[b],
            h = d.iceGatherer,
            i = d.iceTransport,
            j = d.dtlsTransport,
            k = d.localCapabilities,
            l = d.remoteCapabilities;
          if ("0" !== a.split("\n", 1)[0].split(" ", 2)[1]) {
            var m = c.getIceParameters(a, e);
            if (g) {
              var n = c.matchPrefix(a, "a=candidate:").map(function(a) {
                return c.parseCandidate(a)
              }).filter(function(a) {
                return "1" === a.component
              });
              n.length && i.setRemoteCandidates(n)
            }
            var o = c.getDtlsParameters(a, e);
            g && (o.role = "server"), o.role = "client", f.usingBundle && 0 !== b || (i.start(h, m, g ? "controlling" :
              "controlled"), j.start(o));
            var p = f._getCommonCapabilities(k, l);
            f._transceive(d, p.codecs.length > 0, !1)
          }
        })
      }
      switch (this.localDescription = {
        type: b.type,
        sdp: b.sdp
      }, b.type) {
        case "offer":
          this._updateSignalingState("have-local-offer");
          break;
        case "answer":
          this._updateSignalingState("stable");
          break;
        default:
          throw new TypeError('unsupported type "' + b.type + '"')
      }
      var h = arguments.length > 1 && "function" == typeof arguments[1];
      if (h) {
        var i = arguments[1];
        a.setTimeout(function() {
          i(), "new" === f.iceGatheringState && (f.iceGatheringState = "gathering"), f._emitBufferedCandidates()
        }, 0)
      }
      var j = a.Promise.resolve();
      return j.then(function() {
        h || ("new" === f.iceGatheringState && (f.iceGatheringState = "gathering"), a.setTimeout(f._emitBufferedCandidates
          .bind(f), 500))
      }), j
    }, a.RTCPeerConnection.prototype.setRemoteDescription = function(b) {
      var d = this,
        e = new a.MediaStream,
        f = [],
        g = c.splitSections(b.sdp),
        h = g.shift(),
        i = c.matchPrefix(h, "a=ice-lite").length > 0;
      switch (this.usingBundle = c.matchPrefix(h, "a=group:BUNDLE ").length > 0, g.forEach(function(g, j) {
        var k, l, m, n, o, p, q, r, s, t, u, v, w = c.splitLines(g),
          x = w[0].substr(2).split(" "),
          y = x[0],
          z = "0" === x[1],
          A = c.getDirection(g, h),
          B = c.parseRtpParameters(g);
        z || (u = c.getIceParameters(g, h), v = c.getDtlsParameters(g, h), v.role = "client"), v.role = "client", r = c
          .parseRtpEncodingParameters(g);
        var C = c.matchPrefix(g, "a=mid:");
        C = C.length ? C[0].substr(6) : c.generateIdentifier();
        var D, E = c.matchPrefix(g, "a=ssrc:").map(function(a) {
          return c.parseSsrcMedia(a)
        }).filter(function(a) {
          return "cname" === a.attribute
        })[0];
        E && (D = E.value);
        var F = !0,
          G = c.matchPrefix(g, "a=candidate:").map(function(a) {
            return c.parseCandidate(a)
          }).filter(function(a) {
            return "1" === a.component
          });
        if (G.push({}), "offer" !== b.type || z) "answer" !== b.type || z || (k = d.transceivers[j], l = k.iceGatherer,
          m = k.iceTransport, n = k.dtlsTransport, o = k.rtpSender, p = k.rtpReceiver, q = k.sendEncodingParameters, s =
          k.localCapabilities, d.transceivers[j].recvEncodingParameters = r, d.transceivers[j].remoteCapabilities = B,
          d.transceivers[j].cname = D, (i || F) && G.length && m.setRemoteCandidates(G), d.usingBundle && 0 !== j || (m
          .start(l, u, "controlling"), n.start(v)), d._transceive(k, "sendrecv" === A || "recvonly" === A, "sendrecv" ===
          A || "sendonly" === A), !p || "sendrecv" !== A && "sendonly" !== A ? delete k.rtpReceiver : (t = p.track, f.push(
          [t, p]), e.addTrack(t)));
        else {
          var H = d.usingBundle && j > 0 ? {
            iceGatherer: d.transceivers[0].iceGatherer,
            iceTransport: d.transceivers[0].iceTransport,
            dtlsTransport: d.transceivers[0].dtlsTransport
          } : d._createIceAndDtlsTransports(C, j);
          if (F && H.iceTransport.setRemoteCandidates(G), s = a.RTCRtpReceiver.getCapabilities(y), q = [{
            ssrc: 1001 * (2 * j + 2)
          }], p = new a.RTCRtpReceiver(H.dtlsTransport, y), t = p.track, f.push([t, p]), e.addTrack(t), d.localStreams.length >
          0 && d.localStreams[0].getTracks().length >= j) {
            var I;
            "audio" === y ? I = d.localStreams[0].getAudioTracks()[0] : "video" === y && (I = d.localStreams[0].getVideoTracks()[
              0]), I && (o = new a.RTCRtpSender(I, H.dtlsTransport))
          }
          d.transceivers[j] = {
            iceGatherer: H.iceGatherer,
            iceTransport: H.iceTransport,
            dtlsTransport: H.dtlsTransport,
            localCapabilities: s,
            remoteCapabilities: B,
            rtpSender: o,
            rtpReceiver: p,
            kind: y,
            mid: C,
            cname: D,
            sendEncodingParameters: q,
            recvEncodingParameters: r
          }, d._transceive(d.transceivers[j], !1, "sendrecv" === A || "sendonly" === A)
        }
      }), this.remoteDescription = {
        type: b.type,
        sdp: b.sdp
      }, b.type) {
        case "offer":
          this._updateSignalingState("have-remote-offer");
          break;
        case "answer":
          this._updateSignalingState("stable");
          break;
        default:
          throw new TypeError('unsupported type "' + b.type + '"')
      }
      return e.getTracks().length && (d.remoteStreams.push(e), a.setTimeout(function() {
        var b = new Event("addstream");
        b.stream = e, null !== d.dispatchEvent && d.dispatchEvent(b), null !== d.onaddstream && a.setTimeout(function() {
          d.onaddstream(b)
        }, 0), f.forEach(function(c) {
          var f = c[0],
            g = c[1],
            h = new Event("track");
          h.track = f, h.receiver = g, h.streams = [e], null !== d.dispatchEvent && d.dispatchEvent(b), null !== d.ontrack &&
          a.setTimeout(function() {
            d.ontrack(h)
          }, 0)
        })
      }, 0)), arguments.length > 1 && "function" == typeof arguments[1] && a.setTimeout(arguments[1], 0), a.Promise.resolve()
    }, a.RTCPeerConnection.prototype.close = function() {
      this.transceivers.forEach(function(a) {
        a.iceTransport && a.iceTransport.stop(), a.dtlsTransport && a.dtlsTransport.stop(), a.rtpSender && a.rtpSender.stop(),
        a.rtpReceiver && a.rtpReceiver.stop()
      }), this._updateSignalingState("closed")
    }, a.RTCPeerConnection.prototype.createOffer = function() {
      var b = this;
      if (this._pendingOffer) throw new Error("createOffer called while there is a pending offer.");
      var d;
      1 === arguments.length && "function" != typeof arguments[0] ? d = arguments[0] : 3 === arguments.length && (d =
        arguments[2]);
      var e = [],
        f = 0,
        g = 0;
      if (this.localStreams.length && (f = this.localStreams[0].getAudioTracks().length, g = this.localStreams[0].getVideoTracks()
        .length), d) {
        if (d.mandatory || d.optional) throw new TypeError("Legacy mandatory/optional constraints not supported.");
        void 0 !== d.offerToReceiveAudio && (f = d.offerToReceiveAudio), void 0 !== d.offerToReceiveVideo && (g = d.offerToReceiveVideo)
      }
      for (this.localStreams.length && this.localStreams[0].getTracks().forEach(function(a) {
        e.push({
          kind: a.kind,
          track: a,
          wantReceive: "audio" === a.kind ? f > 0 : g > 0
        }), "audio" === a.kind ? f-- : "video" === a.kind && g--
      }); f > 0 || g > 0;) f > 0 && (e.push({
        kind: "audio",
        wantReceive: !0
      }), f--), g > 0 && (e.push({
        kind: "video",
        wantReceive: !0
      }), g--);
      var h = c.writeSessionBoilerplate(),
        i = [];
      e.forEach(function(d, e) {
        var f, g, h = d.track,
          j = d.kind,
          k = c.generateIdentifier(),
          l = b.usingBundle && e > 0 ? {
            iceGatherer: i[0].iceGatherer,
            iceTransport: i[0].iceTransport,
            dtlsTransport: i[0].dtlsTransport
          } : b._createIceAndDtlsTransports(k, e),
          m = a.RTCRtpSender.getCapabilities(j),
          n = [{
            ssrc: 1001 * (2 * e + 1)
          }];
        h && (f = new a.RTCRtpSender(h, l.dtlsTransport)), d.wantReceive && (g = new a.RTCRtpReceiver(l.dtlsTransport,
          j)), i[e] = {
          iceGatherer: l.iceGatherer,
          iceTransport: l.iceTransport,
          dtlsTransport: l.dtlsTransport,
          localCapabilities: m,
          remoteCapabilities: null,
          rtpSender: f,
          rtpReceiver: g,
          kind: j,
          mid: k,
          sendEncodingParameters: n,
          recvEncodingParameters: null
        }
      }), this.usingBundle && (h += "a=group:BUNDLE " + i.map(function(a) {
        return a.mid
      }).join(" ") + "\r\n"), e.forEach(function(a, d) {
        var e = i[d];
        h += c.writeMediaSection(e, e.localCapabilities, "offer", b.localStreams[0])
      }), this._pendingOffer = i;
      var j = new RTCSessionDescription({
        type: "offer",
        sdp: h
      });
      return arguments.length && "function" == typeof arguments[0] && a.setTimeout(arguments[0], 0, j), a.Promise.resolve(
        j)
    }, a.RTCPeerConnection.prototype.createAnswer = function() {
      var b = this,
        d = c.writeSessionBoilerplate();
      this.usingBundle && (d += "a=group:BUNDLE " + this.transceivers.map(function(a) {
        return a.mid
      }).join(" ") + "\r\n"), this.transceivers.forEach(function(a) {
        var e = b._getCommonCapabilities(a.localCapabilities, a.remoteCapabilities);
        d += c.writeMediaSection(a, e, "answer", b.localStreams[0])
      });
      var e = new RTCSessionDescription({
        type: "answer",
        sdp: d
      });
      return arguments.length && "function" == typeof arguments[0] && a.setTimeout(arguments[0], 0, e), a.Promise.resolve(
        e)
    }, a.RTCPeerConnection.prototype.addIceCandidate = function(b) {
      if (null === b) this.transceivers.forEach(function(a) {
        a.iceTransport.addRemoteCandidate({})
      });
      else {
        var d = b.sdpMLineIndex;
        if (b.sdpMid)
          for (var e = 0; e < this.transceivers.length; e++)
            if (this.transceivers[e].mid === b.sdpMid) {
              d = e;
              break
            } var f = this.transceivers[d];
        if (f) {
          var g = Object.keys(b.candidate).length > 0 ? c.parseCandidate(b.candidate) : {};
          if ("tcp" === g.protocol && 0 === g.port) return;
          if ("1" !== g.component) return;
          "endOfCandidates" === g.type && (g = {}), f.iceTransport.addRemoteCandidate(g);
          var h = c.splitSections(this.remoteDescription.sdp);
          h[d + 1] += (g.type ? b.candidate.trim() : "a=end-of-candidates") + "\r\n", this.remoteDescription.sdp = h.join(
            "")
        }
      }
      return arguments.length > 1 && "function" == typeof arguments[1] && a.setTimeout(arguments[1], 0), a.Promise.resolve()
    }, d.mediaConstraints = {
      offerToReceiveVideo: b.video,
      offerToReceiveAudio: b.audio
    };
    var f, g = function(a) {
        e.Logger.error("Error in Edge Stack ", a)
      },
      h = function(a) {
        if ("H264" !== b.videoCodec && "h264" !== b.videoCodec) return a;
        try {
          var c = a.match(/m=video.*\r\n/g)[0],
            d = c.replace(/\s120/, "").replace("\r\n", "") + " 120\r\n";
          return a.replace(c, d)
        } catch (e) {
          return a
        }
      },
      i = function(a) {
        return h(a)
      },
      j = function(a) {
        var c, d;
        return b.video && b.maxVideoBW && (c = a.match(/m=video.*\r\n/), null == c && (c = a.match(/m=video.*\n/)), c && c
          .length > 0 && (d = c[0] + "b=AS:" + b.maxVideoBW + "\r\n", a = a.replace(c[0], d))), b.audio && b.maxAudioBW &&
        (c = a.match(/m=audio.*\r\n/), null == c && (c = a.match(/m=audio.*\n/)), c && c.length > 0 && (d = c[0] +
          "b=AS:" + b.maxAudioBW + "\r\n", a = a.replace(c[0], d))), a
      },
      k = function(a) {
        a.sdp = j(a.sdp), a.sdp = i(a.sdp.replace(/a=ice-options:google-ice\r\n/g, "")), b.callback(a), f = a
      },
      l = function(a) {
        a.sdp = j(a.sdp), a.sdp = a.sdp.replace(/a=ice-options:google-ice\r\n/g, ""), b.callback(a), f = a, d.peerConnection
          .setLocalDescription(f)
      };
    return d.peerConnection = new a.RTCPeerConnection(d.pc_config), b.localCandidates = [], b.remoteCandidates = [], b.remoteDescriptionSet = !
      1, d.peerConnection.onicecandidate = function(a) {
      if (a.candidate) {
        a.candidate.candidate.match(/a=/) || (a.candidate.candidate = "a=" + a.candidate.candidate);
        var c = {
          sdpMLineIndex: a.candidate.sdpMLineIndex,
          sdpMid: a.candidate.sdpMid,
          candidate: a.candidate.candidate
        };
        b.remoteDescriptionSet ? b.callback({
          type: "candidate",
          candidate: c
        }) : b.localCandidates.push(c)
      } else b.localCandidates.push({
        candidate: {}
      }), console.log("End of candidates.")
    }, d.peerConnection.onaddstream = function(a) {
      d.onaddstream && d.onaddstream(a)
    }, d.peerConnection.onremovestream = function(a) {
      d.onremovestream && d.onremovestream(a)
    }, d.peerConnection.oniceconnectionstatechange = function(a) {
      d.oniceconnectionstatechange && d.oniceconnectionstatechange(a)
    }, d.createOffer = function(a) {
      !0 === a ? d.peerConnection.createOffer(k, g, d.mediaConstraints) : d.peerConnection.createOffer(k, g)
    }, d.addStream = function(a) {
      d.peerConnection.addStream(a)
    }, d.processSignalingMessage = function(a) {
      if (e.Logger.debug("Process Signaling Message", a.type), "offer" === a.type) a.sdp = j(a.sdp), console.log(
        "Set offer description \n", a.sdp), d.peerConnection.setRemoteDescription(new RTCSessionDescription(a), function() {
        d.peerConnection.createAnswer(l, function(a) {
          e.Logger.error("Error", a)
        }, d.mediaConstraints), b.remoteDescriptionSet = !0
      }, function(a) {
        e.Logger.error("Error setting Remote Description", a)
      });
      else if ("answer" === a.type) console.log("Set remote description", a.sdp), a.sdp = j(a.sdp), d.peerConnection.setLocalDescription(
        f,
        function() {
          d.peerConnection.setRemoteDescription(new RTCSessionDescription(a), function() {
            for (b.remoteDescriptionSet = !0, e.Logger.info("Remote Description successfully set"); b.remoteCandidates.length >
            0;) d.peerConnection.addIceCandidate(b.remoteCandidates.shift());
            for (; b.localCandidates.length > 0;) b.callback({
              type: "candidate",
              candidate: b.localCandidates.shift()
            })
          }, function(a) {
            e.Logger.error("Failure Setting Remote Description", a)
          })
        },
        function(a) {
          e.Logger.error("Failure Setting Local Description", a)
        });
      else if ("candidate" === a.type) try {
        var c;
        c = "object" == typeof a.candidate ? a.candidate : JSON.parse(a.candidate), c.candidate = c.candidate.replace(
          /a=/g, ""), c.sdpMLineIndex = parseInt(c.sdpMLineIndex, 10);
        var g = new RTCIceCandidate(c);
        if (b.remoteDescriptionSet)
          for (d.peerConnection.addIceCandidate(g); b.remoteCandidates.length > 0;) e.Logger.info(
            "Setting stored remote candidates"), d.peerConnection.addIceCandidate(b.remoteCandidates.shift());
        else b.remoteCandidates.push(g)
      } catch (h) {
        e.Logger.error("Error parsing candidate", a.candidate, h)
      }
    }, d.close = function() {
      d.state = "closed", "closed" !== d.peerConnection.signalingState && d.peerConnection.close()
    }, d.getConnectionStats = function(a, b) {
      b("getConnectionStats is not supported on Edge.")
    }, d.iceRestart = function() {
      e.Logger.error("ICE restart on Edge is not supported yet.")
    }, d
  };
  var d = d || {};
  d.Error = {
    STREAM_LOCAL_ACCESS_DENIED: {
      code: 1101,
      message: "Cannot access to camera or micphone."
    },
    P2P_CONN_SERVER_UNKNOWN: {
      code: 2100,
      message: "Server unknown error."
    },
    P2P_CONN_SERVER_UNAVAILABLE: {
      code: 2101,
      message: "Server is unavaliable."
    },
    P2P_CONN_SERVER_BUSY: {
      code: 2102,
      message: "Server is too busy."
    },
    P2P_CONN_SERVER_NOT_SUPPORTED: {
      code: 2103,
      message: "Method has not been supported by server"
    },
    P2P_CONN_CLIENT_UNKNOWN: {
      code: 2110,
      message: "Client unknown error."
    },
    P2P_CONN_CLIENT_NOT_INITIALIZED: {
      code: 2111,
      message: "Connection is not initialized."
    },
    P2P_CONN_AUTH_UNKNOWN: {
      code: 2120,
      message: "Authentication unknown error."
    },
    P2P_CONN_AUTH_FAILED: {
      code: 2121,
      message: "Wrong username or token."
    },
    P2P_MESSAGING_TARGET_UNREACHABLE: {
      code: 2201,
      message: "Remote user cannot be reached."
    },
    P2P_CHATROOM_ATTENDEE_EXCEED: {
      code: 2301,
      message: "Exceed room's limitation"
    },
    P2P_CHATROOM_PEER_NOT_FOUND: {
      code: 2302,
      message: "Peer not found. Only one client in the room."
    },
    P2P_CLIENT_UNKNOWN: {
      code: 2400,
      message: "Unknown errors."
    },
    P2P_CLIENT_UNSUPPORTED_METHOD: {
      code: 2401,
      message: "This method is unsupported in current browser."
    },
    P2P_CLIENT_ILLEGAL_ARGUMENT: {
      code: 2402,
      message: "Illegal argument."
    },
    P2P_CLIENT_INVALID_STATE: {
      code: 2403,
      message: "Invalid peer state."
    },
    getErrorByCode: function(a) {
      return {
        1101: d.Error.STREAM_LOCAL_ACCESS_DENIED,
        2100: d.Error.P2P_CONN_SERVER_UNKNOWN,
        2101: d.Error.P2P_CONN_SERVER_UNAVAILABLE,
        2102: d.Error.P2P_CONN_SERVER_BUSY,
        2103: d.Error.P2P_CONN_SERVER_NOT_SUPPORTED,
        2110: d.Error.P2P_CONN_CLIENT_UNKNOWN,
        2111: d.Error.P2P_CONN_CLIENT_NOT_INITIALIZED,
        2120: d.Error.P2P_CONN_AUTH_UNKNOWN,
        2121: d.Error.P2P_CONN_AUTH_FAILED,
        2201: d.Error.P2P_MESSAGING_TARGET_UNREACHABLE,
        2301: d.Error.P2P_CHATROOM_ATTENDEE_EXCEED,
        2302: d.Error.P2P_CHATROOM_PEER_NOT_FOUND,
        2400: d.Error.P2P_CLIENT_UNKNOWN,
        2401: d.Error.P2P_CLIENT_UNSUPPORTED_METHOD,
        2402: d.Error.P2P_CLIENT_ILLEGAL_ARGUMENT,
        2403: d.Error.P2P_CLIENT_INVALID_STATE
      } [a]
    }
  };
  var d = d || {};
  d.PeerClient = function(b) {
    "use strict";
    var f, g, h = d.EventDispatcher({}),
      i = {
        READY: 1,
        MATCHED: 2,
        OFFERED: 3,
        PENDING: 4,
        CONNECTING: 5,
        CONNECTED: 6,
        ERROR: 9
      },
      j = {
        READY: 1,
        REQUESTED: 2,
        ACCEPTED: 3,
        NEGOTIATING: 4
      },
      k = {
        MESSAGE: "message",
        FILE: "file"
      },
      l = {
        READY: 1,
        CONNECTING: 2,
        CONNECTED: 3
      },
      m = l.READY,
      n = b,
      o = function(a) {
        return "[object Array]" === Object.prototype.toString.call(a)
      },
      p = 15e3,
      q = null,
      r = {},
      s = {},
      t = null,
      u = !1,
      v = {},
      w = {
        optional: [{
          DtlsSrtpKeyAgreement: "true"
        }]
      },
      x = null,
      y = {
        offerToReceiveAudio: !0,
        offerToReceiveVideo: !0
      },
      z = null,
      A = d.Common.sysInfo(),
      B = !navigator.mozGetUserMedia,
      C = !!navigator.mozGetUserMedia;
    b && (z = {
      iceServers: b.iceServers
    });
    var D = function(a, b) {
        return a.localeCompare(b)
      },
      E = function(a) {
        return a
      },
      F = function(a, b) {
        a.negotiationState = b
      },
      G = function(a, b) {
        a.state !== i.CONNECTED && a.state !== i.CONNECTING || (a.sendDataChannel && a.sendDataChannel.close(), a.receiveDataChannel &&
        a.receiveDataChannel.close(), a.connection && "closed" !== a.connection.iceConnectionState && a.connection.close(),
        a.state !== i.READY && (a.state = i.READY, h.dispatchEvent(new d.ChatEvent({
          target: h,
          type: "chat-stopped",
          peerId: a.id,
          senderId: b
        }))), fa(a.connection))
      },
      H = function(a, b) {
        b.sdk && b.sdk && "JavaScript" === b.sdk.type && b.runtime && "FireFox" === b.runtime.name ? (a.remoteSideSupportsRemoveStream = !
          1, a.remoteSideSupportsPlanB = !1, a.remoteSideSupportsUnifiedPlan = !0, a.preferredVideoCodec = "vp8") : (a.remoteSideSupportsRemoveStream = !
          0, a.remoteSideSupportsPlanB = !0, a.remoteSideSupportsUnifiedPlan = !1)
      },
      I = function() {
        u = !0, m = l.CONNECTED
      },
      J = function() {
        g && g(), f = void 0, g = void 0
      },
      K = function() {
        u = !1, m = l.READY, h.dispatchEvent(new d.ClientEvent({
          target: h,
          type: "server-disconnected"
        }))
      },
      L = function(a, b) {
        var c = r[a];
        c || (ka(a), c = r[a]), H(c, b), c.state === i.READY || c.state === i.PENDING ? (r[a].state = i.PENDING, h.dispatchEvent(
          new d.ChatEvent({
            target: h,
            type: "chat-invited",
            senderId: a
          }))) : c.state === i.OFFERED && D(t, a) < 0 && (c.state = i.PENDING, qa(a, function() {
          h.dispatchEvent(new d.ChatEvent({
            target: h,
            type: "chat-accepted",
            senderId: a
          }))
        }))
      },
      M = function(a) {
        var b = r[a];
        b && b.connection && (b.sendDataChannel && b.sendDataChannel.close(), b.receiveDataChannel && b.receiveDataChannel
          .close(), b.connection.close()), delete r[a], h.dispatchEvent(new d.ChatEvent({
          target: h,
          type: "chat-denied",
          senderId: a
        }))
      },
      N = function(a, b) {
        e.Logger.debug("Received chat accepted.");
        var c = r[a];
        c && (c.state = i.MATCHED, H(c, b), ea(c), c.state = i.CONNECTING, ha(c.id), h.dispatchEvent(new d.ChatEvent({
          target: h,
          type: "chat-accepted",
          senderId: a
        })))
      },
      O = function(a) {
        var b = r[a];
        b && b.connection && G(b, a), delete r[a]
      },
      P = function(a, b) {
        var c = r[b];
        c && c.state === i.CONNECTING && (c.connection || ea(c)), _(c, a)
      },
      Q = function(a, b) {
        s[a.streamId] = a.type, e.Logger.debug("remote stream ID:" + a.streamId + ",type:" + s[a.streamId])
      },
      R = function(a) {
        t = a, f && f(a), f = void 0, g = void 0
      },
      S = function() {
        Ea()
      },
      T = function(a) {
        e.Logger.debug("On negotiation needed."), a.isCaller && "stable" === a.connection.signalingState && a.negotiationState ===
        j.READY ? ja(a) : !a.isCaller && q ? q.sendNegotiationNeeded(a.id) : a.isNegotiationNeeded = !0
      },
      U = function(a, b) {
        b.candidate && q && q.sendSignalMessage(a.id, {
          type: "candidates",
          candidate: b.candidate.candidate,
          sdpMid: b.candidate.sdpMid,
          sdpMLineIndex: b.candidate.sdpMLineIndex
        })
      },
      V = function(a, b) {
        if (a && e.Logger.debug("On remote ice candidate from peer " + a.id), a && (a.state === i.OFFERED || a.state ===
          i.CONNECTING || a.state === i.CONNECTED)) {
          var c = new RTCIceCandidate({
            candidate: b.message.candidate,
            sdpMid: b.message.sdpMid,
            sdpMLineIndex: b.message.sdpMLineIndex
          });
          a.connection ? (e.Logger.debug("Add remote ice candidates."), a.connection.addIceCandidate(c, ba, ca)) : (e.Logger
            .debug("Cache remote ice candidates."), a.remoteIceCandidates || (a.remoteIceCandidates = []), a.remoteIceCandidates
            .push(c))
        }
      },
      W = function(a, b) {
        if (!a) return void e.Logger.debug('"peer" cannot be null or undefined');
        switch (a.state) {
          case i.OFFERED:
          case i.MATCHED:
            a.state = i.CONNECTING, ea(a);
          case i.CONNECTING:
          case i.CONNECTED:
            e.Logger.debug("About to set remote description. Signaling state: " + a.connection.signalingState);
            var c = new RTCSessionDescription(b.message);
            c.sdp = Pa(c.sdp), a.connection.setRemoteDescription(c, function() {
              xa(a), ta(a)
            }, function(a) {
              e.Logger.debug("Set remote description failed. Message: " + JSON.stringify(a))
            });
            break;
          default:
            e.Logger.debug("Unexpected peer state: " + a.state)
        }
      },
      X = function(a, b) {
        if (a && (a.state === i.CONNECTING || a.state === i.CONNECTED)) {
          e.Logger.debug("About to set remote description. Signaling state: " + a.connection.signalingState);
          var c = new RTCSessionDescription(b.message);
          c.sdp = Pa(c.sdp), a.connection.setRemoteDescription(new RTCSessionDescription(c), function() {
            e.Logger.debug("Set remote descripiton successfully."), ta(a), va(a)
          }, function(a) {
            e.Logger.debug("Set remote description failed. Message: " + a)
          })
        }
      },
      Y = function(a, b) {
        var c = s[a.id];
        if (c) {
          var e = {};
          "screen" === c ? e.video = {
            device: "screen"
          } : (e.video = a.getVideoTracks().length > 0 && {
            device: "camera"
          }, e.audio = a.getAudioTracks().length > 0);
          var f = new d.RemoteStream(e);
          return f.mediaStream = a, f.from = b.id, f.id = function() {
            return a.id
          }, f
        }
        return null
      },
      Z = function(a, b) {
        e.Logger.debug("Remote stream added.");
        var c = Y(b.stream, a);
        if (c) {
          var f = new d.StreamEvent({
            target: h,
            type: "stream-added",
            senderId: a.id,
            stream: c
          });
          h.dispatchEvent(f)
        }
      },
      $ = function(a, b) {
        e.Logger.debug("Remote stream removed.");
        var c = Y(b.stream, a);
        if (c) {
          var f = new d.StreamEvent({
            target: h,
            type: "stream-removed",
            stream: c
          });
          h.dispatchEvent(f)
        }
      },
      _ = function(a, b) {
        e.Logger.debug("S->C: " + JSON.stringify(b)), "offer" === b.type ? W(a, {
          message: b
        }) : "answer" === b.type ? X(a, {
          message: b
        }) : "candidates" === b.type && V(a, {
          message: b
        })
      },
      aa = function(a, b) {
        a && (e.Logger.debug("Ice connection state changed. State: " + a.connection.iceConnectionState), "closed" !== a.connection
          .iceConnectionState && "failed" !== a.connection.iceConnectionState || a.state !== i.CONNECTED || (G(a, a.id), q &&
        q.sendChatStopped(a.id), delete r[a.id]), "connected" !== a.connection.iceConnectionState && "completed" !== a.connection
          .iceConnectionState || (a.lastDisconnect = new Date("2099/12/31").getTime(), a.state !== i.CONNECTED && (a.state =
          i.CONNECTED, h.dispatchEvent(new d.ChatEvent({
          target: h,
          type: "chat-started",
          peerId: a.id
        })))), "checking" === a.connection.iceConnectionState && (a.lastDisconnect = new Date("2099/12/31").getTime()),
        "disconnected" === a.connection.iceConnectionState && (a.lastDisconnect = (new Date).getTime(), setTimeout(
          function() {
            (new Date).getTime() - a.lastDisconnect >= p && (e.Logger.debug("Disconnect timeout."), G(a, a.id), a === r[a
              .id] && delete r[a.id])
          }, p)))
      },
      ba = function() {
        e.Logger.debug("Add ice candidate success.")
      },
      ca = function(a) {
        e.Logger.debug("Add ice candidate failed. Error: " + a)
      },
      da = function(a) {
        e.Logger.debug("Signaling state changed: " + a.connection.signalingState), "closed" === a.connection.signalingState ?
          (G(a, a.id), delete r[a.id]) : "stable" === a.connection.signalingState && (F(a, j.READY), a.isCaller && a.isNegotiationNeeded &&
        q ? ja(a) : ua(a))
      },
      ea = function(a) {
        if (!a || a.connection) return !0;
        try {
          a.connection = new RTCPeerConnection(z, w), a.connection.onicecandidate = function(b) {
            U(a, b)
          }, a.connection.onaddstream = function(b) {
            Z(a, b)
          }, a.connection.onremovestream = function(b) {
            $(a, b)
          }, a.connection.oniceconnectionstatechange = function(b) {
            aa(a, b)
          }, a.connection.onnegotiationneeded = function() {
            T(r[a.id])
          }, a.connection.onsignalingstatechange = function() {
            da(a)
          }, a.connection.ondatachannel = function(b) {
            e.Logger.debug(t + ": On data channel"), a.dataChannels[b.channel.label] || (a.dataChannels[b.channel.label] =
              b.channel, e.Logger.debug("Save remote created data channel.")), ga(b.channel, a)
          }
        } catch (b) {
          return e.Logger.debug("Failed to create PeerConnection, exception: " + b.message), !1
        }
        return !0
      },
      fa = function(a) {
        a.onicecandidate = void 0, a.onaddstream = void 0, a.onremovestream = void 0, a.oniceconnectionstatechange = void 0,
          a.onnegotiationneeded = void 0, a.onsignalingstatechange = void 0
      },
      ga = function(a, b) {
        a.onmessage = function(a) {
          Ja(b, a)
        }, a.onopen = function(a) {
          Ka(b, a)
        }, a.onclose = function(a) {
          La(b, a)
        }, a.onerror = function(a) {
          e.Logger.debug("Data Channel Error:", a)
        }
      },
      ha = function(a, b) {
        b || (b = k.MESSAGE), ia(E(a), b)
      },
      ia = function(a, b) {
        var c = r[a];
        if (c && !c.dataChannels[b]) {
          e.Logger.debug("Do create data channel.");
          try {
            var d = c.connection.createDataChannel(b, x);
            ga(d, c), c.dataChannels[k.MESSAGE] = d
          } catch (f) {
            e.Logger.error("Failed to create SendDataChannel, exception: " + f.message)
          }
        }
      },
      ja = function(a) {
        e.Logger.debug("Do renegotiation."), F(a, j.NEGOTIATING), sa(a)
      },
      ka = function(a) {
        return r[a] || (r[a] = {
          state: i.READY,
          id: a,
          pendingStreams: [],
          pendingUnpublishStreams: [],
          remoteIceCandidates: [],
          dataChannels: {},
          pendingMessages: [],
          negotiationState: j.READY,
          lastDisconnect: new Date("2099/12/31").getTime(),
          publishedStreams: [],
          isCaller: !0,
          remoteSideSupportsRemoveStream: !1,
          remoteSideSupportsPlanB: !1,
          remoteSideSupportsUnifiedPlan: !1
        }), r[a]
      },
      la = function(a) {
        var b = r[a];
        e.Logger.debug(t + ": Remote side needs negotiation."), b && (b.isCaller && "stable" === b.connection.signalingState &&
        b.negotiationState === j.READY ? ja(b) : (b.isNegotiationNeeded = !0, e.Logger.error(
          "Should not receive negotiation needed request because user is callee.")))
      },
      ma = function(a, b, f) {
        if (m !== l.READY) return e.Logger.warning("Another peer has already connected"), void(f && f(d.Error.P2P_CLIENT_INVALID_STATE));
        m = l.CONNECTING, q = new c(a), q.onConnected = I, q.onDisconnected = K, q.onConnectFailed = J, q.onChatStopped =
          O, q.onChatAccepted = N, q.onChatDenied = M, q.onChatInvitation = L, q.onChatSignal = P, q.onStreamType = Q, q.onNegotiationNeeded =
          la, q.onAuthenticated = R, q.onForceDisconnect = S, q.connect(a, b, f)
      },
      na = function(a, b) {
        if (!u) return void(b && b(d.Error.P2P_CLIENT_INVALID_STATE));
        Ea(), q && q.finalize(), q = null, a && a()
      },
      oa = function(a, b, c) {
        if (!q) return void(c && c(d.Error.P2P_CONN_CLIENT_NOT_INITIALIZED));
        if (a === t) return void(c && c(d.Error.P2P_CLIENT_ILLEGAL_ARGUMENT));
        r[a] || ka(a);
        var f = r[a];
        f.state === i.READY || f.state === i.OFFERED ? (e.Logger.debug("Send invitation to " + a), f.state = i.OFFERED, q.sendChatInvitation(
          a, A,
          function() {
            b && b()
          },
          function(a) {
            f.state = i.READY, c && c(d.Error.getErrorByCode(a))
          })) : (e.Logger.debug("Invalid state. Will not send invitation."), c && c(d.Error.P2P_CLIENT_INVALID_STATE))
      },
      pa = function(a, b, c, d) {
        oa(a, function() {
          za(b, a)
        }, d)
      },
      qa = function(a, b, c) {
        !q && c && c(d.Error.P2P_CONN_CLIENT_NOT_INITIALIZED), r[a] || ka(a);
        var f = r[a];
        f.isCaller = !1, f.state === i.PENDING ? (f.state = i.MATCHED, q.sendChatAccepted(a, A, b, function(a) {
          f.state = i.PENDING, c && c(d.Error.getErrorByCode(a))
        })) : (e.Logger.debug("Invalid state. Will not send acceptance."), c && c(d.Error.P2P_CLIENT_INVALID_STATE))
      },
      ra = function(a, b, c, d) {
        qa(a, function() {
          za(b, a)
        }, d)
      },
      sa = function(a) {
        return a.connection ? "stable" !== a.connection.signalingState ? void F(a, j.NEGOTIATING) : (ua(a), a.isNegotiationNeeded = !
          1, void a.connection.createOffer(function(b) {
          b.sdp = Qa(b.sdp, a), a.connection.setLocalDescription(b, function() {
            e.Logger.debug("Set local descripiton successfully."), F(a, j.READY), q && q.sendSignalMessage(a.id, b)
          }, function(a) {
            e.Logger.debug("Set local description failed. Message: " + JSON.stringify(a))
          })
        }, function(a) {
          e.Logger.debug("Create offer failed. Error info: " + JSON.stringify(a))
        }, y)) : void e.Logger.error("Peer connection have not been created.")
      },
      ta = function(a) {
        if (a && a.connection && a.remoteIceCandidates && 0 !== a.remoteIceCandidates.length) {
          for (var b = 0; b < a.remoteIceCandidates.length; b++) e.Logger.debug("remoteIce, length:" + remoteIceCandidates.length +
            ", current:" + b), a.state !== i.CONNECTED && a.state !== i.CONNECTING || a.connection.addIceCandidate(
            remoteIceCandidates[b], ba, ca);
          a.remoteIceCandidates = []
        }
      },
      ua = function(a) {
        if (e.Logger.debug("Draining pending streams."), a.connection) {
          e.Logger.debug("Peer connection is ready for draining pending streams.");
          for (var b = 0; b < a.pendingStreams.length; b++) {
            var c = a.pendingStreams[b];
            a.pendingStreams.shift(), c.mediaStream && (wa(c, a), c.onClose || (c.onClose = function() {
              Ma(c)
            }), a.connection.addStream(c.mediaStream), e.Logger.debug("Added stream to peer connection."), Ca(c, a), e.Logger
              .debug("Sent stream type."))
          }
          a.pendingStreams = [];
          for (var d = 0; d < a.pendingUnpublishStreams.length; d++) a.pendingUnpublishStreams[d].mediaStream && (a.connection
            .removeStream(a.pendingUnpublishStreams[d].mediaStream), e.Logger.debug("Remove stream."));
          a.pendingUnpublishStreams = []
        }
      },
      va = function(a) {
        e.Logger.debug("Draining pendding messages.");
        var b = a.dataChannels[k.MESSAGE];
        if (b && "closed" !== b.readyState) {
          for (var c = 0; c < a.pendingMessages.length; c++) b.send(a.pendingMessages[c]);
          a.pendingMessages = []
        }
      },
      wa = function(a, b) {
        var c = a.id();
        v[c] || (v[c] = []), v[c].push(b.id)
      },
      xa = function(a) {
        if (!a.connection) return void e.Logger.error("Peer connection have not been created.");
        ua(a), a.isNegotiationNeeded = !1, a.connection.createAnswer(function(b) {
          b.sdp = Qa(b.sdp, a), a.connection.setLocalDescription(b, function() {
            e.Logger.debug("Set local description successfully."), q && q.sendSignalMessage(a.id, b), e.Logger.debug(
              "Sent answer.")
          }, function(a) {
            e.Logger.error("Error occurred while setting local description. Error message:" + a)
          })
        }, function(a) {
          e.Logger.error("Create answer failed. Message: " + a)
        })
      },
      ya = function(a, b) {
        for (var c = 0; c < a.length; c++)
          if (a[c] === b) return c;
        return -1
      },
      za = function(a, b, c, e) {
        if (!(a instanceof d.LocalStream && a.mediaStream && b)) return void(e && e(d.Error.P2P_CLIENT_ILLEGAL_ARGUMENT));
        Aa(a, b, c, e)
      },
      Aa = function(a, b, c, f) {
        e.Logger.debug("Publish to: " + b);
        var g = E(b);
        if (!g) return void(f && f(d.Error.P2P_CLIENT_ILLEGAL_ARGUMENT));
        r[g] || ka(g);
        var h = r[g],
          j = h.publishedStreams.length + h.pendingStreams.length > 0,
          k = h.remoteSideSupportsUnifiedPlan && C || h.remoteSideSupportsPlanB && B;
        if (j && !k) return e.Logger.warning(
          "Cannot publish more than one streams if local and remote use different multiple media sources plan."), void(f &&
          f(d.Error.P2P_CLIENT_UNSUPPORTED_METHOD));
        switch (h.state) {
          case i.OFFERED:
          case i.MATCHED:
          case i.CONNECTING:
          case i.CONNECTED:
            break;
          default:
            return e.Logger.warning("Cannot publish stream in this state: " + h.state), void(f && f(d.Error.P2P_CLIENT_INVALID_STATE))
        }
        if (ya(h.publishedStreams, a) > -1) return void(f && f("The stream has been published."));
        switch (h.publishedStreams.push(a), o(a) ? h.pendingStreams = h.pendingStreams.concat(a) : a && h.pendingStreams.push(
          a), h.state) {
          case i.CONNECTING:
          case i.CONNECTED:
            h.pendingStreams.length > 0 && ua(h);
            break;
          default:
            return e.Logger.debug("Unexpected peer state: " + h.state), void(f && f(d.Error.P2P_CLIENT_INVALID_STATE))
        }
        c && c()
      },
      Ba = function(a, b, c, f) {
        if (e.Logger.debug("Unpublish stream."), !(a instanceof d.LocalStream)) return e.Logger.warning(
          "Invalid argument stream"), void(f && f(d.Error.P2P_CLIENT_ILLEGAL_ARGUMENT));
        var g = E(b);
        if (!g) return void(f && (e.Logger.warning("Invalid argument targetId"), f(d.Error.P2P_CLIENT_ILLEGAL_ARGUMENT)));
        if (!r[g] || ya(r[g].publishedStreams, a) < 0) return void(f && f(d.Error.P2P_CLIENT_ILLEGAL_ARGUMENT));
        var h = r[g];
        if (navigator.mozGetUserMedia || !h.remoteSideSupportsRemoveStream) return e.Logger.error(
          "Unpublish is not supported on Firefox."), void(f && f(d.Error.P2P_CLIENT_UNSUPPORTED_METHOD));
        var j = ya(h.publishedStreams, a);
        h.publishedStreams.splice(j, 1), h.pendingUnpublishStreams.push(a), h.state === i.CONNECTED && ua(h), c && c()
      },
      Ca = function(a, b) {
        if (null !== a) {
          var c = "audio";
          a.isScreen() ? (c = "screen", a.hide = function() {
            e.Logger.debug("Unpublish screen sharing."), Ba(a, b.id)
          }) : a.hasVideo() && (c = "video"), q && q.sendStreamType(b.id, {
            streamId: a.mediaStream.id,
            type: c
          })
        }
      },
      Da = function(a, b, c) {
        if (r[a] && r[a].state === i.PENDING) {
          if (!q && c) return void c(d.Error.P2P_CONN_CLIENT_NOT_INITIALIZED);
          q.sendChatDenied(a, b, function(a) {
            c && c(d.Error.getErrorByCode(a))
          }), delete r[a]
        } else c && c(d.Error.P2P_CLIENT_INVALID_STATE)
      },
      Ea = function(a, b, c) {
        if (!q) return void(c && c(d.Error.P2P_CONN_CLIENT_NOT_INITIALIZED));
        if (a) {
          var f = r[a];
          if (!f) return void(c && (e.Logger.warning("Invalid target ID for stopping chat."), c(d.Error.P2P_CLIENT_ILLEGAL_ARGUMENT)));
          q.sendChatStopped(f.id), G(f, t), delete r[a]
        } else {
          var g = !0;
          for (var h in r) {
            g = !1;
            var i = r[h];
            q.sendChatStopped(i.id), G(i, t), delete r[i.id]
          }
          if (g) return void(c && (e.Logger.warning("No active connections can be stopped."), c(d.Error.P2P_CLIENT_INVALID_STATE)))
        }
        b && b()
      },
      Fa = function(b, c, e) {
        var f = E(b),
          g = r[f];
        if (!g || !g.connection || g.state !== i.CONNECTED) return void(e && e("failed to get peerconnection statistics"));
        c && g.connection.getStats(null, function(b) {
          c(a.navigator.appVersion.indexOf("Trident") > -1 ? b : d.Common.parseStats(b))
        }, function(a) {
          e && e(a)
        })
      },
      Ga = function(a, b, c) {
        var f = E(a),
          g = r[f];
        if (g && g.connection && g.state === i.CONNECTED || c("Invalid peer connection status."), navigator.mozGetUserMedia)
          return e.Logger.error("GetAudioLevels is not supported on Firefox."), void(c && c(d.Error.P2P_CLIENT_UNSUPPORTED_METHOD));
        b && g.connection.getStats(function(a) {
          b(d.Common.parseAudioLevel(a))
        }, function(a) {
          c && c(a)
        })
      },
      Ha = function(a, b, c, f) {
        return a ? a.length > 65535 ? (e.Logger.warning("Message too long. Max size: 65535."), void(f && f(d.Error.P2P_CLIENT_ILLEGAL_ARGUMENT))) :
          void Ia(a, E(b), c, f) : (e.Logger.warning("Message cannot be undefined, null or empty."), void(f && f(d.Error.P2P_CLIENT_ILLEGAL_ARGUMENT)))
      },
      Ia = function(a, b, c, f) {
        var g = r[b];
        if (!g || g.state !== i.CONNECTED) return void(f && (e.Logger.error("Invalid peer state."), f(d.Error.P2P_CLIENT_INVALID_STATE)));
        var h = g.dataChannels[k.MESSAGE];
        h && "open" === h.readyState ? h.send(a) : (g.pendingMessages.push(a), ha(b)), c && c()
      },
      Ja = function(a, b) {
        var c = new d.DataEvent({
          target: h,
          type: "data-received",
          senderId: a.id,
          data: b.data
        });
        h.dispatchEvent(c)
      },
      Ka = function(a, b) {
        e.Logger.debug("Data Channel is opened"), b.target.label === k.MESSAGE && (e.Logger.debug(
          "Data channel for messages is opened."), va(a))
      },
      La = function(a) {
        e.Logger.debug("Data Channel for " + a + " is closed.")
      },
      Ma = function(a) {
        var b = v[a.getID()];
        if (b)
          for (var c = 0; c < b.length; c++) Ba(a, b[c])
      },
      Na = function(a) {
        return n.bandWidth && n.bandWidth.maxAudioBW ? d.Common.setPreferredBitrate(a, "audio", n.bandWidth.maxAudioBW) :
          a
      },
      Oa = function(a) {
        return n.bandWidth && n.bandWidth.maxVideoBW ? d.Common.setPreferredBitrate(a, "video", n.bandWidth.maxVideoBW) :
          a
      },
      Pa = function(a) {
        return a = Na(a), a = Oa(a)
      },
      Qa = function(a, b) {
        return a = Ra(a), a = Sa(a, b)
      },
      Ra = function(a) {
        return n.audioCodec ? a : d.Common.setPreferredCodec(a, "audio", n.audioCodec)
      },
      Sa = function(a, b) {
        var c;
        return c = navigator.mozGetUserMedia ? "vp8" : b && b.preferredVideoCodec ? b.preferredVideoCodec : n.videoCodec ?
          n.videoCodec : "h264", d.Common.setPreferredCodec(a, "video", c)
      };
    return h.connect = ma, h.disconnect = na, h.invite = oa, h.inviteWithStream = pa, h.publish = za, h.unpublish = Ba,
      h.deny = Da, h.accept = qa, h.acceptWithStream = ra, h.stop = Ea, h.send = Ha, h.getConnectionStats = Fa, h.getAudioLevels =
      Ga, h
  }, a.Erizo = f, a.PureRTC = d, a.L = e
}(window);
/*
 * Intel WebRTC SDK version 3.4.1
 * Copyright (c) 2018 Intel <http://webrtc.purertc.com>
 * Homepage: http://webrtc.purertc.com
 */


(function(window) {


  /* global window, webkitURL, Erizo */
  /*
 * AudioPlayer represents a Licode Audio component that shows either a local or a remote Audio.
 * Ex.: var player = AudioPlayer({id: id, stream: stream, elementID: elementID, doc: document});
 * A AudioPlayer is also a View component.
 */
  Erizo.AudioPlayer = function(spec) {
    'use strict';

    var that = Erizo.View({}),
      onmouseover,
      onmouseout;

    // Variables

    // current document object where AudioPlayer will be located
    that.document = spec.doc || document;

    // AudioPlayer ID
    that.id = spec.id;

    // Stream that the AudioPlayer will play
    that.stream = spec.stream.mediaStream;

    // DOM element in which the AudioPlayer will be appended
    that.elementID = spec.elementID;

    that.stream_url = (window.URL || webkitURL).createObjectURL(that.stream);

    // Audio tag
    that.audio = that.document.createElement('audio');
    that.audio.setAttribute('id', 'stream' + that.id);
    that.audio.setAttribute('style',
      'width: 100%; height: 100%; position: absolute');
    that.audio.setAttribute('autoplay', 'autoplay');

    if (spec.stream instanceof PureRTC.LocalStream) {
      that.audio.volume = 0;
    }

    if (that.elementID !== undefined) {

      // It will stop the AudioPlayer and remove it from the HTML
      that.destroy = function() {
        that.audio.pause();
        that.parentNode.removeChild(that.div);
      };

      onmouseover = function() {
        that.bar.display();
      };

      onmouseout = function() {
        that.bar.hide();
      };

      // Container
      that.div = that.document.createElement('div');
      that.div.setAttribute('id', 'player_' + that.id);
      that.div.setAttribute('style',
        'width: 100%; height: 100%; position: relative; overflow: hidden;');

      that.document.getElementById(that.elementID).appendChild(that.div);
      that.container = that.document.getElementById(that.elementID);

      that.parentNode = that.div.parentNode;

      that.div.appendChild(that.audio);

      // Bottom Bar
      that.bar = new Erizo.Bar({
        elementID: 'player_' + that.id,
        id: that.id,
        stream: spec.stream,
        media: that.audio,
        options: spec.options,
        doc: that.document
      });

      that.div.onmouseover = onmouseover;
      that.div.onmouseout = onmouseout;

    } else {
      // It will stop the AudioPlayer and remove it from the HTML
      that.destroy = function() {
        that.audio.pause();
        that.parentNode.removeChild(that.audio);
      };

      that.document.body.appendChild(that.audio);
      that.parentNode = that.document.body;
    }

    that.audio.src = that.stream_url;

    return that;
  };



  /* global clearTimeout, setTimeout, Erizo */

  /*
 * Bar represents the bottom menu bar of every mediaPlayer.
 * It contains a Speaker and an icon.
 * Every Bar is a View.
 * Ex.: var bar = Bar({elementID: element, id: id, doc: document});
 */
  Erizo.Bar = function(spec) {
    'use strict';

    var that = Erizo.View({}),
      waiting,
      show;

    // Variables

    // current document object where Bar will be located
    that.document = spec.doc || document;

    // DOM element in which the Bar will be appended
    that.elementID = spec.elementID;

    // Bar ID
    that.id = spec.id;

    // Container
    that.div = that.document.createElement('div');
    that.div.setAttribute('id', 'bar_' + that.id);

    // Bottom bar
    that.bar = that.document.createElement('div');
    that.bar.setAttribute('style',
      'width: 100%; height: 15%; max-height: 1.5rem; position: absolute; bottom: 3.8rem; right: 0;'
    );
    that.bar.setAttribute('id', 'subbar_' + that.id);

    // Private functions
    show = function(displaying) {
      if (displaying !== 'block') {
        displaying = 'none';
      } else {
        clearTimeout(waiting);
      }
      that.div.setAttribute('style',
        'width: 100%; height: 100%; position: relative; bottom: 0; right: 0; display:' +
        displaying);
    };

    // Public functions

    that.display = function() {
      show('block');
    };

    that.hide = function() {
      waiting = setTimeout(show, 1000);
    };

    that.document.getElementById(that.elementID).appendChild(that.div);
    that.div.appendChild(that.bar);

    // Speaker component
    if (!spec.stream.screen && (spec.options === undefined || spec.options.speaker ===
      undefined || spec.options.speaker === true)) {
      that.speaker = new Erizo.Speaker({
        elementID: 'subbar_' + that.id,
        id: that.id,
        stream: spec.stream,
        media: spec.media,
        doc: that.document
      });
    }

    that.display();
    that.hide();

    return that;
  };



  /**
   * Copyright 2013 Marc J. Schmidt. See the LICENSE file at the top-level
   * directory of this distribution and at
   * https://github.com/marcj/css-element-queries/blob/master/LICENSE.
   */
  ;
  (function() {

    window.L = window.L || {};

   
    window.L.ElementQueries = function() {
      /**
       *
       * @param element
       * @returns {Number}
       */
      function getEmSize(element) {
        if (!element) {
          element = document.documentElement;
        }
        var fontSize = getComputedStyle(element, 'fontSize');
        return parseFloat(fontSize) || 16;
      }

      /**
       *
       * @copyright https://github.com/Mr0grog/element-query/blob/master/LICENSE
       *
       * @param element
       * @param value
       * @param units
       * @returns {*}
       */
      function convertToPx(element, value) {
        var units = value.replace(/[0-9]*/, '');
        value = parseFloat(value);
        switch (units) {
          case "px":
            return value;
          case "em":
            return value * getEmSize(element);
          case "rem":
            return value * getEmSize();
          // Viewport units!
          // According to http://quirksmode.org/mobile/tableViewport.html
          // documentElement.clientWidth/Height gets us the most reliable info
          case "vw":
            return value * document.documentElement.clientWidth / 100;
          case "vh":
            return value * document.documentElement.clientHeight / 100;
          case "vmin":
          case "vmax":
            var vw = document.documentElement.clientWidth / 100;
            var vh = document.documentElement.clientHeight / 100;
            var chooser = Math[units === "vmin" ? "min" : "max"];
            return value * chooser(vw, vh);
          default:
            return value;
          // for now, not supporting physical units (since they are just a set number of px)
          // or ex/ch (getting accurate measurements is hard)
        }
      }

      function SetupInformation(element) {
        this.element = element;
        this.options = [];
        var i, j, option, width = 0,
          height = 0,
          value, actualValue, attrValues, attrValue, attrName;

        // 
        //   @param option {mode: 'min|max', property: 'width|height', value: '123px'}
        // 
        this.addOption = function(option) {
          this.options.push(option);
        }

        var attributes = ['min-width', 'min-height', 'max-width',
          'max-height'
        ];

        /**
         * Extracts the computed width/height and sets to min/max- attribute.
         */
        this.call = function() {
          // extract current dimensions
          width = this.element.offsetWidth;
          height = this.element.offsetHeight;

          attrValues = {};

          for (i = 0, j = this.options.length; i < j; i++) {
            option = this.options[i];
            value = convertToPx(this.element, option.value);

            actualValue = option.property == 'width' ? width : height;
            attrName = option.mode + '-' + option.property;
            attrValue = '';

            if (option.mode == 'min' && actualValue >= value) {
              attrValue += option.value;
            }

            if (option.mode == 'max' && actualValue <= value) {
              attrValue += option.value;
            }

            if (!attrValues[attrName]) attrValues[attrName] = '';
            if (attrValue && -1 === (' ' + attrValues[attrName] + ' ').indexOf(
              ' ' + attrValue + ' ')) {
              attrValues[attrName] += ' ' + attrValue;
            }
          }

          for (var k in attributes) {
            if (attrValues[attributes[k]]) {
              this.element.setAttribute(attributes[k], attrValues[
                attributes[k]].substr(1));
            } else {
              this.element.removeAttribute(attributes[k]);
            }
          }
        };
      }

      /**
       * @param {HTMLElement} element
       * @param {Object}      options
       */
      function setupElement(element, options) {
        if (element.elementQueriesSetupInformation) {
          element.elementQueriesSetupInformation.addOption(options);
        } else {
          element.elementQueriesSetupInformation = new SetupInformation(
            element);
          element.elementQueriesSetupInformation.addOption(options);
          new ResizeSensor(element, function() {
            element.elementQueriesSetupInformation.call();
          });
        }
        element.elementQueriesSetupInformation.call();
      }

      /**
       * @param {String} selector
       * @param {String} mode min|max
       * @param {String} property width|height
       * @param {String} value
       */
      function queueQuery(selector, mode, property, value) {
        var query;
        if (document.querySelectorAll) query = document.querySelectorAll.bind(
          document);
        if (!query && 'undefined' !== typeof $$) query = $$;
        if (!query && 'undefined' !== typeof jQuery) query = jQuery;

        if (!query) {
          throw 'No document.querySelectorAll, jQuery or Mootools\'s $$ found.';
        }

        var elements = query(selector);
        for (var i = 0, j = elements.length; i < j; i++) {
          setupElement(elements[i], {
            mode: mode,
            property: property,
            value: value
          });
        }
      }

      var regex =
        /,?([^,\n]*)\[[\s\t]*(min|max)-(width|height)[\s\t]*[~$\^]?=[\s\t]*"([^"]*)"[\s\t]*]([^\n\s\{]*)/mgi;

      /**
       * @param {String} css
       */
      function extractQuery(css) {
        var match;
        css = css.replace(/'/g, '"');
        while (null !== (match = regex.exec(css))) {
          if (5 < match.length) {
            queueQuery(match[1] || match[5], match[2], match[3], match[4]);
          }
        }
      }

      /**
       * @param {CssRule[]|String} rules
       */
      function readRules(rules) {
        var selector = '';
        if (!rules) {
          return;
        }
        if ('string' === typeof rules) {
          rules = rules.toLowerCase();
          if (-1 !== rules.indexOf('min-width') || -1 !== rules.indexOf(
            'max-width')) {
            extractQuery(rules);
          }
        } else {
          for (var i = 0, j = rules.length; i < j; i++) {
            if (1 === rules[i].type) {
              selector = rules[i].selectorText || rules[i].cssText;
              if (-1 !== selector.indexOf('min-height') || -1 !== selector.indexOf(
                'max-height')) {
                extractQuery(selector);
              } else if (-1 !== selector.indexOf('min-width') || -1 !==
                selector.indexOf('max-width')) {
                extractQuery(selector);
              }
            } else if (4 === rules[i].type) {
              readRules(rules[i].cssRules || rules[i].rules);
            }
          }
        }
      }

      /**
       * Searches all css rules and setups the event listener to all elements with element query rules..
       */
      this.init = function() {
        for (var i = 0, j = document.styleSheets.length; i < j; i++) {
          var rules;
          try {
            rules = document.styleSheets[i].cssText || document.styleSheets[
              i].cssRules || document.styleSheets[i].rules;
            readRules( rules );
          } catch (e) {
            console.warn("Can't read the css rules of: " + document.styleSheets[i].href, e);
            continue;
          }
        }
      }
    }

    function init() {
      new L.ElementQueries().init();
    }

    if (window.addEventListener) {
      window.addEventListener('load', init, false);
    } else {
      window.attachEvent('onload', init);
    }

   
    window.L.ResizeSensor = function(element, callback) {
      /**
       * Adds a listener to the over/under-flow event.
       *
       * @param {HTMLElement} element
       * @param {Function}    callback
       */
      function addResizeListener(element, callback) {
        if (window.OverflowEvent) {
          // webkit [deprecated!]
          // element.addEventListener('overflowchanged', function(e) {
          //     callback.call(this, e);
          // });
        } else {
          element.addEventListener('overflow', function(e) {
            callback.call(this, e);
          });
          element.addEventListener('underflow', function(e) {
            callback.call(this, e);
          });
        }
      }

      
      function EventQueue() {
        this.q = [];
        this.add = function(ev) {
          this.q.push(ev);
        };

        var i, j;
        this.call = function() {
          for (i = 0, j = this.q.length; i < j; i++) {
            this.q[i].call();
          }
        };
      }

      /**
       * @param {HTMLElement} element
       * @param {String}      prop
       * @returns {String|Number}
       */
      function getComputedStyle(element, prop) {
        if (element.currentStyle) {
          return element.currentStyle[prop];
        } else if (window.getComputedStyle) {
          return window.getComputedStyle(element, null).getPropertyValue(prop);
        } else {
          return element.style[prop];
        }
      }

      /**
       *
       * @param {HTMLElement} element
       * @param {Function}    resized
       */
      function attachResizeEvent(element, resized) {
        if (!element.resizedAttached) {
          element.resizedAttached = new EventQueue();
          element.resizedAttached.add(resized);
        } else if (element.resizedAttached) {
          element.resizedAttached.add(resized);
          return;
        }

        /*if ('onresize' in element) {
          //internet explorer
          if (element.attachEvent) {
              element.attachEvent('onresize', function() {
                  element.resizedAttached.call();
              });
          } else if (element.addEventListener) {
              element.addEventListener('resize', function(){
                  element.resizedAttached.call();
              });
          }
      } else {*/
        var myResized = function() {
          if (setupSensor()) {
            element.resizedAttached.call();
          }
        };
        element.resizeSensor = document.createElement('div');
        element.resizeSensor.className = 'resize-sensor';
        var style =
          'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1;';
        element.resizeSensor.style.cssText = style;
        element.resizeSensor.innerHTML =
          '<div class="resize-sensor-overflow" style="' + style + '">' +
          '<div></div>' +
          '</div>' +
          '<div class="resize-sensor-underflow" style="' + style + '">' +
          '<div></div>' +
          '</div>';
        element.appendChild(element.resizeSensor);

        if ('absolute' !== getComputedStyle(element, 'position')) {
          element.style.position = 'relative';
        }

        var x = -1,
          y = -1,
          firstStyle = element.resizeSensor.firstElementChild.firstChild.style,
          lastStyle = element.resizeSensor.lastElementChild.firstChild.style;

        function setupSensor() {
          var change = false,
            width = element.resizeSensor.offsetWidth,
            height = element.resizeSensor.offsetHeight;

          if (x != width) {
            firstStyle.width = (width - 1) + 'px';
            lastStyle.width = (width + 1) + 'px';
            change = true;
            x = width;
          }
          if (y != height) {
            firstStyle.height = (height - 1) + 'px';
            lastStyle.height = (height + 1) + 'px';
            change = true;
            y = height;
          }
          return change;
        }

        setupSensor();
        addResizeListener(element.resizeSensor, myResized);
        addResizeListener(element.resizeSensor.firstElementChild, myResized);
        addResizeListener(element.resizeSensor.lastElementChild, myResized);
        /*}*/
      }

      if ('array' === typeof element ||
        ('undefined' !== typeof jQuery && element instanceof jQuery) //jquery
        ||
        ('undefined' !== typeof Elements && element instanceof Elements) //mootools
      ) {
        var i = 0,
          j = element.length;
        for (; i < j; i++) {
          attachResizeEvent(element[i], callback);
        }
      } else {
        attachResizeEvent(element, callback);
      }
    }

  })();



  /* global Erizo*/
  /*
 * Speaker represents the volume icon that will be shown in the mediaPlayer, for example.
 * It manages the volume level of the media tag given in the constructor.
 * Every Speaker is a View.
 * Ex.: var speaker = Speaker({elementID: element, media: mediaTag, id: id, doc: document});
 */
  Erizo.Speaker = function(spec) {
    'use strict';

    var that = Erizo.View({}),
      show,
      mute,
      unmute,
      lastVolume = 50;

    // Variables

    // current document object where Speaker will be located
    that.document = spec.doc || document;

    // DOM element in which the Speaker will be appended
    that.elementID = spec.elementID;

    // media tag
    that.media = spec.media;

    // Speaker id
    that.id = spec.id;

    // MediaStream
    that.stream = spec.stream;

    // Container
    that.div = that.document.createElement('div');
    that.div.setAttribute('style',
      'width: 40%; height: 100%; max-width: 32px; position: absolute; right: 0;z-index:0;'
    );

    // Volume icon
    that.icon = that.document.createElement('img');
    that.icon.setAttribute('id', 'volume_' + that.id);
    that.icon.setAttribute('src', PureRTC.Images.sound48);
    that.icon.setAttribute('style',
      'width: 80%; height: 100%; position: absolute;');
    that.div.appendChild(that.icon);


    if (that.stream instanceof PureRTC.RemoteStream) {

      // Volume bar
      that.picker = that.document.createElement('input');
      that.picker.setAttribute('id', 'picker_' + that.id);
      that.picker.type = 'range';
      that.picker.min = 0;
      that.picker.max = 100;
      that.picker.step = 10;
      that.picker.value = lastVolume;
      that.picker.orient = 'vertical'; //  FireFox supports range sliders as of version 23
      that.div.appendChild(that.picker);
      that.media.volume = that.picker.value / 100;
      that.media.muted = false;

      that.picker.oninput = function() {
        if (that.picker.value > 0) {
          that.media.muted = false;
          that.icon.setAttribute('src', PureRTC.Images.sound48);
        } else {
          that.media.muted = true;
          that.icon.setAttribute('src', PureRTC.Images.mute48);
        }
        that.media.volume = that.picker.value / 100;
      };

      // Private functions
      show = function(displaying) {
        that.picker.setAttribute('style',
          'width: 32px; height: 100px; position: absolute; bottom: 90%; z-index: 1;' +
          that.div.offsetHeight +
          'px; right: 0px; -webkit-appearance: slider-vertical; display: ' +
          displaying);
      };

      mute = function() {
        that.icon.setAttribute('src', PureRTC.Images.mute48);
        lastVolume = that.picker.value;
        that.picker.value = 0;
        that.media.volume = 0;
        that.media.muted = true;
      };

      unmute = function() {
        that.icon.setAttribute('src', PureRTC.Images.sound48);
        that.picker.value = lastVolume;
        that.media.volume = that.picker.value / 100;
        that.media.muted = false;
      };

      that.icon.onclick = function() {
        if (that.media.muted) {
          unmute();
        } else {
          mute();
        }
      };

      // Public functions
      that.div.onmouseover = function() {
        show('block');
      };

      that.div.onmouseout = function() {
        show('none');
      };

      show('none');

    } else if (that.stream instanceof PureRTC.LocalStream) {
      mute = function() {
        if (that.stream.mediaStream.getAudioTracks().length == 0)
          return;
        that.media.muted = true;
        that.icon.setAttribute('src', PureRTC.Images.mute48);
        that.stream.mediaStream.getAudioTracks()[0].enabled = false;
      };

      unmute = function() {
        if (that.stream.mediaStream.getAudioTracks().length == 0)
          return;
        that.media.muted = false;
        that.icon.setAttribute('src', PureRTC.Images.sound48);
        that.stream.mediaStream.getAudioTracks()[0].enabled = true;
      };

      that.icon.onclick = function() {
        if (that.media.muted) {
          unmute();
        } else {
          mute();
        }
      };
    }


    that.document.getElementById(that.elementID).appendChild(that.div);
    return that;
  };



  /* global Erizo */

  /*
 * View class represents a HTML component
 * Every view is an EventDispatcher.
 */
  Erizo.View = function(spec) {
    'use strict';

    var that = PureRTC.EventDispatcher(spec);

    // Variables

    // URL where it will look for icons and assets
    that.url = spec.url || '.';
    return that;
  };



  /* global window, webkitURL, Erizo, L */
  /*
 * VideoPlayer represents a Licode video component that shows either a local or a remote video.
 * Ex.: var player = VideoPlayer({id: id, stream: stream, elementID: elementID, doc: document});
 * A VideoPlayer is also a View component.
 */
  Erizo.VideoPlayer = function(spec) {
    'use strict';

    var that = Erizo.View({}),
      onmouseover,
      onmouseout;

    // Variables

    // current document object where VideoPlayer will be located
    that.document = spec.doc || document;

    // VideoPlayer ID
    that.id = spec.id;

    // Stream that the VideoPlayer will play
    that.stream = spec.stream.mediaStream;

    // DOM element in which the VideoPlayer will be appended
    that.elementID = spec.elementID;

    // Private functions
    onmouseover = function() {
      that.bar.display();
    };

    onmouseout = function() {
      that.bar.hide();
    };

    // Public functions

    // It will stop the VideoPlayer and remove it from the HTML
    that.destroy = function() {
      that.video.pause();
      delete that.resizer;
      that.parentNode.removeChild(that.div);
    };

    that.resize = function(hasAbsoluteLeft) {

      var width = that.container.offsetWidth,
        height = that.container.offsetHeight;


      if (hasAbsoluteLeft) {

        that.video.style.width = "calc(100% + " + ((4 / 3) * height - width) +
          "px)";
        that.video.style.height = "100%";

        that.video.style.top = '0px';
        that.video.style.left = -((4 / 3) * height / 2 - width / 2) + 'px';

      } else {

        that.video.style.height = '100%';
        that.video.style.width = '100%';

        that.video.style.left = '0px';
        that.video.style.top = '0px';

      }

      that.containerWidth = width;
      that.containerHeight = height;

    };

    /*window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) {
      document.getElementById(key).value = unescape(value);
  });*/

    // that.stream_url = (window.URL || webkitURL).createObjectURL(that.stream);

    // Container
    that.div = that.document.createElement('div');
    that.div.setAttribute('id', 'player_' + that.id);
    that.div.setAttribute('style',
      'width: 100%; height: 100%; position: relative; background-color: black; overflow: hidden;'
    );

    // Video tag
    that.video = that.document.createElement('video');
    that.video.setAttribute('id', 'stream' + that.id);
    that.video.setAttribute('style',
      'width: 100%; height: 100%; position: absolute');
    that.video.setAttribute('autoplay', 'autoplay');

    if (spec.stream instanceof PureRTC.LocalStream) {
      that.video.volume = 0;
    }

    if (that.elementID !== undefined) {
      that.document.getElementById(that.elementID).appendChild(that.div);
      that.container = that.document.getElementById(that.elementID);
    } else {
      that.document.body.appendChild(that.div);
      that.container = that.document.body;
    }

    var fullscreenHandler = (function() {
      var requestFS = ['requestFullScreen', 'webkitRequestFullScreen',
        'mozRequestFullScreen', 'msRequestFullScreen',
        'oRequestFullScreen'
      ];

      function _enterFullScreen(tag) {
        var method;
        for (var p in requestFS) {
          if (typeof tag[requestFS[p]] === 'function') {
            method = requestFS[p];
            break;
          }
        }
        if (typeof tag[method] === 'function') {
          tag[method]();
        }
      }

      function _exitFullScreen() {
        if (typeof that.document.exitFullscreen === 'function') {
          that.document.exitFullscreen();
        } else if (typeof that.document.webkitExitFullscreen === 'function') {
          that.document.webkitExitFullscreen();
        } else if (typeof that.document.mozCancelFullScreen === 'function') {
          that.document.mozCancelFullScreen();
        }
      }
      return function(tag) {
        if (tag.offsetWidth == screen.width) _exitFullScreen();
        else _enterFullScreen(tag);
      };
    }());

    that.parentNode = that.div.parentNode;

    that.div.appendChild(that.video);
    that.video.addEventListener('playing', function display() {
      if (that.video.videoWidth * that.video.videoHeight > 4) { // why remote video size initially is 2*2 in chrome?
        L.Logger.debug('video dimensions:', that.video.videoWidth, that.video
          .videoHeight);
        return;
      }
      setTimeout(display, 50);
    });
    that.containerWidth = 0;
    that.containerHeight = 0;

    that.resizer = new L.ResizeSensor(that.container, that.resize);

    that.resize();

    that.div.ondblclick = function() {
      fullscreenHandler(that.video);
    };

    // Bottom Bar
    that.bar = new Erizo.Bar({
      elementID: 'player_' + that.id,
      id: that.id,
      stream: spec.stream,
      media: that.video,
      options: spec.options,
      doc: that.document
    });

    that.div.onmouseover = onmouseover;
    that.div.onmouseout = onmouseout;

    //that.video.src = that.stream_url;
    that.video.srcObject = that.stream;

    return that;
  };



  /* global Erizo */

  PureRTC.Images = {
    sound48: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAABGdBTUEAALGOfPtRkwAACidpQ0NQaWNjAAB42p2Wd1RU1xaHz713eqHNMAJSht67wADSe5NeRWGYGWAoAw4zNLEhogIRRUSaIkhQxIDRUCRWRLEQFFSwByQIKDEYRVQsb0bWi66svPfy8vvjrG/ts/e5++y9z1oXAJKnL5eXBksBkMoT8IM8nOkRkVF07ACAAR5ggCkATFZGul+wewgQycvNhZ4hcgJfBAHweli8AnDT0DOATgf/n6RZ6XyB6JgAEZuzORksEReIOCVLkC62z4qYGpcsZhglZr4oQRHLiTlhkQ0++yyyo5jZqTy2iMU5p7NT2WLuFfG2TCFHxIiviAszuZwsEd8SsUaKMJUr4jfi2FQOMwMAFElsF3BYiSI2ETGJHxLkIuLlAOBICV9x3Fcs4GQLxJdySUvP4XMTEgV0HZYu3dTamkH35GSlcAQCwwAmK5nJZ9Nd0lLTmbwcABbv/Fky4trSRUW2NLW2tDQ0MzL9qlD/dfNvStzbRXoZ+LlnEK3/i+2v/NIaAGDMiWqz84strgqAzi0AyN37YtM4AICkqG8d17+6D008L4kCQbqNsXFWVpYRl8MyEhf0D/1Ph7+hr75nJD7uj/LQXTnxTGGKgC6uGystJU3Ip2ekM1kcuuGfh/gfB/51HgZBnHgOn8MTRYSJpozLSxC1m8fmCrhpPDqX95+a+A/D/qTFuRaJ0vgRUGOMgNR1KkB+7QcoChEg0fvFXf+jb774MCB+eeEqk4tz/+83/WfBpeIlg5vwOc4lKITOEvIzF/fEzxKgAQFIAiqQB8pAHegAQ2AGrIAtcARuwBv4gxAQCVYDFkgEqYAPskAe2AQKQTHYCfaAalAHGkEzaAXHQSc4Bc6DS+AauAFug/tgFEyAZ2AWvAYLEARhITJEgeQhFUgT0ofMIAZkD7lBvlAQFAnFQgkQDxJCedBmqBgqg6qheqgZ+h46CZ2HrkCD0F1oDJqGfofewQhMgqmwEqwFG8MM2An2gUPgVXACvAbOhQvgHXAl3AAfhTvg8/A1+DY8Cj+D5xCAEBEaoooYIgzEBfFHopB4hI+sR4qQCqQBaUW6kT7kJjKKzCBvURgUBUVHGaJsUZ6oUBQLtQa1HlWCqkYdRnWgelE3UWOoWdRHNBmtiNZH26C90BHoBHQWuhBdgW5Ct6Mvom+jJ9CvMRgMDaONscJ4YiIxSZi1mBLMPkwb5hxmEDOOmcNisfJYfawd1h/LxAqwhdgq7FHsWewQdgL7BkfEqeDMcO64KBwPl4+rwB3BncEN4SZxC3gpvCbeBu+PZ+Nz8KX4Rnw3/jp+Ar9AkCZoE+wIIYQkwiZCJaGVcJHwgPCSSCSqEa2JgUQucSOxkniMeJk4RnxLkiHpkVxI0SQhaQfpEOkc6S7pJZlM1iI7kqPIAvIOcjP5AvkR+Y0ERcJIwkuCLbFBokaiQ2JI4rkkXlJT0klytWSuZIXkCcnrkjNSeCktKRcpptR6qRqpk1IjUnPSFGlTaX/pVOkS6SPSV6SnZLAyWjJuMmyZApmDMhdkxikIRZ3iQmFRNlMaKRcpE1QMVZvqRU2iFlO/ow5QZ2VlZJfJhslmy9bInpYdpSE0LZoXLYVWSjtOG6a9W6K0xGkJZ8n2Ja1LhpbMyy2Vc5TjyBXJtcndlnsnT5d3k0+W3yXfKf9QAaWgpxCokKWwX+GiwsxS6lLbpaylRUuPL72nCCvqKQYprlU8qNivOKekrOShlK5UpXRBaUaZpuyonKRcrnxGeVqFomKvwlUpVzmr8pQuS3eip9Ar6b30WVVFVU9VoWq96oDqgpq2Wqhavlqb2kN1gjpDPV69XL1HfVZDRcNPI0+jReOeJl6ToZmouVezT3NeS1srXGurVqfWlLactpd2rnaL9gMdso6DzhqdBp1buhhdhm6y7j7dG3qwnoVeol6N3nV9WN9Sn6u/T3/QAG1gbcAzaDAYMSQZOhlmGrYYjhnRjHyN8o06jZ4baxhHGe8y7jP+aGJhkmLSaHLfVMbU2zTftNv0dzM9M5ZZjdktc7K5u/kG8y7zF8v0l3GW7V92x4Ji4Wex1aLH4oOllSXfstVy2krDKtaq1mqEQWUEMEoYl63R1s7WG6xPWb+1sbQR2By3+c3W0DbZ9ojt1HLt5ZzljcvH7dTsmHb1dqP2dPtY+wP2ow6qDkyHBofHjuqObMcmx0knXackp6NOz51NnPnO7c7zLjYu61zOuSKuHq5FrgNuMm6hbtVuj9zV3BPcW9xnPSw81nqc80R7+nju8hzxUvJieTV7zXpbea/z7vUh+QT7VPs89tXz5ft2+8F+3n67/R6s0FzBW9HpD/y9/Hf7PwzQDlgT8GMgJjAgsCbwSZBpUF5QXzAlOCb4SPDrEOeQ0pD7oTqhwtCeMMmw6LDmsPlw1/Cy8NEI44h1EdciFSK5kV1R2KiwqKaouZVuK/esnIi2iC6MHl6lvSp71ZXVCqtTVp+OkYxhxpyIRceGxx6Jfc/0ZzYw5+K84mrjZlkurL2sZ2xHdjl7mmPHKeNMxtvFl8VPJdgl7E6YTnRIrEic4bpwq7kvkjyT6pLmk/2TDyV/SglPaUvFpcamnuTJ8JJ5vWnKadlpg+n66YXpo2ts1uxZM8v34TdlQBmrMroEVNHPVL9QR7hFOJZpn1mT+SYrLOtEtnQ2L7s/Ry9ne85krnvut2tRa1lre/JU8zblja1zWle/Hloft75ng/qGgg0TGz02Ht5E2JS86ad8k/yy/Febwzd3FygVbCwY3+KxpaVQopBfOLLVdmvdNtQ27raB7ebbq7Z/LGIXXS02Ka4ofl/CKrn6jek3ld982hG/Y6DUsnT/TsxO3s7hXQ67DpdJl+WWje/2291RTi8vKn+1J2bPlYplFXV7CXuFe0crfSu7qjSqdla9r06svl3jXNNWq1i7vXZ+H3vf0H7H/a11SnXFde8OcA/cqfeo72jQaqg4iDmYefBJY1hj37eMb5ubFJqKmz4c4h0aPRx0uLfZqrn5iOKR0ha4RdgyfTT66I3vXL/rajVsrW+jtRUfA8eEx55+H/v98HGf4z0nGCdaf9D8obad0l7UAXXkdMx2JnaOdkV2DZ70PtnTbdvd/qPRj4dOqZ6qOS17uvQM4UzBmU9nc8/OnUs/N3M+4fx4T0zP/QsRF271BvYOXPS5ePmS+6ULfU59Zy/bXT51xebKyauMq53XLK919Fv0t/9k8VP7gOVAx3Wr6103rG90Dy4fPDPkMHT+puvNS7e8bl27veL24HDo8J2R6JHRO+w7U3dT7r64l3lv4f7GB+gHRQ+lHlY8UnzU8LPuz22jlqOnx1zH+h8HP74/zhp/9kvGL+8nCp6Qn1RMqkw2T5lNnZp2n77xdOXTiWfpzxZmCn+V/rX2uc7zH35z/K1/NmJ24gX/xaffS17Kvzz0atmrnrmAuUevU18vzBe9kX9z+C3jbd+78HeTC1nvse8rP+h+6P7o8/HBp9RPn/4FA5jz/EHct2gAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAACXZwQWcAAAAwAAAAMADO7oxXAAAHsUlEQVRYw+1XfVSUVRp/ZgYGcPgc+TRNPgtRhDZELEhdPX62kZuSCGoqIIsalKUejazctW3LXE9ohoAppeDXKOdQIpsgioKCqwYKAwoCKSDDt8LAzNx+dwYExMMiq//1/s6ZM/e+931+z+e9zyX64/njGfARkJBEWgjx/xmIF5GBYJiliVBChqT3/1MI+ogQQOQwkoaPvfXRHj+yJGPSHzoF11UPAvTx2+0MERmRtb9XxUGNqi4z1I/sQDFEK4QQLCGzkFGBXIgYY06pT+bkkveNRqVhGk3J0fFeZANK0VDEG5AZ2cXPbkq7tg5CTCFaCF0l0Pml9etq76qZmnW2n/onudJwrBU8qWsMSWrgkBTWKmcsLxZCbCDaiEwcbPVdyJcC18c1tHYyFWuuWLeQRoP+CdykC6PlSNf06LYaNetg6T/QRHIgK7Ja7l62PT6M/kyL9D5I+KVN3QGKguPW3mQLcuHgxUvI2tszP6ajpZO1MyVLOUzTaCw5+k+8naRRN5SsiKAACnfaeqVMibf3m75aTc6IzKCyiQfWhGwX+5UcVimVrA1oZ8dkNJteJo/0rZ0d7ayTFZ13WkZLKTLqQIOyDRS/ppr50IiBbBB0QaQN7MiNr989o1K3sftaPGBHjtPrNIkmvRFSdOMBxu0diXtoEYVYRV8obsO4oXbVUthgBtsfU/J6SEADhNQIfjdB4TjsXNLwaycEtwCtwH12mBO8gtD+dfHXVY2tEFldPnUNBdPqTcmNKqzQpOwiT6TBI7mkyxUTiaXzKGd7R4fRTs5uQVPSt7RWdkB080O0sEMymkveNIHm0Krv0ps0zeyB+sfvYcOK8X8vruZqFOXYTEMuSfo6idekdL1X5S7FyXtptWk1p6ozmks64dUm1tgLTSyZx8CT3GBFoNfWopomkMoLng+hxcKoE7nNWFFdvWAJvYhA6/XWn9ek/YXPVRoV04HnTAtreASNLElGMyH+eRpDMyg8MZPP1jWHboYNYVuSFZoGplD+I5rGI5H1exMYoiY9/7OvkdU/hOIxqGcHZRD8IlnTc3BTQMjuO0oFa9B8t5cC6Z35/668j1WaxN1w4gjIFPQkpARe8z2SXMfuDYg6doATuJAFSUEz66WN12vrQJyWTotpiefHRff46CcZvQZ5w3oTGJMTTTtwtJbVDIha9qOOwAxfjCJfm/DsYk6c81/D5RRs90H+bT7KzDScCXkmPWEWYuBCMxJl1ezugKhmiToCUzjAhrzEwen5nDivSLqSgoZHZstrsCYrx/wv9AKUED1CsF92h/02IO6w/d0EBqgTD2HAyYucOEduFQGCNWeL7mLNmUsWbyIJzPsR7JNVscoBUcX2dROIsTG7mwacvsyJz9ww/hsFWa+5UMJH6TnG85BpFv0I9soq2O0BUcH29iFwC84rqQBxyiURXOT43uVKPjqWIfB/rAUJsnJWNiDKWUKPi6zIY8FqeX05ZnefpBUUNG1TgUI7kqHOXR+NgTNN332oR9Qt4GY/3GJx3QRGqJwJu7aXq24yeWdkAr1Di1Z9Vdp+k5Woo+NRjHyNsHcd2NNk/49PFp9XnFOcqz9bn9tc0FHKSllJH5SyPToCc3xubz394pkyzF2q89lCSygoLvkW1hQ8CNiKY8gRiSzsqWQjVJ43BdhtcN7mGOO40+Fbt7h5x/YUXFXKWXEvyFksJ3gB/rcit81RlY1yiDyYZ/AuBVusOJdfihVnq8ZEYrcdBZmCHgKxtjKn0AIKoVW0hqLoQ/rI8IuVqecab7DrD3GDfcsJXOGe0a5TCzPKMHelfVk8hVLg2xuL6/mK/dnCYOIHZ58NW4TCtoFmE2gyTYcHZ9ObFEQRgk2zE1N+u8auduEai5HRLBpHjgKPn7+uVl7DTFLh8A08AocOl2BFfmdEHL51h436/Q93KdcM3nMG1Tgc7rOww7w/Libu+iV1PuO4zHbwDPkTucdHKeoKMZPV4h9L4RQ0ObKo4grep1a5rOtKA1H/9kQMv0kQHBO8lmK/HEN+cNpq6y8+O5+lzGU5LJdt4weOD/ns+PRqYy47r4pOF63l+h9JkmtysOKTVIT7FRr5+FNZdxoLtdDjRxAaFG96g1Yabg5N/ak5m2WzL3VHpp9wfkRCWlPMZavNFEaBoZvKa3Lx9kS1x2fkDwdZ/u++ghOJYclInF+zaJlgw8wfDt7JYv/iLpoIzNEPm7PD6RNCBbuHFeYVskx2Wr02BQU3BWoZD66B5I2LMfJhLPI6iN7z2P7NxaidSAIP7DQ+sCQQswulwRknKtQZ7DSLLbbbSPOgkPXg20dd8K0QdF96i5ZLwozeRtPihPg4gOZVEM89ElurzGKn2KH6yTvRIb2GNyZP0gB39ab47GWImwGiMdr21xy/Djh7p3657Zemn9nxtqVHUUFzYa3lk7a/1BWNEUhfV2hvCw3FcJ4RNmR7miRcuPT7A4qoNPFamo86eg55KBra/WAY9kcLaC7R3g94EhjC2+40Vxzm/bnZ+7QQ1jlgzRBvObrOj99wuq97PD6mKExv5NVbSGZflJcU7hE+vTsav5hYgGIc4jMeO8Bw2CR8urdMTmGKLLOFs8wgXvT0r7LccbxhNtBeq57BTbmn4X8mwgf1/A4Bm4o+oeC48QAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAwOS0wOS0xM1QxNjoxMjo1OCswMjowMBbbrGkAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMDktMDktMTNUMTY6MTI6NTgrMDI6MDBnhhTVAAAAAElFTkSuQmCC',
    mute48: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAABGdBTUEAALGOfPtRkwAACidpQ0NQaWNjAAB42p2Wd1RU1xaHz713eqHNMAJSht67wADSe5NeRWGYGWAoAw4zNLEhogIRRUSaIkhQxIDRUCRWRLEQFFSwByQIKDEYRVQsb0bWi66svPfy8vvjrG/ts/e5++y9z1oXAJKnL5eXBksBkMoT8IM8nOkRkVF07ACAAR5ggCkATFZGul+wewgQycvNhZ4hcgJfBAHweli8AnDT0DOATgf/n6RZ6XyB6JgAEZuzORksEReIOCVLkC62z4qYGpcsZhglZr4oQRHLiTlhkQ0++yyyo5jZqTy2iMU5p7NT2WLuFfG2TCFHxIiviAszuZwsEd8SsUaKMJUr4jfi2FQOMwMAFElsF3BYiSI2ETGJHxLkIuLlAOBICV9x3Fcs4GQLxJdySUvP4XMTEgV0HZYu3dTamkH35GSlcAQCwwAmK5nJZ9Nd0lLTmbwcABbv/Fky4trSRUW2NLW2tDQ0MzL9qlD/dfNvStzbRXoZ+LlnEK3/i+2v/NIaAGDMiWqz84strgqAzi0AyN37YtM4AICkqG8d17+6D008L4kCQbqNsXFWVpYRl8MyEhf0D/1Ph7+hr75nJD7uj/LQXTnxTGGKgC6uGystJU3Ip2ekM1kcuuGfh/gfB/51HgZBnHgOn8MTRYSJpozLSxC1m8fmCrhpPDqX95+a+A/D/qTFuRaJ0vgRUGOMgNR1KkB+7QcoChEg0fvFXf+jb774MCB+eeEqk4tz/+83/WfBpeIlg5vwOc4lKITOEvIzF/fEzxKgAQFIAiqQB8pAHegAQ2AGrIAtcARuwBv4gxAQCVYDFkgEqYAPskAe2AQKQTHYCfaAalAHGkEzaAXHQSc4Bc6DS+AauAFug/tgFEyAZ2AWvAYLEARhITJEgeQhFUgT0ofMIAZkD7lBvlAQFAnFQgkQDxJCedBmqBgqg6qheqgZ+h46CZ2HrkCD0F1oDJqGfofewQhMgqmwEqwFG8MM2An2gUPgVXACvAbOhQvgHXAl3AAfhTvg8/A1+DY8Cj+D5xCAEBEaoooYIgzEBfFHopB4hI+sR4qQCqQBaUW6kT7kJjKKzCBvURgUBUVHGaJsUZ6oUBQLtQa1HlWCqkYdRnWgelE3UWOoWdRHNBmtiNZH26C90BHoBHQWuhBdgW5Ct6Mvom+jJ9CvMRgMDaONscJ4YiIxSZi1mBLMPkwb5hxmEDOOmcNisfJYfawd1h/LxAqwhdgq7FHsWewQdgL7BkfEqeDMcO64KBwPl4+rwB3BncEN4SZxC3gpvCbeBu+PZ+Nz8KX4Rnw3/jp+Ar9AkCZoE+wIIYQkwiZCJaGVcJHwgPCSSCSqEa2JgUQucSOxkniMeJk4RnxLkiHpkVxI0SQhaQfpEOkc6S7pJZlM1iI7kqPIAvIOcjP5AvkR+Y0ERcJIwkuCLbFBokaiQ2JI4rkkXlJT0klytWSuZIXkCcnrkjNSeCktKRcpptR6qRqpk1IjUnPSFGlTaX/pVOkS6SPSV6SnZLAyWjJuMmyZApmDMhdkxikIRZ3iQmFRNlMaKRcpE1QMVZvqRU2iFlO/ow5QZ2VlZJfJhslmy9bInpYdpSE0LZoXLYVWSjtOG6a9W6K0xGkJZ8n2Ja1LhpbMyy2Vc5TjyBXJtcndlnsnT5d3k0+W3yXfKf9QAaWgpxCokKWwX+GiwsxS6lLbpaylRUuPL72nCCvqKQYprlU8qNivOKekrOShlK5UpXRBaUaZpuyonKRcrnxGeVqFomKvwlUpVzmr8pQuS3eip9Ar6b30WVVFVU9VoWq96oDqgpq2Wqhavlqb2kN1gjpDPV69XL1HfVZDRcNPI0+jReOeJl6ToZmouVezT3NeS1srXGurVqfWlLactpd2rnaL9gMdso6DzhqdBp1buhhdhm6y7j7dG3qwnoVeol6N3nV9WN9Sn6u/T3/QAG1gbcAzaDAYMSQZOhlmGrYYjhnRjHyN8o06jZ4baxhHGe8y7jP+aGJhkmLSaHLfVMbU2zTftNv0dzM9M5ZZjdktc7K5u/kG8y7zF8v0l3GW7V92x4Ji4Wex1aLH4oOllSXfstVy2krDKtaq1mqEQWUEMEoYl63R1s7WG6xPWb+1sbQR2By3+c3W0DbZ9ojt1HLt5ZzljcvH7dTsmHb1dqP2dPtY+wP2ow6qDkyHBofHjuqObMcmx0knXackp6NOz51NnPnO7c7zLjYu61zOuSKuHq5FrgNuMm6hbtVuj9zV3BPcW9xnPSw81nqc80R7+nju8hzxUvJieTV7zXpbea/z7vUh+QT7VPs89tXz5ft2+8F+3n67/R6s0FzBW9HpD/y9/Hf7PwzQDlgT8GMgJjAgsCbwSZBpUF5QXzAlOCb4SPDrEOeQ0pD7oTqhwtCeMMmw6LDmsPlw1/Cy8NEI44h1EdciFSK5kV1R2KiwqKaouZVuK/esnIi2iC6MHl6lvSp71ZXVCqtTVp+OkYxhxpyIRceGxx6Jfc/0ZzYw5+K84mrjZlkurL2sZ2xHdjl7mmPHKeNMxtvFl8VPJdgl7E6YTnRIrEic4bpwq7kvkjyT6pLmk/2TDyV/SglPaUvFpcamnuTJ8JJ5vWnKadlpg+n66YXpo2ts1uxZM8v34TdlQBmrMroEVNHPVL9QR7hFOJZpn1mT+SYrLOtEtnQ2L7s/Ry9ne85krnvut2tRa1lre/JU8zblja1zWle/Hloft75ng/qGgg0TGz02Ht5E2JS86ad8k/yy/Febwzd3FygVbCwY3+KxpaVQopBfOLLVdmvdNtQ27raB7ebbq7Z/LGIXXS02Ka4ofl/CKrn6jek3ld982hG/Y6DUsnT/TsxO3s7hXQ67DpdJl+WWje/2291RTi8vKn+1J2bPlYplFXV7CXuFe0crfSu7qjSqdla9r06svl3jXNNWq1i7vXZ+H3vf0H7H/a11SnXFde8OcA/cqfeo72jQaqg4iDmYefBJY1hj37eMb5ubFJqKmz4c4h0aPRx0uLfZqrn5iOKR0ha4RdgyfTT66I3vXL/rajVsrW+jtRUfA8eEx55+H/v98HGf4z0nGCdaf9D8obad0l7UAXXkdMx2JnaOdkV2DZ70PtnTbdvd/qPRj4dOqZ6qOS17uvQM4UzBmU9nc8/OnUs/N3M+4fx4T0zP/QsRF271BvYOXPS5ePmS+6ULfU59Zy/bXT51xebKyauMq53XLK919Fv0t/9k8VP7gOVAx3Wr6103rG90Dy4fPDPkMHT+puvNS7e8bl27veL24HDo8J2R6JHRO+w7U3dT7r64l3lv4f7GB+gHRQ+lHlY8UnzU8LPuz22jlqOnx1zH+h8HP74/zhp/9kvGL+8nCp6Qn1RMqkw2T5lNnZp2n77xdOXTiWfpzxZmCn+V/rX2uc7zH35z/K1/NmJ24gX/xaffS17Kvzz0atmrnrmAuUevU18vzBe9kX9z+C3jbd+78HeTC1nvse8rP+h+6P7o8/HBp9RPn/4FA5jz/EHct2gAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAACXZwQWcAAAAwAAAAMADO7oxXAAAD9UlEQVRYw+2Ya0wUVxTH/7PAwgo+l1WU9bG+RS0lqY1oNYKvpjWaGGOMxmht4tsPShpstBJbEm3QCssuIIiPTvWD/dIalagoqNFWa9oGoqAr8YVrKaKisuCy7Om5u1IeSrJXhm/OL5OdmZ17/nfOuXfuOQO837p0UxgdgnjX8VEXmA9GGLr5CGUZjSV0CEEEjN/G3U7Km4heLBWkZd913OeeiE6b8+SCt/FyCgayRIhWz6BwX8PQW2c5+GVtOVETFWRgPKL4mqKVeQOMkSNPfu1yetl8Ix3NQzw/QzctBERgw2GKi72yx10rjDeSm9RDmAYLX1c6b14ENmpefPlhT4OHTQte0UEViRjK/yidHTd69EB00qcPzjR5GtmwnwYhML2zAv5x0wuDrItr/m7ymW2mng50WuB1YA3Djm54cdfDJl28N+Oi/ZICorfB7G29j1DGwK7pZxlXlFr/2E117XhJ+VICYoYa0J3dYYTJRz+YzWO2zCs74q5rYHPteSElIALZ3dj/2PyKXRU2h81hd9hv2m/lPzztcnq8Lnr+Fmppn4RAkBiGuQvrK73UGg8H8zk9Y2Nv8ozyAhZQuP9GjCpIq/OKhn78R0875AnlSggYMAAfHdlbQ4HzmPZKCIRjMKbsO1DNzQKlmnKkBCxIyPmxiv4NmCrKlhCI4BunZ6v/kAzSAlmqkx4FjJOyZAXsaiU9pMqAEPfZZQVs6n16EDD3ySYrkKneIxkyZQWs6h2SwSorkKFWkAwZkgKJ6aqDbgeMg9KlJtoQTEu2lrkd1JZbHXKT9ki9i8yYFL3SXlj46Fz12dcU1Vypu+4tp7K3coN2SwiEoi9iMTd0vSm17w+mdFMGYzVnxatJxSerSrylVEKl7SihNIn1IJgTwiGYiLlYhtVYx6zHBmxEMraPzskuveb5i/5sgzj/XmpFC0MflojFJCRwsxmYiVn4HAuwApt679x6/kL9NfqjDVdph4SAP+/swY4ys4yFmw3FcIzGh5iK+Vil37bslxNPf6ff2pAqtej7cwqRSYQzEYxIAEwsF4fZWI7khEOHKy9Sa757h7xIaYWoYTgV4Pw5hnPQxdg4KjP9+rmmYiryUUzbNcnsgjl7jsQITOZ4rIvcufnSiYazVOgjRQOB/6sCdtUEHmMr9SlLj/9ce5pOMd9oItCS/po55LPxhbI58ad8ZwEV0FaNsuuWykxEIwFLsCnGtvvGr+6vcrWqD1qiYcJIfMLRWBu1bVFOzBqeNRpVOK2jYeFofIZFWMgO+4Az2DDtClnFF42eHI2x+JhfLLEYxGch2lbKIhrhPHCj2Tn9ta6TW1yl9814A/8Gdc3HhOZvFUpH5v8DfKpM3OEGjaEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMDktMDktMTNUMTY6MTI6NTgrMDI6MDAW26xpAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDA5LTA5LTEzVDE2OjEyOjU4KzAyOjAwZ4YU1QAAAABJRU5ErkJggg=='
  };

  /**
   * @function show
   * @desc This function displays the stream on HTML in the container with id elementId.
   <br><b>Remarks:</b><br>
   Tag with id value elementId should be pre-created.
   * @memberOf PureRTC.Stream
   * @instance
   * @param {string} elementId tag id of the element on HTML.
   * @param {doc} document object of target window.
   * @return {boolean} true If stream has been displayed already or is now displaying; otherwise false.
   * @example
   <script type="text/JavaScript">
   stream.show('stream-div', document);
   </script>
   */
  PureRTC.Stream.prototype.show = function(elementId, doc) {
    'use strict';
    if (!(this.mediaStream) || this.showing === true) return (this.showing ===
      true);
    if (typeof elementId !== 'string') elementId = 'stream_' + this.id();
    var options = arguments[1] || {};
    this.elementId = elementId;
    if (this.hasVideo()) {
      this.player = new Erizo.VideoPlayer({
        id: this.id(),
        stream: this,
        elementID: elementId,
        options: options,
        doc: doc
      });
      this.showing = true;
    } else if (this.hasAudio()) {
      this.player = new Erizo.AudioPlayer({
        id: this.id(),
        stream: this,
        elementID: elementId,
        options: options,
        doc: doc
      });
      this.showing = true;
    }
    return (this.showing === true);
  };
  /**
   * @function hide
   * @desc This function removes the stream on HTML from its container.
   * @memberOf PureRTC.Stream
   * @instance
   * @example
   <script type="text/JavaScript">
   stream.hide();
   </script>
   */
  PureRTC.Stream.prototype.hide = function() {
    'use strict';
    if (this.showing === true) {
      if (this.player && typeof this.player.destroy === 'function') {
        this.player.destroy();
        this.showing = false;
      }
    }
  };

  /*
 * PureRTC.UI provides UI functions for PureRTC SDK.
 */
  PureRTC.UI = PureRTC.UI || Object.create({});

  PureRTC.UI.attachMediaStream = function() {
    // When attachMediaStream is removed from adapter.js, we should implement by ourself.
    adapter.browserShim.attachMediaStream.apply(this, arguments);
  };



  window.Erizo = Erizo;
  window.PureRTC = PureRTC;
  window.L = L;
}(window));