user/user.js

ModuleBase.define("User", ["Video", "Audio", "Screen", "ScreenAudio", "Board", "Error"], function(Video, Audio, Screen, ScreenAudio, Board, Error) {

	/**
	 * @desc 用户User构造函数。
	 * @constructor
	 * @alias User
	 * @param {String} id - 用户id
	 * @param {String} name - 用户名称
	 * @param {String} data - 用户扩展字段
	 */
	var User = function(id, name, data, roomHandle) {
		this.roomHandle = roomHandle;
		this.id = id;
		this.name = name;
		this.data = data;

		this.eventEmitter = new EventEmitter();

		this.subVideoTrack2ResourceInfo = {}; //用于定位pc.onaddTrack,pc.onremoveTrack时的视频track与所属用户的绑定,map(trackId:resourceInfo)集 //TODO
		this.subAudioTrack2ResourceInfo = {}; //用于定位pc.onaddTrack,pc.onremoveTrack时的音频track与所属用户的绑定,map(trackId:resourceInfo)集 //TODO
		this.subScreenTrack2ResourceInfo = {}; //用于定位pc.onaddTrack,pc.onremoveTrack时的桌面共享track与所属用户的绑定,map(trackId:resourceInfo)集 //TODO

		this.reJoinSubVideoIds = [];
		this.reJoinSubAudioIds = [];
		this.reJoinSubScreenIds = [];

		this.reJoinVideoId2CustomSet = {}; //重新加会时,原应用层设置的视频相关参数信息(width、height、setType、frameRate、aspectRatio、description)依然有效,用于定位videoId与CustomSet对象的绑定,map(videoId:CustomSet)集
		this.reJoinScreenDescription = null; //重新加会时,原应用层设置的桌面共享流名称依然有效,不需要应用层再次设置
		
		this.boards = [];
		
		this.screenStream = null; //TODO 用于唇音同步,当前用户所有打开的桌面共享、桌面共享音频等track都加入到这个stream中
		
		roomHandle.cleanupBoardByReJoinConnected = updateBoardMsgRepCheck(avdEngineHandle.restServerInfo.boxVersion);

		this.init();
	};


	/**
	 * @des user对象初始化
	 * @ignore
	 */
	User.prototype.init = function() {

		this.nodeId = null;
		this.role = null;
		this.userAgent = null;

		this.stream = null; //TODO 用于唇音同步,当前用户所有打开的视频、音频都加入到这个stream中
		
		this.customStream = null;//TODO 用于唇音同步,当前用户所有导入的第三方视频、音频都加入到这个stream中

		this.waitingDelete = false;

		this.videos = [];
		this.audio;
		this.screen;
		this.customAudios = [];  //用于存放所有导入的第三方音频。

		this.toMCUPC; //与MCU服务器间的PeerConnection

		this.mulLanding = false; //user id 多地登陆时,后加会者会踢掉前加会者

		this.deviceId2VideoSdpSsrc = {};
		this.audioSdpSsrc;
		this.screenSdpSsrc;

		this.subVideoIds = []; //我订阅视频的videoId数组
		this.subAudioIds = []; //我订阅音频的audioId数组
		this.subScreenIds = []; //我订阅桌面共享的screenId数组

		//      this.subVideoStream2ResourceInfo = {}; //用于定位pc.onaddstream返回视频stream与所属用户的绑定,map(stream.id:resourceInfo)集
		//      this.subAudioStream2ResourceInfo = {}; //用于定位pc.onaddstream返回音频stream与所属用户的绑定,map(stream.id:resourceInfo)集
		//      this.subScreenStream2ResourceInfo = {}; //用于定位pc.onaddstream返回桌面共享stream与所属用户的绑定,map(stream.id:resourceInfo)集




		//与服务器重连相关
		//      this.reconnectionSubVideoIds= null; 
		//     	this.reconnectionSubAudioIds= null; 
		//     	this.reconnectionSubScreenIds= null;
		//     	this.reconnectionUsers= []; 


		//下面都是P2P会议模式下的定义
		this.toP2PPCs = {}; //自己与其他用户间的peerConnection链接集,map(nodeId:peerConnection)集
		this.sdpP2PMap = {}; //map(nodeId:Sdp)集
		this.videoSdpSsrcMapP2PMap = {}; //map(nodeId:videoSdpSsr)集
		this.audioSdpSsrcP2PMap = {}; //map(nodeId:audioSdpSsr)集
		this.screenSdpSsrcP2PMap = {}; //map(nodeId:screenSdpSsr)集

		this.subMyVideosP2P = []; //订阅我视频的nodeId集
		this.subMyAudiosP2P = []; //订阅我音频的nodeId集
		this.subMyScreensP2P = []; //订阅我桌面共享的nodeId集

	}


	/**
	 * @desc 创建video对象
	 * @param {String} id -  video id
	 * @param {String} name - video name
	 * @ignore
	 */
	User.prototype.createVideo = function(id, name) {
		var video = new Video(id, name, this.roomHandle);
		video.ownerId = this.id;
		this.videos.push(video);
		return video;
	};


	/**
	 * @desc 创建audio对象
	 * @param {String} id -  audio id
	 * @param {String} name -  audio name
	 * @ignore
	 */
	User.prototype.createAudio = function(id, name) {
		var audio = new Audio(id, name, this.roomHandle);
		audio.ownerId = this.id;
		this.audio = audio;
		return audio;
	};


	/** 
	 * @desc 创建screen对象
	 * @param {String} id -  screen id
	 * @param {String} name -  screen name
	 * @ignore
	 */
	User.prototype.createScreen = function(id, name) {
		var screen = new Screen(id, name, this.roomHandle);
		screen.ownerId = this.id;
		this.screen = screen;
		return screen;
	};

    /** 
	 * @desc 创建screenaudio对象
	 * @param {String} id -  screen audio id
	 * @param {String} name -  screen audio name
	 * @ignore
	 */
	User.prototype.createScreenAudio = function(id, name) {
		var screenAudio = new ScreenAudio(id, name, this.roomHandle);
		screenAudio.ownerId = this.id;
		this.screen.screenAudio = screenAudio;
		return screenAudio;
	};


	/**
	 * @desc 初始化桌面共享
	 * 逻辑:
	 *    id:  UUID;
	 *    name: user.name+"_screen";
	 * @ignore
	 */
	User.prototype.initScreen = function() {
		var id = Math.uuid();
		var name = this.name+"-screen";
        var screenAudioId = id + '_screen_audio'
		var screenAudioName = this.name + '-screen-audio';

		var screen = this.createScreen(id, name);
        var screenAudio = this.createScreenAudio(screenAudioId, screenAudioName);
		screen.level = 0;
		screen.description = '';
        screen.screenAudio = screenAudio;
		
		//重新加会时,原应用层设置的视频/桌面共享流名称依然有效,不需要应用层再次设置.
		if(this.reJoinScreenDescription){
		   screen.description = this.reJoinScreenDescription;
		}
		
		this.eventEmitter.emit(UserCallback.screen_status_notify, screen.status, screen.id, screen.name, this.id);

		this.roomHandle.masterServer.addScreenMsg(id, screen);
		this.roomHandle.masterServer.addSpeakerMsg(screenAudioId, screenAudio);
	};


	/**
	 * @desc 初始化视频设备
	 * @param {String} id - 视频设备ID
	 * @param {String} name - 视频设备名称
	 * @ignore
	 */
	User.prototype.initVideo = function(id, name) {
		var video = this.createVideo(id, name);
		video.cameraType = CameraType.unknown;
		video.level = 0;
		video.description = '';
		
		var videoCustomSet = this.reJoinVideoId2CustomSet[id];
        if(videoCustomSet){
            if(videoCustomSet.aspectRatio){
                video.aspectRatio = videoCustomSet.aspectRatio
            }

            if(videoCustomSet.description){
                video.description = videoCustomSet.description
            }

            if(videoCustomSet.width){
                video.resolution.width = videoCustomSet.width
            }

            if(videoCustomSet.height){
                video.resolution.height = videoCustomSet.height
            }


            if(videoCustomSet.setType){
                video.setType = videoCustomSet.setType
            }

            if(videoCustomSet.frameRate){
                video.frameRate = videoCustomSet.frameRate
            }

        }
		
		this.eventEmitter.emit(UserCallback.camera_status_notify, video.status, video.id, video.name, this.id);
        
        var cameraUUID = this.roomHandle.setDeviceIdByUUID(0, id);
		this.roomHandle.masterServer.addWebcamMsg(cameraUUID, video);
	};



	/**
	 * @desc  删除视频设备。如果已经打开,就先关闭; 如果已经pub,就先unpub;
	 * @param {String} id - 视频设备ID
	 * @ignore
	 */
	User.prototype.deleteVideo = function(id) {
		var self = this;
		var video = self.getVideo(id);
		if (video) {
			if(video.status == StreamStatus.published){
				video.unpublish().then(function(){
					arrayUtil.objectSplice(self.videos, id);
					self.eventEmitter.emit(UserCallback.camera_status_notify, StreamStatus.none, video.id, video.name, self.id);
					var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, id);
					self.roomHandle.masterServer.removeWebcamMsg(cameraUUID);
				});
			}else{
				if (video.status == StreamStatus.opened) {
					video.unpreview();
				}
				arrayUtil.objectSplice(self.videos, id);
				self.eventEmitter.emit(UserCallback.camera_status_notify, StreamStatus.none, video.id, video.name, self.id);
				var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, id);
				self.roomHandle.masterServer.removeWebcamMsg(cameraUUID);
			}
		}
	}


	/**
	 * @desc 初始化音频设备
	 * @param {String} id - 音频设备Id
	 * @param {String} name - 音频设备名称
	 * @ignore
	 */
	User.prototype.initAudio = function(id, name) {
		var audio = this.createAudio(id, name);
		avdEngineHandle.checkAudioId = id;
		avdEngineHandle.checkAudioName = name;
		this.eventEmitter.emit(UserCallback.microphone_status_notify, audio.status, audio.id, audio.name, this.id);

        var microphoneUUID = this.roomHandle.setDeviceIdByUUID(2, id);
		this.roomHandle.masterServer.addSpeakerMsg(microphoneUUID, audio);
	};


	/**
	 * @desc  删除音频设备。如果已经打开,就先关闭; 如果已经pub,就先unpub;
	 * @param {String} id - 音频设备ID
	 * @ignore
	 */
	User.prototype.deleteAudio = function(id) {
		var self = this;
		var audio; 
		if(id.indexOf('custom_audio') !== -1 ){
		    audio = self.getAudio(id);
		}else{
		    audio = self.getCustomAudio(id);
		}
		if (audio) {
			if (audio.status == StreamStatus.published) {
				audio.closeMicrophone().then(function(){
					var microphoneUUID = self.roomHandle.setDeviceIdByUUID(2, id);
					self.roomHandle.masterServer.removeSpeakerMsg(microphoneUUID);
					
					if(id.indexOf('custom_audio') !== -1 ){
					    self.audio = null;
				    }else{
						arrayUtil.objectSplice(self.customAudios, id);
					}
					self.eventEmitter.emit(UserCallback.microphone_status_notify, StreamStatus.none, audio.id, audio.name, self.id);
				});
			}else{
				var microphoneUUID = self.roomHandle.setDeviceIdByUUID(2, id);
				self.roomHandle.masterServer.removeSpeakerMsg(microphoneUUID);
				if(id.indexOf('custom_audio') !== -1 ){
				    self.audio = null;
				}else{
					arrayUtil.objectSplice(self.customAudios, id);
				}
				self.eventEmitter.emit(UserCallback.microphone_status_notify, StreamStatus.none, audio.id, audio.name, self.id);
			}
			
		}
	}


	/**
	 * @desc 获取Video对象
	 * @param {String} cameraId - 摄像头设备Id
	 * @returns {Object} - video Video对象
	 */
	User.prototype.getVideo = function(cameraId) {
		var video = null;
		for (var i = 0; i < this.videos.length; i++) {
			var srcVideo = this.videos[i];
			if (srcVideo.id == cameraId) {
				video = srcVideo;
				break;
			}
		}
		return video;
	}
	
	/**
	 * @desc 获取Audio对象
	 * @param {String} customAudioId - 第三方音频设备Id
	 * @returns {Object} - audio Aduio对象
	 */
	User.prototype.getCustomAudio = function(customAudioId) {
		var audio = null;
		for (var i = 0; i < this.customAudios.length; i++) {
			var srcAudio = this.customAudios[i];
			if (srcAudio.id == customAudioId) {
				audio = srcAudio;
				break;
			}
		}
		return audio;
	}
	
	



	/**
	 * @desc 获取Screen对象
	 * @returns {Object} - screen Screen对象
	 */
	User.prototype.getScreen = function() {
		return this.screen;
	}

    /**
	 * @desc 获取ScreenAudio对象
	 * @returns {Object} - screenAudio ScreenAudio对象
	 */
	User.prototype.getScreenAudio = function() {
		return this.screen.screenAudio;
	}



	/**
	 * @desc 获取Audio对象
	 * @param {String} microphoneId - 麦克风设备ID
	 * @returns {Object} - audio Audio对象
	 */
	User.prototype.getAudio = function(microphoneId) {
		var srcAudio = null;
		if (this.audio && this.audio.id == microphoneId) {
			srcAudio = this.audio;
		}
		return srcAudio;
	}



	/**
	 * @desc  打开摄像头和麦克风
	 * @async
	 * @param {Obejct} video - video对象
	 * @param {Obejct} audio - audio对象
	 * @param {Object} videoElement - 视频控件对象
	 * @param {Object} audioElement - 音频控件对象
	 */
	User.prototype.openCameraAndMicrophone = function(video, audio, videoElement, audioElement) {
		log.info("===user.openCameraAndMicrophone(),预览摄像头及发布流和打开麦克风");
		avdEngineHandle.loggerReport.info("预览摄像头及发布流和打开麦克风");

		var deferred = when.defer();

		var self = this;

		if (video && audio == null) {
			video.publish();
		} else if (video == null && audio) {
			audio.openMicrophone();
		} else if (video && audio) {
			obtainUserMedia(audio, video,
				function(stream) {

					//about video 
					var orderId = getRandomNum(1, 3000); //用于PubRoomResourceMsgRep中的ID

					video.status = StreamStatus.opened;
					video.track = stream.videoStream.getVideoTracks()[0];

					if (self.roomHandle.selfUser.stream == null) {
						self.roomHandle.selfUser.stream = stream.videoStream;
					} else {
						self.roomHandle.selfUser.stream.addTrack(video.track);
					}

                    var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, video.id);
					var videoStream = self.getNewStreamByTrack(video.track);
					self.roomHandle.masterServer.videoPublishHandle(cameraUUID, videoStream, orderId);

					self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_success, function(id) {
						if (id == orderId) {
							video.element = videoElement;
							videoElement.srcObject = videoStream;

							video.status = StreamStatus.published;
							if (!self.roomHandle.hasObject(self.roomHandle.pubVideos, video)) {
								self.roomHandle.pubVideos.push(video);
							}
							self.eventEmitter.emit(UserCallback.camera_status_notify, video.status, video.id, video.name, self.id);
						}
					});

					self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_error, function(error, id) {
						if (id == orderId) {
							deferred.reject(error);
						}
					});


					//about audio
					var audioOrderId = getRandomNum(30001, 6000); //用于PubRoomResourceMsgRep中的ID
					audio.state = StreamStatus.opened;
					audio.track = stream.audioStream.getAudioTracks()[0];

					if (self.roomHandle.selfUser.stream == null) {
						self.roomHandle.selfUser.stream = stream.audioStream;
					} else {
						self.roomHandle.selfUser.stream.addTrack(audio.track);
					}

                    var microphoneUUID = self.roomHandle.setDeviceIdByUUID(2, audio.id);
					var audioStream = self.getNewStreamByTrack(audio.track);
					self.roomHandle.masterServer.audioPublishHandle(microphoneUUID, audioStream, audioOrderId);

					self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_success, function(id) {
						if (id == audioOrderId) {
							if(audioElement){
							   audio.element = audioElement;
							   audioElement.srcObject = audioStream;
							}
							
							audio.status = StreamStatus.published;
							if (!self.roomHandle.hasObject(self.roomHandle.pubAudios, audio)) {
								self.roomHandle.pubAudios.push(audio);
							}
							self.eventEmitter.emit(UserCallback.microphone_status_notify, audio.status, audio.id, audio.name, self.id);
						}
					});

					self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_error, function(error, id) {
						if (id == audioOrderId) {
							deferred.reject(error);
						}
					});


				},
				function(e) {
					var error = new Error(ErrorConstant.failed_access_localMedia);
					deferred.reject(error);
				});
		}

		return deferred.promise;
	}


	/**
	 * @desc 命令远端用户发布某一摄像头视频
	 * @param {Obejct} video - 视频对象
	 */
	User.prototype.remotecmdPublishCamera = function(video) {
		log.info("===user.remotecmdPublishCamera(),userId:"+video.ownerId+",设备Id:"+ video.id);
		avdEngineHandle.loggerReport.info("命令远端用户发布某一摄像头视频,userId:"+video.ownerId+",设备Id:"+ video.id);
		
		var nodeId = this.roomHandle.getNodeId(video.ownerId);
		this.roomHandle.masterServer.videoIndiction(true, video.id, nodeId);
	}


	/**
	 * @desc 命令远端用户取消发布摄像头视频
	 * @param {String} userId - 用户Id
	 * @param {String} cameraId - 摄像头设备Id
	 */
	User.prototype.remotecmdUnpublishCamera = function(userId,cameraId) {
		log.info("===user.remotecmdUnpublishCamera(),userId:" + userId +",cameraId:"+cameraId);
		avdEngineHandle.loggerReport.info("命令远端用户取消发布摄像头视频, userId:" + userId +",cameraId:"+ cameraId);

		var nodeId = this.roomHandle.getNodeId(userId);
		this.roomHandle.masterServer.videoIndiction(false, cameraId, nodeId);
	}


  /**
     * @desc 命令远端用户打开麦克风
     * @param {String} userId - 远端用户Id
     */
    User.prototype.remotecmdOpenMicrophone = function(userId){
    	    log.info("===user.remotecmdOpenMicrophone(),userId:"+userId);
    	    avdEngineHandle.loggerReport.info("命令远端用户打开麦克风, userId:"+ userId);
    	    
    	    var user = this.roomHandle.getUser(userId);
    	    var nodeId = this.roomHandle.getNodeId(userId);
    	    this.roomHandle.masterServer.audioIndiction("pub",user.audio.id,nodeId);
    }
	
    
    
    /**
     * @desc 命令远端用户关闭麦克风
     * @param {String} userId - 远端用户Id
     */
    User.prototype.remotecmdCloseMicrophone = function(userId){
    	     log.info("===user.remotecmdCloseMicrophone(),userId:"+userId);
    	    avdEngineHandle.loggerReport.info("命令远端用户关闭麦克风, userId:"+ userId);
    	     
    	     var user = this.roomHandle.getUser(userId);
    	     var nodeId = this.roomHandle.getNodeId(userId);
    	     this.roomHandle.masterServer.audioIndiction("unpub",user.audio.id,nodeId);
    }
	
	
	/**
	 * @desc 命令远端用户麦克风静音
	 * @param {String} userId - 远端用户Id
	 */
	User.prototype.remotecmdMuteMicrophone = function(userId){
		    log.info("===user.remotecmdMuteMicrophone(),userId:"+userId);
		    avdEngineHandle.loggerReport.info("命令远端用户麦克风静音, userId:"+ userId);
		    
		    var user = this.roomHandle.getUser(userId);
		    var nodeId = this.roomHandle.getNodeId(userId);
		    this.roomHandle.masterServer.audioIndiction("mute",user.audio.id,nodeId);
	}
	
	
	/**
	 * @desc 命令远端用户麦克风取消静音
	 * @param {String} userId - 远端用户Id
	 */
	User.prototype.remotecmdUnmuteMicrophone = function(userId){
		     log.info("===user.remotecmdUnmuteMicrophone(),userId:"+userId);
		     avdEngineHandle.loggerReport.info("命令远端用户麦克风取消静音, userId:"+ userId);
		     
		     var user = this.roomHandle.getUser(userId);
		     var nodeId = this.roomHandle.getNodeId(userId);
		     this.roomHandle.masterServer.audioIndiction("unmute",user.audio.id,nodeId);
	}
	
	


	/**
	 * @desc 更新用户名称
	 * @param {String} userName - 用户名称
	 */
	User.prototype.updateUserName = function(userName) {
		log.info("===user.updateUserName(),userName:"+userName);
		avdEngineHandle.loggerReport.info("===user.updateUserName(), userName:" + userName);
        
		var self = this;
        this.roomHandle.participants.forEach(function(user){
			if (user.id == self.id) {
				user.name = userName;
			}
		})
		this.roomHandle.masterServer.updateUserName(this.nodeId, userName);
	};


	/**
	 * @desc 更新用户扩展内容
	 * @param {String} userData - 用户扩展内容
	 */
	User.prototype.updateUserData = function(userData) {
		this.data = userData;
		this.roomHandle.masterServer.updateUserDataMsg(userData);
	};


	/**
	 * @desc 获得用户的扩展信息
	 * @param {String} userId - 用户id不填,表示自己
	 */
	User.prototype.getUserData = function(userId) {
		if (typeof(userId) == 'undefined' || userId == null || userId == '') {
			return this.data;
		} else {
			var user = this.roomHandle.getUser(userId);
			if (user) {
				return user.data;
			}
		}
	};


	/**
	 * @desc 视频流渲染进视频控件
	 * @param {Object} element - 视频控件对象 
	 * @param {Object} stream - 视频流
	 */
	User.prototype.attachVideoElementMediaStream = function(element, stream) {
		if (stream) {
			var track = stream.getTracks()[0];
			var resourceInfo = this.subVideoTrack2ResourceInfo[track.id];
			var user = this.getUserByVideoTrackId(track.id);
			if (user) {
				var video = user.getVideo(resourceInfo.resource_id);
				if (video) {
					video.element = element;
				}
			}
		}

		if (element) {
			log.debug("===attachVideoElementMediaStream(),userId:" + this.id + ",userName:" + this.name + ",elementId:" +element.id);
			element.srcObject = stream;
		}
	};



	/**
	 * @desc 桌面共享流渲染进视频控件
	 * @param {Object} element - 视频控件对象 
	 * @param {Object} stream - 桌面共享流
	 */
	User.prototype.attachScreenElementMediaStream = function(element, stream) {
		if (stream) {
			var track = stream.getTracks()[0];
			var resourceInfo = this.subScreenTrack2ResourceInfo[track.id];
			var user = this.getUserByScreenTrackId(track.id);
			if (user) {
				var screen = user.getScreen(resourceInfo.resource_id);
				if (screen) {
					screen.element = element;
				}
			}
		}
		if (element) {
			element.srcObject = stream;
		}
	};

    /**
	 * @desc 桌面共享音频流渲染进音频控件
	 * @param {Object} element -音频控件对象 
	 * @param {Object} stream - 桌面共享音频流
	 */
	User.prototype.attachScreenAudioElementMediaStream = function(element, stream) {
		if (element) {
			element.srcObject = stream;
		}
	};

	/**
	 * @desc 音频流渲染进音频控件
	 * @param {Object} element - 音频控件对象 
	 * @param {Object} stream - 音频流
	 */
	User.prototype.attachAudioElementMediaStream = function(element, stream) {
		if (stream) {
			var track = stream.getTracks()[0];
			var resourceInfo = this.subAudioTrack2ResourceInfo[track.id];
			var user = this.getUserByAudioTrackId(track.id);
			if (user) {
				var audio = user.getAudio(resourceInfo.resource_id);
				if (audio) {
					audio.element = element;
				}
			}
		}

		if (element) {
			element.srcObject = stream;
		}
	};


	/**
	 * @desc 用户级别的回调
	 * @param {UserCallback} type - 回调枚举标识
	 * @param {Object} callback -回调方法名,可以自定义
	 * 
	 * @example 
	 * 
	 * 
	 * user.addCallback(UserCallback.microphone_status_notify, onMicrophoneStatusNotify);
	 * user.addCallback(UserCallback.camera_status_notify, onCameraStatusNotify);
	 * user.addCallback(UserCallback.screen_status_notify, onScreenStatusNotify);
	 * 
	 * 
	 * user.addCallback(UserCallback.camera_data_notify, onCameraDataNotify);
	 * user.addCallback(UserCallback.screen_data_notify, onScreenDataNotify);
	 *
	 * user.addCallback(UserCallback.publish_camera_notify, onPublishCameraNotify);
	 * user.addCallback(UserCallback.unpublish_camera_notify, onUnpublishCameraNotify);
	 * user.addCallback(UserCallback.subscrible_camera_result, onSubscribleCameraResult);
	 * user.addCallback(UserCallback.unsubscrible_camera_result, onUnsubscribleCameraResult);
	 *   
	 * user.addCallback(UserCallback.publish_screen_notify, onPublishScreenNotify);
	 * user.addCallback(UserCallback.unpublish_screen_notify, onUnpublishScreenNotify);
	 * user.addCallback(UserCallback.subscrible_screen_result, onSubscribleScreenResult);
	 * user.addCallback(UserCallback.unsubscrible_screen_result, onUnsubscribleScreenResult);
	 *
	 * user.addCallback(UserCallback.publish_microphone_notify, onPublishMicrophoneNotify);
	 * user.addCallback(UserCallback.unpublish_microphone_notify, onUnpublishMicrophoneNotify);
	 * user.addCallback(UserCallback.subscrible_microphone_result, onSubscribleMicrophoneResult);
	 * user.addCallback(UserCallback.unsubscrible_microphone_result, onUnsubscribleMicrophoneResult);
     * 
     * 
	 * user.addCallback(UserCallback.subscrible_screen_audio_result, onSubscribleScreenAudioResult);
	 * user.addCallback(UserCallback.unsubscrible_screen_audio_result, onUnsubscribleScreenAudioResult);
	 * 
	 * user.addCallback(UserCallback.board_add_result, onBoardAddResult);
	 * user.addCallback(UserCallback.board_update_result, onBoardUpateResult);
	 * user.addCallback(UserCallback.board_remove_result, onBoardremoveResult);
	 * 
	 * 
	 * 麦克风状态更新
	 * param:status - 状态
	 * param:microphoneId - 麦克风设备Id 
	 * param:microphoneName - 麦克风设备名称
	 * param:userId - 麦克风设备所属者ID
	 *
	 * function onMicrophoneStatusNotify(status, microphoneId, microphoneName, userId) {
	 * 
	 * }
	 * 
	 * 
	 *摄像头状态更新
	 * param : status - 状态
	 * param : cameraId - 摄像头设备Id
	 * param : cameraName- 摄像头设备名称
	 * param : userId- 摄像头设备所属者ID
	 * 
	 * function onCameraStatusNotify(status, cameraId, cameraName, userId) { 
	 * 
	 * } 
	 * 
	 *桌面共享状态更新
	 * param : status - 状态
	 * param : cameraId - 桌面共享设备Id
	 * param : cameraName- 桌面共享设备名称
	 * param : userId- 桌面共享设置所属者ID
	 * 
	 * function onScreenStatusNotify(status, screenId, screenName, userId) {
	 *
	 * }
	 * 
	 * 
	 * 
	 * 摄像头数据更新
	 * param : level - 级别
	 * param : description - 描述
	 * param : cameraId - 摄像头设备Id
	 * param : cameraName- 摄像头设备名称
	 * param : userId- 摄像头设备所属者ID
	 * function onCameraDataNotify(level, description, cameraId, cameraName, userId) {
	 * 	
	 * }
	 * 
	 * 
	 *桌面共享数据更新
	 * param : level - 级别
	 * param : description - 描述
	 * param : screenId - 桌面共享设备Id
	 * param : screenName- 桌面共享设备名称
	 * param : userId- 桌面共享设置所属者ID
	 * 
	 * function onScreenDataNotify(level, description, screenId, screenName, userId) {
	 *
	 * }
	 *
	 *
	 * 
	 *发布视频的回调
	 * param : videos - 发布的视频数组
	 *function onPublishCameraNotify(videos) { 
	 *
	 * }
	 * 
	 * 取消发布视频的回调
	 * param : video - 取消发布的视频
	 * 
	 * function onUnpublishCameraNotify(video) {
	 * 
	 * }
	 * 
	 * 
	 * 订阅远端视频流反馈
	 * param: stream - 远端视频流
	 * param :userId - 所属用户ID
	 * param :userName- 所属用户名称
	 * param :cameraId- 摄像头设备ID
	 * 
	 *function onSubscribleCameraResult(stream, userId, userName,cameraId) {
	 * 
	 * }
	 * 
	 * 
	 * 取消订阅远端视频流反馈
	 * param :userId- 所属用户ID
	 * param :userName-所属用户名称
	 * param : cameraId-摄像头设备ID
	 * 
	 *function onUnsubscribleCameraResult(userId, userName,cameraId){
	 * 
	 * }
	 * 
	 *
	 * 发布桌面共享的回调
	 * param : screens - 发布的桌面共享数组
	 * 
	 * function onPublishScreenNotify(screens){
	 * 
	 * }
	 * 
	 * 取消发布桌面共享的回调
	 * param : screen - 取消发布的桌面共享
	 * 
	 * function onUnpublishScreenNotify(screen) {
	 * 
	 * }
	 * 
	 * 
	 * 订阅远端桌面共享流反馈
	 * param: stream - 远端桌面共享流
	 * param :userId - 所属用户ID
	 * param :userName- 所属用户名称
	 * param :screenId- 桌面共享设备ID
	 * param :width- 分辨率宽度
	 * param :height- 分辨率高度
	 * param :framerate- 帧率
	 * 
	 * function onSubscribleScreenResult(stream, userId, userName,screenId,width,height,framerate) {
	 * 
	 * }
	 * 
	 * 
	 * 取消订阅远端桌面共享流反馈
	 * param :userId- 所属用户ID
	 * param :userName-所属用户名称
	 * param : cameraId-桌面共享设备ID
	 * 
	 * function onUnsubscribleScreenResult(userId, userName,screenId){
	 * 
	 * }
	 * 
	 * 
	 * 
	 *发布音频的回调
	 * param : audios - 发布的音频数组
	 *function onPublishMicrophoneNotify(audios) { 
	 *
	 * }
	 * 
	 * 取消发布音频的回调
	 * param : audio - 取消发布的音频
	 * 
	 * function onUnpublishMicrophoneNotify(audio) {
	 * 
	 * }
	 *  
	 * 
	 * 
	 * 订阅远端音频流反馈
	 * param:stream- 远端音频流
	 * param:userId- 所属用户ID
	 * param:userName-所属用户名称
	 * 
	 *function onSubscribleMicrophoneResult(stream, userId, userName){
	 * 
	 *} 
	 * 
	 * 
	 * 
	 *取消订阅远端音频流反馈
	 *param :userId- 所属用户ID
	 *param : userName-所属用户名称
	 * 
	 *function onUnsubscribleMicrophoneResult(userId, userName){ 
	 * 
	 *}
	 * 
     * 
     * * 订阅远端屏幕共享音频流反馈
	 * param:stream- 远端屏幕共享音频流
	 * param:userId- 所属用户ID
	 * param:userName-所属用户名称
	 * 
	 *function onSubscribleMicrophoneResult(stream, userId, userName){
	 * 
	 *} 
	 * 
	 * 
	 * 
	 *取消订阅远端屏幕共享音频流反馈
	 *param :userId- 所属用户ID
	 *param : userName-所属用户名称
	 * 
	 *function onUnsubscribleMicrophoneResult(userId, userName){ 
	 * 
	 *}
	 * 
	 * 
	 * 
	 *白板新增操作反馈
	 *param :board-白板对象
	 * 
	 *function onBoardAddResult(board){ 
	 * 
	 *} 
	 *
	 *白板删除操作反馈
	 *param :boardId-白板对象Id
	 * 
	 *function onBoardRemoveResult(boardId){ 
	 * 
	 *} 
	 */
	User.prototype.addCallback = function(type, callback) {
		this.eventEmitter.addListener(type, callback);
	}


	/**
	 * @desc  用于定位pc.onaddtrack或pc.onremovetrack对应的视频track与所属用户的绑定,map(trackId:resourceInfo)集
	 * @param {String} trackId -track id
	 * @ignore
	 */
	User.prototype.getUserByVideoTrackId = function(trackId) {
		var resourceInfo = this.subVideoTrack2ResourceInfo[trackId];
		if (resourceInfo) {
			var nodeId = resourceInfo.owner_id;
			return this.roomHandle.getUserByNodeId(nodeId);
		} else {
			return null;
		}
	}




	/**
	 * @desc  用于定位pc.onaddtrack或pc.onremovetrack对应音频track与所属用户的绑定,map(trackId:resourceInfo)集
	 * @param {String} trackId -track id
	 * @ignore
	 */
	User.prototype.getUserByAudioTrackId = function(trackId) {
		var resourceInfo = this.subAudioTrack2ResourceInfo[trackId];
		if (resourceInfo) {
			var nodeId = resourceInfo.owner_id;
			return this.roomHandle.getUserByNodeId(nodeId);
		} else {
			return null;
		}
	}



	/**
	 * @desc  用于定位pc.onaddtrack或pc.onremovetrack对应桌面共享track与所属用户的绑定,map(trackId:resourceInfo)集
	 * @param {String} trackId -track id
	 * @ignore
	 */
	User.prototype.getUserByScreenTrackId = function(trackId) {
		var resourceInfo = this.subScreenTrack2ResourceInfo[trackId];
		if (resourceInfo) {
			var nodeId = resourceInfo.owner_id;
			return this.roomHandle.getUserByNodeId(nodeId);
		} else {
			return null;
		}
	}


	/**
	 * @desc 由track产生一个新的stream,且该stream中仅包含当前track
	 * @param {String} currTrack -track
	 * @ignore
	 */
	User.prototype.getNewStreamByTrack = function(currTrack) {
		var streamTemp = null;
		if (currTrack && this.stream && this.stream.getTracks().length > 0) {
			var streamTemp = this.stream.clone();
			streamTemp.getTracks().forEach(function(track) {
				streamTemp.removeTrack(track);
			});
			streamTemp.addTrack(currTrack);
		}
		return streamTemp;
	}




	/**
	 * @desc 仅针对有微信小程序端加入的情况下使用,且当前web端有多个摄像头。一个摄像头原则上不用调该接口。
	 * @param {String} audioId  音频设备ID
	 * @param {String} videoId  视频设备ID
	 */
	User.prototype.bindAudioToVideo = function(audioId, videoId) {
		var deferred = when.defer();

		var orderId = 109;
		this.roomHandle.masterServer.bindAudioToVideo(orderId, audioId, videoId);

		this.roomHandle.addCallback(EngineCallback.bind_audio2video_rep_success, function(id) {
			if (id == orderId) {
				deferred.resolve();
			}
		});

		this.roomHandle.addCallback(EngineCallback.bind_audio2video_rep_error, function(id) {
			if (id == orderId) {
				deferred.reject();
			}
		});

		return deferred.promise;
	}



	/**
	 * @desc 创建白板
	 * @async
	 * @param {int} width -  白板的渲染宽度
	 * @param {int} height - 白板的渲染高度
	 * @param {String} backgroundColor - 白板的背景色,格式为RGBA 例如 透明:"0,0,0,0"   红色不透明: "255,0,0,1"
	 * @param {String} backgroundImage - 白板的背景图访问路径
	 * @param {int} outputWidth - 白板的输出宽度
	 * @param {int} outputHeight - 白板的输出高度
	 * @param {String} title - 白板标题
	 */
	User.prototype.createBoard = function(width, height, backgroundColor, backgroundImage, title, outputWidth,
		outputHeight, description, extendData) {
		
		var deferred = when.defer();
		
		var self = this;
		var board = new Board(this.roomHandle, this.id, width, height, backgroundColor, backgroundImage, title, outputWidth, outputHeight, description, extendData);

        // 上面new的时候设置的inputWidth和inputHeight会被置为null,暂不清楚为何,注释掉
		// board.inputWidth = null;
		// board.inputHeight = null;
		
		var supperBoardAuth = supperBoardAuthCheck(avdEngineHandle.restServerInfo.boxVersion);
		if(!supperBoardAuth){
			this.boards.push(board);
			this.roomHandle.boardId2userId[board.id] = this.id;
			this.roomHandle.masterServer.addBoardMsg(OperType.add, board);
			
			//二个方法都是同步的,可能会导致订阅端收到 add或 share的PDU时序不确认,固在方法之间设置延时处理
			setTimeout(function(){
				self.shareBoardById(board.id);
				deferred.resolve(board);
			},600);
			
			return deferred.promise;
		}else {
		    this.roomHandle.boardId2userId[board.id] = this.id;
			this.roomHandle.masterServer.addBoardMsg(OperType.add, board);			 
			var curTimeout = setTimeout(function(){
				var timeoutError = new Error(10000,'user.createBoard(), addBoardMsg send time out');
				delete self.roomHandle.boardId2userId[board.id];
				deferred.reject(timeoutError);
			},5000);
			
			this.addCallback(EngineCallback.updateboardmsg_add_rep_success, function(boardId) {
				if(board.id == boardId) {
				   clearTimeout(curTimeout);
				   
				   var isExit = arrayUtil.objectSplice(self.boards,boardId);
				   if(!isExit){
					  self.boards.push(board);
					  self.shareBoardById(board.id); 
				   }
			       deferred.resolve(board);
				}
			});
			
			this.addCallback(EngineCallback.updateboardmsg_add_rep_error, function(error,boardId) {
				if(board.id == boardId) {
				   clearTimeout(curTimeout);
				   delete self.roomHandle.boardId2userId[board.id];
				   deferred.reject(error);
				}
			});
			
			return deferred.promise;
		}
	}
	
	/**
	 * @description 更新白板
	 * @async
	 * @param {Object} board 对象
	 */
	User.prototype.updateBoard = function(board) {
		var deferred = when.defer();
		var self = this;
		
		var isSupper = updateBoardMsgRepCheck(avdEngineHandle.restServerInfo.boxVersion);
		if(!isSupper){
		    this.roomHandle.masterServer.addBoardMsg(OperType.update, board);
			deferred.resolve();
			return deferred.promise;
			
		}else{
			this.roomHandle.masterServer.addBoardMsg(OperType.update, board);
			var curTimeout = setTimeout(function(){
				var timeoutError = new Error(10000,'user.updateBoard(), addBoardMsg send time out');
				deferred.reject(timeoutError);
			},5000);
			
			this.addCallback(EngineCallback.updateboardmsg_update_rep_success, function(boardId) {
				if(board.id == boardId) {
			       deferred.resolve();
				}
			});
			
			this.addCallback(EngineCallback.updateboardmsg_update_rep_error, function(error,boardId) {
				if(board.id == boardId) {
				   clearTimeout(curTimeout);
				   deferred.reject(error);
				}
			});
			
			return deferred.promise;
		}
	}


	/**
	 * @desc 通过白板Id获取白板
	 * @param {String} boardId - 白板Id
	 */
	User.prototype.getBoard = function(boardId) {
		var retBoard;
		for (var i = 0; i < this.boards.length; i++) {
			var board = this.boards[i];
			if (board.id == boardId) {
				retBoard = board;
				break;
			}
		}
		return retBoard;
	}



	/**
	 * @desc 获取所有的白板
	 */
	User.prototype.getAllBoards = function() {
		return this.boards;
	}


	/**
	 * @desc 删除指定的白板
	 * @async
	 * @param {String} boardId - 白板Id
	 * @param {String} userId  - 白板所属的用户id,允许不传,不传时,白板必现是当前自己的白板。填写是userid所属的白板
	 */
	User.prototype.removeBoardById = function(boardId, userId) {
		var deferred = when.defer();
		
		var self = this;
		
		var isSupper = updateBoardMsgRepCheck(avdEngineHandle.restServerInfo.boxVersion);
		if(!isSupper){
		    this.roomHandle.masterServer.removeBoardMsg(OperType.remove, boardId, userId);
			setTimeout(function(){
		       arrayUtil.objectSplice(self.boards, boardId);
		       delete self.roomHandle.boardId2userId[boardId];
		       delete self.roomHandle.annotation2boardId[boardId];
			   deferred.resolve();
			},200);
			return deferred.promise;
		}else{
			this.roomHandle.masterServer.removeBoardMsg(OperType.remove, boardId, userId);
			if(userId){
				//删除别人的白板,收不到updateboardmsg_remove_rep_success或updateboardmsg_remove_rep_error的回调,所以这里直接操作。by roymond 2024/11/21
				delete self.roomHandle.userid2boardsBy812[userId];
				delete self.roomHandle.boardId2userId[boardId];
				delete self.roomHandle.annotation2boardId[boardId];
				deferred.resolve();
			}else{
				var curTimeout = setTimeout(function(){
					var timeoutError = new Error(10000,'user.removeBoardById(), removeBoardMsg send time out');
					deferred.reject(timeoutError);
				},5000);
				
				this.addCallback(EngineCallback.updateboardmsg_remove_rep_success, function(retBoardId) {
					if(boardId == retBoardId) {
					   clearTimeout(curTimeout);
					   arrayUtil.objectSplice(self.boards, boardId);
					   delete self.roomHandle.boardId2userId[boardId];
					   delete self.roomHandle.annotation2boardId[boardId];
					   deferred.resolve();
					}
				});
				
				this.addCallback(EngineCallback.updateboardmsg_remove_rep_error, function(error,retBoardId) {
					if(boardId == retBoardId) {
					   clearTimeout(curTimeout);
					   deferred.reject(error);
					}
				});
				
			}
			
			return deferred.promise;
		}
	}


	/**
	 * @desc 关闭指定的共享白板
	 * @async
	 * @param {String} boardId - 白板Id
	 */
	User.prototype.closeBoardById = function(boardId) {
		var deferred = when.defer();
		
		var self = this;
		
		var board = this.getBoard(boardId);
		if (board.status == boardStatusEnum.close) {
			deferred.resolve();
			return deferred.promise;
		}
		
		var isSupper = updateBoardMsgRepCheck(avdEngineHandle.restServerInfo.boxVersion);
		if(!isSupper){
			this.roomHandle.masterServer.closeBoardMsg(OperType.close, boardId);
			setTimeout(function(){
				if (self.roomHandle.annotation2boardId[boardId]) {
					self.roomHandle.annotation2boardId[boardId].queueArr = [];
					for (var k in self.roomHandle.annotation2boardId[boardId].storageArr) {
						self.roomHandle.annotation2boardId[boardId].queueArr.push(self.roomHandle.annotation2boardId[boardId].storageArr[k]);
					}
				}
				board.status = boardStatusEnum.close;
				board.annotation = null;
				
			    deferred.resolve();
			},200);
			return deferred.promise;
		}else{
			this.roomHandle.masterServer.closeBoardMsg(OperType.close, boardId);
			var curTimeout = setTimeout(function(){
				var timeoutError = new Error(10000,'user.closeBoardById(), closeBoardMsg send time out');
				deferred.reject(timeoutError);
			},5000);
			
			this.addCallback(EngineCallback.updateboardmsg_close_rep_success, function(boardId) {
				if(board.id == boardId) {
				   clearTimeout(curTimeout);
				   
				   if (self.roomHandle.annotation2boardId[boardId]) {
						self.roomHandle.annotation2boardId[boardId].queueArr = [];
						for (var k in self.roomHandle.annotation2boardId[boardId].storageArr) {
							self.roomHandle.annotation2boardId[boardId].queueArr.push(self.roomHandle.annotation2boardId[boardId].storageArr[k]);
						}
				   }
				   board.status = boardStatusEnum.close;
				   board.annotation = null;
				   
			       deferred.resolve();
				}
			});
			
			this.addCallback(EngineCallback.updateboardmsg_close_rep_error, function(error,boardId) {
				if(board.id == boardId) {
				   clearTimeout(curTimeout);
				   deferred.reject(error);
				}
			});
			
			return deferred.promise;
			
		}
	}


	/**
	 * @desc 共享指定白板
	 * @async
	 * @param {String} boardId - 白板Id
	 */
	User.prototype.shareBoardById = function(boardId) {
        var deferred = when.defer();
        var self = this;
		var board = this.getBoard(boardId);
		if (board.status == boardStatusEnum.open) {
			deferred.resolve();
			return deferred.promise;
		}
		var isSupper = updateBoardMsgRepCheck(avdEngineHandle.restServerInfo.boxVersion);
		if(!isSupper){
			self.roomHandle.masterServer.shareBoardMsg(OperType.share, boardId, board);
			setTimeout(function(){
				board.status = boardStatusEnum.open;
				var annotation = self.roomHandle.annotation2boardId[boardId];
				if (annotation) {
					 /**
					 * desc: 防止有注释时重绘但此时annotation init还没完成zr对象为空导致zr.add报错的问题,让重绘在init完成之后有zr对象了再触发
					 * by: 王博 
					 * date: 2021/6/24
					 */
				    setTimeout(function(){
					    log.debug("==KKKKKKKKKKK222222222,annotation.queueArr.length:"+annotation.queueArr.length);
					   self.roomHandle.updateAnnotationDrow(annotation.queueArr);
					   deferred.resolve();
	                }, 50);
				}
			},200);
			return deferred.promise;
		}else{
			self.roomHandle.masterServer.shareBoardMsg(OperType.share, boardId, board);
			
			var curTimeout = setTimeout(function(){
				var timeoutError = new Error(10000,'user.shareBoardById(), shareBoardMsg send time out');
				deferred.reject(timeoutError);
			},5000);
			this.addCallback(EngineCallback.updateboardmsg_share_rep_success, function(boardId) {
				if(board.id == boardId) {
				   clearTimeout(curTimeout);
				   
				   board.status = boardStatusEnum.open;
				   var annotation = self.roomHandle.annotation2boardId[boardId];
				   if (annotation) {
						 /**
						 * desc: 防止有注释时重绘但此时annotation init还没完成zr对象为空导致zr.add报错的问题,让重绘在init完成之后有zr对象了再触发
						 * by: 王博 
						 * date: 2021/6/24
						 */
						setTimeout(function(){
						log.debug("==KKKKKKKKKKK33333333333,annotation.queueArr.length:"+annotation.queueArr.length);
						   self.roomHandle.updateAnnotationDrow(annotation.queueArr);
						   deferred.resolve();
		                }, 50);
				    }
				}
			});
			
			this.addCallback(EngineCallback.updateboardmsg_share_rep_error, function(error,boardId) {
				if(board.id == boardId) {
				   clearTimeout(curTimeout);
				   deferred.reject(error);
				}
			});
			
			return deferred.promise;
		}
	}


	/**
	 * @desc 删除自己所属的全部白板
	 * @param {String} userId - 白板创建者Id
	 */
	User.prototype.removeBoard = function() {
		var self = this;
		self.boards.forEach(function(board){
			delete self.roomHandle.boardId2userId[board.id];
			delete self.roomHandle.annotation2boardId[board.id];
			
			arrayUtil.objectSplice(self.boards, board.id);
		})
	}
	
	
	/**
	 * @desc 针对移动端H5打开前置摄像头
	 * @async
	 * @param {Object} element - 视频控件对象
	 */
	User.prototype.openMobileFrontVideo = function(element) {
        log.info("===video openMobileFrontVideo...")
		var deferred = when.defer();
		var self = this;
		
		//部分Adnroid手机(oppo realmeX2 Android11 微信8.0.31 ,红米K30i Android10 微信8.0.11 等)切换前后置摄像头无效
		//		var constraints = {
		//		    audio: false,
		//		    video:{ 'facingMode': "user" }
		//		};
        
		var video = self.videos[0];
		
        var constraints = {
            audio: false,
 	        video:{ 
                   facingMode: {
                      exact: "user"
                   }
            }
       };
	   
	   if (video.setType == ResolutionSetType.ideal) {
		    constraints.video.width = {
		    	ideal: video.resolution.width
		    };
		    constraints.video.height = {
		    	ideal: video.resolution.height
		    };
	   }else{
		   constraints.video.width = {
		   	exact: video.resolution.width
		   };
		   constraints.video.height = {
		   	exact: video.resolution.height
		   };
	   }
	  
	    constraints.video.frameRate = {
	     	ideal: video.frameRate
	    };
		
		log.info("===video openMobileFrontVideo(),constraints:" + JSON.stringify(constraints));
		navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
			log.debug("===video openMobileFrontVideo(),stream:",stream);
			
			currentVideoParameter.videoWidth= video.resolution.width;
			currentVideoParameter.videoHeight= video.resolution.height;
			currentVideoParameter.frameRate = video.frameRate;
			
			video.track = stream.getVideoTracks()[0];
			
			if (self.roomHandle.selfUser.stream == null) {
				self.roomHandle.selfUser.stream = stream;
			} else {
				self.roomHandle.selfUser.stream.addTrack(video.track);
			}
			
			video.isPubState = true;
			video.publishHandle(element).then(function() {
				log.info("===video openMobileFrontVideo(),预览前置摄像头及发布流成功,videoId:"+ video.id+",videoName:"+video.name+",userId:"+video.ownerId);
				deferred.resolve();
			}).otherwise(function(error) {
				video.isPubState = false;
				log.error("===video openMobileFrontVideo(),预览前置摄像头及发布视频流失败,videoId:"+ video.id+",videoName:"+video.name+",userId:"+video.ownerId+",error:"+JSON.stringify(error));
			    deferred.reject(error);
			});
			
		}).catch(function(error) {
		    log.error("===video openMobileFrontVideo() failed,error: ", error);
			video.isPubState = false;
			deferred.reject(error);
		});
		
		return deferred.promise;
	}
	
	
	/**
	* @desc 针对移动端H5关闭前置摄像头
	* @async
	*/
	User.prototype.closeMobileFrontVideo = function() {
        log.info("===video closeMobileFrontVideo...")
		var deferred = when.defer();
		
		var video = this.videos[0];
		video.unpreview();
		video.unpublish().then(function(){
            log.info("===video closeMobileFrontVideo success")
			deferred.resolve();
		}).otherwise(function(error){
            log.info("===video closeMobileFrontVideo error")
			deferred.reject(error);
		});
		return deferred.promise;
	}
	
	
	/**
	* @desc 针对移动端H5关闭后置摄像头
	* @async
	*/
	User.prototype.closeMobileBackVideo = function() {
        log.info("===video closeMobileBackVideo...")
		var deferred = when.defer();
		
		var video = this.videos[1];
		video.unpreview();
		video.unpublish().then(function(){
            log.info("===video closeMobileBackVideo success")
		    deferred.resolve();
		}).otherwise(function(error){
            log.info("===video closeMobileBackVideo error")
			deferred.reject(error);
		});
		return deferred.promise;	
	}
	
	
	/**
	* @desc 针对移动端H5打开后置摄像头
	* @async
	* @param {Object} element - 视频控件对象
	*/
	User.prototype.openMobileBackVideo = function(element) {
        log.info("===video openMobileBackVideo()...")
		var deferred = when.defer();
		var self = this;
		
		
		/**
		 * 手机H5打开后置摄像头,部分机型(华为mate60,p40,p30等)打开的是放大的长焦视频。本地做兼容处理逻辑如下:
		 * 当前操作系统为Android,摄像头个数>2,取最后一个摄像头作为后置。
		 * 存在的隐患或不确定:
		 *   1)小米\vivo目前测试都是返回2个摄像头,未来新款手机会不会返回超过2个摄像头不确定
		 *   2)摄像头列表的最后一个现作为后置摄像头,目前测试没有问题。未来新款手机会不会出现另类不确定
		 */
		var isCustomise = false;
		var cameraLen = 0;
		var customiseCameraId;
		var customiseCameraName;
		if(avdEngineHandle.detect.osName == "Android"){
			for(var key in avdEngineHandle.cameraMap) {
				var val = avdEngineHandle.cameraMap[key];
				cameraLen++;
				customiseCameraId = key;
				customiseCameraName = val;
			}
			if(cameraLen > 2){
				isCustomise = true; 
				log.debug("===video openMobileBackVideo(), detect.osName:Android, cameraLen:"+ cameraLen +", customiseCameraId:" + customiseCameraId+", customiseCameraName:"+customiseCameraName);
			}
		}
		
		
		//部分Adnroid手机(oppo realmeX2 Android11 微信8.0.31 ,红米K30i Android10 微信8.0.11 等)切换前后置摄像头无效
		//		var constraints = {
		//		    audio: false,
		//		    video:{ 'facingMode': "environment" }
		//		};

		var video = self.videos[1];
		
		var constraints = {
			audio: false,
			video:{ 
				facingMode: {
					exact: "environment"
				}
			}
		};
		
		if(isCustomise){
			constraints = {
				audio: false,
				video:{ 
					deviceId:{
						exact: customiseCameraId
					}
				}
			};
		}
		 
		if (video.setType == ResolutionSetType.ideal) {
			constraints.video.width = {
				ideal: video.resolution.width
			};
			constraints.video.height = {
				ideal: video.resolution.height
			};
		}else{
		   constraints.video.width = {
			exact: video.resolution.width
		   };
		   constraints.video.height = {
			exact: video.resolution.height
		   };
		}
			  
		 constraints.video.frameRate = {
			ideal: video.frameRate
		 };
		
		log.info("===video openMobileBackVideo(),constraints:" + JSON.stringify(constraints));
		navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
			log.debug("===video openMobileBackVideo(),stream:",stream);
			
			currentVideoParameter.videoWidth= video.resolution.width;
			currentVideoParameter.videoHeight= video.resolution.height;
			currentVideoParameter.frameRate = video.frameRate;
			
			video.track = stream.getVideoTracks()[0];
			
			if (self.roomHandle.selfUser.stream == null) {
				self.roomHandle.selfUser.stream = stream;
			} else {
				self.roomHandle.selfUser.stream.addTrack(video.track);
			}
			
			video.isPubState = true;
			video.publishHandle(element).then(function() {
				log.info("===video openMobileBackVideo(),预览后置摄像头及发布流成功,videoId:"+ video.id+",videoName:"+video.name+",userId:"+video.ownerId);
				deferred.resolve();
			}).otherwise(function(error) {
				video.isPubState = false;
				log.error("===video openMobileBackVideo(),预览后置摄像头及发布视频流失败,videoId:"+ video.id+",videoName:"+video.name+",userId:"+video.ownerId+",error:"+JSON.stringify(error));
				deferred.reject(error);
			});
			
		}).catch(function(error) {
			log.error("===video openMobileBackVideo() failed,error: ", error);
			video.isPubState = false;
			deferred.reject(error);
		});
		
		return deferred.promise;
	}
	
	
	/**
	* @desc 创建一个自定义的视频轨道
	* @async
	* @param {MediaStreamTrack}  mediaStreamTrack - 视频轨道
	* @param {String}  trackName - 视频轨道名称,非必填项,不填时默认为userName + "_custom_video"
	* 
	* @example 
	*  以从Canvas中获取流为例:
	*  var stream = customCanvas.captureStream();
	*  var track = stream.getVideoTracks()[0];
	*  user.createCustomVideoTrack(track).then(function(video){
	*	   video.publish().then(function(){});
	*  }).otherwise(function(error){});
	*/
	User.prototype.createCustomVideoTrack = function(mediaStreamTrack, trackName) {
		log.info("===video createCustomVideoTrack...");
		var deferred = when.defer();
		var self = this;
		if(!mediaStreamTrack){
			log.error('===video createCustomVideoTrack(),mediaStreamTrack params are mandatory and cannot be empty');
			var error = new Error(ErrorConstant.params_require);
			deferred.reject(error);
		}
		
		if(mediaStreamTrack instanceof MediaStreamTrack){
			var id = Math.uuid() + "_custom_video";
			var name = this.name + "_custom_video";
			if(trackName){
				name = trackName;
			}
			
			try{
				this.initVideo(id, name);
				setTimeout(function(){
					var video = self.getVideo(id);
					video.track = mediaStreamTrack;
					
					if (self.customStream == null) {
						self.customStream = new MediaStream();
					}	
					self.customStream.addTrack(mediaStreamTrack);
					
					deferred.resolve(video);
				},500);
			}catch(e){
				deferred.reject(e);
			}
		}else{
			log.error('===video createCustomVideoTrack(),mediaStreamTrack parameter type mismatch');
			var error = new Error(ErrorConstant.params_type_mismatch);
			deferred.reject(error);
		}
		return deferred.promise;
	}
	
	
	/**
	* @desc 删除一个自定义的视频轨道
	* @async
	* @param {String} videoId  视频设备ID
	*/
	User.prototype.deleteCustomVideoTrack = function(videoId) {
		log.info("===video deleteCustomVideoTrack...");
		var deferred = when.defer();
		var self = this;
		
		var video = self.getVideo(videoId);
		if(video){
			if(video.status == StreamStatus.published){
				video.unpreview();
				video.unpublish().then(function(){
				    log.info("===video deleteCustomVideoTrack, unpublish success");
					try{
						var customStream = self.customStream;
						if(customStream){
						    customStream.getTracks().forEach(function(track) {
							    if(video.track && track.id ==  video.track.id){
								   track.stop();
							    }
							});
						}		
						if(video.track){
							video.track.stop();
							video.track = null;
						} 
						
						self.deleteVideo(videoId);
						setTimeout(function(){
							deferred.resolve();
						},500);
					}catch(e){
						deferred.reject(e);
					}
				}).otherwise(function(error){
				    log.info("===video deleteCustomVideoTrack, unpublish  error");
					deferred.reject(error);
				});
			}else{
				try{
					self.deleteVideo(videoId);
					setTimeout(function(){
						deferred.resolve();
					},500);
				}catch(e){
					deferred.reject(e);
				}
			}
		}else{
			log.error("===video deleteCustomVideoTrack, video is null");
			deferred.reject();
		}
		return deferred.promise;
	}
	
	
	
	/**
	* @desc 创建一个自定义的音频轨道
	* @async
	* @param {MediaStreamTrack}  mediaStreamTrack - 音频轨道
	* @param {String}  trackName - 音频轨道名称,非必填项,不填时默认为userName + "_custom_audio"
	*/
	User.prototype.createCustomAudioTrack = function(mediaStreamTrack, trackName) {
		log.info("===audio createCustomAudioTrack...");
		var deferred = when.defer();
		var self = this;
		if(!mediaStreamTrack){
			log.error('===audio createCustomAudioTrack(),mediaStreamTrack params are mandatory and cannot be empty');
			var error = new Error(ErrorConstant.params_require);
			deferred.reject(error);
		}
		
		if(mediaStreamTrack instanceof MediaStreamTrack){
			var id = Math.uuid() + "_custom_audio";
			var name = this.name + "_custom_audio";
			if(trackName){
				name = trackName;
			}
			
			try{
				var audio = new Audio(id, name, this.roomHandle);
				audio.ownerId = this.id;
				this.customAudios.push(audio);
				this.eventEmitter.emit(UserCallback.microphone_status_notify, audio.status, audio.id, audio.name, this.id);
				
			    var microphoneUUID = this.roomHandle.setDeviceIdByUUID(2, id);
			    this.roomHandle.masterServer.addSpeakerMsg(microphoneUUID, audio);
				
				setTimeout(function(){
					audio.track = mediaStreamTrack;
					
					if (self.customStream == null) {
						self.customStream = new MediaStream();
					}	
					self.customStream.addTrack(mediaStreamTrack);
					
					deferred.resolve(audio);
				},500);
			}catch(e){
				deferred.reject(e);
			}
		}else{
			log.error('===aduio createCustomAudioTrack(),mediaStreamTrack parameter type mismatch');
			var error = new Error(ErrorConstant.params_type_mismatch);
			deferred.reject(error);
		}
		return deferred.promise;
	}
	
	/**
	* @desc 删除一个自定义的音频轨道
	* @async
	* @param {String} audioId  音频设备ID
	*/
	User.prototype.deleteCustomAudioTrack = function(audioId) {
		log.info("===audio deleteCustomAudioTrack...");
		var deferred = when.defer();
		var self = this;
		
		var audio = self.getCustomAudio(audioId);
		if(audio){
			if(audio.status == StreamStatus.published){
				audio.closeMicrophone().then(function(){
				    log.info("===audio deleteCustomAudioTrack, closeMicrophone success");
					try{
						var customStream = self.customStream;
						if(customStream){
						    customStream.getTracks().forEach(function(track) {
							    if(audio.track && track.id ==  audio.track.id){
								   track.stop();
							    }
							});
						}		
						if(audio.track){
							audio.track.stop();
							audio.track = null;
						} 
						
						self.deleteAudio(audioId);
						setTimeout(function(){
							deferred.resolve();
						},500);
					}catch(e){
						deferred.reject(e);
					}
				}).otherwise(function(error){
				    log.info("===audio deleteCustomAudioTrack, closeMicrophone  error");
					deferred.reject(error);
				});
			}else{
				try{
					self.deleteAudio(audioId);
					setTimeout(function(){
						deferred.resolve();
					},500);
				}catch(e){
					deferred.reject(e);
				}
			}
		}else{
			log.error("===audio deleteCustomAudioTrack, audio is null");
			deferred.reject();
		}
		return deferred.promise;
	}
	
	
	
	//兼容老服务器版本不校验白板功能授权(config/serverinfo接口返回的box_version值,如果>=3.1.7则开启支持白板功能授权校验)
	function supperBoardAuthCheck(boxVersion){
		var subjectVersion = "3.1.7";
		var isSupper = false;
		if(boxVersion){
			var ret = versionStringCompare(boxVersion, subjectVersion);
			if(ret >= 0){
				isSupper = true;
			}
		}
		if(!isSupper){
			log.info("===user 当前服务器版本不支持白板功能授权校验,客户端也将不启用校验。");
		}
		return isSupper;
	}
	
	
	//兼容老服务器版本不下发operType=update/share/close/remove的UPDATE_BOARD_MSG_REP,(config/serverinfo接口返回的box_version值,如果>=3.1.8则开始下发相关PDU)
	function updateBoardMsgRepCheck(boxVersion){
		var subjectVersion = "3.1.8";
		var isSupper = false;
		if(boxVersion){
			var ret = versionStringCompare(boxVersion, subjectVersion);
			if(ret >= 0){
				isSupper = true;
			}
		}
		if(!isSupper){
			log.info("===user 当前服务器版本不下发operType=update/share/close/remove的UPDATE_BOARD_MSG_REP PDU.");
		}
		return isSupper;
	}

	return User;
});