room/room.js

ModuleBase.define("Room", ["SignalFactory", "RestServer", "MasterServer", "P2PServer", "User", "Error"], function(
	SignalFactory, RestServer, MasterServer, P2PServer, User, Error) {

	/**
	 * @desc  房间Room构造函数。
	 * @constructor
	 * @alias Room
	 * 
	 * @example 
	 * var room = new Room(roomId);
	 */
	var Room = function(roomId) {
		this.id = roomId;
		this.roomTocken = null; //dtls加密用到,浏览器暂时不用,服务器返回
		this.signalTocken = null; //websocket重连用到,服务器返回

		this.outgoingAddr = null;

		this.userAgent = USER_AGENT_AVD_DEFAULT;

		this.roomPassword = null; //会议密码

		//		this.roomControl = {
		//			 record: 0,               //普通录制
		//			 mix_record : 0,          //混屏录制
		//			 avc_model: 0,            //avc模式
		//			 resolution: "640*480",   //分辨率设置
		//			 liver: 0,         //直播
		//			 mixer_liver: 0    //混屏直播
		//		};
		//		
		//		this.autoRecord = false;  //自动录制
		//		this.autoAvcModel = false; //自动avc混屏


		this.pingTimeout; //ping包饱和测试超时时间,服务器返回。到达时,服务器会踢掉该用户,客户端需要通知用户重连失败,重新加会
		this.pingInterval; //ping包饱和测试问隔时间,服务器返回

		this.pingCountTime = 0; //ping包饱和测试无响应时间累计
		this.pingIntervalId = null;
		this.pingTimeoutId = null;

		this.lastPDUSeq = 0; //记录当前最近的PDU last Seq值(roomPduType.PONG除外),用于重连过程中末收到的PDU的重新接收

		this.roomInfo = null;
		this.status = RoomStatus.opening;
		this.appData = {};

		this.selfUser = null;
		this.participants = []; //参会者,包括自己

		this.muteSpeakerFlag = false; //屏蔽会议声音标识,默认false:不屏蔽

		this.masterServer = null;
		this.p2pServer = null;
		this.cmdProtobufCoded = null;
		
		//pc.addIceCandidate()必须在PC.setRemoteDescription()之后,为了保证该时序,设置下面2个全局变量
		this.setRemoteDescriptionSuccess = false; //PC.setRemoteDescription处理成功的返回状态
		this.addIcecandidateList = []; //PC.setRemoteDescription没有成功处理前,signalPduType.CANDIDATE_MSG1,返回的candidateMsg进行缓存,等setRemoteDescriptionSuccess==true后处理
		
		this.traceablePeerConnection = null;
		this.traceableP2PPeerConnection = null;

		this.eventEmitter = new EventEmitter();

		this.fingerPrint;
		this.setup;
		this.iceUfrag;
		this.icePwd;

		//视频编码格式Payload定义
		this.codecPayloadVP8 = null;
		this.codecPayloadVP9 = null;
		this.codecPayloadH264 = null;
		this.codecPayloadH265 = null;
		this.codecPayloadAV1 = null;

		//音频编码格式Payload定义
		this.codecPayloadISAC_16K = null;
		this.codecPayloadISAC_32K = null;
		this.codecPayloadOpus = null;
		this.codecPayloadG722 = null;
		this.codecPayloadPCMU = null;
		this.codecPayloadPCMA = null;
		this.codecPayloadCN_8k = null;
		this.codecPayloadCN_16k = null;
		this.codecPayloadCN_32k = null;
       
		this.versionToServer; //上报给服务器的客户端版本
		this.audioCodecPayloadMap; //上报给服务器的音频playload集
		this.videoCodecRtxMap;//上报给服务器的视频playload与Rtx对应集

		//sdp模板
		this.remoteSdpTemplate;
		this.videoMediaTemplate;
		this.audioMediaTemplate;

		//answer端设置Remote的sdp
		this.remoteDescriptionSDP;

		this.midNum = 1;
		this.midObjSet = [{
			mid: 0,
			type: 'audio',
			isReceiver: false,
			isSender: false,
			receiverInfo: {
				trackId: '',
				nodeId: '',
				deviceId: ''
			}
		}, {
			mid: 1,
			type: 'video',
			isReceiver: false,
			isSender: false,
			receiverInfo: {
				trackId: '',
				nodeId: '',
				deviceId: ''
			}
		}];


		//this.offerSessionDescription;
		this.audioMediaSDPInit;
		this.videoMediaSDPInit;
		this.mediaMid = 0;

		//this.sdp = null; //远端带ssrc的sdp,用于中转用

		this.connectionInfoCollector = null; //与mcu PeerConnection网络连接情况指示器
		this.audioLevel = null; //与audio Input Level PeerConnection

		this.connectionStatistics = null; // 媒体状态统计指示器
	
		this.toMCUReconnectionPC = null; //与服务器重连时,新产生的PeerConnection对象,中间重转换引用

		this.isExistReJoinState = false;
		
		this.reconnectMaxTimes = 3;   //信令重连最大次数,默认3次
		this.reconnectTimeout = 15000;  //信令重连最时时长,默认15秒
		
		this.currentReconnectTimes = 0; //当前信令重连次数
		this.currentConnectTimeout=  0; //当前信令重连时长
		
		this.connectionState = 0; // 0: 正常, 1: 重连中,2:重新加会中
        this.rejoinTimeout = 60000; //重新加会最时时长,默认60秒
        this.currentRejoinTimeout = 0; //当前重新加会时长
        this.rejoinAlways = false; //true:表示一直重新加会,不受rejoinTimeout限制
		
		this.rejoinInterval = 3000; //二次重新加会之间间隔的时长,默认为3秒,以免太频繁引发服务器的黑名单机制
        this.rejoinLastTimeStamp = null; //上一次重新加会触发时的时间戳
		this.rejoinTemp;
		
		this.reJoinAfterTimer = null;
		this.reJoinAfterUsers = []; //重新加会后,服务器返回的参会者用户
		
		/**
		 * 重新加会成功后,应用层需要清理白板的标识,true代表要清理。
		 * 原因是服务器版本3.1.8开始,当客户端重新加会成功后,会重新下发所有之前白板相关信息包括批注,所以应用层需要先删除掉原来的白板相关界面内容,否则会重复渲染,可能会存在错位。
		 */
		this.cleanupBoardByReJoinConnected = true; 
		

		this.pubVideos = []; //会议中已经发布的视频
		this.pubAudios = []; //会议中已经发布的音频
		this.pubScreens = []; //会议中已经发布的桌面共享


		//用于记录当前设置视频带宽值
		this.currMinBandwidth = null;
		this.currMaxBandwidth = null;

		this.currScreenMinBandwidth = 1200;
		this.currScreenMaxBandwidth = 3200;
		this.currScreenHandle = false;

		this.updateMinBandwidth = null; //修改带宽时,在updateBandwidthSdpHandle方法中做个比较值


		//用于网络状态回调
		this.videoSsrc2user = {}; //用于网络状态回调,用于定位video ssrc与所属用户对象的绑定,map(ssrc:user)集 
		this.audioSsrc2user = {}; //用于网络状态回调及语音激励,用于定位audio ssrc与所属用户对象的绑定,map(ssrc:user)集 
		this.screenSsrc2user = {}; //用于网络状态回调,用于定位screen ssrc与所属用户对象的绑定,map(ssrc:user)集 

		this.videoSsrc2deviceId = {}; //用于网络状态回调,用于定位video ssrc与设备Id的绑定,map(ssrc:deviceId)集 


		this.addSdpLocalStates = 0; //为保证操作SDP队列的顺序时,在set Local SDP中设置。
		log.debug("===DDDDDDDDDDDDD init this.addSdpLocalStates:" + this.addSdpLocalStates);

		this.boardId2userId = {}; //用于board与所属用户的绑定,map(boardId:userId)集 
		this.annotation2boardId = {}; //用于board与所属批注的绑定,map(annotation:boardId)集 
        this.style2annotation = {}; //用于批注style与所属批注的绑定,map(style:annotationId)集

		this.isDoPDUSeq = 0; //与服务器重连中,服务器返回可能丢失的PDU处理,为兼容老服务器,设置该标识,0时不处理


		this.videoSsrc = null;
		this.audioSsrc = null;

		this.updateTimer = null;

		this.boardOutputXRatio = 1; //白板输出宽度/渲染宽度
		this.boardOutputYRatio = 1; //白板输出高度/渲染高度

		this.boardInputXRatio = 1; //渲染宽度/白板输入宽度
		this.boardInputYRatio = 1; //渲染高度/白板输出高度

		this.baseMediaPalyEventStopTime = null;
		this.mediaPalyEventStopIntervalId = null;

		//原始设备ID 与上传到服务器的uuid值的mapping映射
		this.cameraId2uuid = {}; //用于cameraId与uuid的绑定,map(cameraId:uuid)集 
		this.microphoneId2uuid = {}; //用于microphoneId与uuid的绑定,map(microphoneId:uuid)集
		this.speakerId2uuid = {}; //用于speakerId与uuid的绑定,map(speakerId:uuid)集
		
		this.userid2boardsBy812 = {}; //用于用户心跳超时掉线(如异常退会)812场景下,缓存掉线用户的board对象,map(userId:boards)集  //by roymond 2024/11//19
		

		this.mp4ImporterUserAudioStateIntervalId = null;
		this.mp4ImporterUserAudioRtpPacketTemp = 0;
		this.mp4ImporterUserAudioRtpPacketReal = 0;
		this.mp4ImporterUserAudioRate = 20; //毫秒
		
		this.websocketConnectTime = 0; //针对有客户环境重连时,websocket会同一时该返回多条webSocket.onopen,做2秒内的去重.该值记录初始时间
        this.roomJoinState = RoomStateEnum.DISCONNECTED; // 房间加入的状态


        this.isReceivedAddBoardId = ""; // 用户兼容服务器下发白板消息add和share消息时序相反的情况
        this.isReceivedRemoveBoardId = ""; // 用户兼容服务器关闭和移除白板消息close和remove消息时序相反的情况
		
		
		//应用层客户调用接口自行设置的扬声器设备ID
		this.clientSetSpeakerDeviceId = null;
		
		//应用层客户调用接口自行设置的扬声器音量值(0-1),该值只是应用层的设置与物理设备的具体音量值无关
		this.clientSetSpeakerVolume = null;
  

	};


	/**
	 * attributes 解析
	 * @param {int32} attributes
	 * @ignore
	 */
	//	Room.prototype.attributesAnalysis =  function (attributes){
	//		    if(attributes){
	//		    	     var ROOM_TYPE_HOST = 1 << 1;  //主持人加会以后才能加会
	//			     var ROOM_TYPE_NETWORK_ACC = 1 << 2;   //加速模式
	//			     var ROOM_TYPE_AREA_CONTROL = 1 << 3;  //场控模式
	//			     var ROOM_TYPE_AUTO_RECORD = 1 << 5;   //自动录制房间内所有音频和视频  
	//			     var ROOM_TYPE_MIX_RECORD = 1 << 6;    //混屏录制
	//			     var ROOM_TYPE_AVC_MODEL = 1 << 7;     //avc模式
	//			     var ROOM_TYPE_RECORD = 1 << 8;   //普通录制
	//			     var ROOM_TYPE_LIVER = 1 << 9;    //直播
	//			     var ROOM_TYPE_MIX_LIVER = 1 << 10;  //混屏直播
	//			     var ROOM_TYPE_AUTO_AVC_MODEL = 1 << 11;  //自动avc混屏
	//		    	     
	//		    	     var attributesPriviligeMap = attributes;   //(attributes).toString(2);
	//		    	     
	//		    	     var isHasByTypeRecord = room.isHasPrivilige(ROOM_TYPE_RECORD,attributesPriviligeMap);
	//		    	     var isHasByTypeMixRecord = room.isHasPrivilige(ROOM_TYPE_MIX_RECORD,attributesPriviligeMap);
	//		    	    
	//		    	     var isHasByTypeLive = room.isHasPrivilige(ROOM_TYPE_LIVER,attributesPriviligeMap);
	//		    	     var isHasByTypeMixLive = room.isHasPrivilige(ROOM_TYPE_MIX_LIVER,attributesPriviligeMap);
	//		    	     
	//		    	     var isHasByTypeAvcModel = room.isHasPrivilige(ROOM_TYPE_AVC_MODEL,attributesPriviligeMap);
	//		    	     
	//		    	     var isHasByTypeAutoRecord = room.isHasPrivilige(ROOM_TYPE_AUTO_RECORD,attributesPriviligeMap);
	//		    	     var isHasByTypeAutoAvcModel = room.isHasPrivilige(ROOM_TYPE_AUTO_AVC_MODEL,attributesPriviligeMap);
	//		    	     
	//		    	     
	//		         this.roomControl = {
	//			          record: isHasByTypeRecord,               
	//					  mix_record: isHasByTypeMixRecord,         
	//					  avc_model: isHasByTypeAvcModel,  
	//					  liver: isHasByTypeLive,         
	//					  mixer_liver: isHasByTypeMixLive    
	//		         };
	//		
	//			     this.autoRecord = isHasByTypeAutoRecord;  
	//			     this.autoAvcModel = isHasByTypeAutoAvcModel; 
	//		    }
	//	}



	/**
	 * @desc 设置当前登陆用户的userAgent属性,默认为USER_AGENT_AVD_DEFAULT
	 * @param {String} userAgent
	 * 
	 * @example
	 * room.setUserAgent(USER_AGENT_AVD_MEDIA_MIXER_ADMIN);
	 */
	Room.prototype.setUserAgent = function(userAgent) {
		this.userAgent = userAgent;
	}
	
	
	/**
	 * @desc 设置信令重连最大次数,默认3次
	 * @param {Int} count -重连最大次数
	 * 
	 * @example
	 * room.setReconnectMaxTimes(5);
	 */
	Room.prototype.setReconnectMaxTimes = function(count) {
		log.info("===room.setReconnectMaxTimes(),count:"+ count);
		this.reconnectMaxTimes = count;
	}
	
	/**
	 * @desc 设置信令重连最时时长,单元毫秒,默认15000毫秒
	 * @param {Int} timeout -重连最时时长
	 * 
	 * @example
	 * room.setReconnectTimeout(20000);
	 */
	Room.prototype.setReconnectTimeout = function(timeout) {
		log.info("===room.setReconnectTimeout(),timeout:"+ timeout);
		this.reconnectTimeout = timeout;
	}
	
	Room.prototype.MaxReconnectTimeout = function(pingInterval){
		log.info("===room.MaxReconnectTimeout(),pingInterval:"+ pingInterval);
		if(this.reconnectTimeout < pingInterval * 3){
			this.reconnectTimeout = pingInterval * 3;
			log.info("===room.MaxReconnectTimeout(),reconnectTimeout:"+ this.reconnectTimeout);
		}
	}
	
	/**
	 * @desc 高效加会,与room.join()接口的区别是不再需要另外调用avdEngine.init()
	 * @async
	 * @param {String} serverURI - MCU服务器地址
	 * @param {String} accessToken - 访问令牌
	 * @param {String} userId - 用户的Id
	 * @param {String} userName - 用户名
	 * @param {String} userData - 用户扩展字段
	 * @param {String} password - 会议密码
	 * 
	 * @example 
     * room.joinEfficient('avd.nice2meet.cn:9610','ZDgyNGU1MGU0ZGY2MTJlZWY3NjkwMjQ5NDY0YWE3MDkxZGJjNmRiZg==','user01','ds','','').then(function(){}).otherwise(function(error){});
	 */
	Room.prototype.joinEfficient = function(serverURI, accessToken, userId, userName, userData, password) {
		  var deferred = when.defer();
		  var self = this;
		  avdEngineHandle.init(serverURI,accessToken,false).then(function(){
			   self.join(userId, userName, userData, password).then(function(){
				    deferred.resolve();
			   }).otherwise(function(error){
				    self.roomJoinState = RoomStateEnum.DISCONNECTED;
			        deferred.reject(error);
		       });
		  }).otherwise(function(error){
			   self.roomJoinState = RoomStateEnum.DISCONNECTED;
			   deferred.reject(error);
		  });
		  return deferred.promise;
	}
	
	
	//获取服务器地址
	Room.prototype.serverinfoHandle = function(){
		log.info("===room.serverinfoHandle()...");
		var self = this;
		
		var deferred = when.defer();
		var restServer = null;
		if(avdEngineHandle.applyToElectron){
		    restServer = new RestServer(avdEngineHandle.electronProtocol);
		}else{
			if(avdEngineHandle.restFullProtocol){
				restServer = new RestServer(avdEngineHandle.restFullProtocol);
			}else{
				 restServer = new RestServer();
			}
		   
		}
		restServer.getServerinfo(avdEngineHandle.restServerInfo.mcuServerUrl, avdEngineHandle.accessToken, avdEngineHandle.isUseCascade, self.id).then(function() {
			
			//notQueryGetMcu为false时,且为集群版本,根据roomId&accessToken重新获取服务器地址
			if(!avdEngineHandle.restServerInfo.notQueryGetMcu  && avdEngineHandle.restServerInfo.isCluster){
				log.info("===room.serverinfoHandle(),restServer.getMcu()");
				restServer.getMcu(self.id, avdEngineHandle.accessToken, avdEngineHandle.clientIsUseCascade).then(function() {
					deferred.resolve();
				}).otherwise(function(error) {
			        self.roomJoinState = RoomStateEnum.DISCONNECTED;
					deferred.reject(error);
				});
			} else {
				deferred.resolve();
			}
		}).otherwise(function(error) {
			self.roomJoinState = RoomStateEnum.DISCONNECTED;
			log.error("===room.serverinfoHandle(),restServer.getServerinfo()失败!error:"+ error.message);
			
			deferred.reject(error);
		});
		return deferred.promise;
	}
	
	//websocket处理
	Room.prototype.websocketConnect = function(deferred) {
		var self = this;
		if(!deferred){
		   deferred = when.defer();
		}
		if(self.masterServer && self.masterServer.signalingChannel){
			self.masterServer.signalingChannel.close();
		}
		var signalFactory = new SignalFactory();
		var websocket = signalFactory.getDataChannel(SignalType.websocket, self);
		websocket.connect().then(function(signal){
			self.currentReconnectTimes = 0;
			self.currentConnectTimeout=  0;
			deferred.resolve(signal);
		}).otherwise(function(error) {
			websocket.close();
			if(self.currentConnectTimeout === 0 ){
				self.currentConnectTimeout = new Date();
			}
			var timeout = new Date() - self.currentConnectTimeout;
			
			if(self.currentReconnectTimes > 0){
				log.error("===room.websocketConnect(),Websocket第" + self.currentReconnectTimes + "次重连,累计耗时"+timeout+"毫秒");
			}
			if (self.currentReconnectTimes < self.reconnectMaxTimes || timeout  < self.reconnectTimeout ) {
				self.currentReconnectTimes ++;
				self.websocketConnect(deferred);
		    }else{
                self.roomJoinState = RoomStateEnum.DISCONNECTED;
				deferred.reject();
			}
		})
		return deferred.promise;
	}
	


	/**
	 * @desc 加会
	 * @async
	 * @param {String} userId - 用户的Id
	 * @param {String} userName - 用户名
	 * @param {String} userData - 用户扩展字段
	 * @param {String} password - 会议密码
	 * 
	 * @example
     * room.join('user01','ds','','').then(function(){}).otherwise(function(error){});
	 */
	Room.prototype.join = function(userId, userName, userData, password) {
		var deferred = when.defer();
		
		var self = this;
      
        if (self.roomJoinState == RoomStateEnum.CONNECTING || self.roomJoinState == RoomStateEnum.CONNECTED) {
            log.debug('===room.join(), return by curr room state:', self.roomJoinState )
            return;
        }
        self.roomJoinState = RoomStateEnum.CONNECTING;

		if (userId == null || userId == '') {
			var error = new Error(ErrorConstant.userId_required);
			log.error("===room.join() error, code:" + error.code +", message:"+ error.message);
			self.roomJoinState = RoomStateEnum.DISCONNECTED;
			deferred.reject(error);
			return deferred.promise;
		}else  if(typeof userId !== 'string'){
			var error = new Error(ErrorConstant.exporter_parameter_type_error);
			error.message = error.message+"[ UserId required string type]";
			log.error("===room.join() error, code:" + error.code +", message:"+ error.message);
			self.roomJoinState = RoomStateEnum.DISCONNECTED;
			deferred.reject(error);
			return deferred.promise;
		}

        if (typeof(userData) == "undefined") {
        	userData = "";
        }
		
		if (typeof(password) == "undefined") {
			password = "";
		}
		
		self.serverinfoHandle().then(function(){		
			//终端日志上报开启
			if(avdEngineHandle.loggerReportConfig.open_logger && (parseInt(avdEngineHandle.loggerReportConfig.open_logger) == 1)){
				 avdEngineHandle.loggerReport.start();
				 for(var i = 0; i < avdEngineHandle.logReportWait.length; i++) {
					var logReportWait = avdEngineHandle.logReportWait[i];
					avdEngineHandle.loggerReport.info(logReportWait);
				}
			} 
			
			log.info("===room.join(), JS SDK 版本号:"+ avdEngineHandle.getVersion());
			avdEngineHandle.loggerReport.info("JS SDK 版本号:"+ avdEngineHandle.getVersion());
			  
			log.info("===room.join(), 浏览器内核名称:"+ avdEngineHandle.browserName+ ", 内核版本:"+avdEngineHandle.fullVersion+", 操作系统名称:"+avdEngineHandle.browserOsName + " " + avdEngineHandle.detect.osVersion+", checkBrowserSupport:"+avdEngineHandle.checkBrowserSupport()+", GetUserMedia:"+avdEngineHandle.detect.getUserMediaSupport+", RTCPeerConnection:"+avdEngineHandle.detect.RTCPeerConnectionSupport+", WebSocket:"+avdEngineHandle.detect.WebSocketSupport);
			avdEngineHandle.loggerReport.info("浏览器内核名称:"+ avdEngineHandle.browserName+ ", 内核版本:"+avdEngineHandle.fullVersion+", 操作系统名称:"+avdEngineHandle.browserOsName);
				
			// Android Firefox不支持VP8, 自动调整成H264
			if(avdEngineHandle.isFirefox  &&  avdEngineHandle.browserOsName == 'Android' && avdEngineHandle.videoCoding == VideoCodingType.VP8){
				log.info("===room.join(), Android Firefox不支持VP8, 自动调整成H264");
				avdEngineHandle.videoCoding = VideoCodingType.H264; 
			}
				
			log.info("===room.join(), 视频编解码:"+ avdEngineHandle.videoCoding);
			avdEngineHandle.loggerReport.info("视频编解码:"+ avdEngineHandle.videoCoding);
				
			log.info("===room.join(), 音频编解码:"+ avdEngineHandle.audioCoding);
			avdEngineHandle.loggerReport.info("音频编解码:"+ avdEngineHandle.audioCoding);
			
			if (avdEngineHandle.restServerInfo.isCluster) {
			   log.info("===room.join(), 服务器是集群版本");
			   avdEngineHandle.loggerReport.info("room.join(),服务器是集群版本");
			}else{
			   log.info("===room.join(), 服务器是单机版本");
			   avdEngineHandle.loggerReport.info("room.join(),服务器是单机版本");
			}
			
			if(avdEngineHandle.restServerInfo.masterVersion){
				log.info("===room.join(), 服务器端 masterVersion:"+ avdEngineHandle.restServerInfo.masterVersion);
				avdEngineHandle.loggerReport.info(" 服务器端masterVersion:"+ avdEngineHandle.restServerInfo.masterVersion);
			}
			
			if(avdEngineHandle.restServerInfo.masterPackageVersion){
				log.info("===room.join(), 服务器端 masterPackageVersion:"+ avdEngineHandle.restServerInfo.masterPackageVersion);
				avdEngineHandle.loggerReport.info(" 服务器端masterPackageVersion:"+ avdEngineHandle.restServerInfo.masterPackageVersion);
			}
			
			if(avdEngineHandle.restServerInfo.boxVersion){
				log.info("===room.join(), 服务器端 boxVersion:"+ avdEngineHandle.restServerInfo.boxVersion);
				avdEngineHandle.loggerReport.info(" 服务器端boxVersion:"+ avdEngineHandle.restServerInfo.boxVersion);
			}
		
			if(avdEngineHandle.restServerInfo.boxPackageVersion){
				log.info("===room.join(), 服务器端 boxPackageVersion:"+ avdEngineHandle.restServerInfo.boxPackageVersion);
				avdEngineHandle.loggerReport.info(" 服务器端boxPackageVersion:"+ avdEngineHandle.restServerInfo.boxPackageVersion);
			}
			
			self.doJoin(userId, userName, userData, password, deferred);
		}).otherwise(function(error) {
			if(error) {
				log.error("===room.join(), error, code:" + error.code +", message:"+ error.message);
				deferred.reject(error);
			} else {
				var err = new Error(ErrorConstant.avdEngine_init_failed);
				log.error("===room.join(), error, code:" + error.code +", message:"+ error.message);
				deferred.reject(err);
			}
		});
		
		return deferred.promise;
	};


	/**
	 * @desc OEM加会
	 * @async
	 * @param {Object} userId - 用户的Id
	 * @param {Object} userName - 用户名
	 * @param {Object} userData - 用户扩展字段
	 * @param {String} password - 会议密码
	 */
	Room.prototype.joinWithOEM = function(userId, userName, userData, password, oemAccessToken) {
		var deferred = when.defer();

        var self = this;

		//OEM 默认都是集群版本,根据roomId & oemAccessToken 重新获取服务器地址
		var restServer = new RestServer();
		restServer.getMcuWithOEM(self.id, oemAccessToken).then(function() {
			self.doJoin(userId, userName, userData, password, deferred);
		}).otherwise(function(error) {
			deferred.reject(error);
		});

		return deferred.promise;
	};


   

	Room.prototype.doJoin = function(userId, userName, userData, password, deferred) {
		var self = this;
		var detect = avdEngineHandle.getBrowserDetect();
		self.roomPassword = password;
		self.selfUser = new User(userId, userName, userData, self);
		self.selfUser.userAgent = self.userAgent;
		
		// 判断是否为 网页录制 
		if(detect.isWebRecord) {
			self.selfUser.userAgent = USER_AGENT_AVD_WEB_RECORD;
			self.userAgent = USER_AGENT_AVD_WEB_RECORD;
		}
	
		self.websocketConnect().then(function(websocket){
			self.openReqSend(websocket).then(function(room) {
			    self.roomJoinState = RoomStateEnum.CONNECTED;
				deferred.resolve(room);
			}).otherwise(function(error) {
                log.error("===room.doJoin(),openReqSend faild, reason: " + JSON.stringify(error));
			    self.roomJoinState = RoomStateEnum.DISCONNECTED;
				deferred.reject(error);	 
			});	
			
		}).otherwise(function() {
			log.error("===room.doJoin(),登录加会超时:" + ErrorConstant.join_room_timeout.code);
			avdEngineHandle.loggerReport.debug("room.doJoin(),登录加会超时.");
			
			self.roomJoinState = RoomStateEnum.DISCONNECTED;
			var error = new Error(ErrorConstant.join_room_timeout);
			deferred.reject(error);
		});
	}
	
	
	    /**
		 * @desc 与服务器重连处理
		 * @ignore
		 */
		Room.prototype.reconnectionHandle = function() {
			var self = this;
			self.connectionState = 1;
			self.websocketConnect().then(function(websocket){
				log.info("===WebSocket,room.reconnectionHandle(),websocket重连成功");
				self.masterServer.pingInit();
				
				self.masterServer.signalingChannel = websocket;
				log.info("===WebSocket,room.reconnectionHandle(),to masterServer.openReqReconnectionSend()");
				self.openReqReconnectionSend().then(function(){
					 log.debug("===WebSocket,room.reconnectionHandle(),OPEN_RQP Send, acception OPEN_REP result=0");
				}).otherwise(function(error) {
					self.roomJoinState = RoomStateEnum.DISCONNECTED;
				    log.info("===WebSocket,room.reconnectionHandle(),OPEN_RQP 发送失败,进行重新加会处理。 reason: " + JSON.stringify(error));
					
					log.debug("===Room.reconnectionHandle(),callback RoomCallback.connection_status,connectionStatus:" + ConnectionStatus.connectFailed);
					self.eventEmitter.emit(RoomCallback.connection_status, ConnectionStatus.connectFailed);
				    
				    if(self.roomJoinState != RoomStateEnum.RECONNECTING){
				    	log.info("===to room.reJoin() by room.reconnectionHandle(),openReqReconnectionSend() failed");
				    	self.reJoin();
				    }
				}); 
			}).otherwise(function() {
				log.info("===WebSocket,room.reconnectionHandle(),websocket重连超出预设次数及超时,进行重新加会处理。");
				
				log.debug("===Room.reconnectionHandle(),callback RoomCallback.connection_status,connectionStatus:" + ConnectionStatus.connectFailed);
		        self.eventEmitter.emit(RoomCallback.connection_status, ConnectionStatus.connectFailed);
				
				if(self.roomJoinState != RoomStateEnum.RECONNECTING){
					log.info("===to room.reJoin() by room.reconnectionHandle(), websocketConnect() failed");
					self.reJoin();
				}
			});
		};



	/**
	 * @desc 重新加会
	 * @ignore
	 */
    Room.prototype.reJoin = function() {
		 var self = this;
		 log.info("===room.reJoin(), current roomJoinState: " + self.roomJoinState);
		 
		 // reJoin会被多次调用,如果房间是正在重新加会中,忽略
		 if(self.roomJoinState == RoomStateEnum.RECONNECTING){
		     log.debug('===room.rejoin(), roomJoinState is reconnecting to return');
		     return;
		 }
		 
		 if(self.currentRejoinTimeout == 0){
		    self.currentRejoinTimeout = new Date();
		 }
		 
		 var rejoinDurationFromFirst = new Date() - self.currentRejoinTimeout;
		 
		 log.info("===room.reJoin(), duration since first rejoin: " + rejoinDurationFromFirst + ",rejoinTimeout: " + self.rejoinTimeout);
		 
		 if(self.rejoinAlways == false){
			 if(rejoinDurationFromFirst > self.rejoinTimeout){
				 log.info("===room.reJoin(), rejoinAlways false, rejoinTimeout, rejoinDurationFromFirst: " + rejoinDurationFromFirst);
				 
				 log.debug("===Room.reJoin(), rejoinAlways false, callback RoomCallback.connection_status,connectionStatus:" + ConnectionStatus.reJoinRoomTimeOut);
				 self.eventEmitter.emit(RoomCallback.connection_status, ConnectionStatus.reJoinRoomTimeOut);
				
				 return;
			 }else{
				 if(!self.rejoinLastTimeStamp){
					  log.info("===room.reJoin(), rejoinAlways false, self.rejoinLastTimeStamp is null, to room.reJoinHandle()");
					  self.rejoinLastTimeStamp = new Date();
					  self.reJoinHandle();
				 }else{
					  var differenceDate = new Date() - self.rejoinLastTimeStamp;
					  if(differenceDate < self.rejoinInterval){
						   var ss = self.rejoinInterval - differenceDate; 
						   
						   clearTimeout(self.rejoinTemp);
						   self.rejoinTemp = setTimeout(function(){
							   self.rejoinLastTimeStamp = new Date();
							   log.info("===room.reJoin(), rejoinAlways false, rejoinInterval Not established, by setTimeout  to room.reJoinHandle()");
							   self.reJoinHandle();
						   },ss);
					  }else{
						  log.info("===room.reJoin(), rejoinAlways false, rejoinInterval established, to room.reJoinHandle()");
						  self.rejoinLastTimeStamp = new Date();
						  self.reJoinHandle();
					  }
				 }
				 
			 }
		 }else{
			 if(!self.rejoinLastTimeStamp){
				  log.info("===room.reJoin(), rejoinAlways true, self.rejoinLastTimeStamp is null, to room.reJoinHandle()");
				  self.rejoinLastTimeStamp = new Date();
				  self.reJoinHandle();
			 }else{
				  var differenceDate = new Date() - self.rejoinLastTimeStamp;
				  if(differenceDate < self.rejoinInterval){
					   var ss = self.rejoinInterval - differenceDate; 
					   
					   clearTimeout(self.rejoinTemp);
					   self.rejoinTemp = setTimeout(function(){
						   self.rejoinLastTimeStamp = new Date();
						   log.info("===room.reJoin(), rejoinAlways true, rejoinInterval Not established, by setTimeout  to room.reJoinHandle()");
						   self.reJoinHandle();
					   },ss);
				  }else{
					  log.info("===room.reJoin(), rejoinAlways true, rejoinInterval established, to room.reJoinHandle()");
					  self.rejoinLastTimeStamp = new Date();
					  self.reJoinHandle();
				  }
			 }
		 }
	}

		

	/**
	 * @desc 重新加会
	 * @ignore
	 */
	Room.prototype.reJoinHandle = function() {
		var self = this;
		log.info("===room.reJoinHandle()...");

		if (self.connectionInfoCollector) {
		    self.connectionInfoCollector.stop(); //停止收集网络情况
		}
		
		if (self.connectionStatistics) {
		    self.connectionStatistics.stop(); //停止上报媒体信息
		}
		
		self.connectionState = 2;
        self.roomJoinState = RoomStateEnum.RECONNECTING;
		self.isDoPDUSeq = 0;

		// 重新加会时原参会者列表移除除自己外的所有用户
		if(self && self.participants && self.participants.length > 0){
			self.participants.forEach(function(participant){
				if (participant.id != self.selfUser.id) {
					arrayUtil.objectSplice(self.participants, participant.id);
				}
			})
		}

		//重新加会时,用户信息初始化
		if (self.selfUser.reJoinPubVideoIds && self.selfUser.reJoinPubVideoIds.length > 0) {

		} else {
			self.selfUser.reJoinPubVideoIds = [];
			self.selfUser.reJoinPubVideoElements = [];

			var videos = self.pubVideos;
			if (videos && videos.length > 0) {
				log.debug("===room.reJoinHandle(),重新加会初始化videos之前先把自己pub的视频unpub掉,room.pubVideos.length:" + videos
					.length);
				for (var i = 0; i < videos.length; i++) {
					if (videos[i].ownerId == self.selfUser.id) {
						var video = self.selfUser.getVideo(videos[i].id);
						self.selfUser.reJoinPubVideoIds.push(videos[i].id);
						self.selfUser.reJoinPubVideoElements.push(video.element);
						if (video) {
							log.debug("===room.reJoinHandle(),重新加会初始化videos之前先把自己pub的视频unpub掉,selfUser.video.id:" + videos[i].id);
                            
                            // 遍历自己的stream对象,将视频track都停掉,防止重新加会之前打开的视频自动打开后再关闭时,摄像头指示灯依然是开启状态
                            var videoTracks = self.selfUser.stream.getVideoTracks();
                            if(videoTracks.length > 0){
                                videoTracks.forEach(function(videoTrack) {
                                    videoTrack.stop();
                                })
                            }
						
							log.info("===video.unpublish()不会被执行,因为当前是在重新加会的流程中,这里不需要执行。");
							//video.unpublish();
						}
					}
				}
			}
		}

		if (self.selfUser.reJoinPubAudioId && self.selfUser.reJoinPubAudioId != "") {

		} else {
			self.selfUser.reJoinPubAudioId = "";
			var audios = self.pubAudios;
			if (audios && audios.length > 0) {
				log.debug("===room.reJoinHandle(),重新加会初始化audio之前先把自己pub的音频unpub掉,room.pubAudios.length:" + audios
					.length);
				for (var i = 0; i < audios.length; i++) {
					if (audios[i].ownerId == self.selfUser.id) {
						var audio = self.selfUser.getAudio(audios[i].id);
						self.selfUser.reJoinPubAudioId = audios[i].id;
						if (audio) {
							if(audio.status === StreamStatus.muted){
								log.debug("===room.reJoinHandle(),后重新加会初始化audio之前先把自己pub的音频unpub掉,当前audio的状态为Mute");
								self.selfUser.reJoinPubAudioButMute = true;
							}
							log.debug("===room.reJoinHandle(),后重新加会初始化audio之前先把自己pub的音频unpub掉,selfUser.audio.id:" + audios[i].id);

                            // 遍历自己的stream对象,将音频track都停掉,防止重新加会之前打开的音频自动打开后再关闭时,麦克风指示灯依然是开启状态
                            var audioTracks = self.selfUser.stream.getAudioTracks();
                            if(audioTracks.length > 0){
                                audioTracks.forEach(function(videoTrack) {
                                    videoTrack.stop();
                                })
                            }
                      
					        log.info("===audio.closeMicrophone()不会被执行,因为当前是在重新加会的流程中,这里不需要执行。");
							//audio.closeMicrophone();
						}
					}
				}
			}
		}


		//402后,重新加会,加会前如已经打开屏幕共享,暂不处理 TODO
		var screens = self.pubScreens;
		if (screens  && screens.length > 0) {
			log.debug("===room.reJoinHandle(),重新加会初始化screen之前先把自己pub的桌面共享unpub掉,room.pubScreens.length:" + screens.length);
			for (var i = 0; i < screens.length; i++) {
				if (screens[i].ownerId == self.selfUser.id) {
					var screen = self.selfUser.getScreen(screens[i].id);
					if (screen) {
						log.debug("===room.reJoinHandle(),重新加会初始化screen之前先把自己pub的桌面共享unpub掉,selfUser.screen.id:" + screens[i].id);
						
						//因为打开桌面共享指定不了应用的ID及屏幕ID, 这里记录桌面共享流,用于重新加会成功后再次发布该流。
						self.selfUser.reJoinPubScreenStram = self.selfUser.screenStream;	
							
						// 遍历自己的screenStream对象,将流track都停掉,防止重新加会之前打开的流自动打开后再关闭时,弹出的共享窗还在
						// var tracks = self.selfUser.screenStream.getTracks();
						// if(tracks.length > 0){
						//     tracks.forEach(function(track) {
						//         track.stop();
						//     })
						// }	
						
						log.info("===screen.unpublish()不会被执行,因为当前是在重新加会的流程中,这里不需要执行。");
						//screen.unpublish();
					}
				}
			}
		}

		clearTimeout(self.reJoinAfterTimer);
		self.reJoinAfterUsers = [];

		if (self.selfUser) {
			if (self.selfUser.toMCUPC) {
				self.selfUser.toMCUPC.msgQueue = [];
				self.selfUser.toMCUPC.close();
				self.selfUser.toMCUPC = null;
			}
		}

		log.debug("===room.reJoinHandle(),重新加会之前sub的视频subVideoIds:" + JSON.stringify(self.selfUser.subVideoIds));
		self.selfUser.reJoinSubVideoIds = self.selfUser.subVideoIds;

		log.debug("===room.reJoinHandle(),重新加会之前sub的音频subAudioIds:" + JSON.stringify(self.selfUser.subAudioIds));
		self.selfUser.reJoinSubAudioIds = self.selfUser.subAudioIds;

		log.debug("===room.reJoinHandle(),重新加会之前sub的桌面共享subScreenIds:" + JSON.stringify(self.selfUser
			.subScreenIds));
		self.selfUser.reJoinSubScreenIds = self.selfUser.subScreenIds;

		self.selfUser.init();

        var detect = avdEngineHandle.getBrowserDetect();
        self.selfUser.userAgent = self.userAgent;
		// 判断是否为 网页录制 
		if(detect.isWebRecord) {
			self.selfUser.userAgent = USER_AGENT_AVD_WEB_RECORD;
			self.userAgent = USER_AGENT_AVD_WEB_RECORD;
		}


		var deferred = when.defer();

		if (self.toMCUReconnectionPC) {
			self.traceablePeerConnection.close(self.toMCUReconnectionPC);
			self.toMCUReconnectionPC.msgQueue = [];
			self.toMCUReconnectionPC = null;
		}

		

		self.serverinfoHandle().then(function(){
			 self.websocketConnect().then(function(websocket){
				 self.openReqSend(websocket).then(function(newRoom) {
						self.pingTimeout = newRoom.pingTimeout;
						self.pingInterval = newRoom.pingInterval;
						self.signalTocken = newRoom.signalTocken;
						self.masterServer = newRoom.masterServer;
				 
						self.pubVideos = []; //会议中已经发布的视频
						self.pubAudios = []; //会议中已经发布的音频
						self.pubScreens = []; //会议中已经发布的桌面共享
						
						self.midNum = 1;
						self.midObjSet = [{
							mid: 0,
							type: 'audio',
							isReceiver: false,
							isSender: false,
							receiverInfo: {
								trackId: '',
								nodeId: '',
								deviceId: ''
							}
						}, {
							mid: 1,
							type: 'video',
							isReceiver: false,
							isSender: false,
							receiverInfo: {
								trackId: '',
								nodeId: '',
								deviceId: ''
							}
						}];
						 self.roomJoinState = RoomStateEnum.CONNECTED;
						deferred.resolve(self);
				 }).otherwise(function(error) {
					 self.roomJoinState = RoomStateEnum.DISCONNECTED;
                     log.debug("===room.reJoinHandle(),openReqSend faild, reason: " + JSON.stringify(error));
                     self.reJoin();
					deferred.reject(error);
				 });  
			 }).otherwise(function(error) {
				self.roomJoinState = RoomStateEnum.DISCONNECTED;
                log.debug("===room.reJoinHandle(),websocketConnect faild, reason: " + JSON.stringify(error));
                self.reJoin();
				deferred.reject(error); 
			 });
		}).otherwise(function(error) {
            log.debug("===room.reJoinHandle(),serverinfoHandle faild, reason: " + JSON.stringify(error));
			self.roomJoinState = RoomStateEnum.DISCONNECTED;
			if(error) {
				log.error("===room.reJoinHandle(), error code:" + error.code +", error message:"+ error.message);
			} else {
				var err = new Error(ErrorConstant.avdEngine_init_failed);
				error = err;
				log.error("===room.reJoinHandle(), error code:" + error.code +", error message:"+ error.message);
			} 
            self.reJoin();
			deferred.reject(error);
		})
		return deferred.promise;
	};

	/**
	 * @desc 多次后重新加会超时后,开启持续永久重新加会
	 */
	Room.prototype.continuousReJoin = function() {
		var self = this;
		if(self.rejoinAlways){
			return;
		}
		log.info("===room.continuousReJoin(),重新加会超时后,开启持续永久重新加会");
        self.rejoinAlways = true;
		avdEngineHandle.setRemoteDescriptionSuccess = false;

		this.addSdpLocalStates = 0;
		log.debug("===DDDDDDD0000000000,room.continuousReJoin(),this.addSdpLocalStates:", this
			.addSdpLocalStates);

		this.addIcecandidateList = [];
		this.reJoinAfterTimer = null;
		this.reJoinAfterUsers = []; //重新加会后,服务器返回的参会者用户
		
        self.reJoin();
	};




	Room.prototype.getMcuByReJoin = function() {
		var self = this;
		var deferred = when.defer();
		var restServer = new RestServer();
		restServer.getMcu(self.id, avdEngineHandle.accessToken).then(function() {
			log.debug("===room.getMcuByReJoin(),  getMcu success");
			self.reJoin();
		}).otherwise(function(error) {
			log.error("===room.getMcuByReJoin(), getMcu error,code:" + error.code + ",message:" +
				error.message);
			self.reJoin();
		});

		return deferred.promise;
	};


	/**
	 * @desc 获取房间号
	 * @returns {String} roomId - 房间号
	 */
	Room.prototype.getRoomId = function() {
		return this.roomInfo.room_id;
	};


	/**
	 * @desc 获取会议主题
	 * @returns {String} topic - 会议主题
	 */
	Room.prototype.getRoomTopic = function() {
		return this.roomInfo.topic;
	};


	/**
	 * @desc 获取会议建创者Id
	 * @returns {String} ownerId - 会议建创者Id
	 */
	Room.prototype.getOwnerId = function() {
		return this.roomInfo.owner_id;
	};


	/**
	 * @desc 判断当前会议模式是否为MCU模式
	 * @returns {Boolean} isMcuRoomMode - 是否为MCU模式. true: mcu模式;false:P2P模式
	 */
	Room.prototype.isMcu = function() {
		var IsMcuRoomMode = true;
		if (this.roomInfo == null) {
			return IsMcuRoomMode;
		} else {
			var roomMode = this.roomInfo.room_mode;
			if (roomMode == 3 || roomMode == 4) { //TODO: 暂时用1,2,3,4, 后面会用enum
				IsMcuRoomMode = false;
			}
			return IsMcuRoomMode;
		}
	};


	/**
	 * @desc 会议类型判断
	 * @param {Enum} roomTypEnum - 传入会议类型枚举
	 * @returns {Boolean}  true: 是枚举对应的会议类型,false:非枚举对应的会议类型
	 */
	Room.prototype.getRoomType = function(roomTypEnum) {
		//room.roomInfo.room_type是按位计算的,具体参考enum.js中的roomTypePrivilige

		var isHas = false;
		if (this.roomInfo && this.roomInfo.room_type) {
			var roomType = this.roomInfo.room_type;
			var roomTypePriviligeMap = (roomType).toString(2);
			if (roomTypEnum == RoomTypeEnum.join_host_first) {
				isHas = this.isHasPrivilige(roomTypePrivilige.join_host_first, roomTypePriviligeMap);
			} else if (roomTypEnum == RoomTypeEnum.net_acc) {
				isHas = this.isHasPrivilige(roomTypePrivilige.net_acc, roomTypePriviligeMap);
			} else if (roomTypEnum == RoomTypeEnum.host_control) {
				isHas = this.isHasPrivilige(roomTypePrivilige.host_control, roomTypePriviligeMap);
			} else if (roomTypEnum == RoomTypeEnum.free_speech) {
				isHas = !this.isHasPrivilige(roomTypePrivilige.host_control, roomTypePriviligeMap);
			}
		}
		return isHas;
	};



	/**
	 * @desc 判断当前会议类型有效 
	 * currPrivilige:当前会议类型
	 * priviliges: 会议类型集
	 * 返回:boolean
	 * 例子:isHasPrivilige(roomTypePrivilige.join_host_first,roomTypePriviligeMap);
	 * @ignore
	 */
	Room.prototype.isHasPrivilige = function(currPrivilige, priviliges) {
		var isHas = false;
		if ((currPrivilige & priviliges) > 0) {
			isHas = true;
		}
		return isHas;
	};



	/**
	 * @desc 获取允许的最大音频路数
	 * @returns {Int} maxAudio - 最大音频路数
	 */
	Room.prototype.getMaxAudio = function() {
		return this.roomInfo.max_audio;
	}

	/**
	 * @desc 获取允许的最大视频路数
	 * @returns {Int} maxVideo - 最大视频路数
	 */
	Room.prototype.getMaxVideo = function() {
		return this.roomInfo.max_video;
	}

	/**
	 * @desc 获取允许的最大参加会议人数
	 * @returns {Int} maxAttendee  - 最大参加会议人数
	 */
	Room.prototype.getMaxAttendee = function() {
		return this.roomInfo.attendee_max;
	}


	/**
	 * @desc 获取视频路数的剩余可用数。
	 *    <p>逻辑:允许的最大视频路数 - 已发布的视频路数</p>
	 *    <p>注:主要用于界面显示用,具体打开的限制提示时,会采用服务器端的返回号来处理</p>
	 * @returns {Int} usableVideo  - 剩余可用视频路数
	 */
	Room.prototype.getUsableVideo = function() {
		var num = this.getMaxVideo() - this.pubVideos.length;
		if (num < 0) {
			num = 0;
		}
		return num;
	}


	/**
	 *@desc 获取音频路数的剩余可用数。
	 *  <p>逻辑:允许的最大音频路数 - 已发布的音频路数</p>
	 *  <p>注:主要用于界面显示用,具体打开的限制提示时,会采用服务器端的返回号来处理</p>
	 * @returns {Int} usableAudio  - 剩余可用音频路数
	 */
	Room.prototype.getUsableAudio = function() {
		var num = this.getMaxAudio() - this.pubAudios.length;
		if (num < 0) {
			num = 0;
		}
		return num;
	}


	/**
	 * @desc 修改会议房间状态
	 * @param {Enum} roomStatus - 房间状态枚举
	 */
	Room.prototype.updateRoomStatus = function(roomStatus) {
		this.status = roomStatus;
	};

	/*
	 * @desc 获取会议房间状态
	 * @returns {Enum} roomStatus - 会议房间状态
	 */
	Room.prototype.getRoomStatus = function() {
		return this.status;
	};



	//	/**
	//	 * @desc 返回会议主持人
	//	 */
	//	Room.prototype.getHost = function() {
	//		for(var i = 0; i != this.participants.length; ++i) {
	//			var user = this.participants[i];
	//			if(user.role == 1) {
	//				return user;
	//			}
	//		}
	//	};


	/**
	 * @desc 返回自己对象
	 * @returns {user} selfUser - 自己对象
	 */
	Room.prototype.getSelfUser = function() {
		return this.selfUser;
	};


	/**
	 * @desc 返回自己对象的Id
	 * @returns {String} userId - 自己对象的Id
	 */
	Room.prototype.getSelfUserId = function() {
		return this.selfUser.id;
	};


	/**
	 * @desc 返回参会者数组
	 * @returns {Object} participants -参会者数组
	 */
	Room.prototype.getParticipants = function() {
		return this.participants;
	};
	
	
	/**
	 * @desc 返回参会者人数
	 * @returns {int} participantsNumber -参会者人数
	 */
	Room.prototype.getParticipantsNumber = function() {
		var participantsNumber = 0;
		if(this.participants){
			participantsNumber = this.participants.length;
		}
		return participantsNumber;
	};


	/**
	 * @desc 通过用户ID获取用户对象
	 * @param {String} userId - 用户的Id
	 * @returns {Object} user - 用户对象
	 */
	Room.prototype.getUser = function(userId) {
		var retUser;
		for (var i = 0; i < this.participants.length; i++) {
			var user = this.participants[i];
			if (user.id == userId) {
				retUser = user;
				break;
			}
		}
		return retUser;
	};

	/**
	 * @desc 通过用户ID获取用户对象
	 * @param {String} userId - 用户的Id
	 * @returns {Object} user - 用户对象
	 */
	Room.prototype.getUserByUserIdAndNodeId = function(userId, nodeId) {
		var retUser;
		for (var i = 0; i < this.participants.length; i++) {
			var user = this.participants[i];
			if (user.id == userId && user.nodeId == nodeId) {
				retUser = user;
				break;
			}
		}
		return retUser;
	};




	/**
	 * @desc 通过用户ID,判断用户对象是否存在。
	 * @param {String} userId - 用户的Id
	 * @returns{boolean} isExist - true:存在; false: 不存在
	 */
	Room.prototype.hasUser = function(userId) {
		var isExist = false;
		for (var i = 0; i < this.participants.length; i++) {
			var user = this.participants[i];
			if (user.id == userId) {
				isExist = true;
				break;
			}
		}
		return isExist;
	};


	/**
	 * @desc 通过用户ID,nodeId,判断用户对象是否存在。
	 * @param {String} userId - 用户的Id
	 * @param {String} nodeId - 用户的nodeId
	 * @returns{boolean} isExist - true:存在; false: 不存在
	 *  @ignore
	 */
	Room.prototype.hasUserByUserIdAndNodeId = function(userId, nodeId) {
		var isExist = false;
		for (var i = 0; i < this.participants.length; i++) {
			var user = this.participants[i];
			if (user.id == userId && user.nodeId == nodeId) {
				isExist = true;
				break;
			}
		}
		return isExist;
	};
	
	
	/**
	 * @desc 获取房间内当前白板数
	 * @param {boardStatusEnum}  status - 白板状态值, 可以不填,不填时包括boardStatusEnum.open和boardStatusEnum.close的
	 * @returns {int} boardNumber -白板数
	 * 
	 * @example 
	 *  room.getBoardNumber(boardStatusEnum.open);
	 */
	Room.prototype.getBoardNumber = function(status) {
		var boardNumber = 0;
		if(this.participants){
			for (var i = 0; i < this.participants.length; i++) {
				var user = this.participants[i];
				if(user && user.boards){
					for (var j = 0; j < user.boards.length; j++) {
						var board = user.boards[j];
						if( status === null || status === undefined){
							boardNumber ++;
						}else{
							if(status == board.status){
								boardNumber ++;
							}
						}
					}
				}
			}	
		}
		return boardNumber;
	};



	// /**
	//  * @desc 本地网络变化,ICE重新启动.如:从WIFI切换到有线
	//  */
	// Room.prototype.localICERestart = function() {
	// 	var self = this;
	// 	log.info('===room.localICERestart(),PeerConnection createOffer ICE Restart');
	// 	var offerOptions = {
	// 		iceRestart: true
	// 	};
	// 	self.selfUser.toMCUPC.createOffer(
	// 		offerOptions
	// 	).then(function(offer) {
	// 		offer.sdp = updateSDPIcePwd(offer.sdp, self.icePwd);
	// 		log.debug("===room.localICERestart(),LocalDescription:" + offer.sdp);

	// 		return self.selfUser.toMCUPC.setLocalDescription(offer);
	// 	});
	// };



	// /**
	//  * @desc 远端candidate更新,ICE重新启动
	//  * @param {Object} iceRestartMsg - 远端下发的ice restart candidate
	//  */
	// Room.prototype.remoteICERestart = function(iceRestartMsg) {
	// 	if (iceRestartMsg.candidate) {
	// 		log.info("===room.remoteICERestart(),candidate:" + iceRestartMsg.candidate);
	// 		this.masterServer.traceablePeerConnection.doAddIceCandidate(iceRestartMsg);
	// 		this.localICERestart();
	// 	}
	// };
	
	
	/**
	 * @desc 本客户端做为Answer端,服务器做为Offer端的场景
	 * @ignore
	 */
	Room.prototype.iCERestart = function() {
		 var self = this;
		 
		 var offer = { type: 'offer', sdp: self.remoteDescriptionSDP};
		 log.debug('===room.iCERestart(), calling pc.setRemoteDescription(), remoteDescriptionSDP:', offer.sdp);
		 
		 self.selfUser.toMCUPC.setRemoteDescription(offer).then(function() {
		 	log.info("===room.iCERestart(), doSetRemoteDescription(), set remote sessionDescription success");
			
			var answerOptions = {
				iceRestart: true
			};
			self.selfUser.toMCUPC.createAnswer(answerOptions).then(function(sessionDescription) {
				
				var sdp = sessionDescription.sdp;
				var iceUfragRandom = Math.uuid(4);
				var icePwdRandom = Math.uuid(24);
				sdp = updateSDPIceUfrag(sdp, iceUfragRandom);
				sdp = updateSDPIcePwd(sdp,icePwdRandom);
				log.debug('===room.iCERestart(), calling pc.setLocalDescription(), LocalDescriptionSDP:', sdp);
				
				var msgobj = {};
				msgobj.sdp = sdp;
				msgobj.type = "answer";
				var desc = new RTCSessionDescription(msgobj);
				
				self.selfUser.toMCUPC.setLocalDescription(desc).then(function() {
					log.info("===room.iCERestart(), doSetLocalDescription(), set local sessionDescription success");
				});
			});
		});
	};


	/**
	 * @desc 获取房间中所有已发布的摄像头视频数量
	 * @returns{int} publishedCameraCount - 所有已发布的摄像头视频数量
	 */
	Room.prototype.getPublishedCameraCount = function() {
		return this.pubVideos.length;
	};


	/**
	 * @desc 获取房间中所有已发布的摄像头视频对象集
	 * @returns{Object} publishedCamera - 所有已发布的摄像头视频对象集
	 */
	Room.prototype.getPublishedCamera = function() {
		return this.pubVideos;
	};



	/**
	 * @desc 获取房间中所有已发发布的共享屏幕窗口视频数量
	 * @returns{int} publishedScreenCount - 所有已发发布的共享屏幕窗口视频数量
	 */
	Room.prototype.getPublishedScreenCount = function() {
		return this.pubScreens.length;
	};


	/**
	 * @desc 获取房间中所有已发布的共享屏幕窗口视频对象集
	 * @returns{Object} publishedScreen - 所有已发布的共享屏幕窗口视频对象集
	 */
	Room.prototype.getPublishedScreen = function() {
		return this.pubScreens;
	};


	/**
	 * @desc 新增用户
	 * @ignore
	 */
	Room.prototype.addUser = function(user) {
		if (!this.hasUser(user.id)) {
			this.participants.push(user);
		}
	};

	/**
	 * @desc 新增用户
	 * @ignore
	 */
	Room.prototype.addUserByUserIdAndUserId = function(user) {
		if (!this.hasUserByUserIdAndNodeId(user.id, user.nodeId)) {
			this.participants.push(user);
		}
	};



	/**
	 * @desc  删除用户
	 * @ignore
	 */
	Room.prototype.removeUser = function(userId) {
		if (this.participants && this.participants.length > 0) {
			arrayUtil.objectSplice(this.participants, userId);
		}
	};

	/**
	 * @desc 删除用户
	 * @ignore
	 */
	Room.prototype.removeUserByUserIdAndNodeId = function(userId, nodeId) {
		if (this.participants && this.participants.length > 0) {
			arrayUtil.userIdAndNodeIdSplice(this.participants, userId, nodeId);
		}
	};


	/**
	 * @desc 通过用户nodeId获得用户对象
	 * @param {String} nodeId - 用户的nodeId
	 * @ignore
	 */
	Room.prototype.getUserByNodeId = function(nodeId) {
		var retUser;
		for (var i = 0; i < this.participants.length; i++) {
			var user = this.participants[i];
			if (user.nodeId == nodeId) {
				retUser = user;
				break;
			}
		}
		return retUser;
	};


	/**
	 * @desc 通过用户userId获得用户nodeid
	 * @param {String} userId - 用户的Id
	 * @ignore
	 */
	Room.prototype.getNodeId = function(userId) {
		var nodeId = null;
		var retUser = this.getUser(userId);
		if (retUser) {
			nodeId = retUser.nodeId;
		}
		return nodeId;
	}


	/**
	 * @desc 通过用户nodeId获得用户userid
	 * @param {String} nodeId - 用户的nodeId
	 * @ignore
	 */
	Room.prototype.getUserId = function(nodeId) {
		var userId = null;
		var retUser = this.getUserByNodeId(nodeId);
		if (retUser) {
			userId = retUser.id;
		}
		return userId;
	}



	/**
	 * @desc 发送公聊消息
	 * @async
	 * @param {String} message - 消息内容
	 * 
	 * @example 
     * room.sendPublicMessage("hello").then(function(){}).otherwise(function(error){});
	 */
	Room.prototype.sendPublicMessage = function(message) {
		var deferred = when.defer();
		this.masterServer.sendMessage(message);
		deferred.resolve();
		return deferred.promise;
	};


	/**
	 * @desc 发送私聊消息
	 * @async
	 * @param {String} message - 消息内容
	 * @param {String} userId -  消息接受用户ID
	 *
	 * @example 
     * room.sendPrivateMessage("hello",'user03').then(function(){}).otherwise(function(error){});
	 */
	Room.prototype.sendPrivateMessage = function(message, userId) {
		var deferred = when.defer();
		this.masterServer.sendMessage(message, userId);
		deferred.resolve();
		return deferred.promise;
	};



	/**
	 * @desc  发送公有透明通道
	 * @
	 * @param {Object} dataBinary - ArrayBinary类型的内容
	 *      <p>注: ArrayBinary : 二进制数据的原始缓冲区,该缓冲区用于存储各种类型化数组的数据</p>
	 * 
	 * @example
	 * var dataArrayBuffer = typeConversionUtil.String2ArrayBuffer("This is an important piece of news.");
	 * room.sendPublicData(dataArrayBuffer).then(dataSendSuccess).otherwise(dataSendError);
	 * function dataSendSuccess(dataMsg) {
	 * }
	 */
	Room.prototype.sendPublicData = function(dataBinary) {
		var deferred = when.defer();
		this.masterServer.sendData(dataBinary).then(function(){
			deferred.resolve();
		}).otherwise(function(error){
			log.error("===room.sendPublicData() faild, error:" + JSON.stringify(ErrorConstant.data_oversize))
			deferred.reject(error);
		});
		return deferred.promise;
	};



	/**
	 * @desc  发送私有透明通道
	 * @async
	 * @param {Object} dataBinary - ArrayBinary类型的内容
	 *       <p>注: ArrayBinary : 二进制数据的原始缓冲区,该缓冲区用于存储各种类型化数组的数据</p>
	 * @param {String} userId - 内容接受用户ID
	 * 
	 * @example
	 * var dataArrayBuffer = typeConversionUtil.String2ArrayBuffer("This is an important piece of news.");
	 * room.sendPrivateData(dataArrayBuffer,'15').then(dataSendSuccess).otherwise(dataSendError);
	 * function dataSendSuccess(dataMsg) {
	 * }
	 */
	Room.prototype.sendPrivateData = function(dataBinary, userId) {
		var deferred = when.defer();
		this.masterServer.sendData(dataBinary, userId).then(function(){
			deferred.resolve();
		}).otherwise(function(error){
			log.error("===room.sendPrivateData() faild, error:" + JSON.stringify(ErrorConstant.data_oversize))
			deferred.reject(error);
		});
		return deferred.promise;
	};
	
	
	/**
	 * @desc  发送私有二进制数据。与room.sendPrivateData()的区别,本方法只需要知道对方的userId,内部实现时不用知道对方的nodeId,如一些特殊useAgent的用户,是取不到nodeId的。
	 * @async
	 * @param {Object} dataBinary  -ArrayBinary类型的内容
	 *      <p>注: ArrayBinary : 二进制数据的原始缓冲区,该缓冲区用于存储各种类型化数组的数据</p>
	 * @param {String} userId - 内容接受用户ID
	 *
	 * @example
	 * var dataArrayBuffer = typeConversionUtil.String2ArrayBuffer("This is an important piece of news.");
	 * room.sendPrivateDataToUser(dataArrayBuffer,'15').then(dataSendSuccess).otherwise(dataSendError);
	 * function dataSendSuccess(dataMsg) {
	 * }
	 */
	Room.prototype.sendPrivateDataToUser = function(dataBinary, userId) {
		var deferred = when.defer();
		this.masterServer.sendPrivateDataToUser(dataBinary, userId).then(function(){
		    deferred.resolve();
		}).otherwise(function(error){
		    log.error("===room.sendPrivateDataToUser() faild, error:" + JSON.stringify(ErrorConstant.data_oversize))
		    deferred.reject(error);
		});
		
		return deferred.promise;
	}
	


	/**
	 * @desc 更新房间应用扩展字段
	 * @param {String} key - key值
	 * @param {String} value - value值
	 */
	Room.prototype.updateAppData = function(key, value) {
		this.appData[key] = value;
		this.masterServer.roomDataInfo(key, value);
	};


	/**
	 * @desc 获得房间应用扩展字段信息
	 * @param {String} key - key值
	 * @returns {String} value-  value值
	 */
	Room.prototype.getAppData = function(key) {
		if (typeof(key) == 'undefined' || key == null || key == '') {
			return this.appData;
		} else {
			return this.appData[key];
		}
	};


	/**
	 * @desc  设置摄像头分辨率或帧率
	 * @async
	 * @param {String} cameraId - 摄摄像头ID
	 * @param {String} resolution -分辨率枚举值,如
	 * @param {int} frameRate -帧率
	 * @param {Object} resolutionSetType- 分辨率设置类型,可不填写,null时默认为ResolutionSetType.exact
	 * @param {Number} aspectRatio- 分辨率宽高比(1.7777777778, 1.3333333333),可不填写(null,0时浏览器自动处理),16:9或4:3时会开启强制
	 */
	Room.prototype.setCameraResolutionOrFrameRate = function(cameraId, resolution, frameRate, resolutionSetType,
		aspectRatio) {
		log.info("===room.setCameraResolutionOrFrameRate(),cameraId:" + cameraId + ",resolution:" +
			resolution + ",frameRate:" + frameRate + ",resolutionSetType:" + resolutionSetType +
			",aspectRatio:" + aspectRatio);
		var deferred = when.defer();
		var video = this.selfUser.getVideo(cameraId);
		if (video) {
			if (typeof(frameRate) == 'string') {
				frameRate = Number(frameRate);
			}

			if (typeof(aspectRatio) == 'string') {
				aspectRatio = Number(aspectRatio);
			}

			if (video.resolution != Resolution[resolution] || video.setType != resolutionSetType || video
				.frameRate != frameRate || video.aspectRatio != aspectRatio) {
				video.setResolution(resolution, resolutionSetType);
				video.setFrameRate(frameRate);
				video.setAspectRatio(aspectRatio);
				if (video.track) {
					video.applyConstraints();
				}
				deferred.resolve();
			}
		} else {
			var error = new Error(ErrorConstant.cameraId_does_not_exist);
			deferred.reject(error);
		}

		return deferred.promise;
	}

	/**
	 * @desc  设置摄像头分辨率或帧率
	 * @async
	 * @param {String} cameraId -摄摄像头ID
	 * @param {int} resolutionWidth -分辨率宽
	 * @param {int} resolutionHeight -分辨率高
	 * @param {int} frameRate -帧率
	 * @param {Object} resolutionSetType- 分辨率设置类型,可不填写,null时默认为ResolutionSetType.exact
	 * @param {Number} aspectRatio- 分辨率宽高比(1.7777777778, 1.3333333333),可不填写(null,0时浏览器自动处理),16:9或4:3时会开启强制
	 */
	Room.prototype.setCameraResolutionWHOrFrameRate = function(cameraId, resolutionWidth, resolutionHeight,
		frameRate, resolutionSetType, aspectRatio) {
		log.info("===room.setCameraResolutionWHOrFrameRate(),cameraId:" + cameraId + ",resolutionWidth:" +
			resolutionWidth + ",resolutionHeight:" + resolutionHeight + ",frameRate:" + frameRate +
			",resolutionSetType:" + resolutionSetType + ",aspectRatio:" + aspectRatio);
		var deferred = when.defer();
		var video = this.selfUser.getVideo(cameraId);
		if (video) {
			if (typeof(frameRate) == 'string') {
				frameRate = Number(frameRate);
			}

			if (typeof(aspectRatio) == 'string') {
				aspectRatio = Number(aspectRatio);
			}

			if (typeof(video.resolution) == 'undefined') {
				video.setResolutionWH(resolutionWidth, resolutionHeight, resolutionSetType);
				video.setFrameRate(frameRate);
				deferred.resolve();
			} else {
				if (video.resolution.width != resolutionWidth || video.resolution.height !=
					resolutionHeight || video.setType != resolutionSetType || video.frameRate !=
					frameRate || video.aspectRatio != aspectRatio) {
					video.setResolutionWH(resolutionWidth, resolutionHeight, resolutionSetType);
					video.setFrameRate(frameRate);
					video.setAspectRatio(aspectRatio);
					if (video.track) {
						video.applyConstraints();
					}
					deferred.resolve();
				}
			}
		} else {
			var error = new Error(ErrorConstant.cameraId_does_not_exist);
			deferred.reject(error);
		}

		return deferred.promise;
	}


	/**
	 * @desc 屏蔽会议声音
	 */
	Room.prototype.muteSpeaker = function() {
		log.info("===room.muteSpeaker()");
		
		var self = this;
		self.muteSpeakerFlag = true;
		self.participants.forEach(function(user) {
			if (user.id != self.selfUser.id) {
				var track = user.audio.track;
				if (track) {
					track.enabled = false;
				}
			}
		});
	};


	/**
	 * @desc 取消屏蔽会议声音
	 */
	Room.prototype.unmuteSpeaker = function() {
		log.info("===room.unmuteSpeaker()");
		var self = this;
		self.muteSpeakerFlag = false;
		self.participants.forEach(function(user) {
			if (user.id != self.selfUser.id) {
				var track = user.audio.track;
				if (track) {
					track.enabled = true;
				}
			}
		});
	};


	/**
	 * @desc 获取屏蔽会议声音的当前状态
	 * @returns {Boolean} - 屏蔽会议声音的当前状态
	 */
	Room.prototype.ismuteSpeaker = function() {
		return this.muteSpeakerFlag;
	};



	Room.prototype.hasObject = function(objs, curObj) {
		var isExist = false;
		for (var i = 0; i < objs.length; i++) {
			var obj = objs[i];
			if (obj.id == curObj.id && obj.ownerId == curObj.ownerId) {
				isExist = true;
				break;
			}
		}
		return isExist;
	};




	/**
	 * @desc 踢人
	 * @param {int} reason - 踢人原因,填写808,其它值也都会重置成808
	 * @param {String} userId - 被踢用户id
	 */
	Room.prototype.kickoffUser = function(reason, userId) {
		reason = 808;//
		log.info("===room.kickoffUser(),reason:" + reason + ",userId:" + userId);
		this.masterServer.userIndiction(reason, userId);
	};


	/**
	 * @desc 退会
	 * @async
	 * @param {int} reason - 退会原因,可以填写 1
	 * @param {boolean} kickoffOpt - 退会类型: true:被踢退会,false:主动退会,可以不填
	 * 
	 * @example 
	 * room.leave(1).then(function(){}).otherwise(function(error){});
	 * 
	 */
	Room.prototype.leave = function(reason, kickoffOpt) {
		log.info("===room.leave(),reason:" + reason + ",kickoffOpt:" + kickoffOpt);

		var self = this;
		var deferred = when.defer();

		self.mp4ImporterUserAudioRtpPacketTemp = 0;
		self.mp4ImporterUserAudioRtpPacketReal = 0;
		if (self.mp4ImporterUserAudioStateIntervalId) {
			clearInterval(self.mp4ImporterUserAudioStateIntervalId);
			self.mp4ImporterUserAudioStateIntervalId = null;
		}


		//用户离开清场
		self.userClearInfo();

		setTimeout(function() {
			self.masterServer.userMsg(reason, kickoffOpt);
			
			self.roomClearInfo(true);
   
			deferred.resolve();
		}, 1000);

		return deferred.promise;
	};



	/**
	 * @desc 关闭房间
	 * @param {String} userId - 关闭房间的操作用户ID
	 */
	Room.prototype.closeRoom = function(userId) {
		log.info("===room.closeRoom(),userId:", userId);
		
        var self = this;
		
		self.mp4ImporterUserAudioRtpPacketTemp = 0;
		self.mp4ImporterUserAudioRtpPacketReal = 0;
		if (self.mp4ImporterUserAudioStateIntervalId) {
			clearInterval(self.mp4ImporterUserAudioStateIntervalId);
			self.mp4ImporterUserAudioStateIntervalId = null;
		}

		//用户离开清场
		self.userClearInfo();

		setTimeout(function() {
			self.masterServer.closeRoom(userId);
			self.roomClearInfo();
		}, 1000);
	};



	/**
	 * @desc 用户离开清场
	 * @ignore
	 */
	Room.prototype.userClearInfo = function() {
		var self = this;
		//1.已pub的视频先unpub
		var videos = this.selfUser.videos;
		if (videos  && videos.length > 0) {
			videos.forEach(function(video){
				if (video.status == StreamStatus.published) {
                    					
					//当本地离开会议或关闭会议时,video.unpublish()有可能进入unpub_roomresourcemsg_rep  timeout case, 这时视频可能就没有关掉。
					if(video.track){
						video.track.stop();
					}
					
					video.unpreview();
					video.unpublish();
				}
			})
		}

		//2. 已pub的音频先unpub
		var audio = this.selfUser.audio;
		if (audio && audio.status == StreamStatus.published) {
            			
			//当本地离开会议或关闭会议时,audio.closeMicrophone()有可能进入unpub_roomresourcemsg_rep  timeout case, 这时音频可能就没有关掉。
			if(audio.track){
				audio.track.stop();
			}
            
			audio.closeMicrophone();
		}

		//3. 已pub的桌面共享先unpub
		var screen = this.selfUser.screen;
		if (screen && screen.status == StreamStatus.published) {
            			
			//当本地离开会议或关闭会议时,screen.unpublish()有可能进入unpub_roomresourcemsg_rep  timeout case, 这时桌面共享的共享条可能就没有关掉。
			if(screen.track){
				screen.track.stop();
			}
			
			screen.unpublish();
		}
		
		//1.已打开的白板先删除
		var boards = this.selfUser.boards;
		if (boards  && boards.length > 0) {
			boards.forEach(function(board){
				self.selfUser.removeBoardById(board.id);
			})
		}	
		
	};
	

    /**
	 * @desc 离开清场
	 * @ignore
	 */
	Room.prototype.roomClearInfo = function(isLeaveRoom) {
		var self  = this;
		if (self) {
			if (self.pingIntervalId) {
				clearInterval(self.pingIntervalId);
				self.pingIntervalId = null;
			}
			
			if (self.pingTimeoutId) {
				clearInterval(self.pingTimeoutId);
				self.pingTimeoutId = null;
			}
			
			if (self.selfUser.changePublishVideoQualityInterval) {
				clearInterval(self.selfUser.changePublishVideoQualityInterval);
				self.selfUser.changePublishVideoQualityInterval = null;
			}
			
			if (self.selfUser) {
				if (self.selfUser.toMCUPC) {
					self.selfUser.toMCUPC.msgQueue = [];
					self.selfUser.toMCUPC.close();
					self.selfUser.toMCUPC = null;
				}
			}
			
			avdEngineHandle.setRemoteDescriptionSuccess = false; //PC.setRemoteDescription处理成功的返回状态
			this.addIcecandidateList = [];
			
			
            
			if (self.connectionInfoCollector) {
                self.connectionInfoCollector.stop(); //停止收集网络情况
			}
            
			if (self.connectionStatistics) {
                self.connectionStatistics.stop(); //停止上报媒体信息
			}
          
            if(isLeaveRoom){
                self.roomJoinState = RoomStateEnum.DISCONNECTED;
                self.masterServer.signalingChannel.close();
                self = null;
            }

            avdEngineHandle.rooms = [];
		}
	}

	/**
     * @desc 房间级别的回调
     * @param {RoomCallback} type - 回调枚举标识
     * @param {Object} callback -回调方法名,可以自定义,建议保持默认值
     * @example 
     * 
     * room.addCallback(RoomCallback.connection_status, onConnectionStatus);
     * room.addCallback(RoomCallback.connection_indicator,onConnectionIndicator);
	 * room.addCallback(RoomCallback.room_status_notify, onRoomStatusNotify);
	 * 
	 * room.addCallback(RoomCallback.user_join_notify, onUserJoinNotify);
	 * room.addCallback(RoomCallback.user_leave_notify, onUserLeaveNotify);
	 * room.addCallback(RoomCallback.leave_indication, onLeaveIndication);
	 * room.addCallback(RoomCallback.close_room_result, onCloseRoomResult);
	 * room.addCallback(RoomCallback.close_room_notify, onCloseRoomNotify);
	
	 * room.addCallback(RoomCallback.app_data_notify, onAppdataNotify);
     * room.addCallback(RoomCallback.user_data_notify, onUserDataNotify);
     * room.addCallback(RoomCallback.user_name_notify, onUserNameNotify);
     * 
     * room.addCallback(RoomCallback.public_message, onPublicMessage);
	 * room.addCallback(RoomCallback.private_message, onPrivateMessage);
	 * room.addCallback(RoomCallback.public_data, onPublicData);
	 * room.addCallback(RoomCallback.private_data, onPrivateData);
	 * 
	 * room.addCallback(RoomCallback.screen_sharing_ended, onScreenSharingEnded);
	 * 
	 * room.addCallback(RoomCallback.outgoing_invite_status_notify, onOutgoingInviteStatusNotify);
	 
	 * room.addCallback(RoomCallback.room_control_change_notify, onRoomControlChangeNotify);
	 * 
	 * 
	 * 与服务器连接状态回调
	 * param:status - 连接状态
	 * 
	 * function onConnectionStatus(status) {
	 *    if (status == ConnectionStatus.connecting) {
	 *	       //TODO 与服务器重连中
	 *    } else if (status == ConnectionStatus.connected) {
	 *	       //TODO 与服务器重连成功
	 *    } else if (status == ConnectionStatus.connectFailed) {
	 *	      //TODO 与服务器重连失败,需要重新登陆
	 *   }
     *  }
     * 
     * 
	 * 与服务器连接指示器回调
	 * param: connectionIndicator - 连接指示器
	 * 
     * function onConnectionIndicator(connectionIndicator){
     *            if(connectionIndicator){
     * 	              var roomConnection = connectionIndicator.getRoomConnection();
     *               
     *                //房间级别
     *                roomConnection.localAddress   本地地址
     *                roomConnection.localPort      本地端口
	 *                roomConnection.localProtocol  本地协议
     *                roomConnection.remoteAddress  远端地址
     *                roomConnection.remotePort     远端端口
     *                roomConnection.remoteProtocol  远端协议
	 * 
     *                roomConnection.downloadBitrate  下载流量
     *                roomConnection.uploadBitrate    上传流量
	 * 
     *                roomConnection.downloadPacket  下载数据包
     *                roomConnection.uploadPacket    上传数据包
	 
	 *                roomConnection.downloadPacketLost  下载数据包丢包数
	 *                roomConnection.uploadPacketLost   上传数据包丢包数
	 *
	 *                roomConnection.downloadPacketLostRate  下载数据包丢包率
	 *                roomConnection.uploadPacketLostRate   上传数据包丢包率
	 *
	 *                roomConnection.currentRoundTripTime   当前的往返时间(RTT)
	 *                roomConnection.totalRoundTripTime     累计的往返时间(RTT)
     * 
     *                //某路音频相关
     *                var audioConnection = connectionIndicator.getAudioConnection(userId);
     *                audioConnection.downloadBitrate   音频下载流量
     *                audioConnection.uploadBitrate     音频上传流量
	 *
	 *                audioConnection.downloadPacket   音频下载数据包
	 *                audioConnection.uploadPacket     音频上传数据包
	 *
	 *                audioConnection.packetsLost     音频数据包丢包数
	 *                audioConnection.packetLostRate  音频数据包丢包率
	 *
	 *                audioConnection.ssrc
	 *                audioConnection.trackId
	 *                audioConnection.payloadType
	 *                audioConnection.mimeType
	 *                audioConnection.clockRate
     * 
     *                //某路视频相关
     *                videoConnection = connectionIndicator.getVideoConnection(userId, cameraId);
     *                videoConnection.downloadBitrate   视频下载流量
     *                videoConnection.uploadBitrate     视频上传流量
	 *
	 *                videoConnection.downloadPacket    视频下载数据包
     *                videoConnection.uploadPacket     视频传数据包
	 *
	 *                videoConnection.packetsLost     视频数据包丢包数
	 *                videoConnection.packetLostRate  视频数据包丢包率
	 *
	 *                videoConnection.resolution     分辨率
     *                videoConnection.frameRate      帧率
	 *
	 *                videoConnection.qpSum           媒体数据包的总量化值(qpSum
	 *                videoConnection.framesEncoded   已编码帧的数量 
	 *                videoConnection.framesDecoded   已解码帧的数量
	 *                videoConnection.perFrameQP      每个视频帧的平均量化值(perFrameQP)
	 *
	 *                videoConnection.ssrc
	 *                videoConnection.trackId
	 *                videoConnection.payloadType
	 *                videoConnection.mimeType
	 *                videoConnection.clockRate
	 * 
	 * *              //某路桌面共享相关
     *                screenConnection = connectionIndicator.getScreenConnection(userId);
     *                screenConnection.downloadBitrate   桌面共享下载流量
     *                screenConnection.uploadBitrate     桌面共享上传流量
	 *
	 *                screenConnection.downloadPacket    桌面共享下载数据包
	 *                screenConnection.uploadPacket     桌面共享传数据包
	 *
	 *                screenConnection.packetsLost      桌面共享数据包丢包数
	 *                screenConnection.packetLostRate   桌面共享数据包丢包率
	 *
     *                screenConnection.resolution        分辨率
     *                screenConnection.frameRate         帧率
	 *                screenConnection.ssrc
	 *                screenConnection.trackId
	 *                screenConnection.payloadType
	 *                screenConnection.mimeType
	 *                screenConnection.clockRate
     *           }
     * }
     * 
     *
     * 参会者加会回调
     * param :users - 参会者数组
     * 
     * function onUserJoinNotify(users) {
	 *    
     * }
     * 
     * 参会者退会回调
     * param:opt - 退会类型
     * param:reason  - 退会原因
     * param:user - 退会用户
     *
     * function onUserLeaveNotify(opt,reason,user) {
	 *
     * }
     * 
     * 
     *
     * 被踢出会议室
     * param:reason - 被踢原因
     * param:userId - 踢人的操作者
     * 
     * function onLeaveIndication(reason,userId) {
     *
     * }
     * 
     * 
     * 
     * 房间应用扩展字段回调
     * param {Object} key - key值
     * param {Object} value - value值
     * function onAppdataNotify(key, value) {
     * 
     * }
     * 
     *用户扩展内容更新回调
     *param:userData - 用户扩展内容
     *param:userId - 用户id
     * 
     * function onUserDataNotify(userData, userId ) {
     * 
     * } 
     * 
     * 
     *
     * 用户修改名字回调
     * param {String} userName - 用户新名字
     * param {String} userId - 用户id
     * 
     *function onUserNameNotify(userName, userId){ 
     * 
	 *}
	 *
	 * 
	 * 
	 *
	 *公聊回调
     *param : Message
     * 
     *function onPublicMessage(Message) {
     * 
     *}
     * 
     * 
     *私聊回调
     *param: Message
     * 
     *function onPrivateMessage(Message) {
     * 
     *}
     * 
     * 
	 *公有透明通道回调
     *param:dataArrayBuffer - DataArrayBuffer对象
     *param:userId - user id
     * 
     *function onPublicData(dataArrayBuffer, userId) {
     * 
     *}
     * 
     * 
     *私有透明通道回调
     *param:dataArrayBuffer - DataArrayBuffer对象
     *param:userId - user id
     * 
     *function onPrivateData(dataArrayBuffer, userId) {
     * 
     *}
     * 
     * 
     * 桌面共享关闭的回调
     * function onScreenSharingEnded(){
     * 
	 * }
	 * 
	 * 
	 * 外部设备邀请状态回调
	 *param:type - OutgoingInviteType枚举值
     *param:roomId - 房间ID
     *param:addr - 设备地址
     *param:status - 邀请状态码
     *param:msg - 邀请返回状态
     * 
	 * function onOutgoingInviteStatusNotify(type,roomId,addr,status,msg){
	 * 	
	 * }
     * 
     *
     * 房间参数设置回调
     * param:roomControl - 参数对象
     * function onRoomControlChangeNotify(roomControl){
     *
     *}
     * 
     */
	Room.prototype.addCallback = function(type, callback) {
		this.eventEmitter.addListener(type, callback);
	};


	/**
	 * @desc 获取房间控制参数
	 * @returns {Object} - roomControl 房间控制参数
	 */
	Room.prototype.getRoomControl = function() {
		return this.roomControl;
	};



	/**
	 * @desc 设置房间控制参数(参数为JSON格式字符串)
	 * @param {String}  attributes -房间控制参数(JSON格式字符串)
	 * @example
	 *  var attributes = '{
	 *	     'record': 0, 
	 *		 'mix_record' : 0, 
	 *		 'avc_model': 0, 
	 *		 'resolution': '640*480', 
	 *		 'liver'': 0,
	 *		 'mixer_live: 0
	 *  }";
	 *  room.setRoomControl(attributes);
	 */
	Room.prototype.setRoomControl = function(attributes) {
		this.masterServer.setRoomControl(this.id, attributes);
	};


	//	/**
	//	 * 开启AVC合屏模式
	//	 */
	//	Room.prototype.setRoomAVCEnable = function(){
	//		  
	//	}
	//	
	//	
	//	/**
	//	 * 关闭AVC合屏模式
	//	 */
	//	Room.prototype.setRoomAVCDisabled = function(){
	//	}


	/**
	 * @desc 原始设备ID转成UUID,作为上传到服务器后逻辑设备ID
	 * @param {int}  type - 媒体类型,0-camera,1-speake,2-microphone,
	 * @param {String} deviceId - 原始设备Id
	 * @ignore
	 */
	Room.prototype.setDeviceIdByUUID = function(type, deviceId) {
		var self = this;
		if (type == 0) {
			var cameraUUID = self.cameraId2uuid[deviceId];
			if (!cameraUUID) {
				//本地桌面共享ID可能会走这个流程
				if (self.selfUser && self.selfUser.screen && self.selfUser.screen.id == deviceId) {
					cameraUUID = deviceId;
				} else {
					cameraUUID = Math.uuid();
					self.cameraId2uuid[deviceId] = cameraUUID;
				}
			}
			return cameraUUID;
		} else if (type == 1) {
			var speakerUUID = self.speakerId2uuid[deviceId];
			if (!speakerUUID) {
				speakerUUID = Math.uuid();
				self.speakerId2uuid[deviceId] = speakerUUID;
			}
			return speakerUUID;
		} else if (type == 2) {
			var microphoneUUID = self.microphoneId2uuid[deviceId];
			if (!microphoneUUID) {
				microphoneUUID = Math.uuid();
				self.microphoneId2uuid[deviceId] = microphoneUUID;
			}
			return microphoneUUID;
		}
	};


	/**
	 * @desc 从服务器上逻辑设备ID(UUID),解析出原始设备ID
	 * @param {int}  type - 媒体类型,0-camera,1-speake,2-microphone,
	 * @param {String} UUID - 服务器上逻辑设备ID
	 * @ignore
	 */
	Room.prototype.getDeviceIdByUUID = function(type, UUID) {
		var deviceId = UUID;
		if (type == 0) {
			for (key in this.cameraId2uuid) {
				var curUUID = this.cameraId2uuid[key];
				if (curUUID == UUID) {
					deviceId = key;
					break;
				}
			}
			return deviceId;
		} else if (type == 1) {
			for (key in this.speakerId2uuid) {
				var curUUID = this.speakerId2uuid[key];
				if (curUUID == UUID) {
					deviceId = key;
					break;
				}
			}
			return deviceId;
		} else if (type == 2) {
			for (key in this.microphoneId2uuid) {
				var curUUID = this.microphoneId2uuid[key];
				if (curUUID == UUID) {
					deviceId = key;
					break;
				}
			}
			return deviceId;
		}
	}


	/**
	 * @desc  根据视频长宽计算初始化速率
	 * @ignore
	 */
	Room.prototype.calInitVideoBitrate = function(width, height) {
		var xy = width * height;
		var xysqrt = Math.sqrt(xy);
		var xyslog = Math.log10(xy);
		var xysqrtlog = Math.log10(xysqrt);
		var xysqrtlogpow2 = Math.pow(xysqrtlog, 2);
		var bitrate = xyslog * xysqrt / xysqrtlogpow2 / 10;
		if (width > 1280) {
			var some = Math.sqrt(width / 1280.0);
			bitrate = bitrate * some;
		}
		var curBitrate = parseInt(bitrate * 8);
		var startBit = curBitrate * 2;
		var minBit = startBit / 2;
		var maxBit = startBit * 2;
		var bit = {
			startBit: startBit,
			minBit: minBit,
			maxBit: maxBit
		}
		return bit;
	};


	/**
	 * @desc  发送添加注释消息
	 * @ignore
	 */
	Room.prototype.sendAddAnnotation = function(objectId, shapeObj, shapeType, lineColor, lineWidth, arrowtype,
		fillType) {
		log.debug("===Room.sendAddAnnotation(),objectId:" + objectId + ",shapeType:" + shapeType +
			",arrowtype:" + arrowtype + ",fillType:" + fillType);
		
		if(!shapeObj || !shapeObj.id){
			return;
		}
		
		this.masterServer.sendAddAnnotationMessage(objectId, shapeObj, shapeType, lineColor, lineWidth,
			arrowtype, fillType);
		return;
	};
	
	/**
	 * @desc  发送修改注释消息
	 * @param {string} objectId  注释对象id
	 * @param {object} shapeObj  注释对象
	 * @param {string} shapeType  注释类型
	 * @ignore
	 */
	Room.prototype.sendUpdateAnnotation = function(objectId, shapeObj, shapeType) {
		log.debug("===Room.sendUpdateAnnotation(),objectId:" + objectId + ",shapeType:" + shapeType);
		this.masterServer.sendUpdateAnnotationMessage(objectId, shapeObj, shapeType);
		return;
	};


	/**
	 * @desc  发送删除注释消息
	 * @param {string} objectId  注释id
	 * @param {string} shapeType  注释类型
	 * @param {string} eraserIds  橡皮擦id
	 * @ignore
	 */
	Room.prototype.sendRemoveAnnotation = function(objectId, annotationType, eraserIds) {
		log.debug("===Room.sendRemoveAnnotation(),objectId:" + objectId + ",annotationType:" +
			annotationType + ",eraserIds:" + eraserIds);
		this.masterServer.sendRemoveAnnotationMessage(objectId, annotationType, eraserIds);
		return;
	};


	/**
	 * @description 初始化多个fake video stream
	 * @param {Object} fakeLen  个数
	 * @ignore
	 */
	Room.prototype.initFakeVideoStream = function(fakeLen) {
		for (var i = 0; i < fakeLen; i++) {
			getFakeVideoStream(function(stream) {
				this.fakeVideoStreams.push(stream);
			});
		}
	};


	/**
	 * @desc 初始化多个fake audio stream
	 * @param {Object} fakeLen  个数
	 * @ignore
	 */
	Room.prototype.initFakeAudioStream = function(fakeLen) {
		for (var i = 0; i < fakeLen; i++) {
			getFakeAudioStream(function(stream) {
				this.fakeAudioStreams.push(stream);
			});
		}
	};

	/**
	 * @desc 添加注释到队列中
	 * @param {Boolean}  是否为自己添加
	 * @ignore
	 */
	// Room.prototype.addAnnotationMsg = function(updateAnnotationMsg,isSelf) {
	// 	if(room.annotation2boardId[updateAnnotationMsg.objectId]){
	// 		if(updateAnnotationMsg.annotationType != shapeTypeEnum.hlight_point){
	//     		room.annotation2boardId[updateAnnotationMsg.objectId].storageArr.push(updateAnnotationMsg);
	//     	}
	// 		if(!isSelf){
	// 			room.annotation2boardId[updateAnnotationMsg.objectId].queueArr.push(updateAnnotationMsg);
	// 		}

	//     }else{
	//     	room.annotation2boardId[updateAnnotationMsg.objectId] = {
	//     		storageArr:[],
	//     		queueArr:[]
	//     	};
	//     	if(updateAnnotationMsg.annotationType != shapeTypeEnum.hlight_point){
	//     		room.annotation2boardId[updateAnnotationMsg.objectId].storageArr.push(updateAnnotationMsg);
	//     	}
	//     	if(!isSelf){
	//     		room.annotation2boardId[updateAnnotationMsg.objectId].queueArr.push(updateAnnotationMsg);
	//     	}
	//     }
	// };

	/**
	 * @desc 添加注释到队列中
	 * @param {Object} updateAnnotationMsg 批注对象
	 * @param  {Boolean} isSelf 是否为自己添加
	 * @param {Boolean}  isClear 是否是清除
	 * @param {string}  imgZrender 是否是图片
	 * @param {string}  imgId 图片id
	 * @ignore
	 */
	Room.prototype.addAnnotationMsg = function(updateAnnotationMsg, isSelf, isClear, imgZrender, imgId) {
		log.debug('===room.addAnnotationMsg, updateAnnotationMsg:'+updateAnnotationMsg.objectId+',isSelf:'+ isSelf+ ',isClear: '+isClear)
        var self = this;
		if (this.annotation2boardId[updateAnnotationMsg.objectId]) {
			var tempStorageArr = this.annotation2boardId[updateAnnotationMsg.objectId].storageArr;
			var tempQueueArr = this.annotation2boardId[updateAnnotationMsg.objectId].queueArr;
			if (updateAnnotationMsg.annotationType != shapeTypeEnum.hlight_point) {
				if (tempStorageArr.length > 0) {
					// 增加判断该批注是否已经存在于storageArr中,不存在才添加
					var isExisted = false;
					for (var i = 0; i < tempStorageArr.length; i++) {
						if (tempStorageArr[i].annotationId == updateAnnotationMsg.annotationId) {
							if (tempStorageArr[i].operType != updateAnnotationMsg.operType && updateAnnotationMsg.operType == OperType.remove) {
								log.debug('===room.addAnnotationMsg, updateAnnotationMsg.remove, remove annotation from annotation2BoardId');
								tempStorageArr.splice(i, 1);
							}
							isExisted = true
						}
					}
					if (!isExisted && updateAnnotationMsg.operType != OperType.remove) {
						tempStorageArr.push(updateAnnotationMsg);
					}
				} else {
					tempStorageArr.push(updateAnnotationMsg);
				}
			}
			if (!isSelf) {
                    tempQueueArr.push(updateAnnotationMsg);
			}

		} else {
			this.annotation2boardId[updateAnnotationMsg.objectId] = {
				storageArr: [],
				queueArr: []
			};
			if (updateAnnotationMsg.annotationType != shapeTypeEnum.hlight_point) {
				this.annotation2boardId[updateAnnotationMsg.objectId].storageArr.push(updateAnnotationMsg);
			}
			if (!isSelf) {
				this.annotation2boardId[updateAnnotationMsg.objectId].queueArr.push(updateAnnotationMsg);
			}
		}

		/**
		 * desc: 增加判断是否收到清除命令,如果收到清除则将本地存储的批注删除
		 * by: 王博 
		 * date: 2021/7/15
		 */
		if (isClear) {
			if (this.annotation2boardId[updateAnnotationMsg.objectId] && this.annotation2boardId[
					updateAnnotationMsg.objectId].storageArr.length > 0) {
				for (var i = 0; i < this.annotation2boardId[updateAnnotationMsg.objectId].storageArr
					.length; i++) {
					var tempShape = this.annotation2boardId[updateAnnotationMsg.objectId].storageArr[i];
					if (tempShape.operUserId == this.selfUser.id && tempShape.operType == OperType.add) {
						this.annotation2boardId[updateAnnotationMsg.objectId].storageArr.splice(i, 1);
						i--;
					}
				}
			}
		}

        if(updateAnnotationMsg.operType == OperType.remove){
            for (var i = 0; i < this.annotation2boardId[updateAnnotationMsg.objectId].storageArr.length; i++) {
                var tempShape = this.annotation2boardId[updateAnnotationMsg.objectId].storageArr[i];
                if (tempShape.annotationId == imgId && tempShape.operUserId == this.selfUser.id && tempShape.operType == OperType.add) {
                    delete tempShape.imgZrender;
                    break;
                }
            }
        }else{
            for (var i = 0; i < this.annotation2boardId[updateAnnotationMsg.objectId].storageArr.length; i++) {
                var tempShape = this.annotation2boardId[updateAnnotationMsg.objectId].storageArr[i];
                if (tempShape.annotationId == imgId && tempShape.operUserId == this.selfUser.id && tempShape.operType == OperType.add) {
                    tempShape.imgZrender = imgZrender;
                    this.style2annotation[imgZrender.id] = {
                        style: JSON.stringify(imgZrender.style),
                        position: JSON.stringify(imgZrender.position),
                        scale: JSON.stringify(imgZrender.scale),
                        origin: JSON.stringify(imgZrender.origin),
                        rotation: JSON.stringify(imgZrender.rotation)
                    };
                    break;
                }
            }
        }
	};



	//TODO  没想到更好的解决方式之前,暂时使用这种写法  by宝旭
	Room.prototype.updateAnnotationDrow = function(annotations) {
		log.debug('===room.updateAnnotationDrow, annotations: ', annotations);
		var self = this;
		while (annotations.length > 0) {
			var updateAnnotationMsg = annotations[0];
			var user = this.getUser(this.boardId2userId[updateAnnotationMsg.objectId]);
			if (!user) {
				break;
			}
			var board = user.getBoard(updateAnnotationMsg.objectId);
			if (!board.annotation) {
				clearTimeout(self.updateTimer);
				self.updateTimer = setTimeout(function() {
					self.updateAnnotationDrow(annotations)
				}, 100)
				break;
			}
			annotations.shift();
			switch (updateAnnotationMsg.operType) {
				case OperType.add:
					if (updateAnnotationMsg.annotationType == shapeTypeEnum.line ||
						updateAnnotationMsg.annotationType == shapeTypeEnum.ellipse ||
						updateAnnotationMsg.annotationType == shapeTypeEnum.rect ||
						updateAnnotationMsg.annotationType == shapeTypeEnum.arrow ||
						updateAnnotationMsg.annotationType == shapeTypeEnum.success ||
						updateAnnotationMsg.annotationType == shapeTypeEnum.failure ||
						updateAnnotationMsg.annotationType == shapeTypeEnum.rhomb ||
						updateAnnotationMsg.annotationType == shapeTypeEnum.hlight_texttag ||
						updateAnnotationMsg.annotationType == shapeTypeEnum.hlight_point) {
						board.annotation.drawToBoard(updateAnnotationMsg.ann2pt);
					} else if (updateAnnotationMsg.annotationType == shapeTypeEnum.polyline) {
						board.annotation.drawToBoard(updateAnnotationMsg.annMutlipt);
					} else if(updateAnnotationMsg.annotationType == shapeTypeEnum.img){
                        board.annotation.drawToBoard(updateAnnotationMsg.Img);
					} else if(updateAnnotationMsg.annotationType == shapeTypeEnum.txt){
						board.annotation.drawTextToBoard(updateAnnotationMsg);
					}
					break;
				case OperType.remove:
					board.annotation.removeToBoard(updateAnnotationMsg);
					break;
				case OperType.update:
					if(updateAnnotationMsg.annotationType == shapeTypeEnum.img){
                        board.annotation.updateDrawToBoard(updateAnnotationMsg.Img);
					}
					break;
				default:
					break;
			}
		}
	}



	//TODO  服务器通知下发,由本地用户来上报最大语音激励值
	Room.prototype.voiceActivatedHandler = function() {
		var statsInterval = 500; //sdk语音激励计算频率
		if (this.audioLevel) {
			this.audioLevel.start(statsInterval).then(audioLevelHandler(this)); //开始语音激励
		}
	}


	/**
	 * @description userAgent为avd_mp4_importer的导入用户,开启按指定频率获取音频的rpc包数等值
	 * @param {Object} interval 采集频率
	 * @ignore
	 */
	Room.prototype.mp4ImporterUserAudioStateStart = function(interval) {
		log.debug("===room.mp4ImporterUserAudioStateStart()");
		var self = this;
		if (!this) {
			return;
		}
		clearInterval(self.mp4ImporterUserAudioStateIntervalId);
		self.mp4ImporterUserAudioStateIntervalId = setInterval(function() {
			var trackId = getAudioTrackIdByMp4ImporterUser(self);
			// log.debug("===room.mp4ImporterUserAudioStateStart(),trackId:",trackId);
			if (trackId && self) {
				var audioReceiver;
				var receivers = self.traceablePeerConnection.getToMCUPeerConnection()
			.getReceivers();
				for (var i = 0; i < receivers.length; i++) {
					var receiver = receivers[i];
					if (receiver && receiver.track && receiver.track.id && receiver.track.id ==
						trackId) {
						audioReceiver = receiver;
						break;
					}
				}
				if (audioReceiver) {
					audioReceiver.getStats().then(function(res) {
						res.forEach(function(report) {
							if (report.type === 'inbound-rtp') {
								var packetsLost = report.packetsLost;
								var packetsReceived = report.packetsReceived;

								self.mp4ImporterUserAudioRtpPacketTemp =
									packetsLost + packetsReceived;
								var currProgressValue = (self
										.mp4ImporterUserAudioRtpPacketTemp - self
										.mp4ImporterUserAudioRtpPacketReal) * self
									.mp4ImporterUserAudioRate;
								// log.debug("===room.mp4ImporterUserAudioStateStart(),packetsLost:",packetsLost+",packetsReceived:"+packetsReceived+",mp4ImporterUserAudioRtpPacketReal:"+self.mp4ImporterUserAudioRtpPacketReal+",mp4ImporterUserAudioRtpPacketTemp:"+self.mp4ImporterUserAudioRtpPacketTemp+",currProgressValue:"+currProgressValue);

								self.eventEmitter.emit(RoomCallback
									.mediaplay_progress_notify,
									currProgressValue);
							}
						});
					});
				}
			} else {
				self.mp4ImporterUserAudioRtpPacketTemp = 0;
				self.mp4ImporterUserAudioRtpPacketReal = 0;
			}
		}, interval);
	}



	/**
	 * @description userAgent为avd_mp4_importer的导入用户,结束获取音频的rpc包数等值
	 * @ignore
	 */
	Room.prototype.mp4ImporterUserAudioStateStop = function() {
		this.mp4ImporterUserAudioRtpPacketReal = this.mp4ImporterUserAudioRtpPacketTemp;
		// log.debug("===room.mp4ImporterUserAudioStateStop(),mp4ImporterUserAudioRtpPacketReal:"+this.mp4ImporterUserAudioRtpPacketReal);
		if (this.mp4ImporterUserAudioStateIntervalId) {
			clearInterval(this.mp4ImporterUserAudioStateIntervalId);
			this.mp4ImporterUserAudioStateIntervalId = null;
		}
	}
	

	Room.prototype.openReqSend = function(websocket) {
		log.debug("===room.openReqSend()...");
		var deferred = when.defer();
		this.masterServer = new MasterServer(websocket, this);
		this.masterServer.openReqSend();

        var curTimeout = setTimeout(function(){
			var timeoutError = new Error(10000,'openReq send time out');
			deferred.reject(timeoutError);
		},5000);
		
		this.addCallback(EngineCallback.open_rep_return_0, function() {
			clearTimeout(curTimeout);
		});
		
		this.addCallback(EngineCallback.room_join_success, function(room) {
			clearTimeout(curTimeout);
			deferred.resolve(room);
		});
	
		this.addCallback(EngineCallback.room_join_error, function(error) {
			clearTimeout(curTimeout);
			deferred.reject(error);
		});
	
		return deferred.promise;
	};
	
	
	Room.prototype.openReqReconnectionSend = function() {
		log.debug("===room.openReqReconnectionSend()...");
		var deferred = when.defer();
		
		this.masterServer.openReqReconnectionSend();
		
		var curTimeout = setTimeout(function(){
			var timeoutError = new Error(10000,'openReq Reconnection send time out');
			deferred.reject(timeoutError);
		},5000);
		
		this.addCallback(EngineCallback.open_rep_return_0, function() {
			clearTimeout(curTimeout);
			deferred.resolve();
		});
		
		this.addCallback(EngineCallback.room_join_error, function(error) {
			clearTimeout(curTimeout);
			deferred.reject(error);
		});
			
		return deferred.promise;
	};
	
	

    /**
     * @description 设置重新加会的超时时间
     * @param {Number} rejoinTimeout 单位ms,默认60000(60秒),请传入大于60000的值
     */
    Room.prototype.setRejoinTimeout = function(rejoinTimeout) {
        if(rejoinTimeout != undefined && (typeof rejoinTimeout != 'number' || rejoinTimeout <= 60000)){
            log.info('===room.setRejoinTimeout(), timeout: ' + rejoinTimeout + ' is invalid to return');
            return;
        }
        this.rejoinTimeout = rejoinTimeout;
    };
	
	
	
	/**
	 * @description 设置二次重新加会之间间隔的时长
	 * @param {Number} rejoinInterval 单位ms,默认3000(3秒)
	 */
	Room.prototype.setRejoinInterval = function(rejoinInterval) {
	    if(rejoinInterval != undefined && (typeof rejoinInterval != 'number')){
	        log.info('===room.setRejoinInterval(), rejoinInterval: ' + rejoinInterval + ' is invalid to return');
	        return;
	    }
	    this.rejoinInterval = rejoinInterval;
	};
	
	
	/**
	 * @desc 设置音频播放设备扬声器
	 * @async
	 * @param {Object} deviceId -扬声器设备ID
	 * @param {Object} audioElements - audio控件对象集,可以不填,不填时自动获取页面上的所有audio控件对象
	 */
	Room.prototype.setSpeakerDevice = function(deviceId, audioElements) {
		log.info("===room.setSpeakerDevice(),deviceId:"+ deviceId);
		var deferred = when.defer();
		
	    if(!audioElements){
	    	audioElements = document.getElementsByTagName('audio');
	    }	
	    if (typeof audioElements[0].sinkId == 'undefined') {
	    	log.error('===room.setSpeakerDevice(),Browser does not support output device selection.');
			var err = new Error(ErrorConstant.set_speakerDevice_notsupport_failed);
			deferred.reject(err);
	    	return deferred.promise;
	    }
	    
	    for (var i = 0; i < audioElements.length; i++) {
			var element = audioElements[i];
			element.setSinkId(deviceId).then(function (){
				log.info('===room.setSpeakerDevice() Success, audio output device attached:' + deviceId +'to element with '+ element.title + 'as source.');
			}).catch(function(error){
				var err;
				if (error.name === 'SecurityError') {
					log.error('===room.setSpeakerDevice(),You need to use HTTPS for selecting audio output device');
					err= new Error(ErrorConstant.set_speakerDevice_notsupport_failed);
				}else{
					log.error('===room.setSpeakerDevice(),Error setting audio output device, error:'+JSON.stringify(error));
					err= new Error(ErrorConstant.set_speakerDevice_notsupport_failed);
				}
				deferred.reject(err);
				return deferred.promise;
			});
	    }
		
		this.clientSetSpeakerDeviceId  = deviceId;
		deferred.resolve();
	};
	
	
	/**
	 * @desc 设置扬声器音量大小
	 * @async
	 * @param {int} volume -音量值,范围0-1,如0.5表示设置音量为50%。 注:该值只是应用层的设置值与物理设备的具体音量值无关
	 * @param {Object} audioElements - audio控件对象集,可以不填,不填时自动获取页面上的所有audio控件对象
	 */
	Room.prototype.setSpeakerVolume = function(volume, audioElements) {
		log.info("===room.setSpeakerVolume(),volume:"+ volume);
		
		var deferred = when.defer();
		if(!audioElements){
			audioElements = document.getElementsByTagName('audio');
		}
			
		//遍历所有的audio元素
		for (var i = 0; i < audioElements.length; i++) {
			var  elements = audioElements[i];
			elements.volume = volume; 
		}
		this.clientSetSpeakerVolume  = volume;
		deferred.resolve();
	}


	function getAudioTrackIdByMp4ImporterUser(roomInstance) {
		var trackId = null;
		if (roomInstance) {
			var participantsLen = roomInstance.participants.length;
			if (participantsLen > 0) {
				for (var i = 0; i < participantsLen; i++) {
					var user = roomInstance.participants[i];
					if (user.userAgent == "avd_mp4_importer") {
						if (user.audio && user.audio.track) {
							trackId = user.audio.track.id;
						}
						break;
					}
				}
			}
		}
		return trackId;
	}



	function audioLevelHandler(context) {
		var self = context;
		if (!self) {
			return;
		}
		var statsInterval = 3000; //应用层获取语音激励最大值的计算频率
       
		clearInterval(self.statsIntervalId);
		self.statsIntervalId = setInterval(
			function() {
				var userId2AudioLevel = {}; //用于user与所属用户的音量值的绑定,map(userId:AudioLevel)集
				var maxAudioLevel = 0;
				var maxAudioLevelUserId = "";
				var participants = self.getParticipants();
				var lastLecturer = self.getAppData(appDataKeyEnum.lecturer) ? self.getAppData(appDataKeyEnum
					.lecturer) : '';
				var lastLecturerNum = 0;
				participants.forEach(function(user) {
					if (user.audio) {
						userId2AudioLevel[user.id] = user.audio.getAudioLevel();
					}
				});

				for (var key in userId2AudioLevel) {
					try {
						var audioLevel = userId2AudioLevel[key];
						if (key == lastLecturer) {
							lastLecturerNum = audioLevel;
						}
						if (audioLevel > maxAudioLevel) {
							maxAudioLevel = audioLevel;
							maxAudioLevelUserId = key;
						}
					} catch (e) {
						log.error(e);
					}
				}

				if (lastLecturer != maxAudioLevelUserId && lastLecturerNum < 4 && ((maxAudioLevel -
						lastLecturerNum) >= 3)) {
					log.debug("===audioLevelHandler()发送更改语音激励通知,语音激励id:" + maxAudioLevelUserId + ",值为" +
						maxAudioLevel);
                    self.updateAppData(appDataKeyEnum.lecturer, maxAudioLevelUserId);
				}
			}, statsInterval);
	}

	

	return Room;
});