avdEngine.js

/**
 * 引擎模块, rtc系统的入口。
 * @module AVDEngine
 * @author roymond.wang
 * @since 2015-09-01
 * 
 * @example 
 * var AVDEngine = ModuleBase.use(ModulesEnum.avdEngine);
 */
var log = log4javascript.getLogger();

var avdEngineHandle;

ModuleBase.define("AVDEngine", ["Room", "RestServer", "Error", "BrowserDetect", "StreamExporterManager","ElectronManager","RtmManager","InviteManager"], function(Room, RestServer, Error, BrowserDetect, StreamExporterManager,ElectronManager,RtmManager,InviteManager) {

	/**
	 * AVDEngine构造函数,实例化avdEngine 对象,用于获取摄像头列表、麦克风列表、检查浏览器兼容性、控制日志等级及日志保存等功能。
	 * @constructor
	 * @alias AVDEngine
	 * 
	 * @example 
	 * var avdEngine = new AVDEngine();
	 */
	var AVDEngine = function() {
		this.avdVersion = "4.0.8.6";
	
		this.accessToken;
		this.clientIsUseCascade = false; //是否级联加入会议
		// this.errMsgi18n = 'en'; err默认语言英文
		
		this.restServerInfo = {
			mcuServerUrl: null,
			clusterRestAddr: null,
			roomServerUrl: null,
			iceServers: null,
			stunUrl: null,
			trunUrl: null,
			iceusername: null,
			iceCredential: null,
			isCluster: false,
			protocolStr: document.location.protocol,
			notQueryGetMcu:false,
			masterVersion: null,
			masterPackageVersion: null,
			boxVersion: null,
			boxPackageVersion: null,
			signalData: null,
			iv: '0000000000000000'
		}
		
		if(this.restServerInfo.protocolStr == "httpsionic:"){
			this.restServerInfo.protocolStr = "https:";
		}
		
		if(this.restServerInfo.protocolStr != "http:" && this.restServerInfo.protocolStr != "https:" && this.restServerInfo.protocolStr != "httpsionic:"){
		   this.restServerInfo.protocolStr = "http:";
		}
		
		this.loggerReportConfig = null; //终端日志上报设置
		this.loggerReport = null; //终端日志上报指示器
		this.logReportWait = []; //终端日志上报前缓存的内容
		
		this.connectionStatisticsConfig = null; //媒体统计设置
	
		this.browserDetect;
		
		this.levelValue = log4javascript.Level.INFO.level;
		this.appSign = null;
		this.appFunName = null;
		
		this.bugout = null;
		
		this.supperStrongWeakNetWork = false; //当前服务器版本是否支持强弱网抗丢包

		this.videoCoding = VideoCodingType.H264; //视频编码格式,默认值为H264
		this.audioCoding = AudioCodingType.opus; //音频编码格式,默认值为opus
		this.audioAutoSub = true; //音频自动订阅,默认值为true
		this.audioBatchEnabled = false; //音频自动订阅时,对多路音频是否开启批量订阅处理,可以只进行一次SDP交互以提高性能,默认值为false

        this.enableEchoCancellation = true; //是否开启回声消除,默认值为true
        this.enableNoiseSuppression = true; //是否开启降噪,默认值为true
        this.enableAutoGainControl = true; //是否开启自动增益控制,默认值为true

        this.simulcastEnabled = false;  //是否开启多流,默认值为false
        
		this.dataCrypto = DataCryptoType.AES128;  //数据加密类型,默认值为AES128
		
	    this.mediaConnectType = 0; //0 udp优先; 1 tcp优先
		
		this.customerTokenEnabled = false; //是否开启客户自己的认证token,默认值为false
		
		this.restfulRequestType = 0; //RestFul API请求类型. 0: GET; 1: POST

		this.cameraMap = {};
		this.microphoneMap = {};
		this.speakerMap = {}; //扬声器
		
		this.hasCamera = false;
		this.hasMicrophone = false;
		this.hasSpeaker = false;
		
		this.checkVideoStream = null;
		this.checkAudioStream = null;
		
		this.checkAudioLocalStatsCollector = null;
		
		this.checkMicrophoneId = null;
		this.checkMicrophoneName = null;
		
		this.mcuClusterRouteParams = null; //分布式集群部署中,加入房间时mcu服务器的分配路由参数,填写格式如:{ip_tag":"local","idc_code":"idc_code"}
		
		this.defaultVideoParamsWidth = 640;
		this.defaultVideoParamsHeight = 480;
		this.defaultVideoParamsResolutionSetType = ResolutionSetType.ideal;
		this.defaultVideoParamsFrameRate = 20;
	
		this.defaultScreenParamsWidth = 1920;
		this.defaultScreenParamsHeight = 1080;
		this.defaultScreenParamsFrameRate = 10;

		this.defaultMediaPublishKeepAliveTime = 15000; //媒体流发布的保活时长(单位为毫秒),默认为15000毫秒即30秒
		
        this.detect = this.getBrowserDetect();
		this.browserName = this.detect.browser.name;
		this.fullVersion = this.detect.browser.fullVersion;
		this.majorVersion = this.fullVersion.split(".")[0];
		this.browserOsName = this.detect.osName;
		this.isChrome = this.detect.isChrome;
		this.isOpera = this.detect.isOpera;
		this.isIE = this.detect.isIE;
		this.isFirefox = this.detect.isFirefox;
		this.isSafari = this.detect.isSafari;
		this.isMicroMessenger = this.detect.isMicroMessenger;
		this.isUCBrowser = this.detect.isUCBrowser;
		
		//当前客户端的资源
		this.clientResource = new Object();
		
		this.restFullProtocol = null;

		this.traceId = null;
		
		//Electron应用参数设置 
		this.applyToElectron = false;
		this.electronProtocol = "http:";
		this.electronManager = null;
		
		this.appender = new log4javascript.BrowserConsoleAppender();
		this.appender.setThreshold(log4javascript.Level.INFO); 
		appenderLayout(this.appender);
		
		this.loggerReport = new LoggerReport();

        this.eventEmitter = new EventEmitter();

        this.deviceChangeListener();
		
		this.rooms = [];

        this.eraserCustomIconPath = '';
        
        this.ignoreModifyResourceReq = false;

        this.enableHardwareEncoding = false;
		
		//Safari 15.1.X使用H264 编码时会导致页面崩溃, 默认值调整了为VP8
		if(this.isSafari && this.fullVersion && this.fullVersion.split(".")[0] =='15' && this.fullVersion.split(".")[1]=='1'){
			this.videoCoding = VideoCodingType.VP8; 
		}

        // Android Chrome/Android微信支持vp8、不支持h264解码,默认值调整为VP8
        if((this.isChrome && this.detect.osName == 'Android') || (this.isMicroMessenger && this.detect.osName == 'Android')){
            log.debug("===avdEngine, Android Chrome or Android MM detected, set default video codec to VP8")
            this.videoCoding = VideoCodingType.VP8;
        }
		
		log.info(`===avdEngine, browserName:${this.browserName}, fullVersion:${this.fullVersion}, checkBrowserSupport:${this.checkBrowserSupport()}, GetUserMedia:${this.detect.getUserMediaSupport}, RTCPeerConnection:${this.detect.RTCPeerConnectionSupport}, WebSocket:${this.detect.WebSocketSupport}`);
		
		avdEngineHandle = this;
	};
	
	
	/**
	 * 获取当前rtc web SDK的版本号
	 * @returns {String} - 版本号
	 * 
	 * @example 
	 * var Version = avdEngine.getVersion();
	 */
	AVDEngine.prototype.getVersion = function() {
		var version = "rtc-" + this.avdVersion;
		return version;
	};
	

    /**
     * 引擎级别的回调
     * @param {EngineCallback} type - 回调枚举标识, 包括EngineCallback.device_microphone_changet 和 device_camera_change.
     * @param {Object} callback - 回调方法名,可以自定义,建议保持默认值
     * @example 
     * avdEngine.addCallback(EngineCallback.device_microphone_change, onMicrophoneDeviceChangeHandle)
     * avdEngine.addCallback(EngineCallback.device_camera_change, onCameraDeviceChangeHandle)
     * 
	 * //param {String} changeType -  设备变化类型,可以为"add"、"remove"
     * //param {Array} deviceIdList - 变动的麦克风设备ID列表
     * function onMicrophoneDeviceChangeHandle(changeType, deviceIdList) {}
     * 
     * //param {String} changeType -  设备变化类型,可以为"add"、"remove"
     * //param {Array} deviceIdList - 变动的摄像头设备ID列表
     * function onCameraDeviceChangeHandle(changeType, deviceIdList) {}
     */
    AVDEngine.prototype.addCallback = function(type, callback) {
		this.eventEmitter.addListener(type, callback);
	};
	

	/**
	 * @desc 获取avdEngine的err语言
	 * @returns {String} - 语言
	 * 
	 * @example 
	 * var Version = avdEngine.getLanguage();
	 */
	// AVDEngine.prototype.getErrLanguage = function() {
	// 	var language = "rtc-errLanguage-" + this.errMsgi18n;
	// 	return language;
	// };
	
	/**
	 * @desc 设置avdEngine的err语言
	 * @returns {String} - 语言
	 * 
	 * @example 
	 * var Version = avdEngine.getLanguage();
	 */
	// AVDEngine.prototype.setErrLanguage = function(lang) {
	// 	this.errMsgi18n = lang
	// 	var language = "rtc-errLanguage-" + this.errMsgi18n;
	// 	return language;
	// };
	
	
	/**
	 * @desc Electron开发应用时,设置应用的协议
	 * @param {Boolean} - islectron  true为应用于Electron
	 * @param {String} - protocolStr  Electron应用时的协议,可不填,默认为http:
	 */
	AVDEngine.prototype.setApplyToElectron = function(islectron,protocolStr){
		log.info(`===avdEngine.setApplyToElectron(), islectron:${islectron}, protocolStr:${protocolStr}`);
		this.logReportWait.push(`avdEngine.setApplyToElectron(), islectron:${islectron}, protocolStr:${protocolStr}`);
		
		 if(islectron){
		 	 this.applyToElectron = islectron;
		 }
		 if(protocolStr){
		 	 this.electronProtocol = protocolStr;
             this.restServerInfo.protocolStr = protocolStr;
		 }
	};
	
	
	/**
	 * @desc 设置RestFull访问时应用的协议。 不设置默认取document.location.protocol
	 * @param {String} - protocolStr  RestFull访问应用时的协议,可填写如https:
	 */
	AVDEngine.prototype.setRestFullProtocol = function(protocolStr){
		log.info(`===avdEngine.setRestFullProtocol(),protocolStr:${protocolStr}`);
		if(protocolStr){
			this.restFullProtocol = protocolStr;
		}
	}	
	
	
	/**
	 * @desc 获取ElectronManager对象
	 * 
	 * @example 
	 * var electronManager = avdEngine.getElectronManager();
	 * 
	 */
	AVDEngine.prototype.getElectronManager= function() {
	    this.electronManager  = new ElectronManager();
		return this.electronManager;
	}
	
	/**
	 * @desc 设置视频是否开启多流,默认值为false
	 * @param {Boolean} - isSimulcast  false为不开启,true为开启
	 * 
	 * @example 
	 * avdEngine.setSimulcastEnabled(true);
	 */
	AVDEngine.prototype.setSimulcastEnabled = function(isSimulcast) {
		if(this.isChrome && this.majorVersion<73){
			this.simulcastEnabled = false;
			log.info("===avdEngine.setSimulcastEnabled(),当前Chromer版本小于73,不支持视频多流功能");
			this.logReportWait.push("avdEngine.setSimulcastEnabled(),当前Chromer版本小于73,不支持视频多流功能");
		}else if(this.isFirefox){ //firefox多流时,订阅端设置为中高视频等级时会看不到视频。目前没有找到原因,暂时firefox禁用多流。
			this.simulcastEnabled = false;
			log.info("===avdEngine.setSimulcastEnabled(),当前为firefox浏览器,不支持视频多流功能");
		}else {
			this.simulcastEnabled = isSimulcast;
			log.info(`===avdEngine.setSimulcastEnabled(),设置视频是否开启多流:${isSimulcast}`);
			this.logReportWait.push(`avdEngine.setSimulcastEnabled(),设置视频是否开启多流:${isSimulcast}`);
		 }
	}
	
	/**
	 * 获取视频是否开启多流的状态值
	 */
	AVDEngine.prototype.getSimulcastEnabled = function() {
		 return this.simulcastEnabled;
	}
	
	/**
	 * @desc 音频自动订阅时,设置对多路音频批量订阅处理,这样可以只进行一次SDP交互以提高性能,默认值为false,
	 * @param {Boolean} - isAudioBatch  false为不开启,true为开启
	 * 
	 * @example 
	 * avdEngine.setAudioBatchEnabled(true);
	 */
	AVDEngine.prototype.setAudioBatchEnabled = function(isAudioBatch) {
		this.audioBatchEnabled = isAudioBatch;
		log.info(`===avdEngine.setAudioBatchEnabled(),音频自动订阅时,多路音频是否开启批量订阅处理:${isAudioBatch}`);
		this.logReportWait.push(`avdEngine.setAudioBatchEnabled(),音频自动订阅时,多路音频是否开启批量订阅处理:${isAudioBatch}`);
	}
	
	
	/**
	 *  获取音频自动订阅时,对多路音频批量订阅处理的状态值
	 */
	AVDEngine.prototype.getAudioBatchEnabled = function() {
		return this.audioBatchEnabled;
	}
	
	
	/**
	 * @desc 设置是否开启客户自己的认证token,不调接口设置时,默认值为不开启
	 * @param {Boolean} - customerTokenEnabled  false为不开启,true为开启
	 * 
	 * @example 
	 * avdEngine.setCustomerTokenEnabled(true);
	 */
	AVDEngine.prototype.setCustomerTokenEnabled = function(customerTokenEnabled) {
		this.customerTokenEnabled = customerTokenEnabled;
		log.info(`===avdEngine.setCustomerTokenEnabled(), customerTokenEnabled:${customerTokenEnabled}`);
		this.logReportWait.push(`avdEngine.setCustomerTokenEnabled(), customerTokenEnabled:${customerTokenEnabled}`);
	}
	
	
	/**
	 *  获取当前是否开启客户自己的认证token状态值
	 */
	AVDEngine.prototype.getCustomerTokenEnabled = function() {
		return this.customerTokenEnabled;
	}
	
	
	/**
	 * @desc 设置RESTful API 请求类型
	 * @param {int} requestType, 0 表示GET; 1表示 POST,默认为0
	 * @example 
	 * avdEngine.setRestfulRequestType(1);
	 */
	AVDEngine.prototype.setRestfulRequestType = function(requestType) {
	    this.restfulRequestType = requestType;
		log.info(`===avdEngine.setRestfulRequestType(), 设置RESTful API 请求类型: ${this.restfulRequestType}`);
		this.logReportWait.push(`avdEngine.setRestfulRequestType(), 设置RESTful API 请求类型: ${this.restfulRequestType}`);
	}
	
	/**
	 * @desc 获取RESTful API 请求类型,0 表示GET; 1表示 POST,默认为0
	 * @example 
	 * avdEngine.getRestfulRequestType();
	 */
	AVDEngine.prototype.getRestfulRequestType = function() {
		return this.restfulRequestType;
	}
	

	/**
	 * @desc 获取浏览器检测结果
	 * @returns {Object} - 浏览器检测结果对象
	 * 
	 * @example
	 * var detect = avdEngine.getBrowserDetect();
	 * 
	 * detect.browser.name:  浏览器内核名称
	 * detect.browser.fullVersion: 内核版本
	 * detect.browser.isCustomized: 是否是定制外壳
	 * detect.browser.shellFullVersion:外壳版本
	 * detect.osName: 操作系统名称
	 * detect.getUserMediaSupport:是否支持webrtc的GetUserMedia
	 * detect.RTCPeerConnectionSupport:是否支持webrtc的RTCPeerConnection
	 * detect.dataChannelSupport: 是否支持webrtc的DataChannel
	 * detect.WebSocketSupport: 是否支持WebSocket
	 * detect.screenSharingSupport: 是否支持屏幕共享(chrome42以上及https访问)
	 * detect.h264Support: 是否支持H264(chrome52及以上)
	 */
	AVDEngine.prototype.getBrowserDetect = function() {
		if(!this.browserDetect){
			this.browserDetect = new BrowserDetect();
		}
		return this.browserDetect.detect;
	}



	/**
	 * @desc 检测当前浏览器是否支持rtc能力,目前仅支持Chrome内核的浏览器、firefox和safair.
	 * @returns {boolean} - 是否支持, false:不支持; true:支持
	 * @example 
	 * var browserSupport = avdEngine.checkBrowserSupport();
	 */
	AVDEngine.prototype.checkBrowserSupport = function() {
		if(!this.browserDetect){
			this.browserDetect = new BrowserDetect();
		}
		return this.browserDetect.checkBrowserSupport();
	};
	
	
	
	/**
	 * @desc 检测当前浏览器是否通过插件支持rtc能力
	 * @returns {boolean} - 是否支持, false:不支持; true:支持
	 *
	 * @example 
	 * var browserPluginSupport = avdEngine.checkBrowserPluginSupport();
	 */
	AVDEngine.prototype.checkBrowserPluginSupport = function() {
		if(!this.browserDetect){
			this.browserDetect = new BrowserDetect();
		}
		return this.browserDetect.checkBrowserPluginSupport();
	};
	


	/**
	 * @desc 设置日志显示方式和日志级别
	 * @param {Appender} appenderModel - 日志显示方式(枚举型), 默认值为Appender.browserConsole
	 * @param {LogLevel} logLevel - 日志级别(枚举型),默认值为LogLevel.info
	 * 
	 * @example 
	 * avdEngine.setLog(Appender.browserConsole, LogLevel.debug);
	 */
	AVDEngine.prototype.setLog = function(appenderModel, logLevel) {
		//var appender;
		if(appenderModel == Appender.alert) {
			this.appender = new log4javascript.AlertAppender();
		} else if(appenderModel == Appender.inpage) {
			this.appender = new log4javascript.InPageAppender();
		} else if(appenderModel == Appender.popup) {
			this.appender = new log4javascript.PopUpAppender();
		} else if(appenderModel == Appender.browserConsole) {
			//this.appender = new log4javascript.BrowserConsoleAppender();
		}

		if(logLevel == LogLevel.all) {
			this.levelValue = log4javascript.Level.ALL.level;
			this.appender.setThreshold(log4javascript.Level.ALL);
		} else if(logLevel == LogLevel.trace) {
			this.levelValue = log4javascript.Level.TRACE.level;
			this.appender.setThreshold(log4javascript.Level.TRACE);
		} else if(logLevel == LogLevel.debug) {
			this.levelValue = log4javascript.Level.DEBUG.level;
			this.appender.setThreshold(log4javascript.Level.DEBUG);
		} else if(logLevel == LogLevel.info) {
			this.levelValue = log4javascript.Level.INFO.level;
			this.appender.setThreshold(log4javascript.Level.INFO);
		} else if(logLevel == LogLevel.warn) {
			this.levelValue = log4javascript.Level.WARN.level;
			this.appender.setThreshold(log4javascript.Level.WARN);
		} else if(logLevel == LogLevel.error) {
			this.levelValue = log4javascript.Level.ERROR.level;
			this.appender.setThreshold(log4javascript.Level.ERROR);
		} else if(logLevel == LogLevel.fatal) {
			this.levelValue = log4javascript.Level.FATAL.level;
			this.appender.setThreshold(log4javascript.Level.FATAL);
		} else if(logLevel == LogLevel.off) {
			this.levelValue = log4javascript.Level.OFF.level;
			this.appender.setThreshold(log4javascript.Level.OFF);
		}
		appenderLayout(this.appender);
	};
	
	
	/**
	 * @desc 设置日志的可输出下载地址
	 * @param {String} logFilename - 输出下载地址
	 * 
	 * @example 
	 * avdEngine.setBugout(avdlog.txt);
	 */
	AVDEngine.prototype.setBugout = function(logFilename) {
		 this.bugout = new debugout;
		 this.bugout.realTimeLoggingOn = false;
         this.bugout.useTimestamps = true;
		 if(logFilename){
		 	 this.bugout.logFilename = logFilename;
			 log.info(`===avdEngine.setBugout(), logFilename: ${logFilename}`);
			 this.logReportWait.push(`avdEngine.setBugout(), logFilename: ${logFilename}`);
		 }
    };
	
	
	/**
	 * 日志内容通过应用层提供的函数进行上报
	 * @param {string} appSign -应用层设置的标识,用于区别某用户某一次加入会议所产生的所有日志
	 * @param {Object} appFunName -应用层设置的方法函数,SDK调用该函数上报日志内容
	 */
	AVDEngine.prototype.setReportLogger = function(appSign, appFunName) {
		if(typeof appSign !== 'string'){
			log.error("===avdEngine.setReportLogger(),appSign setting invalid");
			return;
		}
		if(typeof appFunName !== 'function'){
			log.error("===avdEngine.setReportLogger(),appFunName setting invalid");
			return;
		}
		this.appSign = appSign;
		this.appFunName = appFunName;
	}

	/**
	 * @desc 设置视频编码格式
	 * @async
	 * @param {VideoCodingType} videoCodingType - 视频编码格式(枚举型),默认值为videoCodingType.H264
	 * 
	 * @example 
	 * avdEngine.setVideoCoding(VideoCodingType.VP8).then(function(){}).otherwise(function(error){});
	 */
	AVDEngine.prototype.setVideoCoding = function(videoCodingType) {
		var deferred = when.defer();
		if(typeof(videoCodingType) == "undefined"){
			log.error(`===avdEngine.setVideoCoding(), 设置视频编解码失败。videoCodingType: ${videoCodingType}`);
			var err = new Error(ErrorConstant.set_videCoding_failed);
			deferred.reject(err);
		}else{
			var checkErr = false;
			var showMsg = "";
			
			//Safari 15.1.X使用H264 编码时会导致页面崩溃
			if(this.isSafari && this.fullVersion && this.fullVersion.split(".")[0] =='15' && this.fullVersion.split(".")[1]=='1'){
			    if(videoCodingType == VideoCodingType.H264){
			    	 checkErr = true;
			    	//  showMsg = "Safari 15.1.X使用H264 编码时会导致页面崩溃, 建议设置为VP8";
			    	 showMsg = "In Safari 15.1.X, H264 encoding may cause page crash. Therefore, you are advised to set it to VP8";
			    }
			}

			if(videoCodingType == VideoCodingType.H265){
			    if(this.isChrome && this.majorVersion<136){
					checkErr = true;
					showMsg = "Chrome versions below 136 do not support H265";
				}else{
				   log.warn(`===avdEngine.setVideoCoding(),Chrome 136+supports H265 video encoding, provided that your local computer CPU or discrete graphics card supports H.265 hard decoding and the driver is installed correctly, Chrome can play H.265 videos.`);
			    }
			}
				
			if(checkErr){
				log.error(`===avdEngine.setVideoCoding(), 设置视频编解码失败. ${showMsg}`);
				var err = new Error(ErrorConstant.set_videCoding_failed); 
				err.message = err.message+"," + showMsg;
				deferred.reject(err);
			}else{
			    this.videoCoding = videoCodingType;
				log.info(`===avdEngine.setVideoCoding(), 设置视频编解码: ${this.videoCoding}`);
			    this.logReportWait.push(`avdEngine.setVideoCoding(), 设置视频编解码: ${this.videoCoding}`);
				deferred.resolve();
			}
		}
		return deferred.promise;
	}


	/**
	 * @desc 获取当前的视频编码格式
	 * 
	 * @example 
	 * avdEngine.getVideoCoding();
	 */
	AVDEngine.prototype.getVideoCoding = function() {
		return this.videoCoding;
	}
	
	
	/**
	 * @desc 设置音频编码格式
	 * @async
	 * @param {AudioCodingType} audioCodingType - 音频编码格式(枚举型),默认值为AudioCodingType.opus
	 * 
	 * @example 
	 * avdEngine.setAudioCoding(AudioCodingType.opus).then(function(){}).otherwise(function(error){});
	 */
	AVDEngine.prototype.setAudioCoding = function(audioCodingType) {
		var deferred = when.defer();
		
		if(typeof(audioCodingType) == "undefined"){
			log.error(`===avdEngine.setAudioCoding(), 设置音频编解码失败。audioCodingType: ${audioCodingType}`);
		    var err = new Error(ErrorConstant.set_audioCoding_failed);
		    deferred.reject(err);
		}else{
			var checkErr = false;
			var showMsg = "";
			
			//Chrome 110版本开始不再支持ISAC
			if(this.isChrome && this.majorVersion >= 110){
				if(audioCodingType == AudioCodingType.ISAC_16k || audioCodingType == AudioCodingType.ISAC_32k){
					 checkErr = true;
					 showMsg = "Chrome version 110 no longer supports ISAC";
				}
			}
			
			//Firefox不支持ISAC
			if(this.isFirefox){
				if(audioCodingType == AudioCodingType.ISAC_16k || audioCodingType == AudioCodingType.ISAC_32k){
					 checkErr = true;
					 showMsg = "Firefox no longer supports ISAC";
				}
			}
			
			//Safari不支持ISAC_32K
			if(this.isSafari){
				if(audioCodingType == AudioCodingType.ISAC_32k){
					 checkErr = true;
					 showMsg = "Safari no longer supports ISAC_32K";
				}
			}
			
			if(checkErr){
				log.error(`===avdEngine.setAudioCoding(), 设置音频编解码失败. ${showMsg}`);
				var err = new Error(ErrorConstant.set_audioCoding_failed); 
				err.message = err.message+"," + showMsg;
				deferred.reject(err);
			}else{
				this.audioCoding = audioCodingType;
				log.info(`===avdEngine.setAudioCoding(), 设置音频编解码: ${this.audioCoding}`);
				this.logReportWait.push(`avdEngine.setAudioCoding(), 设置音频编解码: ${this.audioCoding}`);
				
				deferred.resolve();
			}
		} 
		
		return deferred.promise;
	}


	/**
	 * @desc 获取当前的音频编码格式
	 * 
	 * @example 
	 * avdEngine.getAudioCoding();
	 */
	AVDEngine.prototype.getAudioCoding = function() {
		return this.audioCoding;
	}
	
	
	/**
	 * @desc 设置音频订阅方式是否为自动订阅,默认值为true
	 * @param {Boolean} isAudioAutodSub  true为自动订阅,flase为手动订阅
	 * 
	 * @example 
	 * avdEngine.setAudioAutoSub(false);
	 */
	AVDEngine.prototype.setAudioAutoSub = function(isAudioAutoSub) {
		this.audioAutoSub = isAudioAutoSub;
		log.info(`===avdEngine.setAudioAutoSub(), 设置音频自动订阅: ${isAudioAutoSub}`);
		this.logReportWait.push(`avdEngine.setAudioAutoSub(), 设置音频自动订阅: ${isAudioAutoSub}`);
	}
	
	
	/**
	 * @desc 获取当前的音频的订阅方式,true为自动订阅,flase为手动订阅
	 * 
	 * @example 
	 * avdEngine.getAudioAutoSub();
	 */
	AVDEngine.prototype.getAudioAutoSub = function() {
		return this.audioAutoSub;
	}
	
	
	/**
	 * @desc 设置媒体通道协议
	 * @param {int} mediaType, 0 表示udp优先; 1表示 tcp优先,默认为0
	 * 
	 * @example 
	 * avdEngine.setMediaConnectType(1);
	 */
	AVDEngine.prototype.setMediaConnectType = function(mediaType) {
		this.mediaConnectType = mediaType;
		log.info(`===avdEngine.setMediaConnectType(), 设置媒体通道协议: ${this.mediaConnectType}`);
		this.logReportWait.push(`avdEngine.setMediaConnectType(), 设置媒体通道协议: ${this.mediaConnectType}`);
	}
	
	
	/**
	 * @desc 获取媒体通道协议,0 表示udp优先; 1表示 tcp优先,默认为0
	 * @example 
	 * avdEngine.getMediaConnectType();
	 */
	AVDEngine.prototype.getMediaConnectType = function() {
		return this.mediaConnectType;
	}
	
	
	
	/**
	 * @desc 设置数据加密类型
	 * @param {DataCryptoType} dataCryptoType - 数据加密类型(枚举型),默认值为DataCryptoType.AES128
	 * 
	 * @example 
	 * avdEngine.setDataCryptoType(DataCryptoType.AES256);
	 */
	AVDEngine.prototype.setDataCryptoType = function(dataCryptoType){
		if(typeof(dataCryptoType) == "undefined"){
		   log.error(`===avdEngine.setDataCryptoType(), 设置数据加密类型失败。dataCryptoType: ${dataCryptoType}`);
		}else{
		   this.dataCrypto = dataCryptoType;
		   log.info(`===avdEngine.setDataCryptoType(), 设置数据加密类型: ${this.dataCrypto}`);
		   this.logReportWait.push(`avdEngine.setDataCryptoType(), 设置数据加密类型: ${this.dataCrypto}`);
		} 
	}
	
	
	/**
	 * @desc 获取当前的数据加密类型
	 * 
	 * @example 
	 * avdEngine.getDataCryptoType();
	 */
	AVDEngine.prototype.getDataCryptoType = function() {
		return this.dataCrypto;
	}


	/**
	 * @desc 获取摄像头分辨率枚举,该枚举值只是罗列了主流的分辨率,而非真实的当前摄像头支持的分辨率集。
	 * 
	 * @example 
	 * avdEngine.getResolutionEnum().then(fillResolutionElement);
	 * 
	 * function fillResolutionElement(resolutionEnum){
	 *	    for(var key in resolutionEnum){
	 *			 	var resolutionObject = resolutionEnum[key];
	 *			 	var value = resolutionObject.width +" X " + resolutionObject.height;
	 *	     }
	 * }
	 */
	AVDEngine.prototype.getResolutionEnum = function() {
		var deferred = when.defer();
		deferred.resolve(Resolution);

		return deferred.promise;
	}
	
	
	/**
	 * @desc 在分布式集群部署中,设置加入房间时mcu服务器的路由参数分配
	 * @param {Object} mcuClusterRouteParams
	 *     参数值格式参考如:{ip_tag":"local","idc_code":"idc_code"}
	 *     ip_tag:对应于 rtc_node_addr 中的tag标志,用于区分同一台服务器的多网卡地址,可以自定义,然后在参数中传入。
	 *             举例的话,比如 'local','internal','dianxin','liantong'
	 *     idc_code:对应于 rtc_node 中的 idc_code标志,用于区分不同的服务器,唯一,可以自定义。
	 *               比如北京服务器设置为'beijing',杭州的设置为'hangzhou',然后在参数中传入,用于定位到服务器。
	 */
	AVDEngine.prototype.setMcuClusterRouteParams = function(mcuClusterRouteParams) {
		this.mcuClusterRouteParams = mcuClusterRouteParams;
	}
	
	
	
	/**
	 * @desc 设置视频相关的默认参数值
	 * @param {int} width   分辨率宽,默认值为640
	 * @param {int} height  分辨率高,默认值为480
	 * @param {Object} resolutionSetType 分辨率设置类型枚举,默认值为强制即ResolutionSetType.ideal
	 * @param {int} frameRate 视频帧率,默认值为20
	 * 
	 * @example 
	 * avdEngine.setDefaultVideoParams(1080,720,ResolutionSetType.ideal,25);
	 */
	AVDEngine.prototype.setDefaultVideoParams = function(width,height,resolutionSetType,frameRate) {
		   if(width){
		   	   this.defaultVideoParamsWidth = width;
		   }
		   
		   if(height){
		   	   this.defaultVideoParamsHeight = height;
		   }
		   
		   if(resolutionSetType){
		   	   this.defaultVideoParamsResolutionSetType = resolutionSetType;
		   }
		   
		   if(frameRate){
		   	   this.defaultVideoParamsFrameRate = frameRate;
		   }
		 
		   log.info(`===avdEngine.setDefaultVideoParams(), 设置视频相关的默认参数值, 分辨率宽: ${width}, 分辨率高: ${height}, 分辨率设置类型: ${resolutionSetType}, 视频帧率: ${frameRate}`);
		   this.logReportWait.push(`avdEngine.setDefaultVideoParams(), 设置视频相关的默认参数值, 分辨率宽: ${width}, 分辨率高: ${height}, 分辨率设置类型: ${resolutionSetType}, 视频帧率: ${frameRate}`);
	}
	
	
	
	
	/**
	 * @desc 设置桌面共享相关的默认参数值
	 * @param {int} maxWidth   分辨率最大宽度,默认值为1920
	 * @param {int} maxHeight  分辨率最大高度,默认值为1080
	 * @param {int} minFrameRate 最小帧率, 默认值为5
	 * @param {int} maxFrameRate 最大帧率, 默认值为10
	 * 
	 * @example 
	 * avdEngine.setDefaultScreenParams(1080,720,5,10);
	 */
	AVDEngine.prototype.setDefaultScreenParams = function(maxWidth,maxHeight,minFrameRate,maxFrameRate) {
		  if(maxWidth){
		  	    this.defaultScreenParamsWidth = maxWidth;
		  }
		  if(maxHeight){
		  	    this.defaultScreenParamsHeight = maxHeight;
		  }
		  if(maxFrameRate){
		  	    this.defaultScreenParamsFrameRate = maxFrameRate;
		  }
		
		  log.info(`===avdEngine.setDefaultScreenParams(), 设置桌面共享相关的默认参数值, 分辨率最大宽度: ${maxWidth}, 分辨率最大高度: ${maxHeight}, 最小帧率: ${minFrameRate}, 最大帧率: ${maxFrameRate}`);
		 this.logReportWait.push(`avdEngine.setDefaultScreenParams(), 设置桌面共享相关的默认参数值, 分辨率最大宽度: ${maxWidth}, 分辨率最大高度: ${maxHeight}, 最小帧率: ${minFrameRate}, 最大帧率: ${maxFrameRate}`);
    }

    
	/**
	 * @desc  设置媒体流推送的保活时长(单位为毫秒),不设置默认为15000毫秒
	 * @param {int} keepAliveTime -保活时长
	 */
	AVDEngine.prototype.setDefaultMediaPublishKeepAliveTime = function(keepAliveTime) {
        if(keepAliveTime){
			this.defaultMediaPublishKeepAliveTime = keepAliveTime;
		}
		log.info(`===avdEngine.setDefaultMediaPublishKeepAliveTime(), 设置媒体流发布的保活时长(单位为毫秒), keepAliveTime: ${keepAliveTime}`);
	}
	

	/**
	 * @desc 引擎初始化
	 * @async
	 * @param {String} serverURI - MCU服务器地址
	 * @param {String} accessToken - 访问令牌
	 * @param {boolean} isUseCascade - 是否级联加入会议,可以不传,默认为false
	 * 
	 * @example
	 * avdEngine.init('avd.nice2meet.cn:9610','ZDgyNGU1MGU0ZGY2MTJlZWY3NjkwMjQ5NDY0YWE3MDkxZGJjNmRiZg==').then(function(){}).otherwise(function(error){});
	 */
	AVDEngine.prototype.init = function(serverURI, accessToken, isUseCascade) {
		var self = this;
		
		var deferred = when.defer();
		
		if(typeof(isUseCascade) == "undefined"){
			isUseCascade = false;
		}
		log.info(`===avdEngine.init(), 引擎初始化开始, serverURI: ${serverURI}, accessToken: ${accessToken}, isUseCascade: ${isUseCascade}`);
		this.logReportWait.push(`avdEngine.init(), 引擎初始化开始, serverURI: ${serverURI}, accessToken: ${accessToken}, isUseCascade: ${isUseCascade}`);
		
		if(!accessToken || !serverURI){
			var error = new Error(ErrorConstant.avdEngine_init_failed);
			log.error(`===avdEngine.init(), error, code: ${error.code}, message: ${error.message}`);
			deferred.reject(error);
			return deferred.promise;
		}
        
        this.restServerInfo.mcuServerUrl = serverURI;
		this.clientIsUseCascade = isUseCascade;
		this.accessToken = accessToken.replace(/%3D%3D/g, "==");
		this.accessToken = this.accessToken.replace(/%3d%3d/g, "==");
		
		/**
		  * 操作系统版本号及手机型号等信息上报给服务器,规则如下,没有时可以不用上报该属性。
		  * user_resources:{
		  *    os: 'ac OS_10.13.6',
		  *    endpoint: {
			      userAgent: 'abc',
		  * 	  endpointType:'',
		  *       browser:'chrome_103.0.0.0',
		  *       browserShell:'browser_2.1',
		  *       wxVersion:''
		  *    }
		  * }
		  */
		 var endpoint = {};
		 var nAgt = navigator.userAgent;
		 //var nAgt = "Mozilla/5.0 (Linux; U; Android 12; zh-CN; ANA-AN00 Build/HUAWEIANA-AN00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/100.0.4896.58 UWS/5.12.8.0 Mobile Safari/537.36 AliApp(DingTalk/7.6.50) com.alibaba.android.rimet/43925710 Channel/227200 language/zh-CN abi/64 Hmos/4.2.0 xpn/huawei UT4Aplus/0.2.25 colorScheme/light";
		 endpoint.userAgent = nAgt;
		 
		 var uaparser =  new UAParser(nAgt);
		 if(uaparser){
			 var os = uaparser.getOS();
			 if(os){
				self.clientResource.os = os.name + "_" + os.version;
			 }
			 
			 var browser = uaparser.getBrowser();
			 if(browser){
				 endpoint.browser = browser.name + "_" + browser.version;
			 }
		}
				   
		if(self.detect.browser.isCustomized){
			endpoint.browserShell = self.detect.browser.shellName + "_" + self.detect.browser.shellFullVersion;
		}
		self.clientResource.endpoint = endpoint;
		
		
		//获取本地客户端IP 
		if(typeof(navigator.Promise) != "undefined"){
		    getLocalClientIP().then(function(localClientIP){
				log.debug(`===avdEngine.init(), localClientIP: ${localClientIP}`);
			    self.clientResource.ips = localClientIP;
		    });
		}
		
		if(typeof(navigator.connection) != "undefined"){
			 var effectiveType = getEffectiveType()
			 log.debug(`===avdEngine.init(), effectiveType: ${effectiveType}`);
		     self.clientResource.net = effectiveType;
		}
		log.debug("===avdEngine.init(),clientResource:" + JSON.stringify(self.clientResource));
		
		log.info("===avdEngine.init(),引擎初始化成功");
		this.logReportWait.push("引擎初始化成功!");
		 
		deferred.resolve();
		return deferred.promise;
	};
	
	
	/**
	 * @desc avdEngine OEM初始化
	 * @async
	 * @param {String} serverURI - MCU服务器地址
	 * @param {String} oemName - oem名称
	 */
	AVDEngine.prototype.initWithOEM = function(serverURI, oemName) {
		var deferred = when.defer();
		log.info("===avdEngine.initWithOEM(), OEM  init begin");
		
		if(oemName !='qiniu.com'){
			var err = new Error(ErrorConstant.oemname_notfound);
			deferred.reject(err);
		}else{
			this.restServerInfo.mcuServerUrl = serverURI;
			this.restServerInfo.roomServerUrl = serverURI;
			this.restServerInfo.isCluster = true;
			deferred.resolve();
		}
		//TODO  生成一个唯一id 记录每一次访问 用于日志
		return deferred.promise;
	};
	
	

	/**
	 * @desc 获取房间。如房间存在,直接返回,不存在时创建房间后返回
	 * @param {String} roomId - 房间ID
	 * @returns {Object} room - 房间对象
	 * 
	 * @example
	 * var roomId = '13124323454';
	 * room = avdEngine.obtainRoom(roomId);
	 */
	AVDEngine.prototype.obtainRoom = function(roomId) {
		if (roomId == null || roomId == '') {
			var error = new Error(ErrorConstant.roomId_required);
			log.error(`===avdEngine.obtainRoom() error, code: ${error.code}, message: ${error.message}`);
			return;
		}	
		
		if (typeof roomId !== 'string') {
			var error = new Error(ErrorConstant.exporter_parameter_type_error);
			log.error(`===avdEngine.obtainRoom() error, code: ${error.code}, message: ${error.message} [roomId需字符串类型]`);
			return;
		}
			
		var room = getRoomById(roomId, this.rooms);
		if(room == null) {
			room = new Room(roomId);
			
			//平板电脑chrome浏览器不支持H264,在有小程序加会的场景中,需要通知到服务器进行转码
			// if(this.browserOsName == "Android"){
			//     room.userAgent = USER_AGENT_AVD_VIDEO_TRANSCODING;
			// }
			// else if(this.browserOsName == "iOS"){
			// 	room.userAgent = USER_AGENT_AVD_IOS;
			// }
			
			this.rooms.push(room);
		}
		return room;
	};
	
	
	/**
	 *  @desc 获取房间对象
	 *  @param {String} roomId - 房间ID
	 *  @returns {Object} room - 房间对象
	 */
	AVDEngine.prototype.getRoom = function(roomId) {
		if (roomId == null || roomId == '') {
			var error = new Error(ErrorConstant.roomId_required);
			log.error(`===avdEngine.getRoom() error, code: ${error.code}, message: ${error.message}`);
			return;
		}
			  
	    if (typeof roomId !== 'string') {
		   var error = new Error(ErrorConstant.exporter_parameter_type_error);
		   log.error(`===avdEngine.getRoom() error, code: ${error.code}, message: ${error.message} [roomId需字符串类型]`);
		   return;
	    }
		
		var room = getRoomById(roomId, this.rooms);
		return room;
	}
	

	/**
	 * @desc 获取当前所有设备的原始对象,需要应用层自己去疏理摄像头,麦克风等。
	 * @async
	 * @returns {Object} - devices - 所有设备的原始对象
	 * 
	 * @example
	 * avdEngine.getAllDevices().then(onAllDevices).otherwise(showError);
	 * 
	 * function onAllDevices(devices) {
	 *    devices.forEach(function(device){
	 *	     if (device.kind === 'videoinput' || device.kind === 'video') {
	 * 	           console.log("camera:",device);
	 *       }else if(device.kind === 'audioinput' || device.kind === 'audio'){
	 * 	           console.log("microphone:",device);
	 *       }else if(device.kind === 'audiooutput'){
	 * 	           console.log("speaker:",device);
	 *       }
	 *    }
	 * }
	 */
	AVDEngine.prototype.getAllDevices = function() {
		var deferred = when.defer();
		// console.log("aaaaaaaaaaaaaaaaaaaaa");
		if(typeof navigator.mediaDevices !== 'undefined') {
			    log.debug("===avdEngine.getAllDevices(), by navigator.mediaDevices.enumerateDevices()");
				navigator.mediaDevices.enumerateDevices().then(function(devices) {
					// console.log("===avdEngine.getAllDevices(), by navigator.mediaDevices.enumerateDevices(),devices:",devices);
					deferred.resolve(devices);
				});
		} else if(typeof MediaStreamTrack !== 'undefined') {
				if(typeof(MediaStreamTrack.getSources) != "undefined"){
					   log.debug("===avdEngine.getAllDevices(),by MediaStreamTrack.getSources");
				       MediaStreamTrack.getSources(function(sourceInfos) {
				           deferred.resolve(sourceInfos);
				       });
				}else{
				       var error = new Error(ErrorConstant.navigatorUserMediaError_notSupportedError);
			           deferred.reject(error);
				}
		} else {
			var error = new Error(ErrorConstant.not_support_mediaStreamTrack);
			deferred.reject(error);
		}

		return deferred.promise;
	};


	/**
	 * @desc 初始化摄像头、麦克风设备
	 * <p>逻辑:摄像头设备会全部初始化到服务器,麦克风设备只会初始化默认的一个到服务器</p>
	 * @async
	 * @return {int} - 返回初始化完成状态。1:代表完成
     *
	 * @example
	 * avdEngine.initDevice().then(function(result){
	 * 	   if(result == 1){
	 * 	      console.log('设备初始化完成');
	 *     }
	 * }).otherwise(function(error){});
	 */
	AVDEngine.prototype.initDevice = function() {
		var deferred = when.defer();
		var self = this;
		log.info("===avdEngine.initDevice():Camera and Microphone begin...");
		
		if(self.isChrome && self.majorVersion <81){
			self.initDeviceHandler().then(function(state){
				deferred.resolve(state);
			}).otherwise(function(error){
				deferred.reject(error);
			});
		}else{
			 if(self.isFirefox){
			 	var constraints = {
			 	   audio: {deviceId: 'undefined'},
			 	   video: {deviceId: 'undefined'}
			 	};
				log.debug("===avdEngine.initDevice(),firefox constraints:"+JSON.stringify(constraints));
				navigator.mediaDevices.getUserMedia(constraints).then(function(stream){
					 self.initDeviceHandler().then(function(state){
						if (stream) {
							stream.getTracks().forEach(function(track) {
							   track.stop();
							});
							stream = null;
						}	 
						 
						deferred.resolve(state);
					 }).otherwise(function(error){
						 deferred.reject(error);
					 });
											
				}).catch(function(error){
					log.error('===avdEngine.initDevice(),navigator.MediaDevices.getUserMedia of firfox error: ', error.message, error.name);
				    var Error = ModuleBase.use(ModulesEnum.error);
				    var errorObj;
					if(error.name == 'NotSupportedError'){
						errorObj = new Error(ErrorConstant.navigatorUserMediaError_notSupportedError);
					}else if(error.name == 'OverconstrainedError'){
						errorObj = new Error(ErrorConstant.navigatorUserMediaError_overconstrainedError);
					}else if(error.name == 'TrackStartError' || error.name == 'NotReadableError'){
						errorObj = new Error(ErrorConstant.navigatorUserMediaError_trackStartError);
					}else if(error.name == 'NotAllowedError'){
						errorObj = new Error(ErrorConstant.navigatorUserMediaError_notAllowedError);
					}else if(error.name == 'TypeError'){
						errorObj = new Error(ErrorConstant.navigatorUserMediaError_typeError);
					}else if(error.name == 'NotFoundError'){
						errorObj = new Error(ErrorConstant.navigatorUserMediaError_notFoundError);
					}else{
						errorObj = new Error(ErrorConstant.navigatorUserMediaError_unknown);
					}
				   deferred.reject(errorObj);
				});	
			 }else{
					 self.checkDevice().then(function(result)    {
						 if(!result.deviceIdIsUndefined){
							 log.debug("===AVDEngine.initDevice(),chrome Version >= 81 or Other browsers,deviceId is not Undefined");
							 self.initDeviceHandler().then(function(state){
								deferred.resolve(state);
							 }).otherwise(function(error){
								deferred.reject(error);
							 });
						 }else{
							 log.debug("===AVDEngine.initDevice(),chrome Version >= 81 or Other browsers ,deviceId is Undefined");
							 var constraints = {};
							 if(result.video){
								constraints.video = {deviceId: 'undefined'};
							 }
							 
							 if(result.audio){
								constraints.audio = {deviceId: 'undefined'};
							 }
							 log.debug("===avdEngine.initDevice(),chrome version>=81 or Other browsers,constraints:"+JSON.stringify(constraints));
							
							 if(!result.video && ! result.audio){
								  deferred.resolve(1);
								  return deferred.promise;
							 }else{
								 navigator.mediaDevices.getUserMedia(constraints).then(function(stream){
									 if (stream) {
										stream.getTracks().forEach(function(track) {
										   track.stop();
										});
										stream = null;
									 }
												
									 self.initDeviceHandler().then(function(state){
										deferred.resolve(state);
									 }).otherwise(function(error){
										 deferred.reject(error);
									 });
							
								 }).catch(function(error){
									   log.error('===avdEngine.initDevice(),navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
									   var Error = ModuleBase.use(ModulesEnum.error);
									   var errorObj;
										if(error.name == 'NotSupportedError'){
											errorObj = new Error(ErrorConstant.navigatorUserMediaError_notSupportedError);
										}else if(error.name == 'OverconstrainedError'){
											errorObj = new Error(ErrorConstant.navigatorUserMediaError_overconstrainedError);
										}else if(error.name == 'TrackStartError' || error.name == 'NotReadableError'){
											errorObj = new Error(ErrorConstant.navigatorUserMediaError_trackStartError);
										}else if(error.name == 'NotAllowedError'){
											errorObj = new Error(ErrorConstant.navigatorUserMediaError_notAllowedError);
										}else if(error.name == 'TypeError'){
											errorObj = new Error(ErrorConstant.navigatorUserMediaError_typeError);
										}else if(error.name == 'NotFoundError'){
											errorObj = new Error(ErrorConstant.navigatorUserMediaError_notFoundError);
										}else{
											errorObj = new Error(ErrorConstant.navigatorUserMediaError_unknown);
										}
									 deferred.reject(errorObj);
								 });
							}
						}
					}).otherwise(function(error){
						 deferred.reject(error);
					});
			}
		}
		return deferred.promise;
	}
	
	/**
	 * @ignore
	 */
	AVDEngine.prototype.initDeviceHandler = function() {
		var deferred = when.defer();
		var self = this;
		this.getAllDevices().then(
			function(devices) {
				var cameraMap = {};
				var cameraLen = 0;
				
				var microphoneMap = {};
				var microphoneLen = 0;
				var defaultMicrophoneLable = null;
				var microphoneGroupId = null;
				
				var speakerMap = {};
				var speakerLen = 0;
				var defaultSpeakerLable = null;
				var speakerGroupId = null;
				
				for(var i = 0; i != devices.length; ++i) {
					var device = devices[i];
					var deviceId = device.deviceId || device.id;
					if(device.kind === 'videoinput' || device.kind === 'video') {
						self.hasCamera = true;
						cameraLen += 1;
						cameraMap[deviceId] = device.label || 'camera' + cameraLen;
					} else if(device.kind === 'audioinput' || device.kind === 'audio') {
						self.hasMicrophone = true;
						if(device.deviceId == 'default'){
							var lableLines = device.label.split(' - ');
							defaultMicrophoneLable = lableLines[1];
						    microphoneGroupId = device.groupId;
						}else{
							if(device.groupId == microphoneGroupId && device.label == defaultMicrophoneLable){
							 	 microphoneLen += 1;
								 microphoneMap[deviceId] = device.label || 'microphone' + microphoneLen;							
							}
							if(device.groupId != microphoneGroupId){
							 	microphoneLen += 1;
						        microphoneMap[deviceId] = device.label || 'microphone' + microphoneLen;
						    }
						}
					} else if(device.kind === 'audiooutput') {
						self.hasSpeaker = true;
						if(device.deviceId == 'default'){
						     var lableLines = device.label.split(' - ');
						     defaultSpeakerLable = lableLines[1];
						     speakerGroupId = device.groupId;
						}else {
							if(device.groupId == speakerGroupId && device.label == defaultSpeakerLable){
								speakerLen += 1;
								speakerMap[deviceId] = device.label || 'speaker' + speakerLen;
							}
							if(device.groupId != speakerGroupId){
								speakerLen += 1;
						        speakerMap[deviceId] = device.label || 'speaker' + speakerLen;
							}
						}
					}
				}
		
				self.cameraMap = cameraMap;
				log.info("===avdEngine.initDevice(),Camera finish,cameraMap:",JSON.stringify(cameraMap));
				
				//客户端资源上报用
				self.clientResource.cameras = [];
				for(var key in cameraMap) {
					 var val = cameraMap[key];
					 var clientResourceCamera = new Object();
					 clientResourceCamera.id = key;
					 clientResourceCamera.name = val;
					 self.clientResource.cameras.push(clientResourceCamera);
				}
		
				self.microphoneMap = microphoneMap;
				for(var key in microphoneMap) {
					var val = microphoneMap[key];
					if(!self.checkMicrophoneId){
						self.checkMicrophoneId = key;
						self.checkMicrophoneName = val;
					}
					break;
				}
				log.info("===avdEngine.initDevice(),Microphone finish,microphoneMap:",JSON.stringify(microphoneMap));
				
				
				//客户端资源上报用
				self.clientResource.microphones = [];
				for(var key in microphoneMap) {
					 var val = microphoneMap[key];
					 var clientResourceMicrophone = new Object();
					 clientResourceMicrophone.id = key;
					 clientResourceMicrophone.name = val;
					 self.clientResource.microphones.push(clientResourceMicrophone);
				}
		
				self.speakerMap = speakerMap;
				log.info("===avdEngine.initDevice(),Speaker finish,speakerMap:",JSON.stringify(speakerMap));
				
				
				//客户端资源上报用
				self.clientResource.speakers = [];
				for(var key in speakerMap) {
					 var val = speakerMap[key];
					 var clientResourceSpeaker = new Object();
					 clientResourceSpeaker.id = key;
					 clientResourceSpeaker.name = val;  
					 self.clientResource.speakers.push(clientResourceSpeaker);
				}

				deferred.resolve(1);
			}
		).otherwise(
			function(error) {
				  log.error("===avdEngine.initDevice() error, code:" + error.code + "; message:" + error.message);
				  deferred.reject(error);	
			}	
		)
		
		return deferred.promise;
    }

	/**
	 * @desc 设备热插拔时,通过该方法可以更新设备资源。
	 *       <p>逻辑:摄像头设备会全部初始化到服务器,麦克风设备只会初始化默认的一个到服务器</p>
	 * @ignore
	 */
	AVDEngine.prototype.refreshDevice = function() {
		var deferred = when.defer();

		var self = this;

		var oldMicrophoneIdArray = [];
		for(key in self.microphoneMap) {
			oldMicrophoneIdArray.push(key);
		}

		var oldCameraIdArray = [];
		for(key in self.cameraMap) {
			oldCameraIdArray.push(key);
		}
      
		this.getAllDevices().then(
			function(devices) {
				
				var cameraMap = {};
				var cameraLen = 0;
				
				var microphoneMap = {};
				var microphoneLen = 0;
				var defaultMicrophoneLable = null;
				var microphoneGroupId = null;
				
				var speakerMap = {};
				var speakerLen = 0;
				var defaultSpeakerLable = null;
				var speakerGroupId = null;
				
				for(var i = 0; i != devices.length; ++i) {
					var device = devices[i];
					var deviceId = device.deviceId || device.id;
					if(device.kind === 'videoinput' || device.kind === 'video') {
						cameraLen += 1;
						cameraMap[deviceId] = device.label || 'camera' + cameraLen;
					} else if(device.kind === 'audioinput' || device.kind === 'audio') {
						if(device.deviceId == 'default'){
							 var lableLines = device.label.split(' - ');
						     defaultMicrophoneLable = lableLines[1];
						     microphoneGroupId = device.groupId;
						}else{
							 if(device.groupId == microphoneGroupId && device.label == defaultMicrophoneLable){
							  	 microphoneLen += 1;
							 	 microphoneMap[deviceId] = device.label || 'microphone' + microphoneLen;							
							 }
							 if(device.groupId != microphoneGroupId){
							 	 microphoneLen += 1;
						         microphoneMap[deviceId] = device.label || 'microphone' + microphoneLen;
							 }
						}
					} else if(device.kind === 'audiooutput') {
						if(device.deviceId == 'default'){
							var lableLines = device.label.split(' - ');
							defaultSpeakerLable = lableLines[1];
 						    speakerGroupId = device.groupId;
 						}else {
							if(device.groupId == speakerGroupId && device.label == defaultSpeakerLable){
								speakerLen += 1;
								speakerMap[deviceId] = device.label || 'speaker' + speakerLen;
							}
 							if(device.groupId != speakerGroupId){
 								speakerLen += 1;
 						        speakerMap[deviceId] = device.label || 'speaker' + speakerLen;
 							}
 						}
					}
				}
             
				refreshVideoDeviceHandle(oldCameraIdArray, cameraMap, self.rooms, self.cameraMap);
				
				for(var key in microphoneMap) {
					var val = microphoneMap[key];
					self.checkMicrophoneId = key;
					self.checkMicrophoneName = val;
					break;
				}

				refreshAudioDeviceHandle(oldMicrophoneIdArray, microphoneMap, self.rooms, self.microphoneMap);

				self.speakerMap = speakerMap;
			}
		).otherwise(
			function(error) {
				log.error("===avdEngine.refreshDevice(),get device error!error code:" + error.code + "; error message:" + error.message);
			}
		)
		

		setTimeout(function() {
			deferred.resolve();
		}, 2000);

		return deferred.promise;
	}




    /**
	 * 实现逻辑:
	 *     1. 现麦克风设备不存在,新麦克风设备需要做init处理
	 *     2. 新麦克风设备与现麦克风设备ID一样,不做处理
	 *     3. 现麦克风设备状态为init,则新麦克风设备也需要做init处理
	 *     4. 现麦克风设备状态为publish或muted,则新麦克风设备也需要做publish处理
	*/

	/**
	 * @desc 设置成当前使用的麦克风
	 * @param {String} microphoneId - 麦克风设备Id
	 */
	AVDEngine.prototype.setRecordingMicrophone = function(microphoneId) {
		log.info(`===avdEngine.setRecordingMicrophone(), microphoneId: ${microphoneId}`);
		var self = this;
		for(var i = 0; i != self.rooms.length; ++i) {
			var room = self.rooms[i];
            if(room && room.selfUser){
                var audio = room.selfUser.audio;
                if(!audio) {
                    var name = self.microphoneMap[microphoneId];
                    room.selfUser.initAudio(microphoneId, name);
                } else {
                    if(microphoneId != audio.id) {
                        if(audio.status == StreamStatus.init) {
                            room.selfUser.deleteAudio(audio.id);
                            var name = self.microphoneMap[microphoneId];
                            room.selfUser.initAudio(microphoneId, name);
							self.checkMicrophoneId = microphoneId;
							self.checkMicrophoneName = name;
							log.info(`===avdEngine.setRecordingMicrophone(), microphoneId: ${microphoneId}, microphoneName: ${name} success!`);
                        } else if(audio.status == StreamStatus.published || audio.status == StreamStatus.muted) {
                            var element = audio.element;
                            audio.closeMicrophone().then(function(){
								//设置延时,以防远端订阅音频时,原来的设备id已失效引发错误。
								setTimeout(function(){
									room.selfUser.deleteAudio(audio.id);
									var name = self.microphoneMap[microphoneId];
									room.selfUser.initAudio(microphoneId, name);
									room.selfUser.audio.openMicrophone(element);
									self.checkMicrophoneId = microphoneId;
									self.checkMicrophoneName = name;
									log.info(`===avdEngine.setRecordingMicrophone(), microphoneId: ${microphoneId}, microphoneName: ${name} success!`);
								},600);
							});
                        }
                    }
                }
            }
		}
	}
	
	// /**
	//  * @desc 获取所有设备对象
	//  * @returns {Object} - deviceObject = {video: video,audio: audio,speaker: speaker};
	//  * 
	//  * @ignore
	//  * 
	//  * 
	//  * @example
	//  * avdEngine.getDeviceObject().then(showDevices).otherwise(alertError);
	//  * 
	//  * function showDevices(deviceObject) {
	//  *   var video = deviceObject.video;
	//  *   var audio = deviceObject.audio;
	//  *   var speaker = deviceObject.speaker;
	//  * }
	//  */
	// AVDEngine.prototype.getDeviceObject = function() {
	// 	var deferred = when.defer();
	// 	var self = this;
	// 	log.info("===avdEngine.getDeviceObject() begin");
        
	// 	this.getAllDevices().then(
	// 		function(devices) {
	// 			console.log("===avdEngine.getDeviceObject(),devices:",devices);
	// 			var deviceObject = {
	// 				video: null,
	// 				audio: null,
	// 				speaker: null
	// 			};
				
	// 			var cameraMap = {};
	// 			var cameraLen = 0;
				
	// 			var microphoneMap = {};
	// 			var microphoneLen = 0;
	// 			var microphoneGroupId =null;
				
	// 			var speakerMap = {};
	// 			var speakerLen = 0;
	// 			var speakerGroupId =null;
				
	// 			for(var i = 0; i != devices.length; ++i) {
	// 				var device = devices[i];
	// 				var deviceId = device.deviceId || device.id;
	// 				if(device.kind === 'videoinput' || device.kind === 'video') {
	// 					cameraLen += 1;
	// 					cameraMap[deviceId] = device.label || 'camera' + cameraLen;
	// 				} else if(device.kind === 'audioinput' || device.kind === 'audio') {
	// 					if(device.deviceId == 'default'){
	// 						 microphoneLen += 1;
	// 					     microphoneMap[deviceId] = device.label || 'microphone' + microphoneLen;
	// 					     microphoneGroupId = device.groupId;
	// 					}else{
	// 						 if(device.groupId != microphoneGroupId){
	// 						 	microphoneLen += 1;
	// 					        microphoneMap[deviceId] = device.label || 'microphone' + microphoneLen;
	// 						 }
	// 					}
	// 				} else if(device.kind === 'audiooutput') {
	// 					if(device.deviceId == 'default'){
 // 						    speakerLen += 1;
 // 						    speakerMap[deviceId] = device.label || 'speaker' + speakerLen;
 // 						    speakerGroupId = device.groupId;
 // 						}else {
 // 							if(device.groupId != speakerGroupId){
 // 								 speakerLen += 1;
 // 						        speakerMap[deviceId] = device.label || 'speaker' + speakerLen;
 // 							}
 // 						}
	// 				}
	// 			}

	// 			deviceObject.video = cameraMap;
	// 			deviceObject.audio = microphoneMap;
	// 			deviceObject.speaker = speakerMap;
				
	// 			deferred.resolve(deviceObject);
	// 		}
	// 	).otherwise(
	// 		function(error) {
	// 			deferred.reject(error);
	// 		}
	// 	)
		
	// 	return deferred.promise;
	// }
	
	
	
	
	/**
	 * @desc 检查设备是否存在
	 * @returns {Object} - deviceResult = {video: false,audio: false,speaker: false};
	 * 
	 * @ignore
	 * 
	 * @example
	 * avdEngine.checkDevice().then(checkResult).otherwise(checkError);
	 * 
	 * function checkResult(result) {
	 *    console.log("Does the Video Device Exist?" + result.video);
	 *    console.log("Does the Audio Device Exist?" + result.audio);
	 *    console.log("Does the Speaker Device Exist?" + result.speaker);
	 * }
	 */
	AVDEngine.prototype.checkDevice = function() {
		var deferred = when.defer();
		var self = this;
		log.debug('===checkDevice(),start...')
		this.getAllDevices().then(
			function(devices) {
				var deviceResult = {
					video: false,
					audio: false,
					speaker: false,
					deviceIdIsUndefined:false,
				};
				
				for(var i = 0; i != devices.length; ++i) {
					var device = devices[i];
					var deviceId = device.deviceId || device.id;
					if(!deviceId){
						deviceResult.deviceIdIsUndefined = true;
					}
					if(device.kind === 'videoinput' || device.kind === 'video') {
						deviceResult.video = true;
					} else if(device.kind === 'audioinput' || device.kind === 'audio') {
						deviceResult.audio = true;
					} else if(device.kind === 'audiooutput') {
						deviceResult.speaker = true;
					}
				}
				log.debug('===checkDevice(),sucess end...')
				deferred.resolve(deviceResult);
			}
		).otherwise(
			function(error) {
				log.debug('===checkDevice(),error end...')
				deferred.reject(error);
			}
		)
		return deferred.promise;
	}
	
	
	/**
	 * @desc 设备检测,打开摄像头
	 * @async
	 * @param {String} cameraId - 摄像头ID,空字符串时则默认选择打开一个摄像头
	 * @param {Int} resolution - 视频分辨率
	 * @param {Int} framerate - 视频帧率
	 * @returns {Object} - 视频流对象
	 * 
	 * @example
	 * avdEngine.checkOpenVideo(cameraId, resolution, framerate).then(showVideo).otherwise(alertError);
	 * 
	 * function showVideo(stream) {
	 *    checkVideo.srcObject = stream;
	 * }
	 */
	AVDEngine.prototype.checkOpenVideo = function(cameraId, resolution, framerate) {
		var deferred = when.defer();
		var self = this;
		
		self.checkCloseVideo();

		var constraints = {
			video: {},
			audio: false
		};
     
		if(cameraId  && cameraId!="undefined") {				
			constraints.video.deviceId = {
				exact: cameraId
			}
		}
		
		if (resolution) {
			var realResolution = Resolution[resolution];
			constraints.video.width = {
				min: realResolution.width,
				max: realResolution.width
			};
			
			constraints.video.height = {
				min: realResolution.height,
				max: realResolution.height
			};
		}

		if (framerate && (self.isChrome || self.isOpera)) {
			if(typeof(framerate) == 'string') {
				framerate = Number(framerate);
			}
			constraints.video.frameRate = {
				min: framerate,
				max: framerate
			};
		}
		
		try {
			//console.log("aaaa00000000000,constraints:",constraints);
			log.info("===avdEngine.checkOpenVideo(),constraints:"+ JSON.stringify(constraints));
			navigator.mediaDevices.getUserMedia(constraints).then(function(stream){
				self.checkVideoStream = stream;
				deferred.resolve(stream);
			}).catch(function(error){
				var Error = ModuleBase.use(ModulesEnum.error);
				var errorObj;
				
				if(error.name == 'NotSupportedError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_notSupportedError);
				}else if(error.name == 'OverconstrainedError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_overconstrainedError);
				}else if(error.name == 'TrackStartError' || error.name == 'NotReadableError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_trackStartError);
				}else if(error.name == 'NotAllowedError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_notAllowedError);
				}else if(error.name == 'TypeError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_typeError);
				}else if(error.name == 'NotFoundError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_notFoundError);
				}else{
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_unknown);
				}
                log.error("===avdEngine.checkOpenVideo(),errorName:"+ error.name);
				deferred.reject(errorObj);
			});
		} catch(e) {
			var Error = ModuleBase.use(ModulesEnum.error);
			var errorObj = new Error(ErrorConstant.navigatorUserMediaError_unknown);
			deferred.reject(errorObj)
		}
		
		return deferred.promise;
	}
	
	
	/**
	 * @desc 设备检测,打开麦克风
	 * @async
	 * @param {String} microphoneId - 麦克风ID,空字符串时则默认选择打开一个麦克风
	 * @returns {Object} - 音频流对象
	 * 
	 * @example
	 * avdEngine.checkOpenAudio().then(showAudio).otherwise(alertError);
	 * 
	 * function showAudio(stream) {
	 *    checkAudio.srcObject = stream;
	 * }
	 */
	AVDEngine.prototype.checkOpenAudio = function(microphoneId) {
		var deferred = when.defer();
		var self = this;

        self.checkCloseAudio();
		var constraints = {
			video: false,
			audio: {}
		};

		if(microphoneId  && microphoneId!="undefined") {
			constraints.audio.deviceId = {
				exact:microphoneId
			}
		}

		try {
			log.info("===avdEngine.checkOpenAudio(),constraints:"+ JSON.stringify(constraints));
			
			navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
				self.checkAudioStream = stream;
				deferred.resolve(stream);
			}).catch(function(error) {
				var Error = ModuleBase.use(ModulesEnum.error);
				var errorObj;
				
				if(error.name == 'NotSupportedError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_notSupportedError);
				}else if(error.name == 'OverconstrainedError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_overconstrainedError);
				}else if(error.name == 'TrackStartError' || error.name == 'NotReadableError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_trackStartError);
				}else if(error.name == 'NotAllowedError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_notAllowedError);
				}else if(error.name == 'TypeError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_typeError);
				}else if(error.name == 'NotFoundError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_notFoundError);
				}else{
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_unknown);
				}
				deferred.reject(errorObj)
			});
		} catch(e) {
			var Error = ModuleBase.use(ModulesEnum.error);
			var errorObj = new Error(ErrorConstant.navigatorUserMediaError_unknown);
			deferred.reject(errorObj)
		}
		
		return deferred.promise;
	}
	
	
	/**
	 * @desc 设备检测,打开摄像头和麦克风
	 * @async
	 * @param {String} cameraId - 摄像头ID,空字符串时则默认选择打开一个摄像头
	 * @param {String} microphoneId - 麦克风ID,空字符串时则默认选择打开一个麦克风
	 * @param {Int} resolution - 视频分辨率
	 * @param {Int} framerate - 视频帧率
	 * @returns {Object} - 音视频流对象
	 * 
	 * @example
	 * avdEngine.checkOpenVideoAndAudio().then(showVideoAndAudio).otherwise(alertError);
	 * 
	 * function showVideoAndAudio(stream) {
	 *    checkVideo.srcObject = stream.videoStream;
	 *    checkAudio.srcObject = stream.audioStream;
	 * }
	 */
	AVDEngine.prototype.checkOpenVideoAndAudio = function(cameraId,microphoneId,resolution,framerate) {
		var deferred = when.defer();
		var self = this;
		
		self.checkCloseVideo();
		self.checkCloseAudio();
		
		var constraints = {
			video: {},
			audio: {}
		};
		
		if(cameraId && cameraId!="undefined") {				
			constraints.video.deviceId = {
				exact:cameraId
			};
		}
		
		if(microphoneId && microphoneId!="undefined") {
			constraints.audio.deviceId = {
				exact:microphoneId
			}
		}
		
		if (resolution) {
			var realResolution = Resolution[resolution];
			constraints.video.width = {
				min: realResolution.width,
				max: realResolution.width
			};
			constraints.video.height = {
				min: realResolution.height,
				max: realResolution.height
			};
		}

		if (framerate && (self.isChrome || self.isOpera)) {
			if(typeof(framerate) == 'string') {
				framerate = Number(framerate);
			}
			constraints.video.frameRate = {
				min: framerate,
				max: framerate
			};
		}

		try {
			log.info("===avdEngine.checkOpenVideoAndAudio(),constraints:"+ JSON.stringify(constraints));
			navigator.mediaDevices.getUserMedia(constraints).then(function(stream){
				var audioStream = new MediaStream();
				var videoStream = new MediaStream();

				if (stream) {
					var audioTracks = stream.getAudioTracks();
					for (var i = 0; i < audioTracks.length; i++) {
						audioStream.addTrack(audioTracks[i]);
					}
		
					var videoTracks = stream.getVideoTracks();
					for (i = 0; i < videoTracks.length; i++) {
						videoStream.addTrack(videoTracks[i]);
					}
				}
				var newStream = {
					audioStream:audioStream,
					videoStream:videoStream
				}
				self.checkVideoStream = newStream.videoStream;
				self.checkAudioStream = newStream.audioStream;
				deferred.resolve(newStream);
			}).catch(function(error){
				var Error = ModuleBase.use(ModulesEnum.error);
				var errorObj;
				
				if(error.name == 'NotSupportedError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_notSupportedError);
				}else if(error.name == 'OverconstrainedError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_overconstrainedError);
				}else if(error.name == 'TrackStartError' || error.name == 'NotReadableError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_trackStartError);
				}else if(error.name == 'NotAllowedError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_notAllowedError);
				}else if(error.name == 'TypeError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_typeError);
				}else if(error.name == 'NotFoundError'){
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_notFoundError);
				}else{
					errorObj = new Error(ErrorConstant.navigatorUserMediaError_unknown);
				}
				deferred.reject(errorObj);
			});
		} catch(e) {
			var Error = ModuleBase.use(ModulesEnum.error);
			var errorObj = new Error(ErrorConstant.navigatorUserMediaError_unknown);
			deferred.reject(errorObj)
		}
	  
		return deferred.promise;
	}
	
	/**
	 * @desc 设备检测,麦克风音量的检测回调
	 * @param {int} intervalMilis - 音量采购频率,单位为毫秒
	 * @param {Object} showAudioLevel - 音量值回调方法
	 * 
	 * @example
	 * avdEngine.checkAudioLevel(1000,showAudioLevel);
	 * function showAudioLevelm(audioLevel) {}
	 */
	AVDEngine.prototype.checkAudioLevel = function(intervalMilis,showAudioLevel) {
		if(this.checkAudioStream){
			checkAudioLocalStatsCollector = new LocalStatsCollector(this.checkAudioStream, intervalMilis, showAudioLevel);
			checkAudioLocalStatsCollector.start();
		}
	}
	
	
	/**
	 * @desc 设备检测,关闭摄像头
	 * 
	 * @example
	 * avdEngine.checkCloseVideo();
	 */
	AVDEngine.prototype.checkCloseVideo = function() {
		if(this.checkVideoStream){
			this.checkVideoStream.getTracks().forEach(function(track) {
		        track.stop();
		    });
            log.debug('===avdEngine.checkCloseVideo finished')
		}
		this.checkVideoStream = null;
	}
	
	
	/**
	 * @desc 设备检测,关闭麦克风
	 * 
	 * @example
	 * avdEngine.checkCloseAudio();
	 */
	AVDEngine.prototype.checkCloseAudio = function() {
		if(this.checkAudioLocalStatsCollector){
			this.checkAudioLocalStatsCollector.stop();
		}
		this.checkAudioLocalStatsCollector = null;
		
		if(this.checkAudioStream){
			this.checkAudioStream.getTracks().forEach(function(track) {
		       track.stop();
		    });
            log.debug('===avdEngine.checkCloseAudio() finished')
		}
		this.checkAudioStream = null;
	}

   
	function getRoomById(roomId, rooms) {
		var room = null;
		for(var i = 0; i < rooms.length; i++) {
			var srcRoom = rooms[i];
			if(srcRoom.id == roomId) {
				room = srcRoom;
				break;
			}
		}
		return room;
	};


	function appenderLayout(appender) {
		var popUpLayout = new log4javascript.PatternLayout("%d{yyyy-MM-dd HH:mm:ss SSS} %-5p - %m{1}%n");
		appender.setLayout(popUpLayout);
		log.addAppender(appender);
	};


	function refreshAudioDeviceHandle(oldMicrophoneIdArray, microphoneMap, rooms, selfMicrophoneMap) {
		var newMicrophoneIdArray = [];
		for(var key in microphoneMap) {
			newMicrophoneIdArray.push(key);
		}
		//alert("oldMicrophoneIdArray:" + oldMicrophoneIdArray);
		//alert("newMicrophoneIdArray:" + newMicrophoneIdArray);

		var addMicrophoneIdArray = arrayUtil.inANotInB(newMicrophoneIdArray, oldMicrophoneIdArray);
		var deleMicrophoneIdArray = arrayUtil.inANotInB(oldMicrophoneIdArray, newMicrophoneIdArray);

		if(deleMicrophoneIdArray.length == 0 && addMicrophoneIdArray.length == 0) {
			//alert("audio无变化,不做处理");
			return false;
		}

		for(var i = 0; i < this.rooms.length; i++) {
			var srcRoom = rooms[i];
			if(srcRoom.selfUser) {
				if(addMicrophoneIdArray.length > 0) {
					//alert("有新增,addMicrophoneIdArray:"+ addMicrophoneIdArray);
					for(var j = 0; j < addMicrophoneIdArray.length; j++) {
						var name = microphoneMap[addMicrophoneIdArray[j]];
						selfMicrophoneMap[addMicrophoneIdArray[j]] = name;
					}
				}

				if(deleMicrophoneIdArray.length > 0) {
					//alert("有删除,deleMicrophoneIdArray:"+deleMicrophoneIdArray);
					for(var j = 0; j < deleMicrophoneIdArray.length; j++) {
						var name = selfMicrophoneMap[deleMicrophoneIdArray[j]];
						if(deleMicrophoneIdArray[j] == self.checkMicrophoneId) {
							srcRoom.selfUser.deleteAudio(deleMicrophoneIdArray[j]);
						}
						delete selfMicrophoneMap[deleMicrophoneIdArray[j]];
					}
				}
			}
		}
	};

	function refreshVideoDeviceHandle(oldCameraIdArray, cameraMap, rooms, selfCameraMap) {
		var self = this;
		var newCameraIdArray = [];
		for(var key in cameraMap) {
			newCameraIdArray.push(key);
		}
//		alert("oldCameraIdArray:" + oldCameraIdArray);
//		alert("newCameraIdArray:" + newCameraIdArray);

		var addCameraIdArray = arrayUtil.inANotInB(newCameraIdArray, oldCameraIdArray);
		var deleCameraIdArray = arrayUtil.inANotInB(oldCameraIdArray, newCameraIdArray);

		if(deleCameraIdArray.length == 0 && addCameraIdArray.length == 0) {
			//alert("video无变化,不做处理");
			//console.log("===========wq999999999999");
			return false;
		}
		if(self.isSafari){
			
			//TODO safari 暂时保留这个模块,先不做任何操作  弄懂了在回来写
			
			return false;
			//EndTODO
			
		}else{
			for(var i = 0; i < rooms.length; i++) {
				var srcRoom = rooms[i];
				if(srcRoom.selfUser) {
					if(addCameraIdArray.length > 0) {
	//					alert("有新增,addCameraIdArray:"+ addCameraIdArray);
						for(var j = 0; j < addCameraIdArray.length; j++) {
							var name = cameraMap[addCameraIdArray[j]];
							selfCameraMap[addCameraIdArray[j]] = name;
							srcRoom.selfUser.initVideo(addCameraIdArray[j], name);
						}
					}
	
					if(deleCameraIdArray.length > 0) {
	//					alert("有删除,deleCameraIdArray:"+deleCameraIdArray);
						for(var j = 0; j < deleCameraIdArray.length; j++) {
							delete selfCameraMap[deleCameraIdArray[j]];
							srcRoom.selfUser.deleteVideo(deleCameraIdArray[j]);
						}
					}
				}
			}
		}
	};


	// /**
	//  * @desc 获取录制对象,走Restful API实现。
	//  * @param {String} restServerURI - 录制服务REST API的服务器地址
	//  * @returns {Object} record - 录制对象
	//  */
	// AVDEngine.prototype.obtainRecord = function(restServerURI) {
	// 	var record = new Record(restServerURI);
	// 	return record;
	// };
	

	// /**
	//  * @desc 获取直播对象,走Restful API实现。
	//  * @param {String} restServerURI - 直播服务REST API 的服务器地址
	//  * @returns {Object} live - 直播对象
	//  */
	// AVDEngine.prototype.obtainLive = function(restServerURI) {
	// 	var live = new Live(restServerURI);
	// 	return live;
	// };
	
	
	// /**
	//  * @desc 获取外部设备对象,包括rtsp和h323。走Restful API实现。
	//  * @param {String} restServerURI - 外部设备服务REST API 的服务器地址
	//  * @returns {Object} outgoing - 外部设备对象
	//  */
	// AVDEngine.prototype.obtainOutgoing = function(restServerURI) {
	// 	var outgoing = new Outgoing(restServerURI);
	// 	return outgoing;
	// };
	
	
	
	/**
	 * @desc 获取流导出对象
	 * @ignore
	 * @returns {Object} streamExporterManager - 流导出对象
	 */
	AVDEngine.prototype.getStreamExporterManager = function() {
		var streamExporterManager = new StreamExporterManager();
		return streamExporterManager;
	};


    AVDEngine.prototype.deviceChangeListenerHandler = function() {
        var self = this;
        log.debug('===avdEngine.deviceChangeListenerHandler(), device change detected.')
        // 添加时间戳的比较防止音频设备短时间内多次触发devicechange
        var oldMicrphoneIdArr = Object.keys(self.microphoneMap);
        var oldCameraIdArr = Object.keys(self.cameraMap);

        var newMicrophoneIdArr = null;
        var newCameraIdArr = null;

        self.initDeviceHandler().then(function(state){    
            if(state == 1){
                newMicrophoneIdArr = Object.keys(self.microphoneMap);
                newCameraIdArr = Object.keys(self.cameraMap);

                if(newMicrophoneIdArr.length > oldMicrphoneIdArr.length){
                    //有麦克风新增的情况
                    var tempAddMicrophoneIdArr = newMicrophoneIdArr.filter(function(newMicrophoneId){
                        return oldMicrphoneIdArr.findIndex(function(oldMicrophoneId){
                            return newMicrophoneId === oldMicrophoneId;
                        }) < 0;
                    })
                    log.debug('===avdEngine.deviceChangeListenerHandler(), Microphone device change detected. type:add, deviceIds: ' + tempAddMicrophoneIdArr)
                    self.eventEmitter.emit(EngineCallback.device_microphone_change, changeTypeEnum.add, tempAddMicrophoneIdArr);
                }else if(newMicrophoneIdArr.length < oldMicrphoneIdArr.length){
                    //有麦克风移除的情况
                    var removedMicrophoneIdArr = oldMicrphoneIdArr.filter(function(oldMicrophoneId){
                        return newMicrophoneIdArr.findIndex(function(newMicrophoneId){
                            return newMicrophoneId === oldMicrophoneId;
                        }) < 0;
                    })
                    log.debug('===avdEngine.deviceChangeListenerHandler(), Microphone device change detected. type:remove, deviceIds: ' + removedMicrophoneIdArr)
                    self.eventEmitter.emit(EngineCallback.device_microphone_change, changeTypeEnum.remove, removedMicrophoneIdArr);
					
					//被拔掉的麦克风如果正好是已上报到服务器的,做删除处理
					if(removedMicrophoneIdArr.length > 0) {
						for(var i = 0; i< removedMicrophoneIdArr.length; i++) {
							if(removedMicrophoneIdArr[i] == self.checkMicrophoneId) {
								for(var j = 0; j < self.rooms.length; j++) {
									var srcRoom = self.rooms[j];
									if(srcRoom.selfUser) {
								       srcRoom.selfUser.deleteAudio(removedMicrophoneIdArr[i]);
									}
								}
							}
						}
					}
					
                }
            
                if(newCameraIdArr.length > oldCameraIdArr.length){
                    //有摄像头新增的情况
                    var newCameraIdArr = newCameraIdArr.filter(function(newCameraId){
                        return oldCameraIdArr.findIndex(function(oldCameraId){
                            return newCameraId === oldCameraId;
                        }) < 0;
                    })
                    log.debug('===avdEngine.deviceChangeListenerHandler(), Camera device change detected. type:add, deviceIds: ' + newCameraIdArr)
                    self.eventEmitter.emit(EngineCallback.device_camera_change, changeTypeEnum.add, newCameraIdArr);
					
					//新插入的摄像头上报到服务器
					for(var  i = 0; i < newCameraIdArr.length; i++) {						
						var deviceId = newCameraIdArr[i];
						var deviceName = self.cameraMap[deviceId];
						for(var j = 0; j < self.rooms.length; j++) {
							var srcRoom = self.rooms[j];
							if(srcRoom.selfUser) {
						       srcRoom.selfUser.initVideo(deviceId, deviceName);
							}
						}
					}
					
                }else if(newCameraIdArr.length < oldCameraIdArr.length){
                    //有摄像头移除的情况
                    var removedCameraIdArr = oldCameraIdArr.filter(function(oldCameraId){
                        return newCameraIdArr.findIndex(function(newCameraId){
                            return newCameraId === oldCameraId;
                        }) < 0;
                    })
                    log.debug('===avdEngine.deviceChangeListenerHandler(), Camera device change detected. type: remove, deviceIds: ' + removedCameraIdArr)
                    self.eventEmitter.emit(EngineCallback.device_camera_change, changeTypeEnum.remove, removedCameraIdArr);
					
					//被拔掉的摄像头上报到服务器
					for(var  i = 0; i < removedCameraIdArr.length; i++) {						
						var deviceId = removedCameraIdArr[i];
						for(var j = 0; j < self.rooms.length; j++) {
							var srcRoom = self.rooms[j];
							if(srcRoom.selfUser) {
						       srcRoom.selfUser.deleteVideo(deviceId);
							}
						}
					}
                }
            }
        })
    }


  /**
   * @desc 设备检测变动逻辑做防抖处理,默认2秒防抖间隔。
   * @ignore
   */
    AVDEngine.prototype.deviceChangeListener = function() {
        var self = this;

        if(!navigator || !navigator.mediaDevices){
            return;
        }

        navigator.mediaDevices.ondevicechange = debounce(function(){
            self.deviceChangeListenerHandler();
        }, 2000);
    };

    /**
     * @description 设置回声消除
     * @param {Boolean} enable - 是否开启回声消除,默认为true
     * @example
     * avdEngine.setEchoCancellation(false);
     */
    AVDEngine.prototype.setEchoCancellation = function(enable) {
        if(typeof enable !== 'boolean'){
            log.error("===avdEngine.setEchoCancellation() faild, true or false is needed");
            return;
        }
        log.info("===avdEngine.setEchoCancellation(), enable: " + enable);
        this.enableEchoCancellation = enable;
    };

    /**
     * @description 设置降噪
     * @param {Boolean} enable
     */
    AVDEngine.prototype.setNoiseSuppression = function(enable) {
        if(typeof enable !== 'boolean'){
            log.error("===avdEngine.setNoiseSuppression() faild, true or false is needed");
            return;
        }
        log.info("===avdEngine.setNoiseSuppression(), enable: " + enable);
        this.enableNoiseSuppression = enable;
    };

    /**
     * @description 设置自动增益
     * @param {Boolean} enable
     */
    AVDEngine.prototype.setAutoGainControl = function(enable) {
        if(typeof enable !== 'boolean'){
            log.error("===avdEngine.setAutoGainControl() faild, true or false is needed");
            return;
        }
        log.info("===avdEngine.setAutoGainControl(), enable: " + enable);
        this.enableAutoGainControl = enable;
    };
    
    /**
     * @desc 是否忽略服务器下发的资源调整信令,true则不会响应服务器调整资源的通知,例如没有人订阅时发布的视频不会自动降低分辨率码率等
     * @param {Boolean} isIgnore 是否忽略修改资源请求
     */
    AVDEngine.prototype.setIgnoreModifyResourceReq = function(isIgnore) {
        if(typeof isIgnore !== 'boolean'){
            log.error("===avdEngine.setIgnoreModifyResourceReq() faild, true or false is needed");
            return;
        }
        log.info("===avdEngine.setIgnoreModifyResourceReq(), isIgnore: " + isIgnore);
		this.ignoreModifyResourceReq = isIgnore;
	};

    /**
     * @desc 在加入会议之前设置是否启用硬件编码,默认为false,推荐有独立显卡的电脑设备开启,如性能足够,可实现高分辨率、高码率和高帧率
     * @param {Boolean} isEnable 是否开启硬件编码
     */
    AVDEngine.prototype.setEnableHardwareEncoding = function(isEnable) {
        var gpuInfoStr = getGPUInfo();
        log.info("===avdEngine.setEnableHardwareEncoding(), isEnable: ", isEnable, ', current gpu info is: ', gpuInfoStr);
        if(typeof isEnable !== 'boolean'){
            log.error("===avdEngine.setEnableHardwareEncoding() faild, true or false is needed");
            return;
        }
        
        // 排除Intel的核显GPU
        if(isEnable === true && gpuInfoStr.indexOf('Intel(R) HD Graphics') > -1){
            log.error("===avdEngine.setEnableHardwareEncoding() faild, Detected Intel graphics card, ignored");
            return;
        }

        this.enableHardwareEncoding = isEnable;
    }

    /**
     * @desc 预加载美颜模型(人脸检测),可以在用户点击美颜按钮之前提前调用,显著减少首次启用美颜的等待时间
     * @returns {Promise} - 预加载结果
     * @example 
     * avdEngine.preloadBeautyModel().then(function() { console.log('预加载完成'); }).catch(function(e) { console.error('预加载失败:', e); });
     */
    AVDEngine.prototype.preloadBeautyModel = function() {
        var BeautyMediaPipe = null;
        try {
            BeautyMediaPipe = ModuleBase.use("BeautyMediaPipe");
        } catch (e) {
            log.warn('=== preloadBeautyModel: BeautyMediaPipe not available:', e);
            return Promise.reject(new Error('BeautyMediaPipe not available'));
        }
        
        if (BeautyMediaPipe && BeautyMediaPipe.preload) {
            log.info('=== preloadBeautyModel: starting preload');
            return BeautyMediaPipe.preload().then(function(detector) {
                log.info('=== preloadBeautyModel: preload completed, detector ready');
                return detector;
            })["catch"](function(e) {
                log.error('=== preloadBeautyModel: preload failed:', e);
                return Promise.reject(e);
            });
        }
        
        return Promise.reject(new Error('preload method not available'));
    };


	 /**
     * @desc 设置traceId,用于监控埋点实现
     * @param {String} traceId 
     */
    AVDEngine.prototype.setTraceId = function(traceId) {
		log.info("===avdEngine.setTraceId(), traceId: " + traceId);
		this.traceId = traceId;
	};

	 /**
     * @desc 获取traceId
	 * @returns {String} traceId
     */
    AVDEngine.prototype.getTraceId = function() {
		return this.traceId;
	};


	
	return AVDEngine;
});