# stream/video.js

    ModuleBase.define("Video", ["Base", "Error"], function (Base, Error) {
    	/**
    	 * @desc 视频Video构造函数。
    	 * @constructor
    	 * @alias Video
    	 * @param {String} id - 设备Id
    	 * @param {String} name - 设备名称
    	 */
    	class Video extends Base {

    		constructor(id, name, roomHandle) {
    			super(id, name);

    			this.roomHandle = roomHandle;
    			this.level;
    			this.description;
    			this.resolutionSelect;
    			this.resolution = {
    				width: avdEngineHandle.defaultVideoParamsWidth,
    				height: avdEngineHandle.defaultVideoParamsHeight,
    			};
    			this.setType = avdEngineHandle.defaultVideoParamsResolutionSetType;

    			this.frameRate = avdEngineHandle.defaultVideoParamsFrameRate;

    			this.aspectRatio = 0;

    			this.cameraType;

    			//通过分辨率宽高计算得出的最大带宽值
    			this.maxBandwidth = null;

    			//应用层客户调用接口自行设置的最大带宽值
    			this.clientSetMaxBandwidth = null;

    			this.streamName; //导出时视频流名称
    			this.streamNickName; //导出时视频流昵称

    			this.isPubState = false; //防止重复发布，在发布过程中该状态为true

    			this.rtcRtpSenderIntervalId = null;
    			this.spatialLayerActiveList = [];
    			this.spatialLayerActiveDoing = false;

    			this.currentVideoQuality = null; //当前视频质量值。实现逻辑：一秒钟内取最后一次质量值进行分辨率码及流调整
    			this.changePublishVideoQualityInterval = null; //码流分辨率调整定时器，时间1秒

    			this.videoStreamControlTimer = null;
    			this.publishTimer = null;
    			this.unpublishTimer = null;
    			this.subscribleTimer = null;
    			this.unsubscribleTimer = null;

    			// 虚拟背景相关属性
    			this._virtualBgEnabled = false;  //虚拟背景是否已启用
    			this._virtualBgImage = null; //虚拟背景图片 URL 或颜色值
    			this._virtualBgOptions = {};  //虚拟背景配置选项
    			this._virtualBgProcessor = null;  //虚拟背景处理器实例
    			this._virtualBgTrack = null; //虚拟背景处理后的视频轨道
    			this._virtualBgOriginalStream = null; //虚拟背景处理前的原始视频流
    			this._virtualBgWasEnabled = false; //虚拟背景之前是否启用过
    			this._virtualBgSelfieMode = false; //虚拟背景自拍模式（镜像）
    			this._virtualBgModelSelection = 0; //虚拟背景模型选择（0:通用, 1:自拍）
    		};

    		/**
    		 * @desc 通过track生成新的MediaStream,虚拟背景启用时返回处理后的track
    		 * @returns {MediaStream | null}
    		 */
    		getNewStreamByTrack = function () {
    			var trackToUse = this._virtualBgEnabled && this._virtualBgTrack ? this._virtualBgTrack : this.track;
    			if (trackToUse && trackToUse instanceof MediaStreamTrack) {
    				try {
    					var newStream = new MediaStream();
    					newStream.addTrack(trackToUse);
    					return newStream;
    				} catch (error) {
    					log.error("===Video.getNewStreamByTrack(),Failed to create MediaStream. Error:", error);
    					return null;
    				}
    			} else {
    				log.error("===Video.getNewStreamByTrack(), Invalid or missing track:", trackToUse);
    				return null;
    			}
    		};

    		/**
    		 * @desc 设置摄像头的优先级别
    		 * @param {int} level -优先级别
    		 */
    		setLevel = function (level) {
    			if (typeof level !== 'number' || !Number.isInteger(level)) {
    				log.error(`===video.setLevel(), level must be an integer。level: ${level}`);
    			}else{
    				log.info(`===video.setLevel(), videoId: ${this.id}, level: ${level}`);
    				this.level = level;
    			}
    		};

    		/**
    		 * @desc 设置备注
    		 * @param {String} description - 备注
    		 */
    		setDescription = function (description) {
    			log.info(`===video.setDescription(), videoId: ${this.id}, description: ${description}`);
    			this.description = description;
    			this.saveVideoCustomConfig('description', description)
    		};

    		/**
    		 * @desc 设置导出时视频流名称
    		 * @param {String} streamName - 视频流名称
    		 */
    		setStreamName = function (streamName) {
    			log.info(`===video.setStreamName(), videoId: ${this.id}, streamName: ${streamName}`);
    			this.streamName = streamName;
    		};

    		/**
    		 * @desc 设置导出时视频流昵称
    		 * @param {String} streamNickName - 视频流昵称
    		 */
    		setStreamNickName = function (streamNickName) {
    			log.info(`===video.setStreamNickName(), videoId: ${this.id}, streamNickName: ${streamNickName}`);
    			this.streamNickName = streamNickName;
    		};

    		/**
    		 * @desc 设置分辨率
    		 * @param {String} resolution- 分辨率标识
    		 * @param {Object} resolutionSetType- 分辨率设置类型,可不填写,null时默认为期望即ResolutionSetType.ideal,也可填写强制,即ResolutionSetType.exact;
    		 */
    		setResolution = function (resolution, resolutionSetType) {
    			log.info(`===video.setResolution(), videoId: ${this.id}, resolution: ${resolution}, resolutionSetType: ${resolutionSetType}`);
    			this.resolutionSelect = resolution;
    			this.resolution = Resolution[resolution];

    			if (resolutionSetType) {
    				this.setType = resolutionSetType;
    			}
    		};

    		/**
    		 * @desc 设置分辨率
    		 * @param {int} width- 宽度
    		 * @param {int} height- 高度
    		 * @param {string} resolutionSetType- 分辨率设置类型,可不填写,null时默认为期望即ResolutionSetType.ideal,也可填写强制,即ResolutionSetType.exact;
    		 */
    		setResolutionWH = function (width, height, resolutionSetType, isInnerCall) {
    			log.info(`===video.setResolutionWH(), videoId: ${this.id}, width: ${width}, height: ${height}, resolutionSetType: ${resolutionSetType}`);

    			this.resolutionSelect = width;

    			var resolutionObject = {};
    			resolutionObject.order = 1;
    			resolutionObject.width = width;
    			resolutionObject.height = height;
    			if (!isInnerCall) {
    				this.resolution = resolutionObject;
    				this.saveVideoCustomConfig('width', width)
    				this.saveVideoCustomConfig('height', height)
    				if (resolutionSetType != undefined) {
    					this.setType = resolutionSetType;
    					this.saveVideoCustomConfig('setType', resolutionSetType)
    				}
    			}

    		}

    		/**
    		 * @desc 设置帧率
    		 * @param {String} frameRate- 帧率
    		 */
    		setFrameRate = function (frameRate) {
    			log.info(`===video.setFrameRate(), videoId: ${this.id}, frameRate: ${frameRate}`);
    			if (typeof (frameRate) == 'string') {
    				frameRate = Number(frameRate);
    			}
    			this.frameRate = frameRate;

    			this.saveVideoCustomConfig('frameRate', frameRate)
    		};

    		/**
    		 * @desc 设置分辨率宽高比
    		 * @param {Object} aspectRatio- 分辨率宽高比(1.7777777778, 1.3333333333),16:9或4:3时会开启强制
    		 */
    		setAspectRatio = function (aspectRatio) {
    			log.info(`===video.setAspectRatio(), videoId: ${this.id}, aspectRatio: ${aspectRatio}`);
    			if (typeof (aspectRatio) == 'string') {
    				aspectRatio = Number(aspectRatio);
    			}
    			this.aspectRatio = aspectRatio;

    			this.saveVideoCustomConfig('aspectRatio', aspectRatio)
    		}

    		/**
    		 * @desc 应用生效更新后的视频约束(分辨率、帧率等)
    		 */
    		applyConstraints = function () {
    			if (this.track) {
    				var constraints = getApplyConstraints(this);
    				this.track.applyConstraints(constraints);
    			}
    		}

    		/**
    		 * 视频打开时，分辨率即时更新生效，且通知给服务器
    		 * @param {int} width
    		 * @param {int} height
    		 * @param {string} resolutionSetType- 分辨率设置类型,可不填写,null时默认为期望即ResolutionSetType.ideal,也可填写强制,即ResolutionSetType.exact;
    		 */
    		applyConstraintsWH = function (width, height, resolutionSetType) {
    			if (this.track) {
    				var tempResolutionSetType = resolutionSetType == ResolutionSetType.ideal ? ResolutionSetType
    					.ideal : ResolutionSetType.exact;
    				this.setResolutionWH(width, height, tempResolutionSetType);
    				var constraints = getApplyConstraints(this);
    				this.track.applyConstraints(constraints);

    				var maxBandwidth = this.maxBandwidth;
    				if (this.clientSetMaxBandwidth) {
    					maxBandwidth = this.clientSetMaxBandwidth;
    				}
    				this.sendVideoStreamControl(maxBandwidth, this.resolution.width, this.resolution.height);
    			}
    		}

    		/**
    		 * @desc 设置带宽
    		 * @param {int} maxBandwidth- 最大带宽
    		 */
    		setBandwidth = function (maxBandwidth) {
    			log.debug("===video.setBandwidth(),maxBandwidth:" + maxBandwidth + "Kpbs, videoId:" + this.id +
    				", userId:" + this.ownerId);
    			var self = this;

    			if ('RTCRtpSender' in window && 'setParameters' in window.RTCRtpSender.prototype) {
    				var sender = self.RTCRtpSender;
    				if (sender) {
    					var parameters = sender.getParameters();
    					//console.log("===video.setBandwidth(),parameters:",parameters);
    					if (!parameters.encodings) {
    						parameters.encodings = [{}];
    					} else if (parameters.encodings == []) {
    						parameters.encodings[0] = {};
    					}

    					////如果设置为unlimited
    					if (!maxBandwidth || maxBandwidth == 0) {
    						for (var i = 0; i < parameters.encodings.length; i++) {
    							delete parameters.encodings[i].maxBitrate;
    						}
    					} else {
    						if (!avdEngineHandle.isFirefox) {
    							var len = parameters.encodings.length;
    							if (len == 1) {
    								parameters.encodings[0].maxBitrate = maxBandwidth * 1000;
    							} else if (len == 2) {
    								parameters.encodings[0].maxBitrate = maxBandwidth * 500;
    								parameters.encodings[1].maxBitrate = maxBandwidth * 1000;
    							} else if (len == 3) {
    								parameters.encodings[0].maxBitrate = maxBandwidth * 250;
    								parameters.encodings[1].maxBitrate = maxBandwidth * 500;
    								parameters.encodings[2].maxBitrate = maxBandwidth * 1000;
    							}
    						}
    						if (avdEngineHandle.isFirefox) {
    							var len = parameters.encodings.length;
    							if (len == 1) {
    								parameters.encodings[0].maxBitrate = maxBandwidth * 1000;
    							} else if (len == 2) {
    								parameters.encodings[0].maxBitrate = maxBandwidth * 1000;
    								parameters.encodings[1].maxBitrate = maxBandwidth * 500;
    							} else if (len == 3) {
    								parameters.encodings[0].maxBitrate = maxBandwidth * 1000;
    								parameters.encodings[1].maxBitrate = maxBandwidth * 500;
    								parameters.encodings[2].maxBitrate = maxBandwidth * 250;
    							}
    						}
    					}

    					sender.setParameters(parameters).then(function () {
    						self.clientSetMaxBandwidth = maxBandwidth;
    						log.info("===video.setBandwidth(),带宽设置成功,clientSetMaxBandwidth:" +
    							maxBandwidth + "Kpbs, videoId:" + self.id + ", userId:" + self
    								.ownerId);

    						self.sendVideoStreamControl(maxBandwidth, self.resolution.width, self
    							.resolution.height);
    					}).catch(function (e) {
    						log.error("===video.setBandwidth(),带宽设置失败,clientSetMaxBandwidth:" +
    							maxBandwidth + "Kpbs, videoId:" + self.id + ", userId:" + self
    								.ownerId + ",error:", e);
    					});
    				} else {
    					//设备打开前，设置带宽的处理。
    					self.clientSetMaxBandwidth = maxBandwidth;
    					log.debug("===video.setBandwidth(),RTCRtpSender is null,clientSetMaxBandwidth:" +
    						maxBandwidth + "Kpbs, videoId:" + self.id + ", userId:" + self.ownerId);
    				}
    			}
    		};

    		/**
    		 * @description 发PDU消息给服务器,如可以实现1080P的码流达到10M
    		 * @async
    		 * @param {Object} maxBandwidth 最大带宽
    		 * @param {int} width 宽
    		 * @param {int} height  高
    		 */

    		sendVideoStreamControl = function (maxBandwidth, width, height) {
    			log.info("===video.sendVideoStreamControl(),maxBandwidth:" + maxBandwidth + "Kpbs, width:" +
    				width + ", height:" + height + ", videoId:" + this.id + ", nodeId:" + this.roomHandle
    					.selfUser.nodeId);

    			var deferred = when.defer();

    			var self = this;

    			var orderId = getRandomNum(50000, 51000); //用于VideoStreamControlReq中的ID
    			var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, self.id);
    			self.roomHandle.masterServer.sendVideoStreamControl(cameraUUID, maxBandwidth, width, height,
    				orderId);

    			clearTimeout(self.videoStreamControlTimer);
    			self.videoStreamControlTimer = setTimeout(function () {
    				log.error("===video.sendVideoStreamControl(),带宽设置发送服务器端超时. maxBandwidth:" +
    					maxBandwidth + "Kpbs, width:" + width + ", height:" + height +
    					", resoucceId:" + cameraUUID + ", nodeId:" + self.roomHandle.selfUser.nodeId
    				);
    				deferred.reject(new Error(ErrorConstant.operate_timeout.code,
    					'VIDEO_STREAM_CONTROL_REQ' + ErrorConstant.operate_timeout.message))
    			}, 3000)

    			self.roomHandle.addCallback(UserCallback.video_stream_control_rep_success, function (id) {
    				if (id == orderId) {
    					clearTimeout(self.videoStreamControlTimer);
    					log.info("===video.sendVideoStreamControl(), 带宽设置发送服务器端成功. maxBandwidth:" +
    						maxBandwidth + "Kpbs,width:" + width + ", height:" + height +
    						", resoucceId:" + cameraUUID + ", nodeId:" + self.roomHandle.selfUser
    							.nodeId);
    					deferred.resolve();
    				}
    			});

    			self.roomHandle.addCallback(UserCallback.video_stream_control_rep_error, function (error, id) {
    				if (id == orderId) {
    					clearTimeout(self.videoStreamControlTimer);
    					log.error("===video.sendVideoStreamControl(),带宽设置发送服务器端失败. maxBandwidth:" +
    						maxBandwidth + "Kpbs, width:" + width + ", height:" + height +
    						", resoucceId:" + cameraUUID + ", nodeId:" + self.roomHandle.selfUser
    							.nodeId);
    					deferred.reject(error);
    				}
    			});
    		}

    		/**
    		 * @desc 预览摄像头。
    		 * @async
    		 * @param {Object} element - 视频控件对象
    		 */
    		preview = function (element) {
    			log.info("===video.preview()");
    			var deferred = when.defer();

    			var self = this;
    			// var error = doFsCheck(FTConstant.video, Error)
    			// if(error) {
    			// 	deferred.reject(error);
    			// 	return deferred.promise;
    			// }

    			// var resInt = parseInt(self.resolutionSelect);
    			// if(resInt > 640) {
    			// 	var error = doFsCheck(FTConstant.video_hd, Error)
    			// 	if(error) {
    			// 		deferred.reject(error);
    			// 		return deferred.promise;
    			// 	}
    			// }

    			if (self.track) {
    				if (self.status != StreamStatus.published) {
    					self.status = StreamStatus.opened;
    				}
    				self.element = element;
    				element.srcObject = self.getNewStreamByTrack();
    				deferred.resolve();
    			} else {
    				obtainUserMedia(null, self).then(function (stream) {
    					self.track = stream.videoStream.getVideoTracks()[0];
    					if (self.roomHandle.selfUser.stream == null) {
    						self.roomHandle.selfUser.stream = stream.videoStream;
    					} else {
    						self.roomHandle.selfUser.stream.addTrack(self.track);
    					}
    					self.status = StreamStatus.opened;
    					self.element = element;
    					element.srcObject = self.getNewStreamByTrack();

    					deferred.resolve();
    				}).otherwise(function (e) {
    					deferred.reject(e);
    				});
    			}
    			return deferred.promise;
    		};

    		/**
    		 * @desc 取消预览摄像头。
    		 */
    		unpreview = function () {
    			log.info("===video.unpreview()");
    			var self = this;

    			if (self._virtualBgProcessor) {
    				self._virtualBgProcessor.close();
    				self._virtualBgProcessor = null;
    				self._virtualBgEnabled = false;
    				self._virtualBgTrack = null;
    			}
    			if (self.element) {
    				self.element.srcObject = null;
    				self.element = null;
    			}
    			self._virtualBgOriginalStream = null;
    			self.status = StreamStatus.init;
    		};

    		/**
    		 * @desc 发布视频流。
    		 * @async
    		 * @param {boolean} isRejoinCall 是否是重新加入会议。
    		 */
    		publish = function (isRejoinCall) {
    			log.info("===video.publish()");
    			var deferred = when.defer();
    			var self = this;
    			if (self.roomHandle.roomJoinState != RoomStateEnum.CONNECTED) {
    				var err = new Error(ErrorConstant.room_state_invalid);
    				log.debug("===video publish() 发布视频流异常, room state: ", self.roomHandle.roomJoinState);
    				deferred.reject(err);
    				return deferred.promise;
    			}

    			if (self.isPubState) {
    				log.debug("===video publish(),isPubState =true to return");
    				deferred.resolve();
    				return deferred.promise;
    			}

    			self.isPubState = true;
    			this.publishHandle(null, isRejoinCall).then(function () {
    				log.info("===video publish(),发布视频流成功,Id:" + self.id + ",name：" + self.name +
    					",ownerId:" + self.ownerId);
    				avdEngineHandle.loggerReport.info("video publish(),发布视频流成功,Id:" + self.id +
    					"，设备名称：" + self.name + ",ownerId:" + self.ownerId);

    				deferred.resolve();
    			}).otherwise(function (error) {
    				self.isPubState = false;
    				log.error("===video publish(),发布视频流异常,Id:" + self.id + ",name：" + self.name +
    					",ownerId:" + self.ownerId + ",Error:" + JSON.stringify(error));
    				avdEngineHandle.loggerReport.error(
    					"video publish(),The published video stream is abnormal , Id:" + self.id +
    					",name：" + self.name + ",ownerId:" + self.ownerId + ",Error:" + JSON
    						.stringify(error));

    				deferred.reject(error);
    			});
    			return deferred.promise;
    		};

    		/**
    		 * @desc 预览摄像头及发布流
    		 * @async
    		 * @param {Object} element - 视频控件对象
    		 * @param {Object} isRejoinCall - 是否为重新加入会议
    		 */
    		previewAndPublish = function (element, isRejoinCall) {
    			log.info("===video.previewAndPublish()");
    			var deferred = when.defer();
    			var self = this;
    			if (self.roomHandle.roomJoinState != RoomStateEnum.CONNECTED) {
    				var errObj = ErrorConstant.room_state_invalid
    				errObj.message += ("（State: " + self.roomHandle.roomJoinState + "）")
    				var err = new Error(errObj);
    				log.debug("===video publish() 发布视频流异常, room state: " + self.roomHandle.roomJoinState);
    				deferred.reject(err);
    				return;
    			}
    			avdEngineHandle.loggerReport.debug("previewAndPublish()触发...");

    			// var error = doFsCheck(FTConstant.video, Error)
    			// if(error) {
    			// 	log.error(error);
    			// 	deferred.reject(error);
    			// 	return deferred.promise;
    			// }

    			// var resInt = parseInt(this.resolutionSelect);
    			// if(resInt > 640) {
    			// 	var error = doFsCheck(FTConstant.video_hd, Error)
    			// 	if(error) {
    			// 		log.error("===video.previewAndPublish(),error:",error);
    			// 		deferred.reject(error);
    			// 		return deferred.promise;
    			// 	}
    			// }

    			if (self.isPubState) {
    				log.debug("===video previewAndPublish(),isPubState =true to return");
    				deferred.resolve();
    				return deferred.promise;
    			}

    			self.isPubState = true;
    			this.publishHandle(element, isRejoinCall).then(function () {
    				log.info("===video previewAndPublish(),预览摄像头及发布流成功,videoId:" + self.id +
    					",videoName：" + self.name + ",userId:" + self.ownerId);
    				avdEngineHandle.loggerReport.info("video previewAndPublish(),预览摄像头及发布流成功,videoId:" +
    					self.id + ",videoName：" + self.name + ",userId:" + self.ownerId);

    				deferred.resolve();
    			}).otherwise(function (error) {
    				self.isPubState = false;
    				log.error("===video previewAndPublish(),预览摄像头及发布视频流异常,videoId:" + self.id +
    					",videoName：" + self.name + ",userId:" + self.ownerId + ",error:" + JSON
    						.stringify(error));
    				avdEngineHandle.loggerReport.error(
    					"video previewAndPublish(), Previewing cameras and publishing video streams are abnormal, videoId:" +
    					self.id + ",videoName：" + self.name + ",userId:" + self.ownerId +
    					",error:" + JSON.stringify(error));

    				deferred.reject(error);
    			});

    			return deferred.promise;
    		};

    		/**
    		 * publish() and previewAndPublish() 的实现
    		 * @param {Object} element video控件
    		 * @param {Boolean} isRejoinCall 是否是重新加会（重连）的时候调用
    		 * @ignore
    		 */

    		publishHandle = function (element, isRejoinCall) {
    			var deferred = when.defer();
    			var orderId = getRandomNum(10000, 11000); //用于PubRoomResourceMsgRep中的ID

    			var self = this;
    			var bit = self.roomHandle.calInitVideoBitrate(self.resolution.width, self.resolution.height);
    			self.maxBandwidth = bit.maxBit;

    			if (self.status == StreamStatus.init) {
    				if (self.track) {
    					var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, self.id);
    					var videoStream = self.getNewStreamByTrack();
    					self.roomHandle.masterServer.videoPublishHandle(cameraUUID, videoStream, orderId);

    					clearTimeout(self.publishTimer);
    					self.publishTimer = setTimeout(function () {
    						self.isPubState = false;
    						log.debug(
    							"===video publishHandle(status == StreamStatus.init,track!=null),pub_roomresourcemsg_rep timeout"
    						);
    						deferred.reject(new Error(ErrorConstant.operate_timeout.code,
    							'Publish video' + ErrorConstant.operate_timeout.message))
    					}, 3000)

    					self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_success, function (
    						id) {
    						if (id == orderId) {
    							clearTimeout(self.publishTimer);

    							if (element != undefined) {
    								self.status = StreamStatus.opened;
    								self.element = element;
    								element.srcObject = videoStream;
    							}

    							self.status = StreamStatus.published;
    							if (!self.roomHandle.hasObject(self.roomHandle.pubVideos, self)) {
    								self.roomHandle.pubVideos.push(self);
    							}

    							log.info(
    								"===video previewAndPublish(),callback camera_status_notify,self.track != null, status:" +
    								self.status + ",videoId:" + self.id + ",videoName：" + self
    									.name + ",userId:" + self.ownerId);
    							self.roomHandle.selfUser.eventEmitter.emit(UserCallback
    								.camera_status_notify, self.status, self.id, self.name, self
    									.roomHandle.selfUser.id);

    							deferred.resolve();
    						}
    					});

    					self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_error, function (
    						error, id) {
    						if (id == orderId) {
    							clearTimeout(self.publishTimer);
    							deferred.reject(error);
    						}
    					});
    				} else {
    					obtainUserMedia(null, self).then(function (stream) {
    						self.track = stream.videoStream.getVideoTracks()[0];

    						if (self.roomHandle.selfUser.stream == null) {
    							self.roomHandle.selfUser.stream = stream.videoStream;
    						} else {
    							self.roomHandle.selfUser.stream.addTrack(self.track);
    						}
    						self.status = StreamStatus.opened;
    						var videoStream = self.getNewStreamByTrack();
    						var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, self.id);
    						self.roomHandle.masterServer.videoPublishHandle(cameraUUID, videoStream,
    							orderId);

    						clearTimeout(self.publishTimer);
    						self.publishTimer = setTimeout(function () {
    							self.isPubState = false;
    							log.debug(
    								"===video publishHandle(status == StreamStatus.init,track==null),pub_roomresourcemsg_rep timeout"
    							);
    							deferred.reject(new Error(ErrorConstant.operate_timeout.code,
    								'Publish video' + ErrorConstant.operate_timeout
    									.message))
    						}, 3000)

    						self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_success,
    							function (id) {
    								if (id == orderId) {
    									clearTimeout(self.publishTimer);

    									if (element != undefined) {
    										self.status = StreamStatus.opened;
    										self.element = element;
    										element.srcObject = videoStream;
    									}

    									self.status = StreamStatus.published;

    									if (!self.roomHandle.hasObject(self.roomHandle.pubVideos,
    										self)) {
    										self.roomHandle.pubVideos.push(self);
    									}

    									log.info(
    										"===video previewAndPublish(),callback camera_status_notify,self.track= null, status:" +
    										self.status + ",videoId:" + self.id +
    										",videoName：" + self.name + ",userId:" + self
    											.ownerId);
    									self.roomHandle.selfUser.eventEmitter.emit(UserCallback
    										.camera_status_notify, self.status, self.id, self
    										.name, self.roomHandle.selfUser.id);
    									deferred.resolve();
    								}
    							});

    						self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_error,
    							function (error, id) {
    								if (id == orderId) {
    									clearTimeout(self.publishTimer);
    									deferred.reject(error);
    								}
    							});
    					}).otherwise(function (e) {
    						deferred.reject(e);
    					});
    				}
    			} else if (self.status == StreamStatus.opened) {
    				if (self.track) {
    					var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, self.id);
    					self.roomHandle.masterServer.videoPublishHandle(cameraUUID, self.getNewStreamByTrack(),
    						orderId);

    					clearTimeout(self.publishTimer);
    					self.publishTimer = setTimeout(function () {
    						self.isPubState = false;
    						log.debug(
    							"===video publishHandle(status == StreamStatus.opened,track!=null),pub_roomresourcemsg_rep timeout"
    						);
    						deferred.reject(new Error(ErrorConstant.operate_timeout.code,
    							'Publish video' + ErrorConstant.operate_timeout.message))
    					}, 3000)

    					self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_success, function (
    						id) {
    						if (id == orderId) {
    							clearTimeout(self.publishTimer);

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

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

    				} else {
    					obtainUserMedia(null, self).then(function (stream) {
    						self.track = stream.videoStream.getVideoTracks()[0];

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

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

    						clearTimeout(self.publishTimer);
    						self.publishTimer = setTimeout(function () {
    							self.isPubState = false;
    							log.debug(
    								"===video publishHandle(status == StreamStatus.opened,track==null),pub_roomresourcemsg_rep timeout"
    							);
    							deferred.reject(new Error(ErrorConstant.operate_timeout.code,
    								'Publish video' + ErrorConstant.operate_timeout
    									.message))
    						}, 3000);

    						self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_success,
    							function (id) {
    								if (id == orderId) {
    									clearTimeout(self.publishTimer);

    									self.status = StreamStatus.published;

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

    									deferred.resolve();
    								}
    							});

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

    				}

    			} else if (self.status == StreamStatus.published && isRejoinCall) {
    				log.debug("===TTTTTTTTT00000000000000");
    				var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, self.id);
    				self.roomHandle.masterServer.videoPublishHandle(cameraUUID, self.getNewStreamByTrack(),
    					orderId);

    				clearTimeout(self.publishTimer);
    				self.publishTimer = setTimeout(function () {
    					self.isPubState = false;
    					log.debug(
    						"===video publishHandle(status == StreamStatus.opened,&& room rejoin or reconnecting),pub_roomresourcemsg_rep timeout"
    					);
    					deferred.reject(new Error(ErrorConstant.operate_timeout.code, 'Publish video' +
    						ErrorConstant.operate_timeout.message))
    				}, 3000)

    				self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_success, function (id) {
    					if (id == orderId) {
    						clearTimeout(self.publishTimer);
    						self.roomHandle.selfUser.eventEmitter.emit(UserCallback
    							.camera_status_notify, self.status, self.id, self.name, self
    								.roomHandle.selfUser.id);
    						deferred.resolve();
    					}
    				});

    				self.roomHandle.addCallback(EngineCallback.pub_roomresourcemsg_rep_error, function (error,
    					id) {
    					if (id == orderId) {
    						log.debug(
    							"===video publishHandle(status == StreamStatus.opened,&& && room rejoin or reconnecting),pub_roomresourcemsg_rep_error"
    						);
    						clearTimeout(self.publishTimer);
    						deferred.reject(error);
    					}
    				});
    			} else {
    				log.debug("===video publishHandle() status: " + self.status);
    			}

    			return deferred.promise;
    		}

    		/**
    		 * @desc 取消发布视频流。
    		 * @async
    		 */
    		unpublish = function () {
    			log.info("===video.unpublish()");

    			var deferred = when.defer();
    			var orderId = getRandomNum(11001, 12000);

    			var self = this;

    			self.maxBandwidth = null;

    			if (this.status == StreamStatus.published || (this.status == StreamStatus.init && (this.track ||
    				this.track != undefined))) {

    				self.isPubState = false;

    				arrayUtil.objectSplice(self.roomHandle.pubVideos, this.id);

    				this.status = StreamStatus.init;

    				var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, this.id);
    				self.roomHandle.masterServer.videoUnpublishHandle(cameraUUID, this.getNewStreamByTrack(),
    					orderId);

    				clearTimeout(self.unpublishTimer);
    				self.unpublishTimer = setTimeout(function () {
    					self.isPubState = false;
    					log.debug("===video unpublish, unpub_roomresourcemsg_rep timeout");
    					deferred.reject(new Error(ErrorConstant.operate_timeout.code,
    						'Unpublish video' + ErrorConstant.operate_timeout.message))
    				}, 3000);

    				self.roomHandle.addCallback(EngineCallback.unpub_roomresourcemsg_rep_success, function (id) {
    					if (id == orderId) {
    						clearTimeout(self.unpublishTimer);

    						//第三方导入流unpub时，不对本地track清场，在user.deleteCustomVideoTrack()时统一清场。
    						if (self.id.indexOf('custom_video') !== -1) {
    							var virtualBgWasActive = self._virtualBgWasEnabled;

    							if (!virtualBgWasActive) {
    								var stream = self.roomHandle.selfUser.stream;
    								if (stream) {
    									stream.getTracks().forEach(function (track) {
    										if (track.id == self.track.id) {
    											track.stop();
    										}
    									});
    								}
    							}

    							if (self.element) {
    								if (!virtualBgWasActive) {
    									var elementStream = self.element.srcObject;
    									if (elementStream) {
    										elementStream.getTracks().forEach(function (track) {
    											track.stop();
    										});
    									}
    									self.element.srcObject = null;
    									self.element = null;
    								}
    							}

    							if (!virtualBgWasActive && self.track) {
    								self.track.stop();
    								self.track = null;
    							}
    							self._virtualBgWasEnabled = false;
    							self.roomHandle.currBandwidth = null;
    						}

    						log.info("===video unpublish(),取消发布视频流成功,videoId:" + self.id +
    							",videoName：" + self.name + ",userId:" + self.ownerId);
    						avdEngineHandle.loggerReport.info("video unpublish(),取消发布视频流成功,videoId:" +
    							self.id + ",videoName：" + self.name + ",userId:" + self.ownerId);
    						self.roomHandle.selfUser.eventEmitter.emit(UserCallback
    							.camera_status_notify, self.status, self.id, self.name, self
    								.roomHandle.selfUser.id);
    						setTimeout(function () {
    							deferred.resolve();
    						}, 50);
    					}
    				});

    				self.roomHandle.addCallback(EngineCallback.unpub_roomresourcemsg_rep_error, function (error,
    					id) {
    					if (id == orderId) {
    						clearTimeout(self.unpublishTimer);
    						log.error("===video unpublish, unpub_roomresourcemsg_rep_error,videoId:" +
    							self.id + ",videoName：" + self.name + ",userId:" + self.ownerId);
    						deferred.reject(error);
    					}
    				});
    			}

    			return deferred.promise;
    		};

    		/**
    		 * @desc 订阅视频流
    		 * @async
    		 * @param {int} videoQuality- 视频质量举值，可以不填写，默认为VideoQualityType.high
    		 */
    		subscrible = function (videoQuality) {
    			log.info("===video subscrible(),订阅视频流,videoId:" + this.id + ",userId:" + this.ownerId +
    				",videoQuality:" + videoQuality);
    			avdEngineHandle.loggerReport.info("video subscrible(),订阅视频流,videoId:" + this.id + ",userId:" +
    				this.ownerId + ",videoQuality:" + videoQuality);

    			var deferred = when.defer();

    			var orderId = getRandomNum(12001, 13000); //用于subRoomResourceMsgRep中的ID

    			var self = this;

    			if (videoQuality) {
    				self.roomHandle.masterServer.videoSubscribleHandle(self.resourceInfo, videoQuality,
    					orderId);
    			} else {
    				self.roomHandle.masterServer.videoSubscribleHandle(self.resourceInfo, VideoQualityType.high,
    					orderId);
    			}

    			clearTimeout(self.subscribleTimer);
    			self.subscribleTimer = setTimeout(function () {
    				log.debug("===video subscrible, sub_roomresourcemsg_rep_success timeout");
    				deferred.reject(new Error(ErrorConstant.operate_timeout.code, 'Subscribe video' +
    					ErrorConstant.operate_timeout.message))
    			}, 3000);

    			self.roomHandle.addCallback(EngineCallback.sub_roomresourcemsg_rep_success, function (id) {
    				if (id == orderId) {
    					clearTimeout(self.subscribleTimer);

    					setTimeout(function () {
    						log.info("===video subscrible(),订阅视频流成功,videoId:" + self.id +
    							",userId:" + self.ownerId + ",videoQuality:" + videoQuality);
    						deferred.resolve();
    					}, 50);
    				}
    			});

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

    			return deferred.promise;
    		};

    		/**
    		 * @desc 取消订阅视频流。
    		 * @async
    		 */
    		unsubscrible = function () {
    			log.info("===video unsubscrible(),取消订阅视频流,videoId:" + this.id + ",userId:" + this.ownerId);
    			avdEngineHandle.loggerReport.info("video unsubscrible(),取消订阅视频流,videoId:" + this.id +
    				",userId:" + this.ownerId);

    			var deferred = when.defer();
    			var orderId = getRandomNum(13001, 14000);

    			var self = this;

    			self.roomHandle.masterServer.videoUnsubscribleHandle(self.resourceInfo, orderId);

    			clearTimeout(self.unsubscribleTimer);
    			self.unsubscribleTimer = setTimeout(function () {
    				log.debug("===video unsubscrible, unsub_roomresourcemsg_rep_success timeout");
    				deferred.reject(new Error(ErrorConstant.operate_timeout.code, 'Unsubscribe video' +
    					ErrorConstant.operate_timeout.message))
    			}, 3000);

    			self.roomHandle.addCallback(EngineCallback.unsub_roomresourcemsg_rep_success, function (id) {
    				if (id == orderId) {
    					clearTimeout(self.unsubscribleTimer);

    					setTimeout(function () {
    						log.info("===video unsubscrible(),取消订阅视频流成功,videoId:" + self.id +
    							",userId:" + self.ownerId);
    						deferred.resolve();
    					}, 50);
    				}
    			});

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

    			return deferred.promise;
    		};

    		/**
    		 * @desc 禁视频
    		 */
    		muteCamera = function () {
    			log.info("===video.muteCamera()");
    			var self = this;
    			if (this.status == StreamStatus.published) {
    				this.status = StreamStatus.muted;
    				self.roomHandle.selfUser.eventEmitter.emit(UserCallback.camera_status_notify, this.status,
    					this.id, this.name, self.roomHandle.selfUser.id);

    				var stream = self.roomHandle.selfUser.stream;
    				stream.getTracks().forEach(function (track) {
    					if (track.id == self.track.id) {
    						track.enabled = false;
    					}
    				});

    				if (self.element) {
    					var elementStream = self.element.srcObject;
    					if (elementStream) {
    						elementStream.getTracks().forEach(function (track) {
    							if (track.id == self.track.id) {
    								track.enabled = false;
    							}
    						});
    					}
    				}

    				if (self.track) {
    					self.track.enabled = false;
    				}

    				var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, this.id);
    				self.roomHandle.masterServer.updateWebcamMsg(self.roomHandle.selfUser.nodeId, cameraUUID,
    					this);

    				log.info("===video muteCamera(),禁视频. Id:" + self.id + ",ownerId:" + self.ownerId +
    					",status:" + self.status);
    				avdEngineHandle.loggerReport.info("video muteCamera(),禁视频. Id:" + self.id + ",ownerId:" +
    					self.ownerId + ",status:" + self.status);
    			}
    		}

    		/**
    		 * @desc 取消禁视频
    		 */
    		unmuteCamera = function () {
    			log.info("===video.unmuteCamera()");
    			var self = this;
    			if (this.status == StreamStatus.muted) {
    				this.status = StreamStatus.published;
    				self.roomHandle.selfUser.eventEmitter.emit(UserCallback.camera_status_notify, this.status,
    					this.id, this.name, self.roomHandle.selfUser.id);

    				var stream = self.roomHandle.selfUser.stream;
    				stream.getTracks().forEach(function (track) {
    					if (track.id == self.track.id) {
    						track.enabled = true;
    					}
    				});

    				if (self.element) {
    					var elementStream = self.element.srcObject;
    					if (elementStream) {
    						elementStream.getTracks().forEach(function (track) {
    							if (track.id == self.track.id) {
    								track.enabled = true;
    							}
    						});
    					}
    				}

    				if (self.track) {
    					self.track.enabled = true;
    				}

    				var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, this.id);
    				self.roomHandle.masterServer.updateWebcamMsg(self.roomHandle.selfUser.nodeId, cameraUUID,
    					this);

    				log.info("===videe unmuteCamera(),取消禁视频. Id:" + self.id + ",ownerId:" + self.ownerId +
    					",status:" + self.status);
    				avdEngineHandle.loggerReport.info("video unmuteCamera(),取消禁视频. Id:" + self.id +
    					",ownerId:" + self.ownerId + ",status:" + self.status);
    			}
    		};

    		/**
    		 * @desc 获取禁视频状态
    		 */
    		ismuteCamera = function () {
    			var ismute = false;
    			if (this.status == StreamStatus.muted) {
    				ismute = true;
    			}
    			return ismute;
    		};

    		/**
    		 * @desc 更改订阅视频的视频质量
    		 * @param {int} quality- 视频质量枚举值，如可传VideoQualityType.high
    		 */
    		changeSubscribedVideoQuality = function (quality) {
    			var self = this;
    			if (self.roomHandle.selfUser.id == this.ownerId) {
    				log.debug("===video changeSubscribedVideoQuality(),自己发布的视频不能更改。id:" + this.id +
    					",ownerId:" + this.ownerId + ",quality:" + quality);
    			} else {
    				log.debug("===video changeSubscribedVideoQuality(),更改订阅视频的视频质量,id:" + this.id +
    					",ownerId:" + this.ownerId + ",quality:" + quality);
    				avdEngineHandle.loggerReport.debug("video changeSubscribedVideoQuality(),更改订阅视频的视频质量,id:" +
    					this.id + ",ownerId:" + this.ownerId + ",quality:" + quality);
    				self.roomHandle.masterServer.changeSubscribedVideoQuality(this.resourceInfo, this
    					.quality2Level(quality));
    			}
    		}

    		/**
    		 * @desc 更改发布视频源端的视频质量
    		 * @param level 级别
    		 */
    		changePublishVideoQuality = function (level) {
    			log.info("===video.changePublishVideoQuality(),videoId:" + this.id + ",userId:" + this.ownerId +
    				",level:" + level);

    			var self = this;
    			//log.debug("+++DS1111111111,videoId:"+self.id+",level:"+level);
    			self.currentVideoQuality = level;

    			if (self.changePublishVideoQualityInterval) {
    				clearInterval(self.changePublishVideoQualityInterval);
    				self.changePublishVideoQualityInterval = null;
    			}

    			self.changePublishVideoQualityInterval = setInterval(function () {
    				if (self.currentVideoQuality) {
    					if (self.roomHandle) {
    						//log.debug("+++DS222222222,videoId:"+self.id+",level:"+level);
    						self.changePublishVideoQualityHandle(self.currentVideoQuality);
    						self.currentVideoQuality = null;
    						clearInterval(self.changePublishVideoQualityInterval);
    						self.changePublishVideoQualityInterval = null;
    					}
    				}
    			}, 1000);
    		}

    		changePublishVideoQualityHandle = function (level) {
    			var self = this;
    			var quality = this.level2Quality(level);

    			log.debug("===video.changePublishVideoQualityHandle(),userName:" + self.roomHandle.selfUser
    				.name + ",videoId:" + this.id + ",level:" + level + ",resolution.width:" + this
    					.resolution.width + ",resolution.height:" + this.resolution.height);

    			var maxBandwidth = null;
    			if (this.clientSetMaxBandwidth) {
    				maxBandwidth = this.clientSetMaxBandwidth;
    			} else {
    				maxBandwidth = this.maxBandwidth;
    			}

    			if (maxBandwidth) {
    				var currMaxBandwidth = 0;
    				if (quality == VideoQualityType.high) {
    					currMaxBandwidth = maxBandwidth;
    				} else if (quality == VideoQualityType.normal) {
    					currMaxBandwidth = parseInt(maxBandwidth / 2);
    				} else if (quality == VideoQualityType.low) {
    					currMaxBandwidth = parseInt(maxBandwidth / 4);
    				}

    				if ('RTCRtpSender' in window && 'setParameters' in window.RTCRtpSender.prototype) {
    					var sender = this.RTCRtpSender;
    					if (sender) {
    						var parameters = sender.getParameters();
    						if (!parameters.encodings) {
    							parameters.encodings = [{}];
    						} else if (parameters.encodings == []) {
    							parameters.encodings[0] = {};
    						}

    						if (!maxBandwidth || maxBandwidth == 0) {
    							if (parameters.encodings[0]) {
    								delete parameters.encodings[0].maxBitrate;
    							}
    						} else {
    							if (parameters.encodings[0]) {
    								parameters.encodings[0].maxBitrate = currMaxBandwidth * 1000;
    							}
    						}

    						sender.setParameters(parameters).then(function () {
    							log.info("===video.changePublishVideoQualityHandle(),userName:" + self
    								.roomHandle.selfUser.name + ",videoId:" + self.id + ",level:" +
    								level + ",带宽修改成功,maxBandwidth:" + currMaxBandwidth);
    						}).catch(function (e) {
    							log.error("===video.changePublishVideoQualityHandle(),userName:" + self
    								.roomHandle.selfUser.name + ",videoId:" + self.id + ",level:" +
    								level + ",带宽修改失败,maxBandwidth:" + currMaxBandwidth + ",error:",
    								e);
    						});
    					}
    				}
    			}

    			var detect = avdEngineHandle.browserDetect.detect ? avdEngineHandle.browserDetect.detect :
    				avdEngineHandle.getBrowserDetect();

    			// iphone手机，ios版本<17.0时，因服务器下发视频等级调整分辨率时，会引发 iphone手机打开前置，视频看着是很大的横屏。所以这里做了相应的处理。
    			if (detect.osName == 'iOS' && versionStringCompare(detect.browser.fullVersion, '17') < 0) {
    				log.debug("===video.changePublishVideoQualityHandle(), current os: " + detect.osName + ' ' +
    					detect.osVersion + ' to ignore');
    			} else {
    				var resolutionModified = {
    					width: self.resolution.width,
    					height: self.resolution.height
    				}

    				if (quality == VideoQualityType.high) {
    					resolutionModified.width = self.resolution.width;
    					resolutionModified.height = self.resolution.height;
    				} else if (quality == VideoQualityType.normal) {
    					if ((self.resolution.width / 2) >= 320 || (self.resolution.height / 2) >= 180) {
    						resolutionModified.width = self.resolution.width / 2;
    						resolutionModified.height = self.resolution.height / 2;
    					} else {
    						resolutionModified.width = 320;
    						resolutionModified.height = 180;
    					}
    				} else if (quality == VideoQualityType.low) {
    					if ((self.resolution.width / 4) >= 320 || (self.resolution.height / 4) >= 180) {
    						resolutionModified.width = self.resolution.width / 4;
    						resolutionModified.height = self.resolution.height / 4;
    					} else {
    						resolutionModified.width = 320;
    						resolutionModified.height = 180;
    					}
    				}

    				// self.setResolutionWH(resolutionModified.width, resolutionModified.height, ResolutionSetType.ideal, true)
    				// self.applyConstraints();
    				self.adjustVideoResoutionRealTime(resolutionModified.width, resolutionModified.height);
    				log.debug("===video.changePublishVideoQualityHandle(),userName:" + self.roomHandle.selfUser
    					.name + ",videoId:" + self.id + ",level:" + level + ",修改视频分辨率, 分辨率宽:" +
    					resolutionModified.width + "，分辨率高：" + resolutionModified.height);
    			}
    		}

    		quality2Level = function (quality) {
    			var level =
    				3; // 1 terrible， 2 worse， 3 bad， 4 normal. video_level is Temporal level, 1-4, bigger is better
    			if (quality == VideoQualityType.high) {
    				level = 4;
    			} else if (quality == VideoQualityType.normal) {
    				level = 3;
    			} else if (quality == VideoQualityType.low) {
    				level = 2;
    			}
    			return level;
    		}

    		level2Quality = function (level) {
    			var quality = VideoQualityType.normal;
    			if (level >= 4) {
    				quality = VideoQualityType.high;
    			} else if (level == 3) {
    				quality = VideoQualityType.normal;
    			} else {
    				quality = VideoQualityType.low;
    			}
    			return quality;
    		}

    		/**
    		 * @desc 摄像头数据更改
    		 * @param {int} level
    		 * @param {String} description
    		 */
    		updateCameraData = function (level, description) {
    			var self = this;
    			log.debug("===video.updateCameraData(),level:" + level + ",description:" + description);
    			this.level = level;
    			this.description = description;
    			var cameraUUID = self.roomHandle.setDeviceIdByUUID(0, this.id);
    			if (self.roomHandle && self.roomHandle.selfUser) {
    				this.saveVideoCustomConfig('description', description)
    				self.roomHandle.masterServer.updateWebcamMsg(self.roomHandle.selfUser.nodeId, cameraUUID,
    					this);
    			}
    		}

    		/**
    		 * @desc 更新导出时视频流的名称及昵称
    		 * @async
    		 * @param {String} streamName  视频流名称
    		 * @param {String} streamNickName   视频流昵称
    		 */
    		updateExporterStreamName = function (streamName, streamNickName) {
    			var deferred = when.defer();
    			var self = this;
    			self.streamName = streamName;
    			self.streamNickName = streamNickName;
    			self.roomHandle.masterServer.updateExporterStreamName(self).then(function () {
    				deferred.resolve();
    			}).otherwise(function (error) {
    				deferred.reject(error);
    			});
    			return deferred.promise;
    		}

    		/**
    		 * @desc 视频多流时,设置最大空域层数
    		 * @param {int} spatialLayer 最大空域层数
    		 */
    		setMaxSpatialLayer = function (spatialLayer) {
    			if (avdEngineHandle.simulcastEnabled) {
    				if (this.RTCRtpSender) {
    					var parameters = this.RTCRtpSender.getParameters();
    					parameters.encodings.forEach(function (encoding, idx) {
    						log.debug("===video.setMaxSpatialLayer(),spatialLayer:" + spatialLayer +
    							",idx:" + idx);
    						if (idx < spatialLayer) {
    							encoding.active = true;
    						} else {
    							encoding.active = false;
    						}
    					});
    					this.RTCRtpSender.setParameters(parameters);
    				}
    			} else {
    				log.debug("===video.setMaxSpatialLayer()，设置无效因为simulcastEnabled=false");
    			}
    		}

    		/**
    		 * @description 视频多流时,设置空域每层开启的状态。状态值定义： 0-维持原状, 1-unactive, 2-active
    		 * @param {int} baseActive - 第一层状态
    		 * @param {int} ass1Active - 第二层状态
    		 * @param {int} ass2Active - 第三层状态
    		 */
    		setSpatialLayerActive = function (baseActive, ass1Active, ass2Active) {
    			if (avdEngineHandle.isChrome && avdEngineHandle.majorVersion >= 73 && avdEngineHandle
    				.majorVersion <= 76) {
    				log.debug(
    					"===video.setSpatialLayerActive(),chrome版本为73-76，支持多流但不支持服务器下发的各路流active/unactive设置"
    				);
    				return;
    			}

    			var self = this;
    			log.debug("===video.setSpatialLayerActive(),baseActive:" + baseActive + ",ass1Active:" +
    				ass1Active + ",ass2Active:" + ass2Active);
    			if (avdEngineHandle.simulcastEnabled) {
    				var spatialLayerActiveObj = {};
    				spatialLayerActiveObj.baseActive = baseActive;
    				spatialLayerActiveObj.ass1Active = ass1Active;
    				spatialLayerActiveObj.ass2Active = ass2Active;
    				self.spatialLayerActiveList.push(spatialLayerActiveObj);

    				setTimeout(function () {
    					if (!self.spatialLayerActiveDoing) {
    						self.spatialLayerActiveDoing = true;
    						var baseActiveNew = 0;
    						var ass1ActiveNew = 0;
    						var ass2ActiveNew = 0;
    						self.spatialLayerActiveList.forEach(function (spatialLayerActive) {
    							if (spatialLayerActive.baseActive != 0) {
    								baseActiveNew = spatialLayerActive.baseActive;
    							}
    							if (spatialLayerActive.ass1Active != 0) {
    								ass1ActiveNew = spatialLayerActive.ass1Active;
    							}
    							if (spatialLayerActive.ass2Active != 0) {
    								ass2ActiveNew = spatialLayerActive.ass2Active;
    							}
    						});
    						log.debug("===video.setSpatialLayerActive(),baseActiveNew:" +
    							baseActiveNew + ",ass1ActiveNew:" + ass1ActiveNew +
    							",ass2ActiveNew:" + ass2ActiveNew);
    						if (self.RTCRtpSender) {
    							log.debug("===video.setSpatialLayerActive(),RTCRtpSender != null");
    							self.rtcRtpSenderIntervalId = setInterval(
    								function () {
    									var parameters = self.RTCRtpSender.getParameters();
    									if (parameters.encodings.length > 0) {
    										log.debug(
    											"===video.setSpatialLayerActive(),parameters.encodings:",
    											JSON.stringify(parameters.encodings));
    										clearInterval(self.rtcRtpSenderIntervalId);
    										self.rtcRtpSenderIntervalId = null;

    										parameters.encodings.forEach(function (encoding, idx) {
    											if (idx == 0) {
    												if (baseActiveNew == 2) {
    													log.debug(
    														"===video.setSpatialLayerActive(),baseActive active to true"
    													);
    													encoding.active = true;
    												} else if (baseActiveNew == 1) {
    													log.debug(
    														"===video.setSpatialLayerActive(),baseActive  active to false"
    													);
    													encoding.active = false;
    												}
    											}
    											if (idx == 1) {
    												if (ass1ActiveNew == 2) {
    													log.debug(
    														"===video.setSpatialLayerActive(),ass1Active active to true"
    													);
    													encoding.active = true;
    												} else if (ass1ActiveNew == 1) {
    													log.debug(
    														"===video.setSpatialLayerActive(),ass1Active active to false"
    													);
    													encoding.active = false;
    												}
    											}
    											if (idx == 2) {
    												if (ass2ActiveNew == 2) {
    													log.debug(
    														"===video.setSpatialLayerActive(),ass2Active active to true"
    													);
    													encoding.active = true;
    												} else if (ass2ActiveNew == 1) {
    													log.debug(
    														"===video.setSpatialLayerActive(),ass2Active active to false"
    													);
    													encoding.active = false;
    												}
    											}
    										});
    										self.RTCRtpSender.setParameters(parameters);
    										self.spatialLayerActiveDoing = false;
    									}
    								}, 200);
    						}
    					}
    				}, 1000);
    			} else {
    				log.debug("===video.setSpatialLayerActive()，设置无效因为simulcastEnabled=false");
    			}
    		}

    		/**
    		 * @desc 视频流渲染进视频控件
    		 * @param {Object} element － 视频控件对象
    		 * @param {Object} stream － 视频流
    		 */
    		attachVideoElementMediaStream = function (element, stream) {
    			if (stream != null) {
    				this.element = element;
    			}

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

    		saveVideoCustomConfig = function (property, value) {
    			var self = this;
    			if (self.roomHandle && self.roomHandle.selfUser) {
    				var videoCustomSet = self.roomHandle.selfUser.reJoinVideoId2CustomSet[this.id];
    				if (!videoCustomSet) {
    					self.roomHandle.selfUser.reJoinVideoId2CustomSet[this.id] = {};
    				}
    				self.roomHandle.selfUser.reJoinVideoId2CustomSet[this.id][property] = value;
    			}
    		}

    		/**
    		 * @desc 用于无人订阅当前视频自动调整码率时，实时调整当前视频track的分辨率宽高，不影响视频原设置的分辨率
    		 * @param {*} width
    		 * @param {*} height
    		 * @ignore
    		 */
    		adjustVideoResoutionRealTime = function (width, height) {
    			if (isNaN(width) || isNaN(height)) {
    				return;
    			}
    			log.info('===video.adjustVideoResoutionRealTime, width:' + width, ',height:' + height);
    			var self = this;
    			if (self.track) {
    				var constraints = {
    					width: {
    						ideal: parseInt(width)
    					},
    					height: {
    						ideal: parseInt(height)
    					},
    					frameRate: {
    						ideal: self.frameRate
    					},
    					aspectRatio: {
    						ideal: self.aspectRatio
    					}
    				};
    				self.track.applyConstraints(constraints);
    			}
    		}

    		/**
    		 * @desc 检查虚拟背景是否可用
    		 * @returns {Promise<boolean>}
    		 */
    		canUseVirtualBackground = function() {
    			var Factory = ModuleBase.use("VirtualBgFactory");
    			var processorType = this._virtualBgOptions && this._virtualBgOptions.processorType;
    			var processor = Factory.createProcessor(processorType);
    			return processor.canUse();
    		};

    		/**
    		 * @desc 启用虚拟背景
    		 * @param {string} background - 背景图片 URL、颜色值或 'blur'
    		 * @param {Object} options - 可选配置 { processorType: 'mediaPipe', quality: 'high'|'medium'|'low' }
    		 * @returns {Promise}
    		 */
    		enableVirtualBackground = function(background, options) {
    			var self = this;
    			var defaultOptions = {
    				processorType: 'mediaPipe',
    				quality: 'high'
    			};

    			try {
    				self._virtualBgEnabled = true;
    				self._virtualBgImage = background;
    				self._virtualBgOptions = Object.assign(defaultOptions, options || {});

    				if (self.track) {
    					return self._applyVirtualBackground();
    				}
    				return Promise.resolve();
    			} catch (e) {
    				log.error('=== enableVirtualBackground EXCEPTION:', e);
    				return Promise.reject(e);
    			}
    		};

    		/**
    		 * @desc 禁用虚拟背景
    		 */
    		disableVirtualBackground = function() {
    			var self = this;
    			log.info('=== disableVirtualBackground: START');

    			if (!self._virtualBgEnabled) {
    				log.info('=== disableVirtualBackground: not enabled');
    				return null;
    			}

    			self._virtualBgWasEnabled = true;
    			self._virtualBgEnabled = false;

    			if (self._virtualBgProcessor) {
    				self._virtualBgProcessor.close();
    				self._virtualBgProcessor = null;
    			}

    			self._virtualBgTrack = null;
    			self._virtualBgImage = null;

    			var resultStream = self._virtualBgOriginalStream;
    			self._virtualBgOriginalStream = null;

    			log.info('=== disableVirtualBackground: resultStream=' + (resultStream ? 'exists' : 'null'));
    			return resultStream;
    		};

    		/**
    		 * @desc 启用美颜
    		 * @param {Object} beautyConfig - 美颜配置 { smoothingLevel, whiteningLevel, cheekThinningLevel, eyeEnlargeLevel, lipEnlargeLevel, filterType, filterIntensity }
    		 * @param {Object} options - 可选配置 { processorType: 'mediaPipe', quality: 'high'|'medium'|'low' }
    		 * @returns {Promise}
    		 */
    		enableBeauty = function(beautyConfig, options) {
    			var self = this;
    			var defaultOptions = {
    				processorType: 'mediaPipe',
    				quality: 'high'
    			};

    			try {
    				self._beautyEnabled = true;
    				self._beautyConfig = beautyConfig || {};
    				self._beautyOptions = Object.assign(defaultOptions, options || {});

    				if (self.track) {
    					return self._applyBeauty();
    				}
    				return Promise.resolve();
    			} catch (e) {
    				log.error('=== enableBeauty EXCEPTION:', e);
    				return Promise.reject(e);
    			}
    		};

    		/**
    		 * @desc 禁用美颜
    		 * @returns {MediaStream|null} 禁用前的原始视频流
    		 */
    		disableBeauty = function() {
    			var self = this;
    			log.info('=== disableBeauty: START');

    			if (!self._beautyEnabled) {
    				log.info('=== disableBeauty: not enabled');
    				return null;
    			}

    			self._beautyWasEnabled = true;
    			self._beautyEnabled = false;

    			if (self._beautyProcessor) {
    				self._beautyProcessor.close();
    				self._beautyProcessor = null;
    			}

    			self._beautyTrack = null;
    			self._beautyConfig = null;

    			var resultStream = self._beautyOriginalStream;
    			self._beautyOriginalStream = null;

    			log.info('=== disableBeauty: resultStream=' + (resultStream ? 'exists' : 'null'));
    			return resultStream;
    		};

    		/**
    		 * @desc 获取美颜处理器的 FPS
    		 * @returns {number} 美颜 FPS，如果未启用美颜返回 0
    		 */
    		getBeautyFps = function() {
    			if (this._beautyProcessor && this._beautyEnabled) {
    				return this._beautyProcessor.getFps() || 0;
    			}
    			return 0;
    		};

    		/**
    		 * @desc 检查美颜是否已启用
    		 * @returns {boolean}
    		 */
    		isBeautyEnabled = function() {
    			return !!this._beautyEnabled;
    		};

    		/**
    		 * @desc 获取当前美颜配置
    		 * @returns {Object|null} 当前美颜配置，如果未启用返回 null
    		 */
    		getBeautyConfig = function() {
    			if (this._beautyEnabled && this._beautyConfig) {
    				return this._beautyConfig;
    			}
    			return null;
    		};

    		/**
    		 * @desc 实时更新美颜参数（无需重新启用美颜）
    		 * @param {Object} beautyConfig - 美颜配置 { smoothingLevel, whiteningLevel, cheekThinningLevel, eyeEnlargeLevel, lipEnlargeLevel, filterType, filterIntensity }
    		 */
    		setBeautyConfig = function(beautyConfig) {
    			if (!beautyConfig) {
    				return;
    			}

    			this._beautyConfig = Object.assign(this._beautyConfig || {}, beautyConfig);

    			if (this._beautyProcessor) {
    				this._beautyProcessor.setBeautyConfig(this._beautyConfig);
    			}
    		};

    		/**
    		 * @desc 内部方法：应用美颜处理
    		 * @returns {Promise}
    		 * @ignore
    		 */
    		_applyBeauty = function() {
    			var self = this;

    			if (!self._beautyEnabled) {
    				return Promise.resolve();
    			}

    			if (!self._beautyProcessor) {
    				var Factory = ModuleBase.use("BeautyFactory");
    				var processorType = self._beautyOptions.processorType;
    				var processor = Factory.createProcessor(processorType);

    				if (self._beautyOptions.quality === 'low') {
    					processor.setBeautyConfig(Object.assign(processor.getBeautyConfig() || {}, {
    						processWidth: 256,
    						processHeight: 144,
    						targetFps: 15
    					}));
    				} else if (self._beautyOptions.quality === 'medium') {
    					processor.setBeautyConfig(Object.assign(processor.getBeautyConfig() || {}, {
    						processWidth: 384,
    						processHeight: 216,
    						targetFps: 24
    					}));
    				}

    				if (self._beautyConfig) {
    					processor.setBeautyConfig(self._beautyConfig);
    				}

    				self._beautyProcessor = processor;

    				return processor.canUse().then(function() {
    					return self._processBeauty();
    				}).catch(function(e) {
    					log.error('=== _applyBeauty canUse ERROR:', e);
    					throw e;
    				});
    			} else {
    				if (self._beautyConfig) {
    					self._beautyProcessor.setBeautyConfig(self._beautyConfig);
    				}
    				return self._processBeauty();
    			}
    		};

    		/**
    		 * @desc 内部方法：处理美颜
    		 * @returns {Promise}
    		 * @ignore
    		 */
    		_processBeauty = function() {
    			var self = this;
    			var processor = self._beautyProcessor;

    			return new Promise(function(resolve, reject) {
    				var originalStream = new MediaStream([self.track]);

    				if (!originalStream) {
    					reject(new Error('No stream available'));
    					return;
    				}

    				self._beautyOriginalStream = originalStream;

    				log.info('=== _processBeauty: self.element=' + (self.element ? 'exists' : 'null'));

    				processor.onStreamUpdate = function(processedStream) {
    					log.info('=== _processBeauty: stream updated');
    				};

    				processor.pipeMediaStream(originalStream).then(function(processedStream) {
    					var newTrack = processedStream.getVideoTracks()[0];
    					self._beautyTrack = newTrack;
    					self.track = newTrack;
    					return processedStream;
    				}).then(resolve)["catch"](function(e) {
    					log.error('=== _processBeauty ERROR:', e);
    					reject(e);
    				});
    			});
    		};

    		/**
    		 * @desc 内部方法：应用虚拟背景处理
    		 * @returns {Promise}
    		 * @ignore
    		 */
    		_applyVirtualBackground = function() {
    			var self = this;

    			if (!self._virtualBgEnabled) {
    				return Promise.resolve();
    			}

    			if (!self._virtualBgProcessor) {
    				var Factory = ModuleBase.use("VirtualBgFactory");
    				var processorType = self._virtualBgOptions.processorType;
    				var processor = Factory.createProcessor(processorType);

    				if (self._virtualBgOptions.quality === 'low') {
    					processor.setConfig({
    						processWidth: 256,
    						processHeight: 144,
    						targetFps: 15
    					});
    				} else if (self._virtualBgOptions.quality === 'medium') {
    					processor.setConfig({
    						processWidth: 384,
    						processHeight: 216,
    						targetFps: 24
    					});
    				}

    				self._virtualBgProcessor = processor;

    				return processor.canUse().then(function() {
    					return self._processVirtualBg();
    				}).catch(function(e) {
    					log.error('=== _applyVirtualBackground canUse ERROR:', e);
    					throw e;
    				});
    			}

    			return self._processVirtualBg();
    		};

    		/**
    		 * @desc 内部方法：处理虚拟背景
    		 * @returns {Promise}
    		 * @ignore
    		 */
    		_processVirtualBg = function() {
    			var self = this;
    			var originalStream = new MediaStream([self.track]);

    			log.info('=== _processVirtualBg: self.element=' + (self.element ? 'exists' : 'null'));

    			if (self.element) {
    				self._virtualBgOriginalStream = self.element.srcObject;
    				log.info('=== _processVirtualBg: saved _virtualBgOriginalStream');
    				self.element.srcObject = originalStream;
    				log.info('=== _processVirtualBg: set element to originalStream');
    			}

    			return self._virtualBgProcessor.pipeMediaStream(
    				originalStream,
    				self._virtualBgImage
    			).then(function(processedStream) {
    				var newTrack = processedStream.getVideoTracks()[0];
    				self._virtualBgTrack = newTrack;
    				return processedStream;
    			}).catch(function(e) {
    				log.error('===_processVirtualBg error:', e);
    				throw e;
    			});
    		};
    	}
    	return Video;
    });