# 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 － 麦克风设备所属者ＩＤ
    	 *
    	 * function onMicrophoneStatusNotify(status, microphoneId, microphoneName, userId) {
    	 *
    	 * }
    	 *
    	 *
    	 *摄像头状态更新
    	 * param : status － 状态
    	 * param : cameraId － 摄像头设备Id
    	 * param : cameraName－ 摄像头设备名称
    	 * param : userId－ 摄像头设备所属者ＩＤ
    	 *
    	 * function onCameraStatusNotify(status, cameraId, cameraName, userId) {
    	 *
    	 * }
    	 *
    	 *桌面共享状态更新
    	 * param : status － 状态
    	 * param : cameraId － 桌面共享设备Id
    	 * param : cameraName－ 桌面共享设备名称
    	 * param : userId－ 桌面共享设置所属者ＩＤ
    	 *
    	 * function onScreenStatusNotify(status, screenId, screenName, userId) {
    	 *
    	 * }
    	 *
    	 *
    	 *
    	 * 摄像头数据更新
    	 * param : level － 级别
    	 * param : description － 描述
    	 * param : cameraId － 摄像头设备Id
    	 * param : cameraName－ 摄像头设备名称
    	 * param : userId－ 摄像头设备所属者ＩＤ
    	 * function onCameraDataNotify(level, description, cameraId, cameraName, userId) {
    	 *
    	 * }
    	 *
    	 *
    	 *桌面共享数据更新
    	 * param : level － 级别
    	 * param : description － 描述
    	 * param : screenId － 桌面共享设备Id
    	 * param : screenName－ 桌面共享设备名称
    	 * param : userId－ 桌面共享设置所属者ＩＤ
    	 *
    	 * function onScreenDataNotify(level, description, screenId, screenName, userId) {
    	 *
    	 * }
    	 *
    	 *
    	 *
    	 *发布视频的回调
    	 * param : videos － 发布的视频数组
    	 *function onPublishCameraNotify(videos) {
    	 *
    	 * }
    	 *
    	 * 取消发布视频的回调
    	 * param : video － 取消发布的视频
    	 *
    	 * function onUnpublishCameraNotify(video) {
    	 *
    	 * }
    	 *
    	 *
    	 * 订阅远端视频流反馈
    	 * param： stream － 远端视频流
    	 * param ：userId － 所属用户ＩＤ
    	 * param ：userName－ 所属用户名称
    	 * param ：cameraId－ 摄像头设备ＩＤ
    	 *
    	 *function onSubscribleCameraResult(stream, userId, userName,cameraId) {
    	 *
    	 * }
    	 *
    	 *
    	 * 取消订阅远端视频流反馈
    	 * param ：userId－ 所属用户ＩＤ
    	 * param ：userName－所属用户名称
    	 * param ： cameraId－摄像头设备ＩＤ
    	 *
    	 *function onUnsubscribleCameraResult(userId, userName,cameraId){
    	 *
    	 * }
    	 *
    	 *
    	 * 发布桌面共享的回调
    	 * param : screens － 发布的桌面共享数组
    	 *
    	 * function onPublishScreenNotify(screens){
    	 *
    	 * }
    	 *
    	 * 取消发布桌面共享的回调
    	 * param : screen － 取消发布的桌面共享
    	 *
    	 * function onUnpublishScreenNotify(screen) {
    	 *
    	 * }
    	 *
    	 *
    	 * 订阅远端桌面共享流反馈
    	 * param： stream － 远端桌面共享流
    	 * param ：userId － 所属用户ＩＤ
    	 * param ：userName－ 所属用户名称
    	 * param ：screenId－ 桌面共享设备ＩＤ
    	 * param ：width－ 分辨率宽度
    	 * param ：height－ 分辨率高度
    	 * param ：framerate－ 帧率
    	 *
    	 * function onSubscribleScreenResult(stream, userId, userName,screenId,width,height,framerate) {
    	 *
    	 * }
    	 *
    	 *
    	 * 取消订阅远端桌面共享流反馈
    	 * param ：userId－ 所属用户ＩＤ
    	 * param ：userName－所属用户名称
    	 * param ： cameraId－桌面共享设备ＩＤ
    	 *
    	 * function onUnsubscribleScreenResult(userId, userName,screenId){
    	 *
    	 * }
    	 *
    	 *
    	 *
    	 *发布音频的回调
    	 * param : audios － 发布的音频数组
    	 *function onPublishMicrophoneNotify(audios) {
    	 *
    	 * }
    	 *
    	 * 取消发布音频的回调
    	 * param : audio － 取消发布的音频
    	 *
    	 * function onUnpublishMicrophoneNotify(audio) {
    	 *
    	 * }
    	 *
    	 *
    	 *
    	 * 订阅远端音频流反馈
    	 * param：stream－ 远端音频流
    	 * param：userId－ 所属用户ＩＤ
    	 * param：userName－所属用户名称
    	 *
    	 *function onSubscribleMicrophoneResult(stream, userId, userName){
    	 *
    	 *}
    	 *
    	 *
    	 *
    	 *取消订阅远端音频流反馈
    	 *param ：userId－ 所属用户ＩＤ
    	 *param ： userName－所属用户名称
    	 *
    	 *function onUnsubscribleMicrophoneResult(userId, userName){
    	 *
    	 *}
    	 *
         *
         * * 订阅远端屏幕共享音频流反馈
    	 * param：stream－ 远端屏幕共享音频流
    	 * param：userId－ 所属用户ＩＤ
    	 * param：userName－所属用户名称
    	 *
    	 *function onSubscribleMicrophoneResult(stream, userId, userName){
    	 *
    	 *}
    	 *
    	 *
    	 *
    	 *取消订阅远端屏幕共享音频流反馈
    	 *param ：userId－ 所属用户ＩＤ
    	 *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;
    });