李勇

1.修复录制回放文件匹配;2.优化日志上报

... ... @@ -63,7 +63,7 @@ export default class MessageEntrance extends Emiter {
super();
this.lastClassActiveTime=0;//最后一次课堂激活的时间戳
//sdk 信息
GlobalConfig.sdkVersion = "v2.26.9.20171107";
GlobalConfig.sdkVersion = "v2.27.11.20171109";
loger.warn("sdkVersion:" + GlobalConfig.sdkVersion);
console.log("sdkVersion:" + GlobalConfig.sdkVersion);
//设置
... ... @@ -126,6 +126,7 @@ export default class MessageEntrance extends Emiter {
_mcu.on('*', (type, data) => this._emit(type, data));
_mcu.on(MessageTypes.CLASS_JOIN_MCU_SUCCESS, this._mcuJoinMCUClassSuccessHandler.bind(this)); //加入MCU课堂完成
_mcu.on(MessageTypes.SWITCH_MCU_IP, this._switchMcuIpHandler.bind(this)); //切换MCU,重新选点
_mcu.on(MessageTypes.SOCKET_MAX_RECONNECT_FAILED, this._socketMaxReconnectFailed.bind(this)); //mcu断线重连已经达到最大次数,不再重连
//录制回放
_recordPlayback = RecordPlayBackParse;
... ... @@ -445,9 +446,16 @@ export default class MessageEntrance extends Emiter {
return;
}
this._leaveClass(_type);
this._leaveChannel();
//记录是否已经离开课堂,离开之后不做MCU重连
GlobalConfig.classExit=true;
LogManager.IS_OPEN_SEND_LOG = false;//断开之后不再上报日志
LogManager.sendLogToServer();
this._emit(MessageTypes.CLASS_EXIT, {type:6});
//2秒后停止日志上报
setTimeout(()=>{
LogManager.IS_OPEN_SEND_LOG = false;//断开之后不再上报日志
},2000);
}
//当前的课堂状态信息发生改变,需要保存课堂状态到Sass
... ... @@ -1276,6 +1284,11 @@ export default class MessageEntrance extends Emiter {
}
}
//MCU已经达到最大重连次数,不在继续重连,需要退出课堂
_socketMaxReconnectFailed(_param){
//执行离开课堂的逻辑
this._runClassExit();
}
//切换MCU ->_param->{reConnect:false} //reConnect(是否立即替换当前的ip并且重新连接)
_switchMcuIpHandler(_param) {
if (GlobalConfig.isRecordPlayBack) {
... ... @@ -1718,13 +1731,6 @@ export default class MessageEntrance extends Emiter {
console.log("已经离开课堂",_param);
return;
}
//离开视频通话频道
/* if(GlobalConfig.deviceType==0||GlobalConfig.deviceType==3){
if(_webRtc){
_webRtc.leaveChannel();
}
}*/
//停止推流
if (_video_ape) {
_video_ape.stopPublishVideo();
... ... @@ -2541,7 +2547,7 @@ export default class MessageEntrance extends Emiter {
loger.log("文档加入频道成功->isHost=", GlobalConfig.isHost, "当前总人数:", GlobalConfig.rosterNumber, "sassDoclength=", GlobalConfig.docListPrepare.length);
//如果当前课堂内只有自己或者离开上次课堂的时间大于8分钟,需要停止服务端的视频录制,设备不是H5
if(GlobalConfig.rosterNumber<=1&&interval>=(8*60)&&GlobalConfig.deviceType!=3){
if(GlobalConfig.rosterNumber<=1&&interval>=(6*60)&&GlobalConfig.deviceType!=3){
loger.log("调用服务端音视频停止录制->interval:"+interval);
this._mediaRecordControl({"status": WebRtcApe.RECORD_STATUS_2});
}
... ... @@ -3280,7 +3286,7 @@ export default class MessageEntrance extends Emiter {
if (!_params) {
return;
}
loger.log("设置appConfig");
loger.log("设置appConfig",_params);
if (GlobalConfig.appId) {
loger.log("本地已经设置appConfig,不需要再设置");
return;
... ... @@ -3294,6 +3300,7 @@ export default class MessageEntrance extends Emiter {
GlobalConfig.stopRecordingInterfaces = _params.stopRecordingInterfaces || "";
GlobalConfig.getTxRecordInfoInterfaces = _params.getTxRecordInfoInterfaces || "";
GlobalConfig.getRecordFileURLAgoInterfaces = _params.getRecordFileURLAgoInterfaces || "";
GlobalConfig.recordFileSever = _params.recordFileSever || "";
//去掉协议头
try {
... ... @@ -3313,7 +3320,10 @@ export default class MessageEntrance extends Emiter {
GlobalConfig.getRecordFileURLAgoInterfaces = GlobalConfig.getRecordFileURLAgoInterfaces.replace('http://', "");
GlobalConfig.getRecordFileURLAgoInterfaces = GlobalConfig.getRecordFileURLAgoInterfaces.replace('https://', "");
}
if (GlobalConfig.recordFileSever) {
GlobalConfig.recordFileSever = GlobalConfig.recordFileSever.replace('http://', "");
GlobalConfig.recordFileSever = GlobalConfig.recordFileSever.replace('https://', "");
}
if (GlobalConfig.stopRecordingInterfaces) {
GlobalConfig.stopRecordingInterfaces = GlobalConfig.stopRecordingInterfaces.replace('http://', "");
GlobalConfig.stopRecordingInterfaces = GlobalConfig.stopRecordingInterfaces.replace('https://', "");
... ... @@ -3325,8 +3335,6 @@ export default class MessageEntrance extends Emiter {
} catch (err) {
}
}
//录制状态控制和推流状态控制
... ...
... ... @@ -15,12 +15,13 @@
import Emiter from 'Emiter';
import Loger from 'Loger';
import GlobalConfig from 'GlobalConfig';
import LogManager from 'LogManager';
let loger = Loger.getLoger('EverSocket');
const MCU_MAX_RECONNECTION = 4;//最多重连次数
const MCU_MAX_RECONNECTION = 3;//最多重连次数
class EverSocket extends Emiter {
constructor() {
super();
this.mcuReconnectCounter=0;//(重要)记录mcu连续重连的次数,最大连续8次就不再重连
this._connected = false;
this._lastActiveTime = 0;//最后一次收到消息的时间
this._enableEverSocket = false;
... ... @@ -111,10 +112,17 @@ class EverSocket extends Emiter {
this._clear();
window.clearTimeout(this.reConnectionTimeout);
this.reConnectionCounter++;
this.mcuReconnectCounter++;
if (this.reConnectionCounter > MCU_MAX_RECONNECTION) {
loger.warn('MCU断线重连->已经达到最大重连次数!');
if( this.mcuReconnectCounter>=MCU_MAX_RECONNECTION*2){
loger.warn('MCU断线重连->已经达到最大重连次数->停止重连');
this._emit(EverSocket.ERROR, EverSocket.ERR_SOCKET_MAX_RECONNECT_FAILED);
return;
}
this._emit(EverSocket.ERROR, EverSocket.ERR_SOCKET_RECONNECT_FAILED);
this.reConnectionCounter = 0;
}
this.reConnectionTimeout = window.setTimeout(() => {
... ... @@ -175,6 +183,7 @@ class EverSocket extends Emiter {
_onOpen() {
loger.log('WebSocket建立成功', this.wsURL);
this.reConnectionCounter = 0;
this.mcuReconnectCounter=0;
//启动心跳,检查socket链接状态
this.pingTimer = window.setInterval(this._sendPingHandler.bind(this), EverSocket.PING_INTERVAL);
... ... @@ -239,7 +248,8 @@ EverSocket.prototype.PING_INTERVAL = EverSocket.PING_INTERVAL = 10000;//敹歲
EverSocket.prototype.RECONN_INTERVAL = EverSocket.RECONN_INTERVAL = 5000;//重连的间隔
EverSocket.prototype.ERR_SOCKET_RECONNECT_FAILED = EverSocket.ERR_SOCKET_RECONNECT_FAILED = 20001;//MCU自动重连失败,已经达到最大重连次数
EverSocket.prototype.ERR_SOCKET_RECONNECT_FAILED = EverSocket.ERR_SOCKET_RECONNECT_FAILED = 20001;//MCU自动重连失败,
EverSocket.prototype.ERR_SOCKET_MAX_RECONNECT_FAILED = EverSocket.ERR_SOCKET_MAX_RECONNECT_FAILED = 20002;//MCU自动重连失败,已经达到最大重连次数
EverSocket.prototype.CONNECTING = EverSocket.CONNECTING = 0;
EverSocket.prototype.OPEN = EverSocket.OPEN = 1;
... ...
... ... @@ -621,7 +621,7 @@ GlobalConfig.stopRecordingInterfaces="";//停止录制的接口
GlobalConfig.getTxRecordInfoInterfaces="";//获取媒体录制信息数据的接口(tx)
GlobalConfig.getRecordFileURLAgoInterfaces="";//获取媒体录制信息数据的接口(ago)
GlobalConfig.getChannelToken="";//获取token的地址
GlobalConfig.recordFileSever="";//录制文件路径和文件地址(ago)
GlobalConfig.videoScale=1;//视频的缩放倍数,默认1倍无缩放
export default GlobalConfig;
... ...
... ... @@ -117,6 +117,8 @@ MessageTypes.SWITCH_MCU_IP = "switch_mcu_ip"; //切换mcu 重新选点
MessageTypes.SWITCH_MS_IP = "switch_ms_ip"; //切换ms 重新选点
MessageTypes.SWITCH_RTMP_PULL_IP = "switch_rtmp_pull_ip"; //切换ms MS拉流选点
MessageTypes.SWITCH_HLS_IP = "switch_hls_ip"; //切换ms HLS拉流选点
MessageTypes.SOCKET_MAX_RECONNECT_FAILED = "socket_max_reconnect_failed"; //mcu重连次数已经达到最大次数
//录制回放
MessageTypes.RECORD_PLAYBACK_UPDATE = "record_playback_update"; //录制回放更新信息
... ...
... ... @@ -14,6 +14,7 @@ import Base64 from 'base64-js';
import GlobalConfig from 'GlobalConfig';
import EngineUtils from 'EngineUtils';
import TimerCounter from "TimerCounter";
import RecordInfoMatch from "RecordInfoMatch";
let parseBuffer;
// 日志对象
... ... @@ -53,6 +54,10 @@ class RecordPlayBackParse extends Emiter {
this.agoTiemstampMessages={};//ago推流时间戳消息数据集合
this.agoAllMedias={};//ago录制的文件集合
this.allStreams={};//记录课堂内所有的流id
this.recordInfoMatch=RecordInfoMatch;
this.recordInfoMatch.on(RecordInfoMatch.RECORD_INFO_MATCH_COMPLETE,this.onRecordInfoMatchComplete.bind(this));
}
//method--------------------内部---------------------------------------------
... ... @@ -208,6 +213,25 @@ class RecordPlayBackParse extends Emiter {
}
}
//消息和录制文件匹配完成
onRecordInfoMatchComplete(_data){
console.log("消息和录制文件匹配完成",_data);
if(_data){
for(let k in _data){
//优先使用TXY的,没有的时候再使用AGO
/* if(! MediaModule.streams[k]){
MediaModule.streams[k]=_data[k];
}else {
console.log(k+" 已经存在",MediaModule.streams[k]);
}*/
MediaModule.streams[k]=_data[k];
}
}
console.warn("最终匹配完成的视频流数据", MediaModule.streams);
//解析课堂录制的rec文件
this.parseArrayBuf();
}
//保存各个模块的MCU原始数据
saveParseData(data, timestamp, apeMessages) {
let messageItem = apeMessages[timestamp];
... ... @@ -302,7 +326,8 @@ class RecordPlayBackParse extends Emiter {
parseBuffer.clear();
parseBuffer.append(arrayBuffer);
//rec数据加载完成后,继续加载音视频相关的录制数据,然后再解析rec数据
this.getMediaRecrodInfoFromTx(()=>{
/* this.getMediaRecrodInfoFromTx(()=>{
//解析录制的rec数据
this.isLoadTxRecordInfo=true;
if(this.isLoadTxRecordInfo&&this.isLoadAgoRecordInfo&&this.isgetRecordFileURLFromAgo){
... ... @@ -325,7 +350,12 @@ class RecordPlayBackParse extends Emiter {
this.matchingVideoUrlFromTime(this.agoTiemstampMessages,this.agoAllMedias);
this.parseArrayBuf();
}
})
})*/
this.getMediaRecrodInfoFromTx(()=>{
//AGO启动录制消息和文件匹配
this.recordInfoMatch.start();
});
}
}
//时间戳转换为UTC 时间字符串
... ... @@ -402,9 +432,9 @@ class RecordPlayBackParse extends Emiter {
for(let k in finelMediaInfo){
let okItem=finelMediaInfo[k];
let seek=(parseInt(noItem.timestamp)-parseInt(okItem.timestamp))/1000;
console.log("seek",seek);
//console.log("seek",seek);
if(noItem.uid==okItem.uid&&seek<15&&noItem.timestamp!=okItem.timestamp){
console.log(noItem,okItem);
//console.log(noItem,okItem);
noItem.video_url=okItem.video_url;
noItem.seek=seek;
finelMediaInfo [noItem.timestamp]=noItem;
... ... @@ -505,7 +535,7 @@ class RecordPlayBackParse extends Emiter {
}
})
.then(ret => {
loger.log('AG-获取媒体录制信息-完成',ret);
loger.log('AG-获取媒体录制信息-完成');
//console.log("getRecordInfo success",ret);
if(ret&&ret.returnData&&ret.returnData.data){
let dataArr=ret.returnData.data;
... ... @@ -564,7 +594,7 @@ class RecordPlayBackParse extends Emiter {
}
})
.then(ret => {
loger.log('TX-获取媒体录制信息-完成',ret);
loger.log('TX-获取媒体录制信息-完成');
/* {"code": 200,
"returnData":{
"data":[
... ... @@ -758,6 +788,14 @@ class RecordPlayBackParse extends Emiter {
return {"code": ApeConsts.RETURN_FAILED, "data": "录制回放还未准备完成"};
}
loger.log("classStatusInfo",GlobalConfig.classStatusInfo);
console.log("所有流ID",this.allStreams);
for(let i in this.allStreams){
if(MediaModule.streams[i]){
console.log("匹配成功的流:"+i,MediaModule.streams[i]);
}else {
console.warn("未匹配成功的流:"+i);
}
}
this._startTimerCounter();
this._emit(MessageTypes.RECORD_PLAYBACK_UPDATE, {"status": PLAY});
}
... ... @@ -1159,6 +1197,8 @@ class RecordPlayBackParse extends Emiter {
let statusStr="关";
if(videoChannelInfo&&videoChannelInfo.status==1){
statusStr="开";
this.allStreams[videoChannelInfo.streamId]=videoChannelInfo;
console.log("视频流"+videoChannelInfo.streamId);
}
this.mediaChannleList[videoChannelInfo.channelId][timestamp]={parseData:videoChannelInfo,byteData:data,timestamp: timestamp,status:statusStr};
} catch (err) {
... ...
... ... @@ -467,7 +467,7 @@ class AudioApe extends Ape {
}
MediaModule.allMediaChannelsList[itemIdx] = unpackChannelInfo;
console.log('MediaModule.allMediaChannelsList', MediaModule.allMediaChannelsList);
//console.log('MediaModule.allMediaChannelsList', MediaModule.allMediaChannelsList);
this._emit(MessageTypes.AUDIO_UPDATE, unpackChannelInfo);
}
... ...
... ... @@ -28,7 +28,7 @@ class ConferApe extends Ape {
this.rosterLen = 0;//当前课堂人数
this.timerCounter = new TimerCounter(); //计时器
this.startRecordTimer;//开始录制的计时器
this.recordStatus=false;//记录录制状态
//第三方消息控制 parent和Iframe直接的通讯
this.thirdMessage = new ThirdMessage();
this.thirdMessage.on(ThirdMessage.RECIVE_MESSAGE, this.onThirdReciveParentMessage.bind(this));
... ... @@ -245,6 +245,10 @@ class ConferApe extends Ape {
optional string filename = 4; // 录像文件名称,filename中增加目录部分
}*/
if(this.recordStatus==conferRecordSendPdu.record){
//已经有录制文件,不再发送
return;
}
//保存当前的录制状态
GlobalConfig.recordStatus = _param.recordStatus || false;
let conferRecordSendPdu = new pdu['RCConferenceRecordRequestPdu'];
... ... @@ -296,12 +300,12 @@ class ConferApe extends Ape {
checkHasRecordControl() {
//loger.warn('检测是否有控制录制操作的权限', "isHost", GlobalConfig.isHost, "recordStatus", GlobalConfig.recordStatus,"当前人数:"+this.rosterLen);
//1.如果自己是老师或者当前课堂只有一个人
if (GlobalConfig.isHost || this.rosterLen <= 1) {
if (GlobalConfig.isHost || this.rosterLen <= 1&&GlobalConfig.deviceType!=3) {
return true;
}
//2.如果自己不是老师,需要判断当前课堂内是否有老师,如果有老师就不做操作
for (let i in this.rosters) {
//如果老师就停止
//如果老师就停止
let rosterItem = this.rosters[i];
if (rosterItem && rosterItem.userRole == ApeConsts.host) {
return false;
... ... @@ -912,7 +916,12 @@ class ConferApe extends Ape {
let conferRecordSendPdu = pdu['RCConferenceRecordRequestPdu'].decode(_data);
//{"initiator":564398684,"record":true,"classTime":39,"filename":"markettest/20170823/1096250804_20170823.rec"}//开启成功
//{"initiator":564398684,"record":false,"classTime":39,"filename":"markettest/20170823/1096250804_20170823.rec"} //停止成功
if(this.recordStatus==conferRecordSendPdu.record){
//已经开启过录制之后就不再处理
return;
}
loger.warn("录制回放控制操作成功->", conferRecordSendPdu);
this.recordStatus=conferRecordSendPdu.record;
if (conferRecordSendPdu) {
if (conferRecordSendPdu.record == true || conferRecordSendPdu.record == "true") {
//每次开启录制的时候,需要把当前显示的文档数据更新一次,否则无法录制已经显示的文件
... ... @@ -1006,7 +1015,10 @@ class ConferApe extends Ape {
this.rosterLen = Object.keys(this.rosters).length;
GlobalConfig.rosterNumber = this.rosterLen;//记录当前的总人数
newNodeData.rosterLen = this.rosterLen;
loger.log("人员加入->", newNodeData);
if(GlobalConfig.classType!= ApeConsts.CLASS_TYPE_2){
loger.log("人员加入->", newNodeData);
}
this._emit(MessageTypes.CLASS_INSERT_ROSTER, {"nodeId": nodeId, "nodeData": newNodeData});
this.emitRosterChange();
} else {
... ... @@ -1015,7 +1027,9 @@ class ConferApe extends Ape {
GlobalConfig.rosterNumber = this.rosterLen;//记录当前的总人数
newNodeData.rosterLen = this.rosterLen;
if (nodeId != GlobalConfig.nodeId) {
loger.log("人员更新信息->", newNodeData);
if(GlobalConfig.classType!= ApeConsts.CLASS_TYPE_2){
loger.log("人员更新信息->", newNodeData);
}
}
this._emit(MessageTypes.CLASS_UPDATE_ROSTER, {"nodeId": nodeId, "nodeData": newNodeData});
}
... ... @@ -1073,7 +1087,10 @@ class ConferApe extends Ape {
} else {
let user = this.rosters[nodeId];
if (user) {
loger.log(nodeId, "->离开课堂->身份->", user.userRole);
if(GlobalConfig.classType!= ApeConsts.CLASS_TYPE_2){
loger.log(nodeId, "->离开课堂->身份->", user.userRole);
}
}
delete this.rosters[nodeId];
GlobalConfig.rosters = this.rosters;
... ...
... ... @@ -403,7 +403,7 @@ class DocApe extends Ape {
let docDataModel = this.docList[paramInfo.itemIdx];
if (docDataModel == null) {
loger.warn('切换文档失败,文档不存在', paramInfo);
this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_APE_INTERFACE_PARAM_WRONG);
// this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_APE_INTERFACE_PARAM_WRONG);
return;
}
... ...
... ... @@ -760,7 +760,7 @@ class VideoApe extends Ape {
) {
//非屏幕共享情况的处理
MediaModule.allMediaChannelsList[itemIdx] = unpackChannelInfo;
console.log('MediaModule.allMediaChannelsList', MediaModule.allMediaChannelsList);
//console.log('MediaModule.allMediaChannelsList', MediaModule.allMediaChannelsList);
this._emit(MessageTypes.VIDEO_UPDATE, unpackChannelInfo);
}
... ...
... ... @@ -828,11 +828,12 @@ class WebRtcApe extends Emiter {
//status:0 停止推流 1:开始推流(同时开启录制),2:停止录制(同时停止推流)
changePublishStatusAndServerRecord(_status) {
if (!GlobalConfig.recordInterfaces) {
loger.log("调用服务器端开启录制->失败->接口地址无效");
loger.log("调用服务器端更新视频录制状态->失败->接口地址无效");
return;
}
let url = GlobalConfig.locationProtocol + GlobalConfig.recordInterfaces;
let data = this.packMediaInfoData(_status);
loger.log("调用服务器端更新视频录制状态->status",_status);
fetch(encodeURI(url), {
method: 'POST',
headers: {
... ... @@ -845,19 +846,19 @@ class WebRtcApe extends Emiter {
if (ret.ok) {
return ret.json();
} else {
loger.error(`调用服务器端开启录制-网络异常.状态码:${ret.status}`);
loger.error(`调用服务器端更新视频录制状态-网络异常.状态码:${ret.status}`);
throw '';
}
})
.then(ret => {
if (ret) {
loger.log('调用服务器端开启录制完成', ret);
loger.log('调用服务器端更新视频录制状态', ret);
} else {
loger.warn('调用服务器端开启录制 失败.', ret);
loger.warn('调用服务器端更新视频录制状态 失败.', ret);
}
})
.catch(err => {
loger.error(`调用服务器端开启录制.状态码:${err}`);
loger.error(`调用服务器端更新视频录制状态.状态码:${err}`);
});
}
... ...
... ... @@ -55,6 +55,18 @@ class MCU extends Emiter {
if (_errorCode == everSocket.ERR_SOCKET_RECONNECT_FAILED) {
this._emit(MessageTypes.SWITCH_MCU_IP);
}
switch(_errorCode){
case everSocket.ERR_SOCKET_MAX_RECONNECT_FAILED:
//mcu已经达到最大重连次数,不再重连
this._emit(MessageTypes.SOCKET_MAX_RECONNECT_FAILED);
break;
case everSocket.ERR_SOCKET_RECONNECT_FAILED:
//重连失败,尝试更换MCU再重连
this._emit(MessageTypes.SWITCH_MCU_IP);
break;
default :
break;
}
}
//MCU-发送加入课堂请求
... ...