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