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