ModuleBase.define("Room", ["SignalFactory", "RestServer", "MasterServer", "P2PServer", "User", "Error"], function(
SignalFactory, RestServer, MasterServer, P2PServer, User, Error) {
/**
* @desc 房间Room构造函数。
* @constructor
* @alias Room
*
* @example
* var room = new Room(roomId);
*/
var Room = function(roomId) {
this.id = roomId;
this.roomTocken = null; //dtls加密用到,浏览器暂时不用,服务器返回
this.signalTocken = null; //websocket重连用到,服务器返回
this.outgoingAddr = null;
this.userAgent = USER_AGENT_AVD_DEFAULT;
this.roomPassword = null; //会议密码
// this.roomControl = {
// record: 0, //普通录制
// mix_record : 0, //混屏录制
// avc_model: 0, //avc模式
// resolution: "640*480", //分辨率设置
// liver: 0, //直播
// mixer_liver: 0 //混屏直播
// };
//
// this.autoRecord = false; //自动录制
// this.autoAvcModel = false; //自动avc混屏
this.pingTimeout; //ping包饱和测试超时时间,服务器返回。到达时,服务器会踢掉该用户,客户端需要通知用户重连失败,重新加会
this.pingInterval; //ping包饱和测试问隔时间,服务器返回
this.pingCountTime = 0; //ping包饱和测试无响应时间累计
this.pingIntervalId = null;
this.pingTimeoutId = null;
this.lastPDUSeq = 0; //记录当前最近的PDU last Seq值(roomPduType.PONG除外),用于重连过程中末收到的PDU的重新接收
this.roomInfo = null;
this.status = RoomStatus.opening;
this.appData = {};
this.selfUser = null;
this.participants = []; //参会者,包括自己
this.muteSpeakerFlag = false; //屏蔽会议声音标识,默认false:不屏蔽
this.masterServer = null;
this.p2pServer = null;
this.cmdProtobufCoded = null;
//pc.addIceCandidate()必须在PC.setRemoteDescription()之后,为了保证该时序,设置下面2个全局变量
this.setRemoteDescriptionSuccess = false; //PC.setRemoteDescription处理成功的返回状态
this.addIcecandidateList = []; //PC.setRemoteDescription没有成功处理前,signalPduType.CANDIDATE_MSG1,返回的candidateMsg进行缓存,等setRemoteDescriptionSuccess==true后处理
this.traceablePeerConnection = null;
this.traceableP2PPeerConnection = null;
this.eventEmitter = new EventEmitter();
this.fingerPrint;
this.setup;
this.iceUfrag;
this.icePwd;
//视频编码格式Payload定义
this.codecPayloadVP8 = null;
this.codecPayloadVP9 = null;
this.codecPayloadH264 = null;
this.codecPayloadH265 = null;
this.codecPayloadAV1 = null;
//音频编码格式Payload定义
this.codecPayloadISAC_16K = null;
this.codecPayloadISAC_32K = null;
this.codecPayloadOpus = null;
this.codecPayloadG722 = null;
this.codecPayloadPCMU = null;
this.codecPayloadPCMA = null;
this.codecPayloadCN_8k = null;
this.codecPayloadCN_16k = null;
this.codecPayloadCN_32k = null;
this.versionToServer; //上报给服务器的客户端版本
this.audioCodecPayloadMap; //上报给服务器的音频playload集
this.videoCodecRtxMap;//上报给服务器的视频playload与Rtx对应集
//sdp模板
this.remoteSdpTemplate;
this.videoMediaTemplate;
this.audioMediaTemplate;
//answer端设置Remote的sdp
this.remoteDescriptionSDP;
this.midNum = 1;
this.midObjSet = [{
mid: 0,
type: 'audio',
isReceiver: false,
isSender: false,
receiverInfo: {
trackId: '',
nodeId: '',
deviceId: ''
}
}, {
mid: 1,
type: 'video',
isReceiver: false,
isSender: false,
receiverInfo: {
trackId: '',
nodeId: '',
deviceId: ''
}
}];
//this.offerSessionDescription;
this.audioMediaSDPInit;
this.videoMediaSDPInit;
this.mediaMid = 0;
//this.sdp = null; //远端带ssrc的sdp,用于中转用
this.connectionInfoCollector = null; //与mcu PeerConnection网络连接情况指示器
this.audioLevel = null; //与audio Input Level PeerConnection
this.connectionStatistics = null; // 媒体状态统计指示器
this.toMCUReconnectionPC = null; //与服务器重连时,新产生的PeerConnection对象,中间重转换引用
this.isExistReJoinState = false;
this.reconnectMaxTimes = 3; //信令重连最大次数,默认3次
this.reconnectTimeout = 15000; //信令重连最时时长,默认15秒
this.currentReconnectTimes = 0; //当前信令重连次数
this.currentConnectTimeout= 0; //当前信令重连时长
this.connectionState = 0; // 0: 正常, 1: 重连中,2:重新加会中
this.rejoinTimeout = 60000; //重新加会最时时长,默认60秒
this.currentRejoinTimeout = 0; //当前重新加会时长
this.rejoinAlways = false; //true:表示一直重新加会,不受rejoinTimeout限制
this.rejoinInterval = 3000; //二次重新加会之间间隔的时长,默认为3秒,以免太频繁引发服务器的黑名单机制
this.rejoinLastTimeStamp = null; //上一次重新加会触发时的时间戳
this.rejoinTemp;
this.reJoinAfterTimer = null;
this.reJoinAfterUsers = []; //重新加会后,服务器返回的参会者用户
/**
* 重新加会成功后,应用层需要清理白板的标识,true代表要清理。
* 原因是服务器版本3.1.8开始,当客户端重新加会成功后,会重新下发所有之前白板相关信息包括批注,所以应用层需要先删除掉原来的白板相关界面内容,否则会重复渲染,可能会存在错位。
*/
this.cleanupBoardByReJoinConnected = true;
this.pubVideos = []; //会议中已经发布的视频
this.pubAudios = []; //会议中已经发布的音频
this.pubScreens = []; //会议中已经发布的桌面共享
//用于记录当前设置视频带宽值
this.currMinBandwidth = null;
this.currMaxBandwidth = null;
this.currScreenMinBandwidth = 1200;
this.currScreenMaxBandwidth = 3200;
this.currScreenHandle = false;
this.updateMinBandwidth = null; //修改带宽时,在updateBandwidthSdpHandle方法中做个比较值
//用于网络状态回调
this.videoSsrc2user = {}; //用于网络状态回调,用于定位video ssrc与所属用户对象的绑定,map(ssrc:user)集
this.audioSsrc2user = {}; //用于网络状态回调及语音激励,用于定位audio ssrc与所属用户对象的绑定,map(ssrc:user)集
this.screenSsrc2user = {}; //用于网络状态回调,用于定位screen ssrc与所属用户对象的绑定,map(ssrc:user)集
this.videoSsrc2deviceId = {}; //用于网络状态回调,用于定位video ssrc与设备Id的绑定,map(ssrc:deviceId)集
this.addSdpLocalStates = 0; //为保证操作SDP队列的顺序时,在set Local SDP中设置。
log.debug("===DDDDDDDDDDDDD init this.addSdpLocalStates:" + this.addSdpLocalStates);
this.boardId2userId = {}; //用于board与所属用户的绑定,map(boardId:userId)集
this.annotation2boardId = {}; //用于board与所属批注的绑定,map(annotation:boardId)集
this.style2annotation = {}; //用于批注style与所属批注的绑定,map(style:annotationId)集
this.isDoPDUSeq = 0; //与服务器重连中,服务器返回可能丢失的PDU处理,为兼容老服务器,设置该标识,0时不处理
this.videoSsrc = null;
this.audioSsrc = null;
this.updateTimer = null;
this.boardOutputXRatio = 1; //白板输出宽度/渲染宽度
this.boardOutputYRatio = 1; //白板输出高度/渲染高度
this.boardInputXRatio = 1; //渲染宽度/白板输入宽度
this.boardInputYRatio = 1; //渲染高度/白板输出高度
this.baseMediaPalyEventStopTime = null;
this.mediaPalyEventStopIntervalId = null;
//原始设备ID 与上传到服务器的uuid值的mapping映射
this.cameraId2uuid = {}; //用于cameraId与uuid的绑定,map(cameraId:uuid)集
this.microphoneId2uuid = {}; //用于microphoneId与uuid的绑定,map(microphoneId:uuid)集
this.speakerId2uuid = {}; //用于speakerId与uuid的绑定,map(speakerId:uuid)集
this.userid2boardsBy812 = {}; //用于用户心跳超时掉线(如异常退会)812场景下,缓存掉线用户的board对象,map(userId:boards)集 //by roymond 2024/11//19
this.mp4ImporterUserAudioStateIntervalId = null;
this.mp4ImporterUserAudioRtpPacketTemp = 0;
this.mp4ImporterUserAudioRtpPacketReal = 0;
this.mp4ImporterUserAudioRate = 20; //毫秒
this.websocketConnectTime = 0; //针对有客户环境重连时,websocket会同一时该返回多条webSocket.onopen,做2秒内的去重.该值记录初始时间
this.roomJoinState = RoomStateEnum.DISCONNECTED; // 房间加入的状态
this.isReceivedAddBoardId = ""; // 用户兼容服务器下发白板消息add和share消息时序相反的情况
this.isReceivedRemoveBoardId = ""; // 用户兼容服务器关闭和移除白板消息close和remove消息时序相反的情况
//应用层客户调用接口自行设置的扬声器设备ID
this.clientSetSpeakerDeviceId = null;
//应用层客户调用接口自行设置的扬声器音量值(0-1),该值只是应用层的设置与物理设备的具体音量值无关
this.clientSetSpeakerVolume = null;
};
/**
* attributes 解析
* @param {int32} attributes
* @ignore
*/
// Room.prototype.attributesAnalysis = function (attributes){
// if(attributes){
// var ROOM_TYPE_HOST = 1 << 1; //主持人加会以后才能加会
// var ROOM_TYPE_NETWORK_ACC = 1 << 2; //加速模式
// var ROOM_TYPE_AREA_CONTROL = 1 << 3; //场控模式
// var ROOM_TYPE_AUTO_RECORD = 1 << 5; //自动录制房间内所有音频和视频
// var ROOM_TYPE_MIX_RECORD = 1 << 6; //混屏录制
// var ROOM_TYPE_AVC_MODEL = 1 << 7; //avc模式
// var ROOM_TYPE_RECORD = 1 << 8; //普通录制
// var ROOM_TYPE_LIVER = 1 << 9; //直播
// var ROOM_TYPE_MIX_LIVER = 1 << 10; //混屏直播
// var ROOM_TYPE_AUTO_AVC_MODEL = 1 << 11; //自动avc混屏
//
// var attributesPriviligeMap = attributes; //(attributes).toString(2);
//
// var isHasByTypeRecord = room.isHasPrivilige(ROOM_TYPE_RECORD,attributesPriviligeMap);
// var isHasByTypeMixRecord = room.isHasPrivilige(ROOM_TYPE_MIX_RECORD,attributesPriviligeMap);
//
// var isHasByTypeLive = room.isHasPrivilige(ROOM_TYPE_LIVER,attributesPriviligeMap);
// var isHasByTypeMixLive = room.isHasPrivilige(ROOM_TYPE_MIX_LIVER,attributesPriviligeMap);
//
// var isHasByTypeAvcModel = room.isHasPrivilige(ROOM_TYPE_AVC_MODEL,attributesPriviligeMap);
//
// var isHasByTypeAutoRecord = room.isHasPrivilige(ROOM_TYPE_AUTO_RECORD,attributesPriviligeMap);
// var isHasByTypeAutoAvcModel = room.isHasPrivilige(ROOM_TYPE_AUTO_AVC_MODEL,attributesPriviligeMap);
//
//
// this.roomControl = {
// record: isHasByTypeRecord,
// mix_record: isHasByTypeMixRecord,
// avc_model: isHasByTypeAvcModel,
// liver: isHasByTypeLive,
// mixer_liver: isHasByTypeMixLive
// };
//
// this.autoRecord = isHasByTypeAutoRecord;
// this.autoAvcModel = isHasByTypeAutoAvcModel;
// }
// }
/**
* @desc 设置当前登陆用户的userAgent属性,默认为USER_AGENT_AVD_DEFAULT
* @param {String} userAgent
*
* @example
* room.setUserAgent(USER_AGENT_AVD_MEDIA_MIXER_ADMIN);
*/
Room.prototype.setUserAgent = function(userAgent) {
this.userAgent = userAgent;
}
/**
* @desc 设置信令重连最大次数,默认3次
* @param {Int} count -重连最大次数
*
* @example
* room.setReconnectMaxTimes(5);
*/
Room.prototype.setReconnectMaxTimes = function(count) {
log.info("===room.setReconnectMaxTimes(),count:"+ count);
this.reconnectMaxTimes = count;
}
/**
* @desc 设置信令重连最时时长,单元毫秒,默认15000毫秒
* @param {Int} timeout -重连最时时长
*
* @example
* room.setReconnectTimeout(20000);
*/
Room.prototype.setReconnectTimeout = function(timeout) {
log.info("===room.setReconnectTimeout(),timeout:"+ timeout);
this.reconnectTimeout = timeout;
}
Room.prototype.MaxReconnectTimeout = function(pingInterval){
log.info("===room.MaxReconnectTimeout(),pingInterval:"+ pingInterval);
if(this.reconnectTimeout < pingInterval * 3){
this.reconnectTimeout = pingInterval * 3;
log.info("===room.MaxReconnectTimeout(),reconnectTimeout:"+ this.reconnectTimeout);
}
}
/**
* @desc 高效加会,与room.join()接口的区别是不再需要另外调用avdEngine.init()
* @async
* @param {String} serverURI - MCU服务器地址
* @param {String} accessToken - 访问令牌
* @param {String} userId - 用户的Id
* @param {String} userName - 用户名
* @param {String} userData - 用户扩展字段
* @param {String} password - 会议密码
*
* @example
* room.joinEfficient('avd.nice2meet.cn:9610','ZDgyNGU1MGU0ZGY2MTJlZWY3NjkwMjQ5NDY0YWE3MDkxZGJjNmRiZg==','user01','ds','','').then(function(){}).otherwise(function(error){});
*/
Room.prototype.joinEfficient = function(serverURI, accessToken, userId, userName, userData, password) {
var deferred = when.defer();
var self = this;
avdEngineHandle.init(serverURI,accessToken,false).then(function(){
self.join(userId, userName, userData, password).then(function(){
deferred.resolve();
}).otherwise(function(error){
self.roomJoinState = RoomStateEnum.DISCONNECTED;
deferred.reject(error);
});
}).otherwise(function(error){
self.roomJoinState = RoomStateEnum.DISCONNECTED;
deferred.reject(error);
});
return deferred.promise;
}
//获取服务器地址
Room.prototype.serverinfoHandle = function(){
log.info("===room.serverinfoHandle()...");
var self = this;
var deferred = when.defer();
var restServer = null;
if(avdEngineHandle.applyToElectron){
restServer = new RestServer(avdEngineHandle.electronProtocol);
}else{
if(avdEngineHandle.restFullProtocol){
restServer = new RestServer(avdEngineHandle.restFullProtocol);
}else{
restServer = new RestServer();
}
}
restServer.getServerinfo(avdEngineHandle.restServerInfo.mcuServerUrl, avdEngineHandle.accessToken, avdEngineHandle.isUseCascade, self.id).then(function() {
//notQueryGetMcu为false时,且为集群版本,根据roomId&accessToken重新获取服务器地址
if(!avdEngineHandle.restServerInfo.notQueryGetMcu && avdEngineHandle.restServerInfo.isCluster){
log.info("===room.serverinfoHandle(),restServer.getMcu()");
restServer.getMcu(self.id, avdEngineHandle.accessToken, avdEngineHandle.clientIsUseCascade).then(function() {
deferred.resolve();
}).otherwise(function(error) {
self.roomJoinState = RoomStateEnum.DISCONNECTED;
deferred.reject(error);
});
} else {
deferred.resolve();
}
}).otherwise(function(error) {
self.roomJoinState = RoomStateEnum.DISCONNECTED;
log.error("===room.serverinfoHandle(),restServer.getServerinfo()失败!error:"+ error.message);
deferred.reject(error);
});
return deferred.promise;
}
//websocket处理
Room.prototype.websocketConnect = function(deferred) {
var self = this;
if(!deferred){
deferred = when.defer();
}
if(self.masterServer && self.masterServer.signalingChannel){
self.masterServer.signalingChannel.close();
}
var signalFactory = new SignalFactory();
var websocket = signalFactory.getDataChannel(SignalType.websocket, self);
websocket.connect().then(function(signal){
self.currentReconnectTimes = 0;
self.currentConnectTimeout= 0;
deferred.resolve(signal);
}).otherwise(function(error) {
websocket.close();
if(self.currentConnectTimeout === 0 ){
self.currentConnectTimeout = new Date();
}
var timeout = new Date() - self.currentConnectTimeout;
if(self.currentReconnectTimes > 0){
log.error("===room.websocketConnect(),Websocket第" + self.currentReconnectTimes + "次重连,累计耗时"+timeout+"毫秒");
}
if (self.currentReconnectTimes < self.reconnectMaxTimes || timeout < self.reconnectTimeout ) {
self.currentReconnectTimes ++;
self.websocketConnect(deferred);
}else{
self.roomJoinState = RoomStateEnum.DISCONNECTED;
deferred.reject();
}
})
return deferred.promise;
}
/**
* @desc 加会
* @async
* @param {String} userId - 用户的Id
* @param {String} userName - 用户名
* @param {String} userData - 用户扩展字段
* @param {String} password - 会议密码
*
* @example
* room.join('user01','ds','','').then(function(){}).otherwise(function(error){});
*/
Room.prototype.join = function(userId, userName, userData, password) {
var deferred = when.defer();
var self = this;
if (self.roomJoinState == RoomStateEnum.CONNECTING || self.roomJoinState == RoomStateEnum.CONNECTED) {
log.debug('===room.join(), return by curr room state:', self.roomJoinState )
return;
}
self.roomJoinState = RoomStateEnum.CONNECTING;
if (userId == null || userId == '') {
var error = new Error(ErrorConstant.userId_required);
log.error("===room.join() error, code:" + error.code +", message:"+ error.message);
self.roomJoinState = RoomStateEnum.DISCONNECTED;
deferred.reject(error);
return deferred.promise;
}else if(typeof userId !== 'string'){
var error = new Error(ErrorConstant.exporter_parameter_type_error);
error.message = error.message+"[ UserId required string type]";
log.error("===room.join() error, code:" + error.code +", message:"+ error.message);
self.roomJoinState = RoomStateEnum.DISCONNECTED;
deferred.reject(error);
return deferred.promise;
}
if (typeof(userData) == "undefined") {
userData = "";
}
if (typeof(password) == "undefined") {
password = "";
}
self.serverinfoHandle().then(function(){
//终端日志上报开启
if(avdEngineHandle.loggerReportConfig.open_logger && (parseInt(avdEngineHandle.loggerReportConfig.open_logger) == 1)){
avdEngineHandle.loggerReport.start();
for(var i = 0; i < avdEngineHandle.logReportWait.length; i++) {
var logReportWait = avdEngineHandle.logReportWait[i];
avdEngineHandle.loggerReport.info(logReportWait);
}
}
log.info("===room.join(), JS SDK 版本号:"+ avdEngineHandle.getVersion());
avdEngineHandle.loggerReport.info("JS SDK 版本号:"+ avdEngineHandle.getVersion());
log.info("===room.join(), 浏览器内核名称:"+ avdEngineHandle.browserName+ ", 内核版本:"+avdEngineHandle.fullVersion+", 操作系统名称:"+avdEngineHandle.browserOsName + " " + avdEngineHandle.detect.osVersion+", checkBrowserSupport:"+avdEngineHandle.checkBrowserSupport()+", GetUserMedia:"+avdEngineHandle.detect.getUserMediaSupport+", RTCPeerConnection:"+avdEngineHandle.detect.RTCPeerConnectionSupport+", WebSocket:"+avdEngineHandle.detect.WebSocketSupport);
avdEngineHandle.loggerReport.info("浏览器内核名称:"+ avdEngineHandle.browserName+ ", 内核版本:"+avdEngineHandle.fullVersion+", 操作系统名称:"+avdEngineHandle.browserOsName);
// Android Firefox不支持VP8, 自动调整成H264
if(avdEngineHandle.isFirefox && avdEngineHandle.browserOsName == 'Android' && avdEngineHandle.videoCoding == VideoCodingType.VP8){
log.info("===room.join(), Android Firefox不支持VP8, 自动调整成H264");
avdEngineHandle.videoCoding = VideoCodingType.H264;
}
log.info("===room.join(), 视频编解码:"+ avdEngineHandle.videoCoding);
avdEngineHandle.loggerReport.info("视频编解码:"+ avdEngineHandle.videoCoding);
log.info("===room.join(), 音频编解码:"+ avdEngineHandle.audioCoding);
avdEngineHandle.loggerReport.info("音频编解码:"+ avdEngineHandle.audioCoding);
if (avdEngineHandle.restServerInfo.isCluster) {
log.info("===room.join(), 服务器是集群版本");
avdEngineHandle.loggerReport.info("room.join(),服务器是集群版本");
}else{
log.info("===room.join(), 服务器是单机版本");
avdEngineHandle.loggerReport.info("room.join(),服务器是单机版本");
}
if(avdEngineHandle.restServerInfo.masterVersion){
log.info("===room.join(), 服务器端 masterVersion:"+ avdEngineHandle.restServerInfo.masterVersion);
avdEngineHandle.loggerReport.info(" 服务器端masterVersion:"+ avdEngineHandle.restServerInfo.masterVersion);
}
if(avdEngineHandle.restServerInfo.masterPackageVersion){
log.info("===room.join(), 服务器端 masterPackageVersion:"+ avdEngineHandle.restServerInfo.masterPackageVersion);
avdEngineHandle.loggerReport.info(" 服务器端masterPackageVersion:"+ avdEngineHandle.restServerInfo.masterPackageVersion);
}
if(avdEngineHandle.restServerInfo.boxVersion){
log.info("===room.join(), 服务器端 boxVersion:"+ avdEngineHandle.restServerInfo.boxVersion);
avdEngineHandle.loggerReport.info(" 服务器端boxVersion:"+ avdEngineHandle.restServerInfo.boxVersion);
}
if(avdEngineHandle.restServerInfo.boxPackageVersion){
log.info("===room.join(), 服务器端 boxPackageVersion:"+ avdEngineHandle.restServerInfo.boxPackageVersion);
avdEngineHandle.loggerReport.info(" 服务器端boxPackageVersion:"+ avdEngineHandle.restServerInfo.boxPackageVersion);
}
self.doJoin(userId, userName, userData, password, deferred);
}).otherwise(function(error) {
if(error) {
log.error("===room.join(), error, code:" + error.code +", message:"+ error.message);
deferred.reject(error);
} else {
var err = new Error(ErrorConstant.avdEngine_init_failed);
log.error("===room.join(), error, code:" + error.code +", message:"+ error.message);
deferred.reject(err);
}
});
return deferred.promise;
};
/**
* @desc OEM加会
* @async
* @param {Object} userId - 用户的Id
* @param {Object} userName - 用户名
* @param {Object} userData - 用户扩展字段
* @param {String} password - 会议密码
*/
Room.prototype.joinWithOEM = function(userId, userName, userData, password, oemAccessToken) {
var deferred = when.defer();
var self = this;
//OEM 默认都是集群版本,根据roomId & oemAccessToken 重新获取服务器地址
var restServer = new RestServer();
restServer.getMcuWithOEM(self.id, oemAccessToken).then(function() {
self.doJoin(userId, userName, userData, password, deferred);
}).otherwise(function(error) {
deferred.reject(error);
});
return deferred.promise;
};
Room.prototype.doJoin = function(userId, userName, userData, password, deferred) {
var self = this;
var detect = avdEngineHandle.getBrowserDetect();
self.roomPassword = password;
self.selfUser = new User(userId, userName, userData, self);
self.selfUser.userAgent = self.userAgent;
// 判断是否为 网页录制
if(detect.isWebRecord) {
self.selfUser.userAgent = USER_AGENT_AVD_WEB_RECORD;
self.userAgent = USER_AGENT_AVD_WEB_RECORD;
}
self.websocketConnect().then(function(websocket){
self.openReqSend(websocket).then(function(room) {
self.roomJoinState = RoomStateEnum.CONNECTED;
deferred.resolve(room);
}).otherwise(function(error) {
log.error("===room.doJoin(),openReqSend faild, reason: " + JSON.stringify(error));
self.roomJoinState = RoomStateEnum.DISCONNECTED;
deferred.reject(error);
});
}).otherwise(function() {
log.error("===room.doJoin(),登录加会超时:" + ErrorConstant.join_room_timeout.code);
avdEngineHandle.loggerReport.debug("room.doJoin(),登录加会超时.");
self.roomJoinState = RoomStateEnum.DISCONNECTED;
var error = new Error(ErrorConstant.join_room_timeout);
deferred.reject(error);
});
}
/**
* @desc 与服务器重连处理
* @ignore
*/
Room.prototype.reconnectionHandle = function() {
var self = this;
self.connectionState = 1;
self.websocketConnect().then(function(websocket){
log.info("===WebSocket,room.reconnectionHandle(),websocket重连成功");
self.masterServer.pingInit();
self.masterServer.signalingChannel = websocket;
log.info("===WebSocket,room.reconnectionHandle(),to masterServer.openReqReconnectionSend()");
self.openReqReconnectionSend().then(function(){
log.debug("===WebSocket,room.reconnectionHandle(),OPEN_RQP Send, acception OPEN_REP result=0");
}).otherwise(function(error) {
self.roomJoinState = RoomStateEnum.DISCONNECTED;
log.info("===WebSocket,room.reconnectionHandle(),OPEN_RQP 发送失败,进行重新加会处理。 reason: " + JSON.stringify(error));
log.debug("===Room.reconnectionHandle(),callback RoomCallback.connection_status,connectionStatus:" + ConnectionStatus.connectFailed);
self.eventEmitter.emit(RoomCallback.connection_status, ConnectionStatus.connectFailed);
if(self.roomJoinState != RoomStateEnum.RECONNECTING){
log.info("===to room.reJoin() by room.reconnectionHandle(),openReqReconnectionSend() failed");
self.reJoin();
}
});
}).otherwise(function() {
log.info("===WebSocket,room.reconnectionHandle(),websocket重连超出预设次数及超时,进行重新加会处理。");
log.debug("===Room.reconnectionHandle(),callback RoomCallback.connection_status,connectionStatus:" + ConnectionStatus.connectFailed);
self.eventEmitter.emit(RoomCallback.connection_status, ConnectionStatus.connectFailed);
if(self.roomJoinState != RoomStateEnum.RECONNECTING){
log.info("===to room.reJoin() by room.reconnectionHandle(), websocketConnect() failed");
self.reJoin();
}
});
};
/**
* @desc 重新加会
* @ignore
*/
Room.prototype.reJoin = function() {
var self = this;
log.info("===room.reJoin(), current roomJoinState: " + self.roomJoinState);
// reJoin会被多次调用,如果房间是正在重新加会中,忽略
if(self.roomJoinState == RoomStateEnum.RECONNECTING){
log.debug('===room.rejoin(), roomJoinState is reconnecting to return');
return;
}
if(self.currentRejoinTimeout == 0){
self.currentRejoinTimeout = new Date();
}
var rejoinDurationFromFirst = new Date() - self.currentRejoinTimeout;
log.info("===room.reJoin(), duration since first rejoin: " + rejoinDurationFromFirst + ",rejoinTimeout: " + self.rejoinTimeout);
if(self.rejoinAlways == false){
if(rejoinDurationFromFirst > self.rejoinTimeout){
log.info("===room.reJoin(), rejoinAlways false, rejoinTimeout, rejoinDurationFromFirst: " + rejoinDurationFromFirst);
log.debug("===Room.reJoin(), rejoinAlways false, callback RoomCallback.connection_status,connectionStatus:" + ConnectionStatus.reJoinRoomTimeOut);
self.eventEmitter.emit(RoomCallback.connection_status, ConnectionStatus.reJoinRoomTimeOut);
return;
}else{
if(!self.rejoinLastTimeStamp){
log.info("===room.reJoin(), rejoinAlways false, self.rejoinLastTimeStamp is null, to room.reJoinHandle()");
self.rejoinLastTimeStamp = new Date();
self.reJoinHandle();
}else{
var differenceDate = new Date() - self.rejoinLastTimeStamp;
if(differenceDate < self.rejoinInterval){
var ss = self.rejoinInterval - differenceDate;
clearTimeout(self.rejoinTemp);
self.rejoinTemp = setTimeout(function(){
self.rejoinLastTimeStamp = new Date();
log.info("===room.reJoin(), rejoinAlways false, rejoinInterval Not established, by setTimeout to room.reJoinHandle()");
self.reJoinHandle();
},ss);
}else{
log.info("===room.reJoin(), rejoinAlways false, rejoinInterval established, to room.reJoinHandle()");
self.rejoinLastTimeStamp = new Date();
self.reJoinHandle();
}
}
}
}else{
if(!self.rejoinLastTimeStamp){
log.info("===room.reJoin(), rejoinAlways true, self.rejoinLastTimeStamp is null, to room.reJoinHandle()");
self.rejoinLastTimeStamp = new Date();
self.reJoinHandle();
}else{
var differenceDate = new Date() - self.rejoinLastTimeStamp;
if(differenceDate < self.rejoinInterval){
var ss = self.rejoinInterval - differenceDate;
clearTimeout(self.rejoinTemp);
self.rejoinTemp = setTimeout(function(){
self.rejoinLastTimeStamp = new Date();
log.info("===room.reJoin(), rejoinAlways true, rejoinInterval Not established, by setTimeout to room.reJoinHandle()");
self.reJoinHandle();
},ss);
}else{
log.info("===room.reJoin(), rejoinAlways true, rejoinInterval established, to room.reJoinHandle()");
self.rejoinLastTimeStamp = new Date();
self.reJoinHandle();
}
}
}
}
/**
* @desc 重新加会
* @ignore
*/
Room.prototype.reJoinHandle = function() {
var self = this;
log.info("===room.reJoinHandle()...");
if (self.connectionInfoCollector) {
self.connectionInfoCollector.stop(); //停止收集网络情况
}
if (self.connectionStatistics) {
self.connectionStatistics.stop(); //停止上报媒体信息
}
self.connectionState = 2;
self.roomJoinState = RoomStateEnum.RECONNECTING;
self.isDoPDUSeq = 0;
// 重新加会时原参会者列表移除除自己外的所有用户
if(self && self.participants && self.participants.length > 0){
self.participants.forEach(function(participant){
if (participant.id != self.selfUser.id) {
arrayUtil.objectSplice(self.participants, participant.id);
}
})
}
//重新加会时,用户信息初始化
if (self.selfUser.reJoinPubVideoIds && self.selfUser.reJoinPubVideoIds.length > 0) {
} else {
self.selfUser.reJoinPubVideoIds = [];
self.selfUser.reJoinPubVideoElements = [];
var videos = self.pubVideos;
if (videos && videos.length > 0) {
log.debug("===room.reJoinHandle(),重新加会初始化videos之前先把自己pub的视频unpub掉,room.pubVideos.length:" + videos
.length);
for (var i = 0; i < videos.length; i++) {
if (videos[i].ownerId == self.selfUser.id) {
var video = self.selfUser.getVideo(videos[i].id);
self.selfUser.reJoinPubVideoIds.push(videos[i].id);
self.selfUser.reJoinPubVideoElements.push(video.element);
if (video) {
log.debug("===room.reJoinHandle(),重新加会初始化videos之前先把自己pub的视频unpub掉,selfUser.video.id:" + videos[i].id);
// 遍历自己的stream对象,将视频track都停掉,防止重新加会之前打开的视频自动打开后再关闭时,摄像头指示灯依然是开启状态
var videoTracks = self.selfUser.stream.getVideoTracks();
if(videoTracks.length > 0){
videoTracks.forEach(function(videoTrack) {
videoTrack.stop();
})
}
log.info("===video.unpublish()不会被执行,因为当前是在重新加会的流程中,这里不需要执行。");
//video.unpublish();
}
}
}
}
}
if (self.selfUser.reJoinPubAudioId && self.selfUser.reJoinPubAudioId != "") {
} else {
self.selfUser.reJoinPubAudioId = "";
var audios = self.pubAudios;
if (audios && audios.length > 0) {
log.debug("===room.reJoinHandle(),重新加会初始化audio之前先把自己pub的音频unpub掉,room.pubAudios.length:" + audios
.length);
for (var i = 0; i < audios.length; i++) {
if (audios[i].ownerId == self.selfUser.id) {
var audio = self.selfUser.getAudio(audios[i].id);
self.selfUser.reJoinPubAudioId = audios[i].id;
if (audio) {
if(audio.status === StreamStatus.muted){
log.debug("===room.reJoinHandle(),后重新加会初始化audio之前先把自己pub的音频unpub掉,当前audio的状态为Mute");
self.selfUser.reJoinPubAudioButMute = true;
}
log.debug("===room.reJoinHandle(),后重新加会初始化audio之前先把自己pub的音频unpub掉,selfUser.audio.id:" + audios[i].id);
// 遍历自己的stream对象,将音频track都停掉,防止重新加会之前打开的音频自动打开后再关闭时,麦克风指示灯依然是开启状态
var audioTracks = self.selfUser.stream.getAudioTracks();
if(audioTracks.length > 0){
audioTracks.forEach(function(videoTrack) {
videoTrack.stop();
})
}
log.info("===audio.closeMicrophone()不会被执行,因为当前是在重新加会的流程中,这里不需要执行。");
//audio.closeMicrophone();
}
}
}
}
}
//402后,重新加会,加会前如已经打开屏幕共享,暂不处理 TODO
var screens = self.pubScreens;
if (screens && screens.length > 0) {
log.debug("===room.reJoinHandle(),重新加会初始化screen之前先把自己pub的桌面共享unpub掉,room.pubScreens.length:" + screens.length);
for (var i = 0; i < screens.length; i++) {
if (screens[i].ownerId == self.selfUser.id) {
var screen = self.selfUser.getScreen(screens[i].id);
if (screen) {
log.debug("===room.reJoinHandle(),重新加会初始化screen之前先把自己pub的桌面共享unpub掉,selfUser.screen.id:" + screens[i].id);
//因为打开桌面共享指定不了应用的ID及屏幕ID, 这里记录桌面共享流,用于重新加会成功后再次发布该流。
self.selfUser.reJoinPubScreenStram = self.selfUser.screenStream;
// 遍历自己的screenStream对象,将流track都停掉,防止重新加会之前打开的流自动打开后再关闭时,弹出的共享窗还在
// var tracks = self.selfUser.screenStream.getTracks();
// if(tracks.length > 0){
// tracks.forEach(function(track) {
// track.stop();
// })
// }
log.info("===screen.unpublish()不会被执行,因为当前是在重新加会的流程中,这里不需要执行。");
//screen.unpublish();
}
}
}
}
clearTimeout(self.reJoinAfterTimer);
self.reJoinAfterUsers = [];
if (self.selfUser) {
if (self.selfUser.toMCUPC) {
self.selfUser.toMCUPC.msgQueue = [];
self.selfUser.toMCUPC.close();
self.selfUser.toMCUPC = null;
}
}
log.debug("===room.reJoinHandle(),重新加会之前sub的视频subVideoIds:" + JSON.stringify(self.selfUser.subVideoIds));
self.selfUser.reJoinSubVideoIds = self.selfUser.subVideoIds;
log.debug("===room.reJoinHandle(),重新加会之前sub的音频subAudioIds:" + JSON.stringify(self.selfUser.subAudioIds));
self.selfUser.reJoinSubAudioIds = self.selfUser.subAudioIds;
log.debug("===room.reJoinHandle(),重新加会之前sub的桌面共享subScreenIds:" + JSON.stringify(self.selfUser
.subScreenIds));
self.selfUser.reJoinSubScreenIds = self.selfUser.subScreenIds;
self.selfUser.init();
var detect = avdEngineHandle.getBrowserDetect();
self.selfUser.userAgent = self.userAgent;
// 判断是否为 网页录制
if(detect.isWebRecord) {
self.selfUser.userAgent = USER_AGENT_AVD_WEB_RECORD;
self.userAgent = USER_AGENT_AVD_WEB_RECORD;
}
var deferred = when.defer();
if (self.toMCUReconnectionPC) {
self.traceablePeerConnection.close(self.toMCUReconnectionPC);
self.toMCUReconnectionPC.msgQueue = [];
self.toMCUReconnectionPC = null;
}
self.serverinfoHandle().then(function(){
self.websocketConnect().then(function(websocket){
self.openReqSend(websocket).then(function(newRoom) {
self.pingTimeout = newRoom.pingTimeout;
self.pingInterval = newRoom.pingInterval;
self.signalTocken = newRoom.signalTocken;
self.masterServer = newRoom.masterServer;
self.pubVideos = []; //会议中已经发布的视频
self.pubAudios = []; //会议中已经发布的音频
self.pubScreens = []; //会议中已经发布的桌面共享
self.midNum = 1;
self.midObjSet = [{
mid: 0,
type: 'audio',
isReceiver: false,
isSender: false,
receiverInfo: {
trackId: '',
nodeId: '',
deviceId: ''
}
}, {
mid: 1,
type: 'video',
isReceiver: false,
isSender: false,
receiverInfo: {
trackId: '',
nodeId: '',
deviceId: ''
}
}];
self.roomJoinState = RoomStateEnum.CONNECTED;
deferred.resolve(self);
}).otherwise(function(error) {
self.roomJoinState = RoomStateEnum.DISCONNECTED;
log.debug("===room.reJoinHandle(),openReqSend faild, reason: " + JSON.stringify(error));
self.reJoin();
deferred.reject(error);
});
}).otherwise(function(error) {
self.roomJoinState = RoomStateEnum.DISCONNECTED;
log.debug("===room.reJoinHandle(),websocketConnect faild, reason: " + JSON.stringify(error));
self.reJoin();
deferred.reject(error);
});
}).otherwise(function(error) {
log.debug("===room.reJoinHandle(),serverinfoHandle faild, reason: " + JSON.stringify(error));
self.roomJoinState = RoomStateEnum.DISCONNECTED;
if(error) {
log.error("===room.reJoinHandle(), error code:" + error.code +", error message:"+ error.message);
} else {
var err = new Error(ErrorConstant.avdEngine_init_failed);
error = err;
log.error("===room.reJoinHandle(), error code:" + error.code +", error message:"+ error.message);
}
self.reJoin();
deferred.reject(error);
})
return deferred.promise;
};
/**
* @desc 多次后重新加会超时后,开启持续永久重新加会
*/
Room.prototype.continuousReJoin = function() {
var self = this;
if(self.rejoinAlways){
return;
}
log.info("===room.continuousReJoin(),重新加会超时后,开启持续永久重新加会");
self.rejoinAlways = true;
avdEngineHandle.setRemoteDescriptionSuccess = false;
this.addSdpLocalStates = 0;
log.debug("===DDDDDDD0000000000,room.continuousReJoin(),this.addSdpLocalStates:", this
.addSdpLocalStates);
this.addIcecandidateList = [];
this.reJoinAfterTimer = null;
this.reJoinAfterUsers = []; //重新加会后,服务器返回的参会者用户
self.reJoin();
};
Room.prototype.getMcuByReJoin = function() {
var self = this;
var deferred = when.defer();
var restServer = new RestServer();
restServer.getMcu(self.id, avdEngineHandle.accessToken).then(function() {
log.debug("===room.getMcuByReJoin(), getMcu success");
self.reJoin();
}).otherwise(function(error) {
log.error("===room.getMcuByReJoin(), getMcu error,code:" + error.code + ",message:" +
error.message);
self.reJoin();
});
return deferred.promise;
};
/**
* @desc 获取房间号
* @returns {String} roomId - 房间号
*/
Room.prototype.getRoomId = function() {
return this.roomInfo.room_id;
};
/**
* @desc 获取会议主题
* @returns {String} topic - 会议主题
*/
Room.prototype.getRoomTopic = function() {
return this.roomInfo.topic;
};
/**
* @desc 获取会议建创者Id
* @returns {String} ownerId - 会议建创者Id
*/
Room.prototype.getOwnerId = function() {
return this.roomInfo.owner_id;
};
/**
* @desc 判断当前会议模式是否为MCU模式
* @returns {Boolean} isMcuRoomMode - 是否为MCU模式. true: mcu模式;false:P2P模式
*/
Room.prototype.isMcu = function() {
var IsMcuRoomMode = true;
if (this.roomInfo == null) {
return IsMcuRoomMode;
} else {
var roomMode = this.roomInfo.room_mode;
if (roomMode == 3 || roomMode == 4) { //TODO: 暂时用1,2,3,4, 后面会用enum
IsMcuRoomMode = false;
}
return IsMcuRoomMode;
}
};
/**
* @desc 会议类型判断
* @param {Enum} roomTypEnum - 传入会议类型枚举
* @returns {Boolean} true: 是枚举对应的会议类型,false:非枚举对应的会议类型
*/
Room.prototype.getRoomType = function(roomTypEnum) {
//room.roomInfo.room_type是按位计算的,具体参考enum.js中的roomTypePrivilige
var isHas = false;
if (this.roomInfo && this.roomInfo.room_type) {
var roomType = this.roomInfo.room_type;
var roomTypePriviligeMap = (roomType).toString(2);
if (roomTypEnum == RoomTypeEnum.join_host_first) {
isHas = this.isHasPrivilige(roomTypePrivilige.join_host_first, roomTypePriviligeMap);
} else if (roomTypEnum == RoomTypeEnum.net_acc) {
isHas = this.isHasPrivilige(roomTypePrivilige.net_acc, roomTypePriviligeMap);
} else if (roomTypEnum == RoomTypeEnum.host_control) {
isHas = this.isHasPrivilige(roomTypePrivilige.host_control, roomTypePriviligeMap);
} else if (roomTypEnum == RoomTypeEnum.free_speech) {
isHas = !this.isHasPrivilige(roomTypePrivilige.host_control, roomTypePriviligeMap);
}
}
return isHas;
};
/**
* @desc 判断当前会议类型有效
* currPrivilige:当前会议类型
* priviliges: 会议类型集
* 返回:boolean
* 例子:isHasPrivilige(roomTypePrivilige.join_host_first,roomTypePriviligeMap);
* @ignore
*/
Room.prototype.isHasPrivilige = function(currPrivilige, priviliges) {
var isHas = false;
if ((currPrivilige & priviliges) > 0) {
isHas = true;
}
return isHas;
};
/**
* @desc 获取允许的最大音频路数
* @returns {Int} maxAudio - 最大音频路数
*/
Room.prototype.getMaxAudio = function() {
return this.roomInfo.max_audio;
}
/**
* @desc 获取允许的最大视频路数
* @returns {Int} maxVideo - 最大视频路数
*/
Room.prototype.getMaxVideo = function() {
return this.roomInfo.max_video;
}
/**
* @desc 获取允许的最大参加会议人数
* @returns {Int} maxAttendee - 最大参加会议人数
*/
Room.prototype.getMaxAttendee = function() {
return this.roomInfo.attendee_max;
}
/**
* @desc 获取视频路数的剩余可用数。
* <p>逻辑:允许的最大视频路数 - 已发布的视频路数</p>
* <p>注:主要用于界面显示用,具体打开的限制提示时,会采用服务器端的返回号来处理</p>
* @returns {Int} usableVideo - 剩余可用视频路数
*/
Room.prototype.getUsableVideo = function() {
var num = this.getMaxVideo() - this.pubVideos.length;
if (num < 0) {
num = 0;
}
return num;
}
/**
*@desc 获取音频路数的剩余可用数。
* <p>逻辑:允许的最大音频路数 - 已发布的音频路数</p>
* <p>注:主要用于界面显示用,具体打开的限制提示时,会采用服务器端的返回号来处理</p>
* @returns {Int} usableAudio - 剩余可用音频路数
*/
Room.prototype.getUsableAudio = function() {
var num = this.getMaxAudio() - this.pubAudios.length;
if (num < 0) {
num = 0;
}
return num;
}
/**
* @desc 修改会议房间状态
* @param {Enum} roomStatus - 房间状态枚举
*/
Room.prototype.updateRoomStatus = function(roomStatus) {
this.status = roomStatus;
};
/*
* @desc 获取会议房间状态
* @returns {Enum} roomStatus - 会议房间状态
*/
Room.prototype.getRoomStatus = function() {
return this.status;
};
// /**
// * @desc 返回会议主持人
// */
// Room.prototype.getHost = function() {
// for(var i = 0; i != this.participants.length; ++i) {
// var user = this.participants[i];
// if(user.role == 1) {
// return user;
// }
// }
// };
/**
* @desc 返回自己对象
* @returns {user} selfUser - 自己对象
*/
Room.prototype.getSelfUser = function() {
return this.selfUser;
};
/**
* @desc 返回自己对象的Id
* @returns {String} userId - 自己对象的Id
*/
Room.prototype.getSelfUserId = function() {
return this.selfUser.id;
};
/**
* @desc 返回参会者数组
* @returns {Object} participants -参会者数组
*/
Room.prototype.getParticipants = function() {
return this.participants;
};
/**
* @desc 返回参会者人数
* @returns {int} participantsNumber -参会者人数
*/
Room.prototype.getParticipantsNumber = function() {
var participantsNumber = 0;
if(this.participants){
participantsNumber = this.participants.length;
}
return participantsNumber;
};
/**
* @desc 通过用户ID获取用户对象
* @param {String} userId - 用户的Id
* @returns {Object} user - 用户对象
*/
Room.prototype.getUser = function(userId) {
var retUser;
for (var i = 0; i < this.participants.length; i++) {
var user = this.participants[i];
if (user.id == userId) {
retUser = user;
break;
}
}
return retUser;
};
/**
* @desc 通过用户ID获取用户对象
* @param {String} userId - 用户的Id
* @returns {Object} user - 用户对象
*/
Room.prototype.getUserByUserIdAndNodeId = function(userId, nodeId) {
var retUser;
for (var i = 0; i < this.participants.length; i++) {
var user = this.participants[i];
if (user.id == userId && user.nodeId == nodeId) {
retUser = user;
break;
}
}
return retUser;
};
/**
* @desc 通过用户ID,判断用户对象是否存在。
* @param {String} userId - 用户的Id
* @returns{boolean} isExist - true:存在; false: 不存在
*/
Room.prototype.hasUser = function(userId) {
var isExist = false;
for (var i = 0; i < this.participants.length; i++) {
var user = this.participants[i];
if (user.id == userId) {
isExist = true;
break;
}
}
return isExist;
};
/**
* @desc 通过用户ID,nodeId,判断用户对象是否存在。
* @param {String} userId - 用户的Id
* @param {String} nodeId - 用户的nodeId
* @returns{boolean} isExist - true:存在; false: 不存在
* @ignore
*/
Room.prototype.hasUserByUserIdAndNodeId = function(userId, nodeId) {
var isExist = false;
for (var i = 0; i < this.participants.length; i++) {
var user = this.participants[i];
if (user.id == userId && user.nodeId == nodeId) {
isExist = true;
break;
}
}
return isExist;
};
/**
* @desc 获取房间内当前白板数
* @param {boardStatusEnum} status - 白板状态值, 可以不填,不填时包括boardStatusEnum.open和boardStatusEnum.close的
* @returns {int} boardNumber -白板数
*
* @example
* room.getBoardNumber(boardStatusEnum.open);
*/
Room.prototype.getBoardNumber = function(status) {
var boardNumber = 0;
if(this.participants){
for (var i = 0; i < this.participants.length; i++) {
var user = this.participants[i];
if(user && user.boards){
for (var j = 0; j < user.boards.length; j++) {
var board = user.boards[j];
if( status === null || status === undefined){
boardNumber ++;
}else{
if(status == board.status){
boardNumber ++;
}
}
}
}
}
}
return boardNumber;
};
// /**
// * @desc 本地网络变化,ICE重新启动.如:从WIFI切换到有线
// */
// Room.prototype.localICERestart = function() {
// var self = this;
// log.info('===room.localICERestart(),PeerConnection createOffer ICE Restart');
// var offerOptions = {
// iceRestart: true
// };
// self.selfUser.toMCUPC.createOffer(
// offerOptions
// ).then(function(offer) {
// offer.sdp = updateSDPIcePwd(offer.sdp, self.icePwd);
// log.debug("===room.localICERestart(),LocalDescription:" + offer.sdp);
// return self.selfUser.toMCUPC.setLocalDescription(offer);
// });
// };
// /**
// * @desc 远端candidate更新,ICE重新启动
// * @param {Object} iceRestartMsg - 远端下发的ice restart candidate
// */
// Room.prototype.remoteICERestart = function(iceRestartMsg) {
// if (iceRestartMsg.candidate) {
// log.info("===room.remoteICERestart(),candidate:" + iceRestartMsg.candidate);
// this.masterServer.traceablePeerConnection.doAddIceCandidate(iceRestartMsg);
// this.localICERestart();
// }
// };
/**
* @desc 本客户端做为Answer端,服务器做为Offer端的场景
* @ignore
*/
Room.prototype.iCERestart = function() {
var self = this;
var offer = { type: 'offer', sdp: self.remoteDescriptionSDP};
log.debug('===room.iCERestart(), calling pc.setRemoteDescription(), remoteDescriptionSDP:', offer.sdp);
self.selfUser.toMCUPC.setRemoteDescription(offer).then(function() {
log.info("===room.iCERestart(), doSetRemoteDescription(), set remote sessionDescription success");
var answerOptions = {
iceRestart: true
};
self.selfUser.toMCUPC.createAnswer(answerOptions).then(function(sessionDescription) {
var sdp = sessionDescription.sdp;
var iceUfragRandom = Math.uuid(4);
var icePwdRandom = Math.uuid(24);
sdp = updateSDPIceUfrag(sdp, iceUfragRandom);
sdp = updateSDPIcePwd(sdp,icePwdRandom);
log.debug('===room.iCERestart(), calling pc.setLocalDescription(), LocalDescriptionSDP:', sdp);
var msgobj = {};
msgobj.sdp = sdp;
msgobj.type = "answer";
var desc = new RTCSessionDescription(msgobj);
self.selfUser.toMCUPC.setLocalDescription(desc).then(function() {
log.info("===room.iCERestart(), doSetLocalDescription(), set local sessionDescription success");
});
});
});
};
/**
* @desc 获取房间中所有已发布的摄像头视频数量
* @returns{int} publishedCameraCount - 所有已发布的摄像头视频数量
*/
Room.prototype.getPublishedCameraCount = function() {
return this.pubVideos.length;
};
/**
* @desc 获取房间中所有已发布的摄像头视频对象集
* @returns{Object} publishedCamera - 所有已发布的摄像头视频对象集
*/
Room.prototype.getPublishedCamera = function() {
return this.pubVideos;
};
/**
* @desc 获取房间中所有已发发布的共享屏幕窗口视频数量
* @returns{int} publishedScreenCount - 所有已发发布的共享屏幕窗口视频数量
*/
Room.prototype.getPublishedScreenCount = function() {
return this.pubScreens.length;
};
/**
* @desc 获取房间中所有已发布的共享屏幕窗口视频对象集
* @returns{Object} publishedScreen - 所有已发布的共享屏幕窗口视频对象集
*/
Room.prototype.getPublishedScreen = function() {
return this.pubScreens;
};
/**
* @desc 新增用户
* @ignore
*/
Room.prototype.addUser = function(user) {
if (!this.hasUser(user.id)) {
this.participants.push(user);
}
};
/**
* @desc 新增用户
* @ignore
*/
Room.prototype.addUserByUserIdAndUserId = function(user) {
if (!this.hasUserByUserIdAndNodeId(user.id, user.nodeId)) {
this.participants.push(user);
}
};
/**
* @desc 删除用户
* @ignore
*/
Room.prototype.removeUser = function(userId) {
if (this.participants && this.participants.length > 0) {
arrayUtil.objectSplice(this.participants, userId);
}
};
/**
* @desc 删除用户
* @ignore
*/
Room.prototype.removeUserByUserIdAndNodeId = function(userId, nodeId) {
if (this.participants && this.participants.length > 0) {
arrayUtil.userIdAndNodeIdSplice(this.participants, userId, nodeId);
}
};
/**
* @desc 通过用户nodeId获得用户对象
* @param {String} nodeId - 用户的nodeId
* @ignore
*/
Room.prototype.getUserByNodeId = function(nodeId) {
var retUser;
for (var i = 0; i < this.participants.length; i++) {
var user = this.participants[i];
if (user.nodeId == nodeId) {
retUser = user;
break;
}
}
return retUser;
};
/**
* @desc 通过用户userId获得用户nodeid
* @param {String} userId - 用户的Id
* @ignore
*/
Room.prototype.getNodeId = function(userId) {
var nodeId = null;
var retUser = this.getUser(userId);
if (retUser) {
nodeId = retUser.nodeId;
}
return nodeId;
}
/**
* @desc 通过用户nodeId获得用户userid
* @param {String} nodeId - 用户的nodeId
* @ignore
*/
Room.prototype.getUserId = function(nodeId) {
var userId = null;
var retUser = this.getUserByNodeId(nodeId);
if (retUser) {
userId = retUser.id;
}
return userId;
}
/**
* @desc 发送公聊消息
* @async
* @param {String} message - 消息内容
*
* @example
* room.sendPublicMessage("hello").then(function(){}).otherwise(function(error){});
*/
Room.prototype.sendPublicMessage = function(message) {
var deferred = when.defer();
this.masterServer.sendMessage(message);
deferred.resolve();
return deferred.promise;
};
/**
* @desc 发送私聊消息
* @async
* @param {String} message - 消息内容
* @param {String} userId - 消息接受用户ID
*
* @example
* room.sendPrivateMessage("hello",'user03').then(function(){}).otherwise(function(error){});
*/
Room.prototype.sendPrivateMessage = function(message, userId) {
var deferred = when.defer();
this.masterServer.sendMessage(message, userId);
deferred.resolve();
return deferred.promise;
};
/**
* @desc 发送公有透明通道
* @
* @param {Object} dataBinary - ArrayBinary类型的内容
* <p>注: ArrayBinary : 二进制数据的原始缓冲区,该缓冲区用于存储各种类型化数组的数据</p>
*
* @example
* var dataArrayBuffer = typeConversionUtil.String2ArrayBuffer("This is an important piece of news.");
* room.sendPublicData(dataArrayBuffer).then(dataSendSuccess).otherwise(dataSendError);
* function dataSendSuccess(dataMsg) {
* }
*/
Room.prototype.sendPublicData = function(dataBinary) {
var deferred = when.defer();
this.masterServer.sendData(dataBinary).then(function(){
deferred.resolve();
}).otherwise(function(error){
log.error("===room.sendPublicData() faild, error:" + JSON.stringify(ErrorConstant.data_oversize))
deferred.reject(error);
});
return deferred.promise;
};
/**
* @desc 发送私有透明通道
* @async
* @param {Object} dataBinary - ArrayBinary类型的内容
* <p>注: ArrayBinary : 二进制数据的原始缓冲区,该缓冲区用于存储各种类型化数组的数据</p>
* @param {String} userId - 内容接受用户ID
*
* @example
* var dataArrayBuffer = typeConversionUtil.String2ArrayBuffer("This is an important piece of news.");
* room.sendPrivateData(dataArrayBuffer,'15').then(dataSendSuccess).otherwise(dataSendError);
* function dataSendSuccess(dataMsg) {
* }
*/
Room.prototype.sendPrivateData = function(dataBinary, userId) {
var deferred = when.defer();
this.masterServer.sendData(dataBinary, userId).then(function(){
deferred.resolve();
}).otherwise(function(error){
log.error("===room.sendPrivateData() faild, error:" + JSON.stringify(ErrorConstant.data_oversize))
deferred.reject(error);
});
return deferred.promise;
};
/**
* @desc 发送私有二进制数据。与room.sendPrivateData()的区别,本方法只需要知道对方的userId,内部实现时不用知道对方的nodeId,如一些特殊useAgent的用户,是取不到nodeId的。
* @async
* @param {Object} dataBinary -ArrayBinary类型的内容
* <p>注: ArrayBinary : 二进制数据的原始缓冲区,该缓冲区用于存储各种类型化数组的数据</p>
* @param {String} userId - 内容接受用户ID
*
* @example
* var dataArrayBuffer = typeConversionUtil.String2ArrayBuffer("This is an important piece of news.");
* room.sendPrivateDataToUser(dataArrayBuffer,'15').then(dataSendSuccess).otherwise(dataSendError);
* function dataSendSuccess(dataMsg) {
* }
*/
Room.prototype.sendPrivateDataToUser = function(dataBinary, userId) {
var deferred = when.defer();
this.masterServer.sendPrivateDataToUser(dataBinary, userId).then(function(){
deferred.resolve();
}).otherwise(function(error){
log.error("===room.sendPrivateDataToUser() faild, error:" + JSON.stringify(ErrorConstant.data_oversize))
deferred.reject(error);
});
return deferred.promise;
}
/**
* @desc 更新房间应用扩展字段
* @param {String} key - key值
* @param {String} value - value值
*/
Room.prototype.updateAppData = function(key, value) {
this.appData[key] = value;
this.masterServer.roomDataInfo(key, value);
};
/**
* @desc 获得房间应用扩展字段信息
* @param {String} key - key值
* @returns {String} value- value值
*/
Room.prototype.getAppData = function(key) {
if (typeof(key) == 'undefined' || key == null || key == '') {
return this.appData;
} else {
return this.appData[key];
}
};
/**
* @desc 设置摄像头分辨率或帧率
* @async
* @param {String} cameraId - 摄摄像头ID
* @param {String} resolution -分辨率枚举值,如
* @param {int} frameRate -帧率
* @param {Object} resolutionSetType- 分辨率设置类型,可不填写,null时默认为ResolutionSetType.exact
* @param {Number} aspectRatio- 分辨率宽高比(1.7777777778, 1.3333333333),可不填写(null,0时浏览器自动处理),16:9或4:3时会开启强制
*/
Room.prototype.setCameraResolutionOrFrameRate = function(cameraId, resolution, frameRate, resolutionSetType,
aspectRatio) {
log.info("===room.setCameraResolutionOrFrameRate(),cameraId:" + cameraId + ",resolution:" +
resolution + ",frameRate:" + frameRate + ",resolutionSetType:" + resolutionSetType +
",aspectRatio:" + aspectRatio);
var deferred = when.defer();
var video = this.selfUser.getVideo(cameraId);
if (video) {
if (typeof(frameRate) == 'string') {
frameRate = Number(frameRate);
}
if (typeof(aspectRatio) == 'string') {
aspectRatio = Number(aspectRatio);
}
if (video.resolution != Resolution[resolution] || video.setType != resolutionSetType || video
.frameRate != frameRate || video.aspectRatio != aspectRatio) {
video.setResolution(resolution, resolutionSetType);
video.setFrameRate(frameRate);
video.setAspectRatio(aspectRatio);
if (video.track) {
video.applyConstraints();
}
deferred.resolve();
}
} else {
var error = new Error(ErrorConstant.cameraId_does_not_exist);
deferred.reject(error);
}
return deferred.promise;
}
/**
* @desc 设置摄像头分辨率或帧率
* @async
* @param {String} cameraId -摄摄像头ID
* @param {int} resolutionWidth -分辨率宽
* @param {int} resolutionHeight -分辨率高
* @param {int} frameRate -帧率
* @param {Object} resolutionSetType- 分辨率设置类型,可不填写,null时默认为ResolutionSetType.exact
* @param {Number} aspectRatio- 分辨率宽高比(1.7777777778, 1.3333333333),可不填写(null,0时浏览器自动处理),16:9或4:3时会开启强制
*/
Room.prototype.setCameraResolutionWHOrFrameRate = function(cameraId, resolutionWidth, resolutionHeight,
frameRate, resolutionSetType, aspectRatio) {
log.info("===room.setCameraResolutionWHOrFrameRate(),cameraId:" + cameraId + ",resolutionWidth:" +
resolutionWidth + ",resolutionHeight:" + resolutionHeight + ",frameRate:" + frameRate +
",resolutionSetType:" + resolutionSetType + ",aspectRatio:" + aspectRatio);
var deferred = when.defer();
var video = this.selfUser.getVideo(cameraId);
if (video) {
if (typeof(frameRate) == 'string') {
frameRate = Number(frameRate);
}
if (typeof(aspectRatio) == 'string') {
aspectRatio = Number(aspectRatio);
}
if (typeof(video.resolution) == 'undefined') {
video.setResolutionWH(resolutionWidth, resolutionHeight, resolutionSetType);
video.setFrameRate(frameRate);
deferred.resolve();
} else {
if (video.resolution.width != resolutionWidth || video.resolution.height !=
resolutionHeight || video.setType != resolutionSetType || video.frameRate !=
frameRate || video.aspectRatio != aspectRatio) {
video.setResolutionWH(resolutionWidth, resolutionHeight, resolutionSetType);
video.setFrameRate(frameRate);
video.setAspectRatio(aspectRatio);
if (video.track) {
video.applyConstraints();
}
deferred.resolve();
}
}
} else {
var error = new Error(ErrorConstant.cameraId_does_not_exist);
deferred.reject(error);
}
return deferred.promise;
}
/**
* @desc 屏蔽会议声音
*/
Room.prototype.muteSpeaker = function() {
log.info("===room.muteSpeaker()");
var self = this;
self.muteSpeakerFlag = true;
self.participants.forEach(function(user) {
if (user.id != self.selfUser.id) {
var track = user.audio.track;
if (track) {
track.enabled = false;
}
}
});
};
/**
* @desc 取消屏蔽会议声音
*/
Room.prototype.unmuteSpeaker = function() {
log.info("===room.unmuteSpeaker()");
var self = this;
self.muteSpeakerFlag = false;
self.participants.forEach(function(user) {
if (user.id != self.selfUser.id) {
var track = user.audio.track;
if (track) {
track.enabled = true;
}
}
});
};
/**
* @desc 获取屏蔽会议声音的当前状态
* @returns {Boolean} - 屏蔽会议声音的当前状态
*/
Room.prototype.ismuteSpeaker = function() {
return this.muteSpeakerFlag;
};
Room.prototype.hasObject = function(objs, curObj) {
var isExist = false;
for (var i = 0; i < objs.length; i++) {
var obj = objs[i];
if (obj.id == curObj.id && obj.ownerId == curObj.ownerId) {
isExist = true;
break;
}
}
return isExist;
};
/**
* @desc 踢人
* @param {int} reason - 踢人原因,填写808,其它值也都会重置成808
* @param {String} userId - 被踢用户id
*/
Room.prototype.kickoffUser = function(reason, userId) {
reason = 808;//
log.info("===room.kickoffUser(),reason:" + reason + ",userId:" + userId);
this.masterServer.userIndiction(reason, userId);
};
/**
* @desc 退会
* @async
* @param {int} reason - 退会原因,可以填写 1
* @param {boolean} kickoffOpt - 退会类型: true:被踢退会,false:主动退会,可以不填
*
* @example
* room.leave(1).then(function(){}).otherwise(function(error){});
*
*/
Room.prototype.leave = function(reason, kickoffOpt) {
log.info("===room.leave(),reason:" + reason + ",kickoffOpt:" + kickoffOpt);
var self = this;
var deferred = when.defer();
self.mp4ImporterUserAudioRtpPacketTemp = 0;
self.mp4ImporterUserAudioRtpPacketReal = 0;
if (self.mp4ImporterUserAudioStateIntervalId) {
clearInterval(self.mp4ImporterUserAudioStateIntervalId);
self.mp4ImporterUserAudioStateIntervalId = null;
}
//用户离开清场
self.userClearInfo();
setTimeout(function() {
self.masterServer.userMsg(reason, kickoffOpt);
self.roomClearInfo(true);
deferred.resolve();
}, 1000);
return deferred.promise;
};
/**
* @desc 关闭房间
* @param {String} userId - 关闭房间的操作用户ID
*/
Room.prototype.closeRoom = function(userId) {
log.info("===room.closeRoom(),userId:", userId);
var self = this;
self.mp4ImporterUserAudioRtpPacketTemp = 0;
self.mp4ImporterUserAudioRtpPacketReal = 0;
if (self.mp4ImporterUserAudioStateIntervalId) {
clearInterval(self.mp4ImporterUserAudioStateIntervalId);
self.mp4ImporterUserAudioStateIntervalId = null;
}
//用户离开清场
self.userClearInfo();
setTimeout(function() {
self.masterServer.closeRoom(userId);
self.roomClearInfo();
}, 1000);
};
/**
* @desc 用户离开清场
* @ignore
*/
Room.prototype.userClearInfo = function() {
var self = this;
//1.已pub的视频先unpub
var videos = this.selfUser.videos;
if (videos && videos.length > 0) {
videos.forEach(function(video){
if (video.status == StreamStatus.published) {
//当本地离开会议或关闭会议时,video.unpublish()有可能进入unpub_roomresourcemsg_rep timeout case, 这时视频可能就没有关掉。
if(video.track){
video.track.stop();
}
video.unpreview();
video.unpublish();
}
})
}
//2. 已pub的音频先unpub
var audio = this.selfUser.audio;
if (audio && audio.status == StreamStatus.published) {
//当本地离开会议或关闭会议时,audio.closeMicrophone()有可能进入unpub_roomresourcemsg_rep timeout case, 这时音频可能就没有关掉。
if(audio.track){
audio.track.stop();
}
audio.closeMicrophone();
}
//3. 已pub的桌面共享先unpub
var screen = this.selfUser.screen;
if (screen && screen.status == StreamStatus.published) {
//当本地离开会议或关闭会议时,screen.unpublish()有可能进入unpub_roomresourcemsg_rep timeout case, 这时桌面共享的共享条可能就没有关掉。
if(screen.track){
screen.track.stop();
}
screen.unpublish();
}
//1.已打开的白板先删除
var boards = this.selfUser.boards;
if (boards && boards.length > 0) {
boards.forEach(function(board){
self.selfUser.removeBoardById(board.id);
})
}
};
/**
* @desc 离开清场
* @ignore
*/
Room.prototype.roomClearInfo = function(isLeaveRoom) {
var self = this;
if (self) {
if (self.pingIntervalId) {
clearInterval(self.pingIntervalId);
self.pingIntervalId = null;
}
if (self.pingTimeoutId) {
clearInterval(self.pingTimeoutId);
self.pingTimeoutId = null;
}
if (self.selfUser.changePublishVideoQualityInterval) {
clearInterval(self.selfUser.changePublishVideoQualityInterval);
self.selfUser.changePublishVideoQualityInterval = null;
}
if (self.selfUser) {
if (self.selfUser.toMCUPC) {
self.selfUser.toMCUPC.msgQueue = [];
self.selfUser.toMCUPC.close();
self.selfUser.toMCUPC = null;
}
}
avdEngineHandle.setRemoteDescriptionSuccess = false; //PC.setRemoteDescription处理成功的返回状态
this.addIcecandidateList = [];
if (self.connectionInfoCollector) {
self.connectionInfoCollector.stop(); //停止收集网络情况
}
if (self.connectionStatistics) {
self.connectionStatistics.stop(); //停止上报媒体信息
}
if(isLeaveRoom){
self.roomJoinState = RoomStateEnum.DISCONNECTED;
self.masterServer.signalingChannel.close();
self = null;
}
avdEngineHandle.rooms = [];
}
}
/**
* @desc 房间级别的回调
* @param {RoomCallback} type - 回调枚举标识
* @param {Object} callback -回调方法名,可以自定义,建议保持默认值
* @example
*
* room.addCallback(RoomCallback.connection_status, onConnectionStatus);
* room.addCallback(RoomCallback.connection_indicator,onConnectionIndicator);
* room.addCallback(RoomCallback.room_status_notify, onRoomStatusNotify);
*
* room.addCallback(RoomCallback.user_join_notify, onUserJoinNotify);
* room.addCallback(RoomCallback.user_leave_notify, onUserLeaveNotify);
* room.addCallback(RoomCallback.leave_indication, onLeaveIndication);
* room.addCallback(RoomCallback.close_room_result, onCloseRoomResult);
* room.addCallback(RoomCallback.close_room_notify, onCloseRoomNotify);
* room.addCallback(RoomCallback.app_data_notify, onAppdataNotify);
* room.addCallback(RoomCallback.user_data_notify, onUserDataNotify);
* room.addCallback(RoomCallback.user_name_notify, onUserNameNotify);
*
* room.addCallback(RoomCallback.public_message, onPublicMessage);
* room.addCallback(RoomCallback.private_message, onPrivateMessage);
* room.addCallback(RoomCallback.public_data, onPublicData);
* room.addCallback(RoomCallback.private_data, onPrivateData);
*
* room.addCallback(RoomCallback.screen_sharing_ended, onScreenSharingEnded);
*
* room.addCallback(RoomCallback.outgoing_invite_status_notify, onOutgoingInviteStatusNotify);
* room.addCallback(RoomCallback.room_control_change_notify, onRoomControlChangeNotify);
*
*
* 与服务器连接状态回调
* param:status - 连接状态
*
* function onConnectionStatus(status) {
* if (status == ConnectionStatus.connecting) {
* //TODO 与服务器重连中
* } else if (status == ConnectionStatus.connected) {
* //TODO 与服务器重连成功
* } else if (status == ConnectionStatus.connectFailed) {
* //TODO 与服务器重连失败,需要重新登陆
* }
* }
*
*
* 与服务器连接指示器回调
* param: connectionIndicator - 连接指示器
*
* function onConnectionIndicator(connectionIndicator){
* if(connectionIndicator){
* var roomConnection = connectionIndicator.getRoomConnection();
*
* //房间级别
* roomConnection.localAddress 本地地址
* roomConnection.localPort 本地端口
* roomConnection.localProtocol 本地协议
* roomConnection.remoteAddress 远端地址
* roomConnection.remotePort 远端端口
* roomConnection.remoteProtocol 远端协议
*
* roomConnection.downloadBitrate 下载流量
* roomConnection.uploadBitrate 上传流量
*
* roomConnection.downloadPacket 下载数据包
* roomConnection.uploadPacket 上传数据包
* roomConnection.downloadPacketLost 下载数据包丢包数
* roomConnection.uploadPacketLost 上传数据包丢包数
*
* roomConnection.downloadPacketLostRate 下载数据包丢包率
* roomConnection.uploadPacketLostRate 上传数据包丢包率
*
* roomConnection.currentRoundTripTime 当前的往返时间(RTT)
* roomConnection.totalRoundTripTime 累计的往返时间(RTT)
*
* //某路音频相关
* var audioConnection = connectionIndicator.getAudioConnection(userId);
* audioConnection.downloadBitrate 音频下载流量
* audioConnection.uploadBitrate 音频上传流量
*
* audioConnection.downloadPacket 音频下载数据包
* audioConnection.uploadPacket 音频上传数据包
*
* audioConnection.packetsLost 音频数据包丢包数
* audioConnection.packetLostRate 音频数据包丢包率
*
* audioConnection.ssrc
* audioConnection.trackId
* audioConnection.payloadType
* audioConnection.mimeType
* audioConnection.clockRate
*
* //某路视频相关
* videoConnection = connectionIndicator.getVideoConnection(userId, cameraId);
* videoConnection.downloadBitrate 视频下载流量
* videoConnection.uploadBitrate 视频上传流量
*
* videoConnection.downloadPacket 视频下载数据包
* videoConnection.uploadPacket 视频传数据包
*
* videoConnection.packetsLost 视频数据包丢包数
* videoConnection.packetLostRate 视频数据包丢包率
*
* videoConnection.resolution 分辨率
* videoConnection.frameRate 帧率
*
* videoConnection.qpSum 媒体数据包的总量化值(qpSum
* videoConnection.framesEncoded 已编码帧的数量
* videoConnection.framesDecoded 已解码帧的数量
* videoConnection.perFrameQP 每个视频帧的平均量化值(perFrameQP)
*
* videoConnection.ssrc
* videoConnection.trackId
* videoConnection.payloadType
* videoConnection.mimeType
* videoConnection.clockRate
*
* * //某路桌面共享相关
* screenConnection = connectionIndicator.getScreenConnection(userId);
* screenConnection.downloadBitrate 桌面共享下载流量
* screenConnection.uploadBitrate 桌面共享上传流量
*
* screenConnection.downloadPacket 桌面共享下载数据包
* screenConnection.uploadPacket 桌面共享传数据包
*
* screenConnection.packetsLost 桌面共享数据包丢包数
* screenConnection.packetLostRate 桌面共享数据包丢包率
*
* screenConnection.resolution 分辨率
* screenConnection.frameRate 帧率
* screenConnection.ssrc
* screenConnection.trackId
* screenConnection.payloadType
* screenConnection.mimeType
* screenConnection.clockRate
* }
* }
*
*
* 参会者加会回调
* param :users - 参会者数组
*
* function onUserJoinNotify(users) {
*
* }
*
* 参会者退会回调
* param:opt - 退会类型
* param:reason - 退会原因
* param:user - 退会用户
*
* function onUserLeaveNotify(opt,reason,user) {
*
* }
*
*
*
* 被踢出会议室
* param:reason - 被踢原因
* param:userId - 踢人的操作者
*
* function onLeaveIndication(reason,userId) {
*
* }
*
*
*
* 房间应用扩展字段回调
* param {Object} key - key值
* param {Object} value - value值
* function onAppdataNotify(key, value) {
*
* }
*
*用户扩展内容更新回调
*param:userData - 用户扩展内容
*param:userId - 用户id
*
* function onUserDataNotify(userData, userId ) {
*
* }
*
*
*
* 用户修改名字回调
* param {String} userName - 用户新名字
* param {String} userId - 用户id
*
*function onUserNameNotify(userName, userId){
*
*}
*
*
*
*
*公聊回调
*param : Message
*
*function onPublicMessage(Message) {
*
*}
*
*
*私聊回调
*param: Message
*
*function onPrivateMessage(Message) {
*
*}
*
*
*公有透明通道回调
*param:dataArrayBuffer - DataArrayBuffer对象
*param:userId - user id
*
*function onPublicData(dataArrayBuffer, userId) {
*
*}
*
*
*私有透明通道回调
*param:dataArrayBuffer - DataArrayBuffer对象
*param:userId - user id
*
*function onPrivateData(dataArrayBuffer, userId) {
*
*}
*
*
* 桌面共享关闭的回调
* function onScreenSharingEnded(){
*
* }
*
*
* 外部设备邀请状态回调
*param:type - OutgoingInviteType枚举值
*param:roomId - 房间ID
*param:addr - 设备地址
*param:status - 邀请状态码
*param:msg - 邀请返回状态
*
* function onOutgoingInviteStatusNotify(type,roomId,addr,status,msg){
*
* }
*
*
* 房间参数设置回调
* param:roomControl - 参数对象
* function onRoomControlChangeNotify(roomControl){
*
*}
*
*/
Room.prototype.addCallback = function(type, callback) {
this.eventEmitter.addListener(type, callback);
};
/**
* @desc 获取房间控制参数
* @returns {Object} - roomControl 房间控制参数
*/
Room.prototype.getRoomControl = function() {
return this.roomControl;
};
/**
* @desc 设置房间控制参数(参数为JSON格式字符串)
* @param {String} attributes -房间控制参数(JSON格式字符串)
* @example
* var attributes = '{
* 'record': 0,
* 'mix_record' : 0,
* 'avc_model': 0,
* 'resolution': '640*480',
* 'liver'': 0,
* 'mixer_live: 0
* }";
* room.setRoomControl(attributes);
*/
Room.prototype.setRoomControl = function(attributes) {
this.masterServer.setRoomControl(this.id, attributes);
};
// /**
// * 开启AVC合屏模式
// */
// Room.prototype.setRoomAVCEnable = function(){
//
// }
//
//
// /**
// * 关闭AVC合屏模式
// */
// Room.prototype.setRoomAVCDisabled = function(){
// }
/**
* @desc 原始设备ID转成UUID,作为上传到服务器后逻辑设备ID
* @param {int} type - 媒体类型,0-camera,1-speake,2-microphone,
* @param {String} deviceId - 原始设备Id
* @ignore
*/
Room.prototype.setDeviceIdByUUID = function(type, deviceId) {
var self = this;
if (type == 0) {
var cameraUUID = self.cameraId2uuid[deviceId];
if (!cameraUUID) {
//本地桌面共享ID可能会走这个流程
if (self.selfUser && self.selfUser.screen && self.selfUser.screen.id == deviceId) {
cameraUUID = deviceId;
} else {
cameraUUID = Math.uuid();
self.cameraId2uuid[deviceId] = cameraUUID;
}
}
return cameraUUID;
} else if (type == 1) {
var speakerUUID = self.speakerId2uuid[deviceId];
if (!speakerUUID) {
speakerUUID = Math.uuid();
self.speakerId2uuid[deviceId] = speakerUUID;
}
return speakerUUID;
} else if (type == 2) {
var microphoneUUID = self.microphoneId2uuid[deviceId];
if (!microphoneUUID) {
microphoneUUID = Math.uuid();
self.microphoneId2uuid[deviceId] = microphoneUUID;
}
return microphoneUUID;
}
};
/**
* @desc 从服务器上逻辑设备ID(UUID),解析出原始设备ID
* @param {int} type - 媒体类型,0-camera,1-speake,2-microphone,
* @param {String} UUID - 服务器上逻辑设备ID
* @ignore
*/
Room.prototype.getDeviceIdByUUID = function(type, UUID) {
var deviceId = UUID;
if (type == 0) {
for (key in this.cameraId2uuid) {
var curUUID = this.cameraId2uuid[key];
if (curUUID == UUID) {
deviceId = key;
break;
}
}
return deviceId;
} else if (type == 1) {
for (key in this.speakerId2uuid) {
var curUUID = this.speakerId2uuid[key];
if (curUUID == UUID) {
deviceId = key;
break;
}
}
return deviceId;
} else if (type == 2) {
for (key in this.microphoneId2uuid) {
var curUUID = this.microphoneId2uuid[key];
if (curUUID == UUID) {
deviceId = key;
break;
}
}
return deviceId;
}
}
/**
* @desc 根据视频长宽计算初始化速率
* @ignore
*/
Room.prototype.calInitVideoBitrate = function(width, height) {
var xy = width * height;
var xysqrt = Math.sqrt(xy);
var xyslog = Math.log10(xy);
var xysqrtlog = Math.log10(xysqrt);
var xysqrtlogpow2 = Math.pow(xysqrtlog, 2);
var bitrate = xyslog * xysqrt / xysqrtlogpow2 / 10;
if (width > 1280) {
var some = Math.sqrt(width / 1280.0);
bitrate = bitrate * some;
}
var curBitrate = parseInt(bitrate * 8);
var startBit = curBitrate * 2;
var minBit = startBit / 2;
var maxBit = startBit * 2;
var bit = {
startBit: startBit,
minBit: minBit,
maxBit: maxBit
}
return bit;
};
/**
* @desc 发送添加注释消息
* @ignore
*/
Room.prototype.sendAddAnnotation = function(objectId, shapeObj, shapeType, lineColor, lineWidth, arrowtype,
fillType) {
log.debug("===Room.sendAddAnnotation(),objectId:" + objectId + ",shapeType:" + shapeType +
",arrowtype:" + arrowtype + ",fillType:" + fillType);
if(!shapeObj || !shapeObj.id){
return;
}
this.masterServer.sendAddAnnotationMessage(objectId, shapeObj, shapeType, lineColor, lineWidth,
arrowtype, fillType);
return;
};
/**
* @desc 发送修改注释消息
* @param {string} objectId 注释对象id
* @param {object} shapeObj 注释对象
* @param {string} shapeType 注释类型
* @ignore
*/
Room.prototype.sendUpdateAnnotation = function(objectId, shapeObj, shapeType) {
log.debug("===Room.sendUpdateAnnotation(),objectId:" + objectId + ",shapeType:" + shapeType);
this.masterServer.sendUpdateAnnotationMessage(objectId, shapeObj, shapeType);
return;
};
/**
* @desc 发送删除注释消息
* @param {string} objectId 注释id
* @param {string} shapeType 注释类型
* @param {string} eraserIds 橡皮擦id
* @ignore
*/
Room.prototype.sendRemoveAnnotation = function(objectId, annotationType, eraserIds) {
log.debug("===Room.sendRemoveAnnotation(),objectId:" + objectId + ",annotationType:" +
annotationType + ",eraserIds:" + eraserIds);
this.masterServer.sendRemoveAnnotationMessage(objectId, annotationType, eraserIds);
return;
};
/**
* @description 初始化多个fake video stream
* @param {Object} fakeLen 个数
* @ignore
*/
Room.prototype.initFakeVideoStream = function(fakeLen) {
for (var i = 0; i < fakeLen; i++) {
getFakeVideoStream(function(stream) {
this.fakeVideoStreams.push(stream);
});
}
};
/**
* @desc 初始化多个fake audio stream
* @param {Object} fakeLen 个数
* @ignore
*/
Room.prototype.initFakeAudioStream = function(fakeLen) {
for (var i = 0; i < fakeLen; i++) {
getFakeAudioStream(function(stream) {
this.fakeAudioStreams.push(stream);
});
}
};
/**
* @desc 添加注释到队列中
* @param {Boolean} 是否为自己添加
* @ignore
*/
// Room.prototype.addAnnotationMsg = function(updateAnnotationMsg,isSelf) {
// if(room.annotation2boardId[updateAnnotationMsg.objectId]){
// if(updateAnnotationMsg.annotationType != shapeTypeEnum.hlight_point){
// room.annotation2boardId[updateAnnotationMsg.objectId].storageArr.push(updateAnnotationMsg);
// }
// if(!isSelf){
// room.annotation2boardId[updateAnnotationMsg.objectId].queueArr.push(updateAnnotationMsg);
// }
// }else{
// room.annotation2boardId[updateAnnotationMsg.objectId] = {
// storageArr:[],
// queueArr:[]
// };
// if(updateAnnotationMsg.annotationType != shapeTypeEnum.hlight_point){
// room.annotation2boardId[updateAnnotationMsg.objectId].storageArr.push(updateAnnotationMsg);
// }
// if(!isSelf){
// room.annotation2boardId[updateAnnotationMsg.objectId].queueArr.push(updateAnnotationMsg);
// }
// }
// };
/**
* @desc 添加注释到队列中
* @param {Object} updateAnnotationMsg 批注对象
* @param {Boolean} isSelf 是否为自己添加
* @param {Boolean} isClear 是否是清除
* @param {string} imgZrender 是否是图片
* @param {string} imgId 图片id
* @ignore
*/
Room.prototype.addAnnotationMsg = function(updateAnnotationMsg, isSelf, isClear, imgZrender, imgId) {
log.debug('===room.addAnnotationMsg, updateAnnotationMsg:'+updateAnnotationMsg.objectId+',isSelf:'+ isSelf+ ',isClear: '+isClear)
var self = this;
if (this.annotation2boardId[updateAnnotationMsg.objectId]) {
var tempStorageArr = this.annotation2boardId[updateAnnotationMsg.objectId].storageArr;
var tempQueueArr = this.annotation2boardId[updateAnnotationMsg.objectId].queueArr;
if (updateAnnotationMsg.annotationType != shapeTypeEnum.hlight_point) {
if (tempStorageArr.length > 0) {
// 增加判断该批注是否已经存在于storageArr中,不存在才添加
var isExisted = false;
for (var i = 0; i < tempStorageArr.length; i++) {
if (tempStorageArr[i].annotationId == updateAnnotationMsg.annotationId) {
if (tempStorageArr[i].operType != updateAnnotationMsg.operType && updateAnnotationMsg.operType == OperType.remove) {
log.debug('===room.addAnnotationMsg, updateAnnotationMsg.remove, remove annotation from annotation2BoardId');
tempStorageArr.splice(i, 1);
}
isExisted = true
}
}
if (!isExisted && updateAnnotationMsg.operType != OperType.remove) {
tempStorageArr.push(updateAnnotationMsg);
}
} else {
tempStorageArr.push(updateAnnotationMsg);
}
}
if (!isSelf) {
tempQueueArr.push(updateAnnotationMsg);
}
} else {
this.annotation2boardId[updateAnnotationMsg.objectId] = {
storageArr: [],
queueArr: []
};
if (updateAnnotationMsg.annotationType != shapeTypeEnum.hlight_point) {
this.annotation2boardId[updateAnnotationMsg.objectId].storageArr.push(updateAnnotationMsg);
}
if (!isSelf) {
this.annotation2boardId[updateAnnotationMsg.objectId].queueArr.push(updateAnnotationMsg);
}
}
/**
* desc: 增加判断是否收到清除命令,如果收到清除则将本地存储的批注删除
* by: 王博
* date: 2021/7/15
*/
if (isClear) {
if (this.annotation2boardId[updateAnnotationMsg.objectId] && this.annotation2boardId[
updateAnnotationMsg.objectId].storageArr.length > 0) {
for (var i = 0; i < this.annotation2boardId[updateAnnotationMsg.objectId].storageArr
.length; i++) {
var tempShape = this.annotation2boardId[updateAnnotationMsg.objectId].storageArr[i];
if (tempShape.operUserId == this.selfUser.id && tempShape.operType == OperType.add) {
this.annotation2boardId[updateAnnotationMsg.objectId].storageArr.splice(i, 1);
i--;
}
}
}
}
if(updateAnnotationMsg.operType == OperType.remove){
for (var i = 0; i < this.annotation2boardId[updateAnnotationMsg.objectId].storageArr.length; i++) {
var tempShape = this.annotation2boardId[updateAnnotationMsg.objectId].storageArr[i];
if (tempShape.annotationId == imgId && tempShape.operUserId == this.selfUser.id && tempShape.operType == OperType.add) {
delete tempShape.imgZrender;
break;
}
}
}else{
for (var i = 0; i < this.annotation2boardId[updateAnnotationMsg.objectId].storageArr.length; i++) {
var tempShape = this.annotation2boardId[updateAnnotationMsg.objectId].storageArr[i];
if (tempShape.annotationId == imgId && tempShape.operUserId == this.selfUser.id && tempShape.operType == OperType.add) {
tempShape.imgZrender = imgZrender;
this.style2annotation[imgZrender.id] = {
style: JSON.stringify(imgZrender.style),
position: JSON.stringify(imgZrender.position),
scale: JSON.stringify(imgZrender.scale),
origin: JSON.stringify(imgZrender.origin),
rotation: JSON.stringify(imgZrender.rotation)
};
break;
}
}
}
};
//TODO 没想到更好的解决方式之前,暂时使用这种写法 by宝旭
Room.prototype.updateAnnotationDrow = function(annotations) {
log.debug('===room.updateAnnotationDrow, annotations: ', annotations);
var self = this;
while (annotations.length > 0) {
var updateAnnotationMsg = annotations[0];
var user = this.getUser(this.boardId2userId[updateAnnotationMsg.objectId]);
if (!user) {
break;
}
var board = user.getBoard(updateAnnotationMsg.objectId);
if (!board.annotation) {
clearTimeout(self.updateTimer);
self.updateTimer = setTimeout(function() {
self.updateAnnotationDrow(annotations)
}, 100)
break;
}
annotations.shift();
switch (updateAnnotationMsg.operType) {
case OperType.add:
if (updateAnnotationMsg.annotationType == shapeTypeEnum.line ||
updateAnnotationMsg.annotationType == shapeTypeEnum.ellipse ||
updateAnnotationMsg.annotationType == shapeTypeEnum.rect ||
updateAnnotationMsg.annotationType == shapeTypeEnum.arrow ||
updateAnnotationMsg.annotationType == shapeTypeEnum.success ||
updateAnnotationMsg.annotationType == shapeTypeEnum.failure ||
updateAnnotationMsg.annotationType == shapeTypeEnum.rhomb ||
updateAnnotationMsg.annotationType == shapeTypeEnum.hlight_texttag ||
updateAnnotationMsg.annotationType == shapeTypeEnum.hlight_point) {
board.annotation.drawToBoard(updateAnnotationMsg.ann2pt);
} else if (updateAnnotationMsg.annotationType == shapeTypeEnum.polyline) {
board.annotation.drawToBoard(updateAnnotationMsg.annMutlipt);
} else if(updateAnnotationMsg.annotationType == shapeTypeEnum.img){
board.annotation.drawToBoard(updateAnnotationMsg.Img);
} else if(updateAnnotationMsg.annotationType == shapeTypeEnum.txt){
board.annotation.drawTextToBoard(updateAnnotationMsg);
}
break;
case OperType.remove:
board.annotation.removeToBoard(updateAnnotationMsg);
break;
case OperType.update:
if(updateAnnotationMsg.annotationType == shapeTypeEnum.img){
board.annotation.updateDrawToBoard(updateAnnotationMsg.Img);
}
break;
default:
break;
}
}
}
//TODO 服务器通知下发,由本地用户来上报最大语音激励值
Room.prototype.voiceActivatedHandler = function() {
var statsInterval = 500; //sdk语音激励计算频率
if (this.audioLevel) {
this.audioLevel.start(statsInterval).then(audioLevelHandler(this)); //开始语音激励
}
}
/**
* @description userAgent为avd_mp4_importer的导入用户,开启按指定频率获取音频的rpc包数等值
* @param {Object} interval 采集频率
* @ignore
*/
Room.prototype.mp4ImporterUserAudioStateStart = function(interval) {
log.debug("===room.mp4ImporterUserAudioStateStart()");
var self = this;
if (!this) {
return;
}
clearInterval(self.mp4ImporterUserAudioStateIntervalId);
self.mp4ImporterUserAudioStateIntervalId = setInterval(function() {
var trackId = getAudioTrackIdByMp4ImporterUser(self);
// log.debug("===room.mp4ImporterUserAudioStateStart(),trackId:",trackId);
if (trackId && self) {
var audioReceiver;
var receivers = self.traceablePeerConnection.getToMCUPeerConnection()
.getReceivers();
for (var i = 0; i < receivers.length; i++) {
var receiver = receivers[i];
if (receiver && receiver.track && receiver.track.id && receiver.track.id ==
trackId) {
audioReceiver = receiver;
break;
}
}
if (audioReceiver) {
audioReceiver.getStats().then(function(res) {
res.forEach(function(report) {
if (report.type === 'inbound-rtp') {
var packetsLost = report.packetsLost;
var packetsReceived = report.packetsReceived;
self.mp4ImporterUserAudioRtpPacketTemp =
packetsLost + packetsReceived;
var currProgressValue = (self
.mp4ImporterUserAudioRtpPacketTemp - self
.mp4ImporterUserAudioRtpPacketReal) * self
.mp4ImporterUserAudioRate;
// log.debug("===room.mp4ImporterUserAudioStateStart(),packetsLost:",packetsLost+",packetsReceived:"+packetsReceived+",mp4ImporterUserAudioRtpPacketReal:"+self.mp4ImporterUserAudioRtpPacketReal+",mp4ImporterUserAudioRtpPacketTemp:"+self.mp4ImporterUserAudioRtpPacketTemp+",currProgressValue:"+currProgressValue);
self.eventEmitter.emit(RoomCallback
.mediaplay_progress_notify,
currProgressValue);
}
});
});
}
} else {
self.mp4ImporterUserAudioRtpPacketTemp = 0;
self.mp4ImporterUserAudioRtpPacketReal = 0;
}
}, interval);
}
/**
* @description userAgent为avd_mp4_importer的导入用户,结束获取音频的rpc包数等值
* @ignore
*/
Room.prototype.mp4ImporterUserAudioStateStop = function() {
this.mp4ImporterUserAudioRtpPacketReal = this.mp4ImporterUserAudioRtpPacketTemp;
// log.debug("===room.mp4ImporterUserAudioStateStop(),mp4ImporterUserAudioRtpPacketReal:"+this.mp4ImporterUserAudioRtpPacketReal);
if (this.mp4ImporterUserAudioStateIntervalId) {
clearInterval(this.mp4ImporterUserAudioStateIntervalId);
this.mp4ImporterUserAudioStateIntervalId = null;
}
}
Room.prototype.openReqSend = function(websocket) {
log.debug("===room.openReqSend()...");
var deferred = when.defer();
this.masterServer = new MasterServer(websocket, this);
this.masterServer.openReqSend();
var curTimeout = setTimeout(function(){
var timeoutError = new Error(10000,'openReq send time out');
deferred.reject(timeoutError);
},5000);
this.addCallback(EngineCallback.open_rep_return_0, function() {
clearTimeout(curTimeout);
});
this.addCallback(EngineCallback.room_join_success, function(room) {
clearTimeout(curTimeout);
deferred.resolve(room);
});
this.addCallback(EngineCallback.room_join_error, function(error) {
clearTimeout(curTimeout);
deferred.reject(error);
});
return deferred.promise;
};
Room.prototype.openReqReconnectionSend = function() {
log.debug("===room.openReqReconnectionSend()...");
var deferred = when.defer();
this.masterServer.openReqReconnectionSend();
var curTimeout = setTimeout(function(){
var timeoutError = new Error(10000,'openReq Reconnection send time out');
deferred.reject(timeoutError);
},5000);
this.addCallback(EngineCallback.open_rep_return_0, function() {
clearTimeout(curTimeout);
deferred.resolve();
});
this.addCallback(EngineCallback.room_join_error, function(error) {
clearTimeout(curTimeout);
deferred.reject(error);
});
return deferred.promise;
};
/**
* @description 设置重新加会的超时时间
* @param {Number} rejoinTimeout 单位ms,默认60000(60秒),请传入大于60000的值
*/
Room.prototype.setRejoinTimeout = function(rejoinTimeout) {
if(rejoinTimeout != undefined && (typeof rejoinTimeout != 'number' || rejoinTimeout <= 60000)){
log.info('===room.setRejoinTimeout(), timeout: ' + rejoinTimeout + ' is invalid to return');
return;
}
this.rejoinTimeout = rejoinTimeout;
};
/**
* @description 设置二次重新加会之间间隔的时长
* @param {Number} rejoinInterval 单位ms,默认3000(3秒)
*/
Room.prototype.setRejoinInterval = function(rejoinInterval) {
if(rejoinInterval != undefined && (typeof rejoinInterval != 'number')){
log.info('===room.setRejoinInterval(), rejoinInterval: ' + rejoinInterval + ' is invalid to return');
return;
}
this.rejoinInterval = rejoinInterval;
};
/**
* @desc 设置音频播放设备扬声器
* @async
* @param {Object} deviceId -扬声器设备ID
* @param {Object} audioElements - audio控件对象集,可以不填,不填时自动获取页面上的所有audio控件对象
*/
Room.prototype.setSpeakerDevice = function(deviceId, audioElements) {
log.info("===room.setSpeakerDevice(),deviceId:"+ deviceId);
var deferred = when.defer();
if(!audioElements){
audioElements = document.getElementsByTagName('audio');
}
if (typeof audioElements[0].sinkId == 'undefined') {
log.error('===room.setSpeakerDevice(),Browser does not support output device selection.');
var err = new Error(ErrorConstant.set_speakerDevice_notsupport_failed);
deferred.reject(err);
return deferred.promise;
}
for (var i = 0; i < audioElements.length; i++) {
var element = audioElements[i];
element.setSinkId(deviceId).then(function (){
log.info('===room.setSpeakerDevice() Success, audio output device attached:' + deviceId +'to element with '+ element.title + 'as source.');
}).catch(function(error){
var err;
if (error.name === 'SecurityError') {
log.error('===room.setSpeakerDevice(),You need to use HTTPS for selecting audio output device');
err= new Error(ErrorConstant.set_speakerDevice_notsupport_failed);
}else{
log.error('===room.setSpeakerDevice(),Error setting audio output device, error:'+JSON.stringify(error));
err= new Error(ErrorConstant.set_speakerDevice_notsupport_failed);
}
deferred.reject(err);
return deferred.promise;
});
}
this.clientSetSpeakerDeviceId = deviceId;
deferred.resolve();
};
/**
* @desc 设置扬声器音量大小
* @async
* @param {int} volume -音量值,范围0-1,如0.5表示设置音量为50%。 注:该值只是应用层的设置值与物理设备的具体音量值无关
* @param {Object} audioElements - audio控件对象集,可以不填,不填时自动获取页面上的所有audio控件对象
*/
Room.prototype.setSpeakerVolume = function(volume, audioElements) {
log.info("===room.setSpeakerVolume(),volume:"+ volume);
var deferred = when.defer();
if(!audioElements){
audioElements = document.getElementsByTagName('audio');
}
//遍历所有的audio元素
for (var i = 0; i < audioElements.length; i++) {
var elements = audioElements[i];
elements.volume = volume;
}
this.clientSetSpeakerVolume = volume;
deferred.resolve();
}
function getAudioTrackIdByMp4ImporterUser(roomInstance) {
var trackId = null;
if (roomInstance) {
var participantsLen = roomInstance.participants.length;
if (participantsLen > 0) {
for (var i = 0; i < participantsLen; i++) {
var user = roomInstance.participants[i];
if (user.userAgent == "avd_mp4_importer") {
if (user.audio && user.audio.track) {
trackId = user.audio.track.id;
}
break;
}
}
}
}
return trackId;
}
function audioLevelHandler(context) {
var self = context;
if (!self) {
return;
}
var statsInterval = 3000; //应用层获取语音激励最大值的计算频率
clearInterval(self.statsIntervalId);
self.statsIntervalId = setInterval(
function() {
var userId2AudioLevel = {}; //用于user与所属用户的音量值的绑定,map(userId:AudioLevel)集
var maxAudioLevel = 0;
var maxAudioLevelUserId = "";
var participants = self.getParticipants();
var lastLecturer = self.getAppData(appDataKeyEnum.lecturer) ? self.getAppData(appDataKeyEnum
.lecturer) : '';
var lastLecturerNum = 0;
participants.forEach(function(user) {
if (user.audio) {
userId2AudioLevel[user.id] = user.audio.getAudioLevel();
}
});
for (var key in userId2AudioLevel) {
try {
var audioLevel = userId2AudioLevel[key];
if (key == lastLecturer) {
lastLecturerNum = audioLevel;
}
if (audioLevel > maxAudioLevel) {
maxAudioLevel = audioLevel;
maxAudioLevelUserId = key;
}
} catch (e) {
log.error(e);
}
}
if (lastLecturer != maxAudioLevelUserId && lastLecturerNum < 4 && ((maxAudioLevel -
lastLecturerNum) >= 3)) {
log.debug("===audioLevelHandler()发送更改语音激励通知,语音激励id:" + maxAudioLevelUserId + ",值为" +
maxAudioLevel);
self.updateAppData(appDataKeyEnum.lecturer, maxAudioLevelUserId);
}
}, statsInterval);
}
return Room;
});