李勇

1.录制回放处理消息和视频地址匹配的问题;2.视频模块推流消息处理播放地址的问题;3.所有课堂开启旁路推流

... ... @@ -61,8 +61,9 @@ let _base64;
export default class MessageEntrance extends Emiter {
constructor() {
super();
this.lastClassActiveTime=0;//最后一次课堂激活的时间戳
//sdk 信息
GlobalConfig.sdkVersion = "v2.22.9.20171027";
GlobalConfig.sdkVersion = "v2.23.0.20171030";
loger.warn("sdkVersion:" + GlobalConfig.sdkVersion);
console.log("sdkVersion:" + GlobalConfig.sdkVersion);
//设置
... ... @@ -1240,8 +1241,8 @@ export default class MessageEntrance extends Emiter {
//主讲人和老师可以设置旁录
if (GlobalConfig.appId && !GlobalConfig.openFlash) {
//加入之前先设置旁录地址,只有直播支持旁路(1路流)
if (_webRtc && GlobalConfig.isTeachOrAssistant && GlobalConfig.maxMediaChannels == 1) {
//加入之前先设置旁录地址,老师和主讲人开启旁路
if (_webRtc && GlobalConfig.isTeachOrAssistant) {
let curTimestamp = new Date().getTime();
let streamId = GlobalConfig.siteId + "_" + GlobalConfig.classId + "_" + GlobalConfig.userId + "_" + curTimestamp;
//传入固定的流Id
... ... @@ -1870,6 +1871,7 @@ export default class MessageEntrance extends Emiter {
let dataObj = JSON.parse(_data.currentInfo);
dataObj.recordStatus = false;
GlobalConfig.setClassStatusInfo(dataObj);
this.lastClassActiveTime=dataObj.lastClassActiveTime||0;
} catch (err) {
loger.warn("从Sass获取的课堂数据JSON转换失败->");
GlobalConfig.setClassStatusInfo(_data.currentInfo);
... ... @@ -1919,6 +1921,7 @@ export default class MessageEntrance extends Emiter {
let dataObj = JSON.parse(_currentInfo);
dataObj.recordStatus = false;
GlobalConfig.setClassStatusInfo(dataObj);
this.lastClassActiveTime=dataObj.lastClassActiveTime||0;
} catch (err) {
loger.warn("getClassRecordInfo获取的课堂数据JSON转换失败->");
}
... ... @@ -2505,8 +2508,15 @@ export default class MessageEntrance extends Emiter {
//文档加入频道成功,同步到MCU服务器上的数据
docJoinChannelSuccess() {
loger.log("最后一次记录的时间->"+GlobalConfig.classStopTime,"当前时间:"+EngineUtils.creatTimestamp());
let distance=new Date().getTime()-parseInt(this.lastClassActiveTime);
loger.log("最后一次记录的时间->"+this.lastClassActiveTime,"当前时间:"+new Date().getTime(),"间隔:"+distance/1000);
loger.log("文档加入频道成功->isHost=", GlobalConfig.isHost, "当前总人数:", GlobalConfig.rosterNumber, "sassDoclength=", GlobalConfig.docListPrepare.length);
//如果当前课堂内只有自己或者离开上次课堂的时间大于15分钟,需要停止服务端的视频录制
if(GlobalConfig.rosterNumber<=1||distance>=(15*60)){
this._mediaRecordControl({"status": WebRtcApe.RECORD_STATUS_2});
}
//如果是主持人,那么需要判断一下文档模块同步的数据和从sass获取的文档数据是否相同,如果mcu服务器不存在的,需要上传
if (GlobalConfig.docListPrepare && GlobalConfig.docListPrepare.length > 0) {
//如果当前身份是老师或者当前课堂内只有一个人,有权限同步文档到MCU
... ... @@ -3282,7 +3292,8 @@ export default class MessageEntrance extends Emiter {
break;
case WebRtcApe.RECORD_STATUS_2:
//停止录制
_webRtc.stopRecordingMedia();
//_webRtc.stopRecordingMedia();
_webRtc.changePublishStatusAndServerRecord(WebRtcApe.RECORD_STATUS_2);
break;
default :
break;
... ...
... ... @@ -51,7 +51,15 @@ class EngineUtils{
timeStr+=curTime.getSeconds();
return timeStr;
}
//根据时间戳字符串转换为时间戳 2017-10-27-15-38-15
static getTimestampFromStr(_timeStr){
if(!_timeStr) return this.creatTimestamp();
let timeArr=_timeStr.split("_");
if(timeArr&&timeArr.length==6){
return parseInt( new Date(timeArr[0],parseInt(timeArr[1])-1,timeArr[2],timeArr[3],timeArr[4],timeArr[5]).getTime()/1000);
}
return this.creatTimestamp();
}
//生成时间戳 格式:"20170209"
static creatTimestampYMD(){
let curTime = new Date();
... ...
... ... @@ -330,13 +330,13 @@ class RecordPlayBackParse extends Emiter {
}
//时间戳转换为UTC 时间字符串
timestampToUTCTime(_timestamp){
var date=new Date(_timestamp);
var y=""+date.getFullYear();
var month=""+(date.getMonth()+1);
var d=""+date.getDate();
var h=""+(date.getHours()-8);//GMT 转UTC 减8
var minutes=""+date.getMinutes();
var s=""+date.getSeconds();
let date=new Date(_timestamp);
let y=""+date.getFullYear();
let month=""+(date.getMonth()+1);
let d=""+date.getDate();
let h=""+(date.getHours()-8);//GMT 转UTC 减8
let minutes=""+date.getMinutes();
let s=""+date.getSeconds();
if (month.length<2){
month="0"+month;
}
... ... @@ -352,7 +352,7 @@ class RecordPlayBackParse extends Emiter {
if (s.length<2){
s="0"+s;
}
var tiemStr=y+month+d+h+minutes+s;
let tiemStr=y+month+d+h+minutes+s;
//console.log(_timestamp,tiemStr,date);
return tiemStr
}
... ... @@ -362,7 +362,13 @@ class RecordPlayBackParse extends Emiter {
console.log("没有数据无法解析匹配",_tiemstampMessages,_allMedias)
return;
}
if(Object.keys(_tiemstampMessages).length<1||Object.keys(_allMedias).length<1){
console.log("数据没有加载完成->不做匹配");
return;
};
let finelMediaInfo={};
let unknowFiles={};//记录没有匹配到的,需要再次匹配
let tItem;
for(let j in _tiemstampMessages){
tItem=_tiemstampMessages[j];
... ... @@ -377,13 +383,41 @@ class RecordPlayBackParse extends Emiter {
if(!videoUrl){
videoUrl=_allMedias[tItem.uid+"_"+(t+1)];
}
if(!videoUrl){
unknowFiles[tItem.timestamp]=tItem;
}
}
//console.log(tItem.uid+"_"+t+" "+videoUrl);
tItem.video_url=videoUrl;
finelMediaInfo[tItem.timestamp]=tItem;
MediaModule.streams[tItem.stream_id]=tItem.video_url;
// MediaModule.streams[tItem.stream_id]=tItem.video_url;
MediaModule.streams[tItem.stream_id]={video_url:tItem.video_url,seek:0};
}
console.log("finelMediaInfo",finelMediaInfo);
//最后处理没有匹配到的
//检测没有匹配到数据的消息
for(let i in unknowFiles){
let noItem=unknowFiles[i];
for(let k in finelMediaInfo){
let okItem=finelMediaInfo[k];
let seek=(parseInt(noItem.timestamp)-parseInt(okItem.timestamp))/1000;
console.log("seek",seek);
if(noItem.uid==okItem.uid&&seek<15&&noItem.timestamp!=okItem.timestamp){
console.log(noItem,okItem);
noItem.video_url=okItem.video_url;
noItem.seek=seek;
finelMediaInfo [noItem.timestamp]=noItem;
//MediaModule.streams[noItem.stream_id]=noItem.video_url;
console.log("根据时间戳查找流",noItem.stream_id,{video_url:noItem.video_url,seek:seek})
MediaModule.streams[noItem.stream_id]={video_url:noItem.video_url,seek:seek};
}
}
}
console.log("服务端所有m3u8文件",_allMedias);
console.log("所有推流消息",_tiemstampMessages);
console.log("最终匹配结束的文件",finelMediaInfo);
console.log("回放中最终使用的视频流数据",MediaModule.streams);
}
//获取媒体录制的地址信息-时间戳流名称和文件地址对应
... ... @@ -647,7 +681,8 @@ class RecordPlayBackParse extends Emiter {
}
if(itemJson&&itemJson.video_url){
if(itemJson.video_url.indexOf(".m3u8")>0){
MediaModule.streams[itemJson.stream_id]=itemJson.video_url;
//MediaModule.streams[itemJson.stream_id]=itemJson.video_url;
MediaModule.streams[itemJson.stream_id]={video_url:itemJson.video_url,seek:0};
}
}
}
... ...
... ... @@ -433,9 +433,13 @@ class Sass extends Emiter {
loger.log('录制回放中,不需要保存课堂信息');
return;
}
if(!_param||!_param.classStatusInfo){
return;
}
//{"classStatusInfo":classStatusInfo}
let timestamp = new Date().getTime();
let authId = MD5(GlobalConfig.classId + "" + timestamp); // (classId+timestamp)的字符串,转成MD5
_param.classStatusInfo.lastClassActiveTime=timestamp;
let classStatusInfo = JSON.stringify(_param.classStatusInfo);
//let url = `http://${GlobalConfig.portal}/3m/api/meeting/saveInfo.do`;
let url = `${GlobalConfig.locationProtocol+GlobalConfig.portal}/3m/api/meeting/saveInfo.do`;
... ...
... ... @@ -103,15 +103,20 @@ class MediaModule {
}
//如果是外部的流地址,不需要拼接,直接使用就可以,是完整的地址
let streamPlayUrl=MediaModule.streams[_param.streamId];
if(streamPlayUrl){
loger.log("使用外部的流地址->",streamPlayUrl);
streamPlayUrl=streamPlayUrl.replace("http://","");
streamPlayUrl=streamPlayUrl.replace("https://","");
streamPlayUrl=GlobalConfig.locationProtocol+streamPlayUrl;
return {"code": ApeConsts.RETURN_SUCCESS, "data": "", "playUrl":streamPlayUrl};
let streamItem=MediaModule.streams[_param.streamId];
if(streamItem){
let streamPlayUrl=streamItem.video_url||"";
let seek=parseInt(streamItem.seek)||0;
if(streamPlayUrl){
loger.log("使用外部的流地址->",streamPlayUrl);
streamPlayUrl=streamPlayUrl.replace("http://","");
streamPlayUrl=streamPlayUrl.replace("https://","");
streamPlayUrl=GlobalConfig.locationProtocol+streamPlayUrl;
return {"code": ApeConsts.RETURN_SUCCESS, "data": "", "playUrl":streamPlayUrl,"seek":seek};
}
}
//M3U8 http://123.56.73.119:6001/live/h5dev_2106728010_8ab3b0ed5a3a9220015a3a958f0d0003_983041_1489113860/total.m3u8
let port = (GlobalConfig.RS_RECORD_PLAY_PORT == "" || GlobalConfig.RS_RECORD_PLAY_PORT == null) ? "" : ":" + GlobalConfig.RS_RECORD_PLAY_PORT;
//let path = "http://" + GlobalConfig.RS_RECORD_PLAY_IP
... ... @@ -129,7 +134,7 @@ class MediaModule {
+rHlsSuffix;
path = path.replace("::", ":");//如果ip和port之间有多的:需要去掉
return {"code": ApeConsts.RETURN_SUCCESS, "data": "", "playUrl": path};
return {"code": ApeConsts.RETURN_SUCCESS, "data": "", "playUrl": path,"seek":0};
}
//推流地址后缀拼接参数
... ...
... ... @@ -697,6 +697,7 @@ class VideoApe extends Ape {
}
if (replay.code == 0) {
receiveChannelInfo.replay = replay.playUrl;
receiveChannelInfo.seek=receiveChannelInfo.seek+replay.seek;
}
if (unpackChannelInfo.mediaType != ApeConsts.MEDIA_TYPE_SHARE&&
... ...