李勇

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

@@ -61,8 +61,9 @@ let _base64; @@ -61,8 +61,9 @@ let _base64;
61 export default class MessageEntrance extends Emiter { 61 export default class MessageEntrance extends Emiter {
62 constructor() { 62 constructor() {
63 super(); 63 super();
  64 + this.lastClassActiveTime=0;//最后一次课堂激活的时间戳
64 //sdk 信息 65 //sdk 信息
65 - GlobalConfig.sdkVersion = "v2.22.9.20171027"; 66 + GlobalConfig.sdkVersion = "v2.23.0.20171030";
66 loger.warn("sdkVersion:" + GlobalConfig.sdkVersion); 67 loger.warn("sdkVersion:" + GlobalConfig.sdkVersion);
67 console.log("sdkVersion:" + GlobalConfig.sdkVersion); 68 console.log("sdkVersion:" + GlobalConfig.sdkVersion);
68 //设置 69 //设置
@@ -1240,8 +1241,8 @@ export default class MessageEntrance extends Emiter { @@ -1240,8 +1241,8 @@ export default class MessageEntrance extends Emiter {
1240 1241
1241 //主讲人和老师可以设置旁录 1242 //主讲人和老师可以设置旁录
1242 if (GlobalConfig.appId && !GlobalConfig.openFlash) { 1243 if (GlobalConfig.appId && !GlobalConfig.openFlash) {
1243 - //加入之前先设置旁录地址,只有直播支持旁路(1路流)  
1244 - if (_webRtc && GlobalConfig.isTeachOrAssistant && GlobalConfig.maxMediaChannels == 1) { 1244 + //加入之前先设置旁录地址,老师和主讲人开启旁路
  1245 + if (_webRtc && GlobalConfig.isTeachOrAssistant) {
1245 let curTimestamp = new Date().getTime(); 1246 let curTimestamp = new Date().getTime();
1246 let streamId = GlobalConfig.siteId + "_" + GlobalConfig.classId + "_" + GlobalConfig.userId + "_" + curTimestamp; 1247 let streamId = GlobalConfig.siteId + "_" + GlobalConfig.classId + "_" + GlobalConfig.userId + "_" + curTimestamp;
1247 //传入固定的流Id 1248 //传入固定的流Id
@@ -1870,6 +1871,7 @@ export default class MessageEntrance extends Emiter { @@ -1870,6 +1871,7 @@ export default class MessageEntrance extends Emiter {
1870 let dataObj = JSON.parse(_data.currentInfo); 1871 let dataObj = JSON.parse(_data.currentInfo);
1871 dataObj.recordStatus = false; 1872 dataObj.recordStatus = false;
1872 GlobalConfig.setClassStatusInfo(dataObj); 1873 GlobalConfig.setClassStatusInfo(dataObj);
  1874 + this.lastClassActiveTime=dataObj.lastClassActiveTime||0;
1873 } catch (err) { 1875 } catch (err) {
1874 loger.warn("从Sass获取的课堂数据JSON转换失败->"); 1876 loger.warn("从Sass获取的课堂数据JSON转换失败->");
1875 GlobalConfig.setClassStatusInfo(_data.currentInfo); 1877 GlobalConfig.setClassStatusInfo(_data.currentInfo);
@@ -1919,6 +1921,7 @@ export default class MessageEntrance extends Emiter { @@ -1919,6 +1921,7 @@ export default class MessageEntrance extends Emiter {
1919 let dataObj = JSON.parse(_currentInfo); 1921 let dataObj = JSON.parse(_currentInfo);
1920 dataObj.recordStatus = false; 1922 dataObj.recordStatus = false;
1921 GlobalConfig.setClassStatusInfo(dataObj); 1923 GlobalConfig.setClassStatusInfo(dataObj);
  1924 + this.lastClassActiveTime=dataObj.lastClassActiveTime||0;
1922 } catch (err) { 1925 } catch (err) {
1923 loger.warn("getClassRecordInfo获取的课堂数据JSON转换失败->"); 1926 loger.warn("getClassRecordInfo获取的课堂数据JSON转换失败->");
1924 } 1927 }
@@ -2505,8 +2508,15 @@ export default class MessageEntrance extends Emiter { @@ -2505,8 +2508,15 @@ export default class MessageEntrance extends Emiter {
2505 2508
2506 //文档加入频道成功,同步到MCU服务器上的数据 2509 //文档加入频道成功,同步到MCU服务器上的数据
2507 docJoinChannelSuccess() { 2510 docJoinChannelSuccess() {
2508 - loger.log("最后一次记录的时间->"+GlobalConfig.classStopTime,"当前时间:"+EngineUtils.creatTimestamp()); 2511 + let distance=new Date().getTime()-parseInt(this.lastClassActiveTime);
  2512 + loger.log("最后一次记录的时间->"+this.lastClassActiveTime,"当前时间:"+new Date().getTime(),"间隔:"+distance/1000);
2509 loger.log("文档加入频道成功->isHost=", GlobalConfig.isHost, "当前总人数:", GlobalConfig.rosterNumber, "sassDoclength=", GlobalConfig.docListPrepare.length); 2513 loger.log("文档加入频道成功->isHost=", GlobalConfig.isHost, "当前总人数:", GlobalConfig.rosterNumber, "sassDoclength=", GlobalConfig.docListPrepare.length);
  2514 +
  2515 + //如果当前课堂内只有自己或者离开上次课堂的时间大于15分钟,需要停止服务端的视频录制
  2516 + if(GlobalConfig.rosterNumber<=1||distance>=(15*60)){
  2517 + this._mediaRecordControl({"status": WebRtcApe.RECORD_STATUS_2});
  2518 + }
  2519 +
2510 //如果是主持人,那么需要判断一下文档模块同步的数据和从sass获取的文档数据是否相同,如果mcu服务器不存在的,需要上传 2520 //如果是主持人,那么需要判断一下文档模块同步的数据和从sass获取的文档数据是否相同,如果mcu服务器不存在的,需要上传
2511 if (GlobalConfig.docListPrepare && GlobalConfig.docListPrepare.length > 0) { 2521 if (GlobalConfig.docListPrepare && GlobalConfig.docListPrepare.length > 0) {
2512 //如果当前身份是老师或者当前课堂内只有一个人,有权限同步文档到MCU 2522 //如果当前身份是老师或者当前课堂内只有一个人,有权限同步文档到MCU
@@ -3282,7 +3292,8 @@ export default class MessageEntrance extends Emiter { @@ -3282,7 +3292,8 @@ export default class MessageEntrance extends Emiter {
3282 break; 3292 break;
3283 case WebRtcApe.RECORD_STATUS_2: 3293 case WebRtcApe.RECORD_STATUS_2:
3284 //停止录制 3294 //停止录制
3285 - _webRtc.stopRecordingMedia(); 3295 + //_webRtc.stopRecordingMedia();
  3296 + _webRtc.changePublishStatusAndServerRecord(WebRtcApe.RECORD_STATUS_2);
3286 break; 3297 break;
3287 default : 3298 default :
3288 break; 3299 break;
@@ -51,7 +51,15 @@ class EngineUtils{ @@ -51,7 +51,15 @@ class EngineUtils{
51 timeStr+=curTime.getSeconds(); 51 timeStr+=curTime.getSeconds();
52 return timeStr; 52 return timeStr;
53 } 53 }
54 - 54 + //根据时间戳字符串转换为时间戳 2017-10-27-15-38-15
  55 + static getTimestampFromStr(_timeStr){
  56 + if(!_timeStr) return this.creatTimestamp();
  57 + let timeArr=_timeStr.split("_");
  58 + if(timeArr&&timeArr.length==6){
  59 + return parseInt( new Date(timeArr[0],parseInt(timeArr[1])-1,timeArr[2],timeArr[3],timeArr[4],timeArr[5]).getTime()/1000);
  60 + }
  61 + return this.creatTimestamp();
  62 + }
55 //生成时间戳 格式:"20170209" 63 //生成时间戳 格式:"20170209"
56 static creatTimestampYMD(){ 64 static creatTimestampYMD(){
57 let curTime = new Date(); 65 let curTime = new Date();
@@ -330,13 +330,13 @@ class RecordPlayBackParse extends Emiter { @@ -330,13 +330,13 @@ class RecordPlayBackParse extends Emiter {
330 } 330 }
331 //时间戳转换为UTC 时间字符串 331 //时间戳转换为UTC 时间字符串
332 timestampToUTCTime(_timestamp){ 332 timestampToUTCTime(_timestamp){
333 - var date=new Date(_timestamp);  
334 - var y=""+date.getFullYear();  
335 - var month=""+(date.getMonth()+1);  
336 - var d=""+date.getDate();  
337 - var h=""+(date.getHours()-8);//GMT 转UTC 减8  
338 - var minutes=""+date.getMinutes();  
339 - var s=""+date.getSeconds(); 333 + let date=new Date(_timestamp);
  334 + let y=""+date.getFullYear();
  335 + let month=""+(date.getMonth()+1);
  336 + let d=""+date.getDate();
  337 + let h=""+(date.getHours()-8);//GMT 转UTC 减8
  338 + let minutes=""+date.getMinutes();
  339 + let s=""+date.getSeconds();
340 if (month.length<2){ 340 if (month.length<2){
341 month="0"+month; 341 month="0"+month;
342 } 342 }
@@ -352,7 +352,7 @@ class RecordPlayBackParse extends Emiter { @@ -352,7 +352,7 @@ class RecordPlayBackParse extends Emiter {
352 if (s.length<2){ 352 if (s.length<2){
353 s="0"+s; 353 s="0"+s;
354 } 354 }
355 - var tiemStr=y+month+d+h+minutes+s; 355 + let tiemStr=y+month+d+h+minutes+s;
356 //console.log(_timestamp,tiemStr,date); 356 //console.log(_timestamp,tiemStr,date);
357 return tiemStr 357 return tiemStr
358 } 358 }
@@ -362,7 +362,13 @@ class RecordPlayBackParse extends Emiter { @@ -362,7 +362,13 @@ class RecordPlayBackParse extends Emiter {
362 console.log("没有数据无法解析匹配",_tiemstampMessages,_allMedias) 362 console.log("没有数据无法解析匹配",_tiemstampMessages,_allMedias)
363 return; 363 return;
364 } 364 }
  365 + if(Object.keys(_tiemstampMessages).length<1||Object.keys(_allMedias).length<1){
  366 + console.log("数据没有加载完成->不做匹配");
  367 + return;
  368 + };
  369 +
365 let finelMediaInfo={}; 370 let finelMediaInfo={};
  371 + let unknowFiles={};//记录没有匹配到的,需要再次匹配
366 let tItem; 372 let tItem;
367 for(let j in _tiemstampMessages){ 373 for(let j in _tiemstampMessages){
368 tItem=_tiemstampMessages[j]; 374 tItem=_tiemstampMessages[j];
@@ -377,13 +383,41 @@ class RecordPlayBackParse extends Emiter { @@ -377,13 +383,41 @@ class RecordPlayBackParse extends Emiter {
377 if(!videoUrl){ 383 if(!videoUrl){
378 videoUrl=_allMedias[tItem.uid+"_"+(t+1)]; 384 videoUrl=_allMedias[tItem.uid+"_"+(t+1)];
379 } 385 }
  386 + if(!videoUrl){
  387 + unknowFiles[tItem.timestamp]=tItem;
  388 + }
380 } 389 }
381 //console.log(tItem.uid+"_"+t+" "+videoUrl); 390 //console.log(tItem.uid+"_"+t+" "+videoUrl);
382 tItem.video_url=videoUrl; 391 tItem.video_url=videoUrl;
383 finelMediaInfo[tItem.timestamp]=tItem; 392 finelMediaInfo[tItem.timestamp]=tItem;
384 - MediaModule.streams[tItem.stream_id]=tItem.video_url; 393 + // MediaModule.streams[tItem.stream_id]=tItem.video_url;
  394 + MediaModule.streams[tItem.stream_id]={video_url:tItem.video_url,seek:0};
385 } 395 }
386 console.log("finelMediaInfo",finelMediaInfo); 396 console.log("finelMediaInfo",finelMediaInfo);
  397 +
  398 + //最后处理没有匹配到的
  399 + //检测没有匹配到数据的消息
  400 + for(let i in unknowFiles){
  401 + let noItem=unknowFiles[i];
  402 + for(let k in finelMediaInfo){
  403 + let okItem=finelMediaInfo[k];
  404 + let seek=(parseInt(noItem.timestamp)-parseInt(okItem.timestamp))/1000;
  405 + console.log("seek",seek);
  406 + if(noItem.uid==okItem.uid&&seek<15&&noItem.timestamp!=okItem.timestamp){
  407 + console.log(noItem,okItem);
  408 + noItem.video_url=okItem.video_url;
  409 + noItem.seek=seek;
  410 + finelMediaInfo [noItem.timestamp]=noItem;
  411 + //MediaModule.streams[noItem.stream_id]=noItem.video_url;
  412 + console.log("根据时间戳查找流",noItem.stream_id,{video_url:noItem.video_url,seek:seek})
  413 + MediaModule.streams[noItem.stream_id]={video_url:noItem.video_url,seek:seek};
  414 + }
  415 + }
  416 + }
  417 + console.log("服务端所有m3u8文件",_allMedias);
  418 + console.log("所有推流消息",_tiemstampMessages);
  419 + console.log("最终匹配结束的文件",finelMediaInfo);
  420 + console.log("回放中最终使用的视频流数据",MediaModule.streams);
387 } 421 }
388 422
389 //获取媒体录制的地址信息-时间戳流名称和文件地址对应 423 //获取媒体录制的地址信息-时间戳流名称和文件地址对应
@@ -647,7 +681,8 @@ class RecordPlayBackParse extends Emiter { @@ -647,7 +681,8 @@ class RecordPlayBackParse extends Emiter {
647 } 681 }
648 if(itemJson&&itemJson.video_url){ 682 if(itemJson&&itemJson.video_url){
649 if(itemJson.video_url.indexOf(".m3u8")>0){ 683 if(itemJson.video_url.indexOf(".m3u8")>0){
650 - MediaModule.streams[itemJson.stream_id]=itemJson.video_url; 684 + //MediaModule.streams[itemJson.stream_id]=itemJson.video_url;
  685 + MediaModule.streams[itemJson.stream_id]={video_url:itemJson.video_url,seek:0};
651 } 686 }
652 } 687 }
653 } 688 }
@@ -433,9 +433,13 @@ class Sass extends Emiter { @@ -433,9 +433,13 @@ class Sass extends Emiter {
433 loger.log('录制回放中,不需要保存课堂信息'); 433 loger.log('录制回放中,不需要保存课堂信息');
434 return; 434 return;
435 } 435 }
  436 + if(!_param||!_param.classStatusInfo){
  437 + return;
  438 + }
436 //{"classStatusInfo":classStatusInfo} 439 //{"classStatusInfo":classStatusInfo}
437 let timestamp = new Date().getTime(); 440 let timestamp = new Date().getTime();
438 let authId = MD5(GlobalConfig.classId + "" + timestamp); // (classId+timestamp)的字符串,转成MD5 441 let authId = MD5(GlobalConfig.classId + "" + timestamp); // (classId+timestamp)的字符串,转成MD5
  442 + _param.classStatusInfo.lastClassActiveTime=timestamp;
439 let classStatusInfo = JSON.stringify(_param.classStatusInfo); 443 let classStatusInfo = JSON.stringify(_param.classStatusInfo);
440 //let url = `http://${GlobalConfig.portal}/3m/api/meeting/saveInfo.do`; 444 //let url = `http://${GlobalConfig.portal}/3m/api/meeting/saveInfo.do`;
441 let url = `${GlobalConfig.locationProtocol+GlobalConfig.portal}/3m/api/meeting/saveInfo.do`; 445 let url = `${GlobalConfig.locationProtocol+GlobalConfig.portal}/3m/api/meeting/saveInfo.do`;
@@ -103,15 +103,20 @@ class MediaModule { @@ -103,15 +103,20 @@ class MediaModule {
103 } 103 }
104 104
105 //如果是外部的流地址,不需要拼接,直接使用就可以,是完整的地址 105 //如果是外部的流地址,不需要拼接,直接使用就可以,是完整的地址
106 - let streamPlayUrl=MediaModule.streams[_param.streamId];  
107 - if(streamPlayUrl){  
108 - loger.log("使用外部的流地址->",streamPlayUrl);  
109 - streamPlayUrl=streamPlayUrl.replace("http://","");  
110 - streamPlayUrl=streamPlayUrl.replace("https://","");  
111 - streamPlayUrl=GlobalConfig.locationProtocol+streamPlayUrl;  
112 - return {"code": ApeConsts.RETURN_SUCCESS, "data": "", "playUrl":streamPlayUrl}; 106 + let streamItem=MediaModule.streams[_param.streamId];
  107 + if(streamItem){
  108 + let streamPlayUrl=streamItem.video_url||"";
  109 + let seek=parseInt(streamItem.seek)||0;
  110 + if(streamPlayUrl){
  111 + loger.log("使用外部的流地址->",streamPlayUrl);
  112 + streamPlayUrl=streamPlayUrl.replace("http://","");
  113 + streamPlayUrl=streamPlayUrl.replace("https://","");
  114 + streamPlayUrl=GlobalConfig.locationProtocol+streamPlayUrl;
  115 + return {"code": ApeConsts.RETURN_SUCCESS, "data": "", "playUrl":streamPlayUrl,"seek":seek};
  116 + }
113 } 117 }
114 118
  119 +
115 //M3U8 http://123.56.73.119:6001/live/h5dev_2106728010_8ab3b0ed5a3a9220015a3a958f0d0003_983041_1489113860/total.m3u8 120 //M3U8 http://123.56.73.119:6001/live/h5dev_2106728010_8ab3b0ed5a3a9220015a3a958f0d0003_983041_1489113860/total.m3u8
116 let port = (GlobalConfig.RS_RECORD_PLAY_PORT == "" || GlobalConfig.RS_RECORD_PLAY_PORT == null) ? "" : ":" + GlobalConfig.RS_RECORD_PLAY_PORT; 121 let port = (GlobalConfig.RS_RECORD_PLAY_PORT == "" || GlobalConfig.RS_RECORD_PLAY_PORT == null) ? "" : ":" + GlobalConfig.RS_RECORD_PLAY_PORT;
117 //let path = "http://" + GlobalConfig.RS_RECORD_PLAY_IP 122 //let path = "http://" + GlobalConfig.RS_RECORD_PLAY_IP
@@ -129,7 +134,7 @@ class MediaModule { @@ -129,7 +134,7 @@ class MediaModule {
129 +rHlsSuffix; 134 +rHlsSuffix;
130 135
131 path = path.replace("::", ":");//如果ip和port之间有多的:需要去掉 136 path = path.replace("::", ":");//如果ip和port之间有多的:需要去掉
132 - return {"code": ApeConsts.RETURN_SUCCESS, "data": "", "playUrl": path}; 137 + return {"code": ApeConsts.RETURN_SUCCESS, "data": "", "playUrl": path,"seek":0};
133 } 138 }
134 139
135 //推流地址后缀拼接参数 140 //推流地址后缀拼接参数
@@ -697,6 +697,7 @@ class VideoApe extends Ape { @@ -697,6 +697,7 @@ class VideoApe extends Ape {
697 } 697 }
698 if (replay.code == 0) { 698 if (replay.code == 0) {
699 receiveChannelInfo.replay = replay.playUrl; 699 receiveChannelInfo.replay = replay.playUrl;
  700 + receiveChannelInfo.seek=receiveChannelInfo.seek+replay.seek;
700 } 701 }
701 702
702 if (unpackChannelInfo.mediaType != ApeConsts.MEDIA_TYPE_SHARE&& 703 if (unpackChannelInfo.mediaType != ApeConsts.MEDIA_TYPE_SHARE&&