李勇

修改录制的逻辑;录制的时间和课堂时间完全区分开;课堂的状态控制和录制也区分开;课堂内进入的第一个人有权限开启录制;录制的时间戳每4秒想SAAS保存一次;MCU10同步一次

!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.iphunter=t():e.iphunter=t()}(this,function(){return function(e){function t(i){if(n[i])return n[i].exports;var o=n[i]={exports:{},id:i,loaded:!1};return e[i].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:3e3;if(!(e&&e.length&&t))throw new Error("ips and callback are required.");new r(e,t,n)}Object.defineProperty(t,"__esModule",{value:!0});var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}();t.default=i;var r=function(){function e(t,i,o){n(this,e),this.version="v2.0.1.20170819",this.ip="",this.ipcallback=i,this.timeoutId=null,this.reqsCache=[];for(var r=0;r<t.length;r++)this.reqsCache.push(this.send(t[r],o-10));this.timeoutId=setTimeout(this.notify.bind(this),o)}return o(e,[{key:"clearAll",value:function(){this.reqsCache&&this.reqsCache.length&&this.reqsCache.forEach(function(e){e.abort()}),clearTimeout(this.timeoutId),this.ip="",this.ipcallback=null,this.timeoutId=null,this.reqsCache=[]}},{key:"clearReq",value:function(e){this.reqsCache.splice(this.reqsCache.indexOf(e),1)}},{key:"notify",value:function(){this.ipcallback&&this.ipcallback(this.ip),this.clearAll()}},{key:"send",value:function(e,t){var n=this,i=new XMLHttpRequest;return i.open("HEAD","//"+e+"/?_="+Date.now()),i.timeout=t,i.onload=function(){n.ip=e,n.clearReq(i),i.onload=null,n.notify()},i.ontimeout=function(){n.clearReq(i),i.ontimeout=null},i.onerror=function(){n.clearReq(i),i.onerror=null},i.onabort=function(){n.clearReq(i),i.onabort=null},i.send(),i}}]),e}();(function(){"undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(r,"IpHunter","D:/work/McuClient/node_modules/iphunter/src/main.js"),__REACT_HOT_LOADER__.register(i,"check","D:/work/McuClient/node_modules/iphunter/src/main.js"))})()}])});
\ No newline at end of file
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.iphunter=t():e.iphunter=t()}(this,function(){return function(e){function t(i){if(n[i])return n[i].exports;var o=n[i]={exports:{},id:i,loaded:!1};return e[i].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:3e3;if(!(e&&e.length&&t))throw new Error("ips and callback are required.");new r(e,t,n)}Object.defineProperty(t,"__esModule",{value:!0});var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}();t.default=i;var r=function(){function e(t,i,o){n(this,e),this.version="v2.0.1.20170819",this.ip="",this.ipcallback=i,this.timeoutId=null,this.reqsCache=[];for(var r=0;r<t.length;r++)this.reqsCache.push(this.send(t[r],o));this.timeoutId=setTimeout(this.notify.bind(this),o)}return o(e,[{key:"clearAll",value:function(){this.reqsCache&&this.reqsCache.length&&this.reqsCache.forEach(function(e){e.abort()}),clearTimeout(this.timeoutId),this.ip="",this.ipcallback=null,this.timeoutId=null,this.reqsCache=[]}},{key:"clearReq",value:function(e){this.reqsCache.splice(this.reqsCache.indexOf(e),1)}},{key:"notify",value:function(){this.ipcallback&&this.ipcallback(this.ip),this.clearAll()}},{key:"send",value:function(e,t){var n=this,i=new XMLHttpRequest;return i.open("HEAD","//"+e+"/?_="+Date.now()),i.timeout=t,i.onload=function(){n.ip=e,n.clearReq(i),i.onload=null,n.notify()},i.ontimeout=function(){n.clearReq(i),i.ontimeout=null},i.onerror=function(){n.clearReq(i),i.onerror=null},i.onabort=function(){n.clearReq(i),i.onabort=null},i.send(),i}}]),e}();(function(){"undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(r,"IpHunter","D:/work/McuClient/node_modules/iphunter/src/main.js"),__REACT_HOT_LOADER__.register(i,"check","D:/work/McuClient/node_modules/iphunter/src/main.js"))})()}])});
\ No newline at end of file
... ...
... ... @@ -7,7 +7,8 @@ class IpHunter {
this.reqsCache = [];
for (let i = 0; i < ips.length; i++) {
this.reqsCache.push(this.send(ips[i], timeout - 10));
//this.reqsCache.push(this.send(ips[i], timeout - 10));
this.reqsCache.push(this.send(ips[i], timeout));
}
this.timeoutId = setTimeout(this.notify.bind(this), timeout);
}
... ...
... ... @@ -58,7 +58,7 @@ export default class MessageEntrance extends Emiter {
constructor() {
super();
//sdk 信息
GlobalConfig.sdkVersion = "v1.81.19.20170828";
GlobalConfig.sdkVersion = "v1.82.11.20170829";
loger.warn("sdkVersion:" + GlobalConfig.sdkVersion);
//设置
... ... @@ -87,7 +87,7 @@ export default class MessageEntrance extends Emiter {
this.isGetFastestRtmpPullCallback = false; //是否RTMP拉流地址测试结束
this.isGetFastestHlsPullCallback = false; //是否HLS拉流地址测试结束
this.isGetFastestRsCallback = false; //是否录制回放HLS拉流地址测试结束
this.saveClassStatusTimer=0;//保存课堂数据的计时器间隔,防止同一瞬间多次提交
//全局的Error处理
this.on(MessageTypes.MCU_ERROR, this._mcuErrorHandler.bind(this));
... ... @@ -420,21 +420,19 @@ export default class MessageEntrance extends Emiter {
//开启录制成功
_onClassRecordSuccess(_param) {
clearTimeout(this.classRecordStatusUpdateTimer);
let _this = this;
this.classRecordStatusUpdateTimer = setTimeout(function () {
this.classRecordStatusUpdateTimer = setTimeout(()=> {
clearTimeout(this.classRecordStatusUpdateTimer);
_this.updaterRecordAllApeStatus(_param);
this.updaterRecordAllApeStatus(_param);
}, 1600);
}
//录制状态发送改变,更新所有模块的当前数据发送到MCU
updaterRecordAllApeStatus(_param) {
if(GlobalConfig.isRecordPlayBack){
if(GlobalConfig.isRecordPlayBack||!_confer_ape){
return;
}
//老师身份和非录制回放的时候执行,录制状态发送改变,需要更新当前的数据,否则已有的消息会录制不上
if (GlobalConfig.isHost||GlobalConfig.rosterNumber<=1) {
if (_confer_ape.checkHasRecordControl()) {
loger.warn('录制状态发送改变->更新所有模块的当前数据发送到MCU');
//目前录制的模块[文档模块、白板模块、视频模块(包含屏幕共享)、音频模块、媒体共享,聊天模块]
if (_doc_ape) {
... ... @@ -452,6 +450,9 @@ export default class MessageEntrance extends Emiter {
if (_mediaShareApe) {
_mediaShareApe.updaterRecordApeStatus();
}
if (_musicShareApe) {
_musicShareApe.updaterRecordApeStatus();
}
//聊天模块不需要更新
}
}
... ... @@ -825,11 +826,11 @@ export default class MessageEntrance extends Emiter {
loger.warn("课堂最终使用的服务列表->来自本地Server.json");
}
loger.warn("mcuListFinal", GlobalConfig.mcuListFinal);
loger.warn("msListFinal", GlobalConfig.msListFinal);
loger.warn("rtmpPullListFinal", GlobalConfig.rtmpPullListFinal);
loger.warn("hlsListFinal", GlobalConfig.hlsPullListFinal);
loger.warn("rsListFinal", GlobalConfig.rsPullListFinal);
loger.warn(" MCU-List", GlobalConfig.mcuListFinal);
loger.warn(" MS-List", GlobalConfig.msListFinal);
loger.warn(" RTMP-List", GlobalConfig.rtmpPullListFinal);
loger.warn(" HLS-List", GlobalConfig.hlsPullListFinal);
loger.warn(" RS-List", GlobalConfig.rsPullListFinal);
}
//从Sass中选择的mcu、ms列表
... ... @@ -876,7 +877,6 @@ export default class MessageEntrance extends Emiter {
GlobalConfig.isp,
ServerConfig.serverList);
}
loger.warn("Sass中获取的服务器信息;");
//拉流地址列表的特殊处理,
// 1.如果RTMP拉流地址没有配置,就默认使用MS推流地址列表
... ... @@ -889,11 +889,11 @@ export default class MessageEntrance extends Emiter {
}
loger.warn("课堂最终使用的服务列表->来自Sass");
loger.warn(" GlobalConfig.mcuListFinal", GlobalConfig.mcuListFinal);
loger.warn(" GlobalConfig.msListFinal", GlobalConfig.msListFinal);
loger.warn(" GlobalConfig.rtmpPullListFinal", GlobalConfig.rtmpPullListFinal);
loger.warn(" GlobalConfig.hlsListFinal", GlobalConfig.hlsPullListFinal);
loger.warn(" GlobalConfig.rsListFinal", GlobalConfig.rsPullListFinal);
loger.warn(" MCU-List", GlobalConfig.mcuListFinal);
loger.warn(" MS-List", GlobalConfig.msListFinal);
loger.warn(" RTMP-List", GlobalConfig.rtmpPullListFinal);
loger.warn(" HLS-List", GlobalConfig.hlsPullListFinal);
loger.warn(" RS-List", GlobalConfig.rsPullListFinal);
}
... ... @@ -955,54 +955,37 @@ export default class MessageEntrance extends Emiter {
//保存课堂状态信息
_sassSaveClassStatusInfo(_param) {
if (!_mcu||!_mcu.connected) {
loger.warn("不能保存课堂数据->MCU已经断开");
return ;
}
if(!_confer_ape){
return;
}
//{isForce:true} isForce->是否强制提交(true为是)
//这个是特殊权限
let isForce = false;
if (_param && _param.isForce == true) {
isForce = true;
}
/* if (GlobalConfig.isHost || isForce) {
//只有加入课堂之后才能保存数据
if (GlobalConfig.getCurrentStatus().code == GlobalConfig.statusCode_2.code) {
//POST 保存数据
_sass.saveClassStatusInfo({"classStatusInfo": GlobalConfig.classStatusInfo}); //保存课堂状态信息
} else {
loger.error("不能保存课堂数据", GlobalConfig.getCurrentStatus());
}
} else {
loger.log("没有保存课堂状态信息的权限->身份", GlobalConfig.userRole);
}*/
if (GlobalConfig.isHost || isForce||GlobalConfig.rosterNumber<=1) {
//只有加入课堂之后才能保存数据
/*if (GlobalConfig.getCurrentStatus().code == GlobalConfig.statusCode_2.code) {
//POST 保存数据
_sass.saveClassStatusInfo({"classStatusInfo": GlobalConfig.classStatusInfo}); //保存课堂状态信息
} else {
loger.error("不能保存课堂数据", GlobalConfig.getCurrentStatus());
}*/
if (_mcu&&_mcu.connected) {
if (_confer_ape.checkHasRecordControl()||isForce) {
//POST 保存数据
_sass.saveClassStatusInfo({"classStatusInfo": GlobalConfig.classStatusInfo}); //保存课堂状态信息
} else {
loger.error("不能保存课堂数据", GlobalConfig.getCurrentStatus());
}
clearTimeout(this.saveClassStatusTimer);
this.saveClassStatusTimer=setTimeout(()=>{
_sass.saveClassStatusInfo({"classStatusInfo": GlobalConfig.classStatusInfo}); //保存课堂状态信息
},600);
} else {
loger.log("没有保存课堂状态信息的权限->身份", GlobalConfig.userRole);
loger.log("没有保存课堂状态信息的权限->当前身份->"+GlobalConfig.userRole);
}
}
//保存会态信息成功
_sassSaveClassStatusInfoSuccessHandler(_data) {
loger.log('保存课堂状态信息成功.');
loger.log(_data);
//loger.log('Saas保存课堂状态信息成功');
}
_sassSaveClassRecordInfoSuccessHandler(_data) {
loger.log('保存课堂录制信息成功.');
loger.log(_data);
//loger.log('Saas保存课堂录制信息成功',_data);
}
//Sass校验流程结束之后,开始加入MCU
... ... @@ -1208,11 +1191,22 @@ export default class MessageEntrance extends Emiter {
GlobalConfig.MS_PUBLISH_IP = _param.ip || "";
GlobalConfig.MS_PUBLISH_PORT = _param.port || "";
}
GlobalConfig.MS_PLAY_RTMP_IP = GlobalConfig.MS_PUBLISH_IP;
GlobalConfig.MS_PLAY_RTMP_PORT =GlobalConfig.MS_PUBLISH_PORT;
loger.warn('手动切换MS->', GlobalConfig.MS_PUBLISH_IP + ":" + GlobalConfig.MS_PUBLISH_PORT);
loger.warn('手动切换RTMP->', GlobalConfig.MS_PLAY_RTMP_IP + ":" + GlobalConfig.MS_PLAY_RTMP_PORT);
//更换完用户当前的MS地址,需要更新用户数据
if (_confer_ape) {
_confer_ape.updateUserInfo();
}
//音视频模块对当前正在播放的流进行更换MS
if(_video_ape){
_video_ape.changeMediaMs();
}
if(_audio_ape){
_audio_ape.changeMediaMs();
}
}
//切换MS ->_param->{reConnect:false} //reConnect(是否立即替换当前的ip并且重新连接)
... ... @@ -1381,13 +1375,10 @@ export default class MessageEntrance extends Emiter {
return;
}
if (_confer_ape) {
//开始录制
setTimeout(()=> {
_confer_ape.startRecord();
},2000);
//开始上课
_confer_ape.startClass(_param);
////开始录制
//_confer_ape.startRecord();
}
}
... ... @@ -2082,7 +2073,7 @@ export default class MessageEntrance extends Emiter {
//文档加入频道成功,同步到MCU服务器上的数据
docJoinChannelSuccess() {
loger.log("docJoinChannelSuccess->isHost=", GlobalConfig.isHost, "当前总人数:", GlobalConfig.rosterNumber, "sassDoclength=", GlobalConfig.docListPrepare.length);
loger.log("文档加入频道成功->isHost=", GlobalConfig.isHost, "当前总人数:", GlobalConfig.rosterNumber, "sassDoclength=", GlobalConfig.docListPrepare.length);
//loger.log("docJoinChannelSuccess docListPrepare=");
//如果是主持人,那么需要判断一下文档模块同步的数据和从sass获取的文档数据是否相同,如果mcu服务器不存在的,需要上传
if (GlobalConfig.docListPrepare && GlobalConfig.docListPrepare.length > 0) {
... ...
... ... @@ -100,9 +100,9 @@ class GlobalConfig {
}
//设置当前的课堂状态的信息
static setClassStatusInfo(_data) {
loger.log("setClassStatusInfo");
//loger.log("setClassStatusInfo");
if (_data == null) {
loger.warn("classStatusInfo error,_data:", _data);
loger.warn("设置当前的课堂状态的信失败:", _data);
return;
}
let data = _data;
... ... @@ -137,7 +137,7 @@ class GlobalConfig {
//this.recordStatus = data.recordStatus || this.recordStatus; //当前录制状态
this.recordStatus = data.recordStatus ||false; //当前录制状态
this.recordTimestamp = data.recordTimestamp || this.recordTimestamp; //相对于首次开始录制的时间戳
this.recordTimestamp =Math.max(parseInt(data.recordTimestamp), this.recordTimestamp); //录制时间取最大值
this.recordFileName = data.recordFileName || this.recordFileName; //录制的文件名
this.recordDownloadUrl = data.recordDownloadUrl || this.recordDownloadUrl; //下载地址
this.currentSceneTableId = data.currentSceneTableId || 0; //文档区域的模块显示
... ... @@ -523,7 +523,8 @@ GlobalConfig.recordReplaytickValues = {}; // 滚动条关键点,用于快进
GlobalConfig.isAutoStartClass = 0; //是否自动开始上课 0-否 ;1 是
GlobalConfig.updateClassInfoDelay = 20; //(秒),每隔30秒同步一次课堂状态的并保存到Sass
GlobalConfig.updateRecordTimeDelay = 4; //(秒),同步一次课堂录制状态的并保存到Sass
GlobalConfig.updateClassInfoDelay = 10; //(秒),同步一次课堂状态的并保存到Sass
GlobalConfig.msDynamicChooseIpDelay = 60 * 3; //(秒)MS动态选点的间隔
//GlobalConfig.serverTimestamp=0;//当前的系统时间戳 用get set 获取
... ...
... ... @@ -404,7 +404,7 @@ class IpManager extends Emiter {
}
//isp province都没有,使用default
let defaultData = countryData.default
let defaultData = countryData.default;
if (defaultData) {
// loger.log("_returnServerMS->defaultData",defaultData);
return defaultData;
... ...
... ... @@ -148,7 +148,7 @@ class LogManager {
})
.then(ret => {
if (ret == 0) {
console.log('保存日志信息 完成');
//console.log('保存日志信息 完成');
tempArr=[];
} else {
console.warn('保存日志信息 失败.', ret);
... ...
... ... @@ -440,7 +440,7 @@ class Sass extends Emiter {
//let url = `http://${GlobalConfig.portal}/3m/api/meeting/saveInfo.do`;
let url = `${GlobalConfig.locationProtocol+GlobalConfig.portal}/3m/api/meeting/saveInfo.do`;
loger.log('saveClassStatusInfo', url);
//loger.log('saveClassStatusInfo', url);
fetch(url, {
method: 'POST',
headers: {
... ... @@ -460,7 +460,7 @@ class Sass extends Emiter {
})
.then(ret => {
if (ret.code === 0) {
loger.log('saveClassStatusInfo 完成');
//loger.log('saveClassStatusInfo 完成');
this._emit(Sass.CLASS_SAVE_STATUS_INFO_SUCCESS, _param);
} else if (ret.code === 1) {
loger.log('saveClassStatusInfo 失败 课堂号为空');
... ... @@ -485,7 +485,7 @@ class Sass extends Emiter {
loger.log('录制回放中,不需要保存');
return;
}
loger.log('保存开始录制信息');
// loger.log('保存开始录制信息');
let key = "3mang123A";
let siteID = GlobalConfig.siteId;
let meetingID = GlobalConfig.classId;
... ... @@ -499,14 +499,14 @@ class Sass extends Emiter {
let confRecordFileName = GlobalConfig.recordFileName;
let downloadUrl = "";
let recordStatus = GlobalConfig.classStatus;
let recordTimestamp = GlobalConfig.classTimestamp;
let recordTimestamp = GlobalConfig.recordTimestamp;//GlobalConfig.classTimestamp;//使用录制时间,不再使用课堂的进行时间
let timestamp = new Date().getTime();
let authId = MD5(key + siteID + meetingID + timestamp);
//let url = `http://${GlobalConfig.portal}/3m/recordingMeeting/insertRecordingMeeting.do`;
let url = `${GlobalConfig.locationProtocol+GlobalConfig.portal}/3m/recordingMeeting/insertRecordingMeeting.do`;
loger.log('saveClassRecordContrlInfo', url);
loger.log('保存开始录制信息:', url);
fetch(encodeURI(url), {
method: 'POST',
... ...
... ... @@ -25,6 +25,8 @@ ApeConsts.CLASS_ACTION_CLOSE_ALL = 1; //所有人关闭课堂
ApeConsts.CLASS_ACTION_HANDUP_STATUS_CHANGE = 2; //更改用户的举手状态
ApeConsts.USER_ACTION_SILENCE_STATUS_CHANGE = 3; //更改用户的禁言状态
ApeConsts.CLASS_ACTION_KICK_OUT_ROSTER=4; //指定的人踢出课堂
ApeConsts.STOP_ALL_PUBLISH=5; //所有人停止推流
//课堂类型 1:1v1(2路流) 2:直播(1路流) 3:小班课(可以多路流)
ApeConsts.CLASS_TYPE_1 = 1; // 互动课堂,通过MS转发音视频,不能进行H5观看 1v1(2路流)
... ...
... ... @@ -384,7 +384,10 @@ class AudioApe extends Ape {
this._emit(MessageTypes.AUDIO_BROADCAST, audioReceivePdu);
}
}
//切换了MS,重新更新一下当前正在播放的流地址
changeMediaMs(){
}
tableUpdateHandler(owner, itemIdx, itemData, seek) {
let unpackChannelInfo = this.unPackPdu(owner, itemIdx, itemData);
loger.log("tableUpdateHandler->channel", itemIdx, 'status->', unpackChannelInfo.status, "seek->", seek);
... ... @@ -479,6 +482,10 @@ class AudioApe extends Ape {
} else {
channelInfo.owner = channelInfo.fromNodeId;
}
//owner为0就是没有使用
if(channelInfo.owner==0){
channelInfo.status = ApeConsts.CHANNEL_STATUS_RELEASED
}
this.sendTableUpdateHandler(channelInfo);
}
}
... ...
... ... @@ -27,7 +27,7 @@ class ConferApe extends Ape {
this.rosters = {}; //用户列表
this.rosterLen = 0;//当前课堂人数
this.timerCounter = new TimerCounter(); //计时器
this.startClassTimer;//开始课堂和开始录制的计时器
this.startRecordTimer;//开始录制的计时器
//第三方消息控制 parent和Iframe直接的通讯
this.thirdMessage = new ThirdMessage();
... ... @@ -271,50 +271,64 @@ class ConferApe extends Ape {
loger.warn('不能再录制,录制时间已经达到最大限制', GlobalConfig.recordTimestamp);
return;
}
loger.warn('检测是否需要开启录制', "isHost", GlobalConfig.isHost, "recordStatus", GlobalConfig.recordStatus,"当前人数:"+this.rosterLen);
//如果已经开始录制就不再开启
if(GlobalConfig.recordStatus&&this.rosterLen>1){
loger.warn('目前已经是录制状态->当前课堂人数:'+this.rosterLen);
return false;
}
//如果是host或者当前课堂只有1个人
if (GlobalConfig.isHost||this.rosterLen<=1) {
/* if(GlobalConfig.recordStatus){
loger.warn("课堂已经是录制状态不->不需要再开启,isHost", GlobalConfig.isHost, "recordStatus", GlobalConfig.recordStatus,"当前人数:"+this.rosterLen);
}else{
loger.warn('开启录制', "isHost", GlobalConfig.isHost, "recordStatus", GlobalConfig.recordStatus,"当前人数:"+this.rosterLen);
GlobalConfig.classStopTime = EngineUtils.creatTimestampStr();
this.sendConferRecordMsg({"recordStatus": true});
this._emit(MessageTypes.CLASS_STATUS_INFO_CHANGE);
this._emit(MessageTypes.CLASS_RECORD_START); //课堂开始录制
}*/
if (this.checkHasRecordControl()) {
loger.warn('开启录制', "isHost", GlobalConfig.isHost, "recordStatus", GlobalConfig.recordStatus,"当前人数:"+this.rosterLen);
GlobalConfig.classStopTime = EngineUtils.creatTimestampStr();
//如果录制的文件名不存在,需要创建一个名字
let timestampYMD = EngineUtils.creatTimestampYMD();
if(!GlobalConfig.recordFileName){
GlobalConfig.recordFileName=GlobalConfig.siteId + "/" + timestampYMD + "/" + GlobalConfig.classId + "_" + timestampYMD + ".rec"; //4、文件名称 $RECORD_HOME/`site id`/`日期`/`filename` 例:/data/record/su/20161216/`filename`
}
GlobalConfig.classStopTime = EngineUtils.creatTimestampStr();
this.sendConferRecordMsg({"recordStatus": true});
this._emit(MessageTypes.CLASS_STATUS_INFO_CHANGE);
this._emit(MessageTypes.CLASS_RECORD_START); //课堂开始录制
}
}
//检测是否有控制录制操作的权限
checkHasRecordControl(){
//loger.warn('检测是否有控制录制操作的权限', "isHost", GlobalConfig.isHost, "recordStatus", GlobalConfig.recordStatus,"当前人数:"+this.rosterLen);
//1.如果自己是老师或者当前课堂只有一个人
if(GlobalConfig.isHost||this.rosterLen<=1){
return true;
}
//2.如果自己不是老师,需要判断当前课堂内是否有老师,如果有老师就不做操作
for(let i in this.rosters){
//如果就老师就停止
let rosterItem=this.rosters[i];
if(rosterItem&&rosterItem.userRole==ApeConsts.host){
return false;
}
}
//3.课堂内有多个人并且都不是老师,选择一个nodeId最小的来操作
for(let k in this.rosters){
//如果选择的nodeId是自己就有权限,否则没有权限
if(k==GlobalConfig.nodeId){
return true;
}else {
return false;
}
}
return false
}
//停止录制
stopRecord(isForce) {
/* if (isForce && isForce == true) {
//强制停止,可以是host之外的身份(比如当前课堂老师异常退出,没有老师,会随机选择一个人来做释放操作)
if (GlobalConfig.recordStatus) {
loger.warn('停止录制', "isHost", GlobalConfig.isHost, "recordStatus", GlobalConfig.recordStatus);
//判断是否有权限
if (this.checkHasRecordControl()){
loger.warn('停止录制', "isHost", GlobalConfig.isHost, "recordStatus", GlobalConfig.recordStatus);
//this.sendUpdaterClassStatusInfo({"actionType":GlobalConfig.classStatus, isStopAllPublishMedia: true});
this.sendConferMsg({"to": 0, "message": "STOP_ALL_PUBLISH", "actionType": ApeConsts.STOP_ALL_PUBLISH});
setTimeout(()=>{
GlobalConfig.classStopTime = EngineUtils.creatTimestampStr();
this.sendConferRecordMsg({"recordStatus": false});
this._emit(MessageTypes.CLASS_STATUS_INFO_CHANGE);
}
} else {
//身份是host,并且当前正在录制中
if (GlobalConfig.isHost && GlobalConfig.recordStatus) {
GlobalConfig.classStopTime = EngineUtils.creatTimestampStr();
this.sendConferRecordMsg({"recordStatus": false});
this._emit(MessageTypes.CLASS_STATUS_INFO_CHANGE);
}
}*/
//身份是host
if (GlobalConfig.isHost) {
loger.warn('停止录制', "isHost", GlobalConfig.isHost, "recordStatus", GlobalConfig.recordStatus);
GlobalConfig.classStopTime = EngineUtils.creatTimestampStr();
this.sendConferRecordMsg({"recordStatus": false});
this._emit(MessageTypes.CLASS_STATUS_INFO_CHANGE);
},2000);
}else {
loger.warn('没有权限停止录制', "isHost", GlobalConfig.isHost, "recordStatus", GlobalConfig.recordStatus);
}
... ... @@ -363,7 +377,6 @@ class ConferApe extends Ape {
this._emit(MessageTypes.CLASS_STATUS_INFO_CHANGE);
//this.sendUpdaterClassStatusInfo({"actionType": 0, isStopAllPublishMedia: true});
this.sendUpdaterClassStatusInfo({"actionType": ApeConsts.CLASS_STATUS_WAIT, isStopAllPublishMedia: false});
loger.log('restorClass');
}
// 全局禁言
... ... @@ -400,10 +413,12 @@ class ConferApe extends Ape {
let timestamp = EngineUtils.creatTimestampStr();
GlobalConfig.classStopTime = timestamp;
/*
//如果录制的文件名不存在,需要创建一个名字
let timestampYMD = EngineUtils.creatTimestampYMD();
GlobalConfig.recordFileName = GlobalConfig.recordFileName ||
GlobalConfig.siteId + "/" + timestampYMD + "/" + GlobalConfig.classId + "_" + timestampYMD + ".rec"; //4、文件名称 $RECORD_HOME/`site id`/`日期`/`filename` 例:/data/record/su/20161216/`filename`
*/
if (GlobalConfig.classStatus == ApeConsts.CLASS_STATUS_WAIT) {
//之前是为开始状态,第一次点开始
... ... @@ -418,8 +433,8 @@ class ConferApe extends Ape {
//同步课堂状态
//this.sendUpdaterClassStatusInfo({"actionType": 1, isStopAllPublishMedia: true});
this.sendUpdaterClassStatusInfo({"actionType": ApeConsts.CLASS_STATUS_STARTED, isStopAllPublishMedia: false});
//开始计时
this.startTimerCounter();
////开始计时
//this.startTimerCounter();
} else {
loger.warn('没有开始课堂的权限');
}
... ... @@ -451,7 +466,7 @@ class ConferApe extends Ape {
loger.warn("调用关闭课堂->");
this.stopTimerCounter();
this.restorClass();
//把所有人都踢出课堂
//停止所有人推流,把所有人都踢出课堂
this.sendConferMsg({"to": 0, "message": "所有人退出课堂", "actionType": ApeConsts.CLASS_ACTION_CLOSE_ALL});
this.stopRecord();//关闭课堂的时候停止录制
}
... ... @@ -557,7 +572,7 @@ class ConferApe extends Ape {
//更新课堂信息
sendUpdaterClassStatusInfo(_param) {
//{"actionType": 1,isStopAllPublishMedia:false} //actionType课堂状态 isStopAllPublishMedia是否停止当前的所有推流
loger.log('发送更新课堂信息->');
//loger.log('发送更新课堂信息->');
if (_param == null || EngineUtils.isEmptyObject(_param)) {
loger.log('发送更新课堂信息->参数错误');
this._emit(MessageTypes.MCU_ERROR, MessageTypes.ERR_APE_INTERFACE_PARAM_WRONG);
... ... @@ -610,21 +625,11 @@ class ConferApe extends Ape {
}
loger.log('课堂模块初始完成->当前课堂状态:'+ GlobalConfig.classStatus,"recordStatus:"+GlobalConfig.recordStatus);
this.timerCounter.addTimerCallBack(this.timerCounterUptate.bind(this), 1);
//2秒后执行开始上课和开始录制的判断逻辑
clearTimeout(this.startClassTimer);
this.startClassTimer=setTimeout(()=>{
//开启录制,录制接口中会自动判断是否需要录制
this.startTimerCounter();
this.startClass();
clearTimeout(this.startRecordTimer);
this.startRecordTimer=setTimeout(()=>{
this.startRecord();
//如果当前课堂正在进行中,开启计时器
if (GlobalConfig.classStatus == ApeConsts.CLASS_STATUS_STARTED) {
//开始计时
this.startTimerCounter();
//this.startRecord();
} else {
loger.log('自动开始上课->classStatus:', GlobalConfig.classStatus, " isHost:", GlobalConfig.isHost, " isAutoStartClass:", GlobalConfig.isAutoStartClass, " isRecordPlayBack:", GlobalConfig.isRecordPlayBack);
this.startClass();
}
},2000);
/* //如果当前课堂正在进行中,开启计时器
... ... @@ -672,7 +677,7 @@ class ConferApe extends Ape {
//以一定的时间间隔同步课堂内所有人的累积上课时间
if (GlobalConfig.classTimestamp % GlobalConfig.updateClassInfoDelay == 0) {
//如果是host身份,需要同步时间给其他人,同时把当前的状态上传到服务器
if (GlobalConfig.isHost||this.rosterLen<=1) {
if (this.checkHasRecordControl()) {
//保存数据到Sass
this._emit(MessageTypes.CLASS_STATUS_INFO_CHANGE);
//同步消息给其他人
... ... @@ -690,8 +695,23 @@ class ConferApe extends Ape {
this._emit(MessageTypes.SWITCH_HLS_IP);
}
}
//更新录制进行时间
GlobalConfig.recordTimestamp = GlobalConfig.recordTimestamp + 1;
if (this.checkHasRecordControl()) {
//以一定的时间间隔同步课堂内所有人的累积上课时间
if (GlobalConfig.recordTimestamp % GlobalConfig.updateRecordTimeDelay == 0) {
//保存数据到Sass
this._emit(MessageTypes.CLASS_STATUS_INFO_CHANGE);
}
if (GlobalConfig.recordTimestamp % GlobalConfig.updateClassInfoDelay == 0) {
//MCU同步消息给其他人
this.sendUpdaterClassStatusInfo({"actionType": GlobalConfig.classStatus, isStopAllPublishMedia: false});
}
}
/* if (GlobalConfig.classStatus == ApeConsts.CLASS_STATUS_STARTED) {
GlobalConfig.classTimestamp = GlobalConfig.classTimestamp + 1; //计时
//老师身份的时候要记录录制的时间
... ... @@ -774,9 +794,11 @@ class ConferApe extends Ape {
tableUpdateHandler(owner, itemIdx, itemData) {
try {
let model = this.unPackPdu(owner, itemIdx, itemData);
loger.log("课堂更新->",model);
//loger.log("课堂更新->",model.classStatusInfo);
//{"itemIdx":720899,"from":976168842,"owner":976168842,"actionType":null,"classStatusInfo":{"nodeId":976168842,"userId":"user_979813","userName":"user_979813","siteId":"markettest","classId":889112694,"className":"ly828-1v1","classType":1,"classStatus":1,"classStartTime":"2017-8-28-16-33-18","classStopTime":"2017-8-29-11-9-28","classTimestamp":1930,"classBeginTime":"2017-08-28 16:00:00","classEndTime":"2017-08-31 18:00:00","recordStatus":false,"recordTimestamp":2825,"recordFileName":"markettest/20170828/889112694_20170828.rec","recordDownloadUrl":"","serverTimestamp":1503976246,"activeDocId":976170739,"activeDocCurPage":1,"isStopAllPublishMedia":false,"currentSceneTableId":0,"silence":false,"silenceUsers":"{}","isEnableDraw":true}}
//处理课堂更新的信息
if (model && model.classStatusInfo) {
// loger.log("课堂更新->",model.classStatusInfo);
try {
model.classStatusInfo.silenceUsers = JSON.parse(model.classStatusInfo.silenceUsers);
} catch (err) {
... ... @@ -801,13 +823,13 @@ class ConferApe extends Ape {
return;
}
if (GlobalConfig.classStatus == ApeConsts.CLASS_STATUS_STARTED) {
/* if (GlobalConfig.classStatus == ApeConsts.CLASS_STATUS_STARTED) {
//如果课堂在进行中,开始计时器
this.startTimerCounter();
} else {
//停止计时
this.stopTimerCounter();
}
}*/
} catch (err) {
loger.warn('课堂更新->error', err.message);
}
... ... @@ -825,8 +847,15 @@ class ConferApe extends Ape {
switch (chatMsg.actionType) {
case ApeConsts.CLASS_ACTION_CLOSE_ALL:
loger.log(chatMsg.message);
//关闭之前停止所有推流
this._emit(MessageTypes.STOP_ALL_MEDIA_PUBLISH);
//收到课堂关闭,所有人都退出,执行自己关闭的流程
this._emit(MessageTypes.CLASS_RUN_EXIT, {'type': 1});
setTimeout(()=>{
this._emit(MessageTypes.CLASS_RUN_EXIT, {'type': 1});
},2000);
break;
case ApeConsts.STOP_ALL_PUBLISH:
this._emit(MessageTypes.STOP_ALL_MEDIA_PUBLISH);
break;
case ApeConsts.CLASS_ACTION_KICK_OUT_ROSTER:
if (chatMsg.toNodeID == GlobalConfig.nodeId) {
... ... @@ -1116,23 +1145,15 @@ class ConferApe extends Ape {
//当前人员列表中抽一个人来检查离开人员是否占用频道
for (let key in this.rosters) {
let randNodeId = parseInt(key);
//如果抽到的人是自己就处理以下操作
//判断是否是自己就处理以下操作
if (randNodeId == GlobalConfig.nodeId) {
loger.log(randNodeId, "有权限检查离开的人员是否占用channel");
loger.log("检查离开的人员是否占用channel");
this._emit(MessageTypes.CLASS_NONENTITY_ROSTER, {"nodeId": nodeId, "rosterLen": this.rosterLen});
//如果离开的人员是老师,需要暂停当前的课堂
if (user && user.role == ApeConsts.NR_HOST) {
this.pauseClass();
/* //强制停止录制
this.stopRecord(true)*/;
}
} else {
loger.warn(GlobalConfig.nodeId, "没有权限检查离开的人员是否占用channel");
}
//查找到一个就跳出操作
return;
}
}
... ... @@ -1207,7 +1228,7 @@ class ConferApe extends Ape {
let modelPdu = pdu['RCClassSendDataModelPdu'].decode(itemData);
return modelPdu;
} catch (err) {
loger.log("课堂收到数据 unPackPdu Pdu解析错误,itemIdx=" + itemIdx + " err:" + err.message);
loger.log("课堂收到数据解析错误:" + itemIdx + " err:" + err.message);
}
return null;
}
... ...
... ... @@ -389,7 +389,7 @@ class DocApe extends Ape {
docDataModel.animationStep = 1;//切换文档之后动画步数还原
//loger.log('切换文档,当前文档和上一个显示的文档都需要更新状态');
loger.log({"oldDoc": oldDocModel, "nowDoc": docDataModel});
//loger.log({"oldDoc": oldDocModel, "nowDoc": docDataModel});
//更新当前选择的文档
this.updaterDoc(docDataModel, docDataModel.itemIdx);
... ... @@ -673,7 +673,7 @@ class DocApe extends Ape {
}
onJoinChannelHandlerSuccess() {
loger.log(this._session_name + ' onJoinChannelHandlerSuccess===========================');
//loger.log(this._session_name + ' onJoinChannelHandlerSuccess===========================');
if (this._apeDelayed) {
// this._apeDelayedMsgs.push(regBuffer);
// this._apeDelayedStart();
... ...
... ... @@ -309,7 +309,7 @@ class VideoApe extends Ape {
//释放nodeId占用的指定的channelId频道
_releaseChannelForNodeId(nodeId, channelId) {
loger.log(nodeId, "_releaseChannelForNodeId-->channelId", channelId);
loger.log(nodeId, "停止-->channelId", channelId);
let channelInfo = this.mediaModule.mediaChannels[channelId];
if (channelInfo && channelInfo.status == ApeConsts.CHANNEL_STATUS_OPENING) {
if (channelInfo.fromNodeId == nodeId) {
... ... @@ -480,11 +480,13 @@ class VideoApe extends Ape {
this._emit(MessageTypes.VIDEO_BROADCAST, videoReceivePdu);
}
}
//切换了MS,重新更新一下当前正在播放的流地址
changeMediaMs(){
}
tableUpdateHandler(owner, itemIdx, itemData, seek) {
let unpackChannelInfo = this.unPackPdu(owner, itemIdx, itemData);
loger.log("tableUpdateHandler->channel", itemIdx, 'mediaType', unpackChannelInfo.mediaType, 'status->', unpackChannelInfo.status, "seek->", seek);
loger.log("视频模块数据更新->channel", itemIdx, 'mediaType', unpackChannelInfo.mediaType, 'status->', unpackChannelInfo.status, "seek->", seek);
//****很重要********
//如果owner的值为0,代表的是这个歌频道已经被释放了(mcu服务端对于占用channel的掉线用户,就是把owner设置为0)
... ... @@ -586,8 +588,6 @@ class VideoApe extends Ape {
});
}
}
if (unpackChannelInfo.mediaType != ApeConsts.MEDIA_TYPE_SHARE) {
//非屏幕共享情况的处理
MediaModule.allMediaChannelsList[itemIdx] = unpackChannelInfo;
... ... @@ -610,7 +610,7 @@ class VideoApe extends Ape {
}
//owner为0就是没有使用
if(channelInfo.owner==0){
channelInfo.status == ApeConsts.CHANNEL_STATUS_RELEASED
channelInfo.status = ApeConsts.CHANNEL_STATUS_RELEASED
}
this.sendTableUpdateHandler(channelInfo);
}
... ...