Commit 7d9cc96ef54399795deb5b7fc7682e6323dc1202

Authored by 648540858
1 parent e9586687

优化国标录像下载,添加进度条以及自动合并文件下载,需要结合新版assist服务使用。

Showing 27 changed files with 761 additions and 591 deletions
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
@@ -31,6 +31,9 @@ public class StreamInfo { @@ -31,6 +31,9 @@ public class StreamInfo {
31 private String rtc; 31 private String rtc;
32 private String mediaServerId; 32 private String mediaServerId;
33 private Object tracks; 33 private Object tracks;
  34 + private String startTime;
  35 + private String endTime;
  36 + private double progress;
34 37
35 public static class TransactionInfo{ 38 public static class TransactionInfo{
36 public String callId; 39 public String callId;
@@ -264,4 +267,29 @@ public class StreamInfo { @@ -264,4 +267,29 @@ public class StreamInfo {
264 public void setHttps_ts(String https_ts) { 267 public void setHttps_ts(String https_ts) {
265 this.https_ts = https_ts; 268 this.https_ts = https_ts;
266 } 269 }
  270 +
  271 +
  272 + public String getStartTime() {
  273 + return startTime;
  274 + }
  275 +
  276 + public void setStartTime(String startTime) {
  277 + this.startTime = startTime;
  278 + }
  279 +
  280 + public String getEndTime() {
  281 + return endTime;
  282 + }
  283 +
  284 + public void setEndTime(String endTime) {
  285 + this.endTime = endTime;
  286 + }
  287 +
  288 + public double getProgress() {
  289 + return progress;
  290 + }
  291 +
  292 + public void setProgress(double progress) {
  293 + this.progress = progress;
  294 + }
267 } 295 }
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java
1 package com.genersoft.iot.vmp.gb28181.bean; 1 package com.genersoft.iot.vmp.gb28181.bean;
2 2
  3 +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
  4 +
3 public class SsrcTransaction { 5 public class SsrcTransaction {
4 6
5 private String deviceId; 7 private String deviceId;
@@ -10,6 +12,7 @@ public class SsrcTransaction { @@ -10,6 +12,7 @@ public class SsrcTransaction {
10 private byte[] dialog; 12 private byte[] dialog;
11 private String mediaServerId; 13 private String mediaServerId;
12 private String ssrc; 14 private String ssrc;
  15 + private VideoStreamSessionManager.SessionType type;
13 16
14 public String getDeviceId() { 17 public String getDeviceId() {
15 return deviceId; 18 return deviceId;
@@ -74,4 +77,12 @@ public class SsrcTransaction { @@ -74,4 +77,12 @@ public class SsrcTransaction {
74 public void setSsrc(String ssrc) { 77 public void setSsrc(String ssrc) {
75 this.ssrc = ssrc; 78 this.ssrc = ssrc;
76 } 79 }
  80 +
  81 + public VideoStreamSessionManager.SessionType getType() {
  82 + return type;
  83 + }
  84 +
  85 + public void setType(VideoStreamSessionManager.SessionType type) {
  86 + this.type = type;
  87 + }
77 } 88 }
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java
@@ -156,8 +156,6 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> { @@ -156,8 +156,6 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
156 List<ParentPlatform> parentPlatforms = parentPlatformMap.get(gbId); 156 List<ParentPlatform> parentPlatforms = parentPlatformMap.get(gbId);
157 if (parentPlatforms != null && parentPlatforms.size() > 0) { 157 if (parentPlatforms != null && parentPlatforms.size() > 0) {
158 for (ParentPlatform platform : parentPlatforms) { 158 for (ParentPlatform platform : parentPlatforms) {
159 -// String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetup.getServerId() + "_Catalog_" + platform.getServerGBId();  
160 -// SubscribeInfo subscribeInfo = redisCatchStorage.getSubscribe(key);  
161 SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(event.getPlatformId()); 159 SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(event.getPlatformId());
162 if (subscribeInfo == null) continue; 160 if (subscribeInfo == null) continue;
163 logger.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), gbId); 161 logger.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), gbId);
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
@@ -30,6 +30,12 @@ public class VideoStreamSessionManager { @@ -30,6 +30,12 @@ public class VideoStreamSessionManager {
30 @Autowired 30 @Autowired
31 private UserSetup userSetup; 31 private UserSetup userSetup;
32 32
  33 + public enum SessionType {
  34 + play,
  35 + playback,
  36 + download
  37 + }
  38 +
33 /** 39 /**
34 * 添加一个点播/回放的事务信息 40 * 添加一个点播/回放的事务信息
35 * 后续可以通过流Id/callID 41 * 后续可以通过流Id/callID
@@ -40,7 +46,7 @@ public class VideoStreamSessionManager { @@ -40,7 +46,7 @@ public class VideoStreamSessionManager {
40 * @param mediaServerId 所使用的流媒体ID 46 * @param mediaServerId 所使用的流媒体ID
41 * @param transaction 事务 47 * @param transaction 事务
42 */ 48 */
43 - public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, ClientTransaction transaction){ 49 + public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, ClientTransaction transaction, SessionType type){
44 SsrcTransaction ssrcTransaction = new SsrcTransaction(); 50 SsrcTransaction ssrcTransaction = new SsrcTransaction();
45 ssrcTransaction.setDeviceId(deviceId); 51 ssrcTransaction.setDeviceId(deviceId);
46 ssrcTransaction.setChannelId(channelId); 52 ssrcTransaction.setChannelId(channelId);
@@ -50,6 +56,7 @@ public class VideoStreamSessionManager { @@ -50,6 +56,7 @@ public class VideoStreamSessionManager {
50 ssrcTransaction.setCallId(callId); 56 ssrcTransaction.setCallId(callId);
51 ssrcTransaction.setSsrc(ssrc); 57 ssrcTransaction.setSsrc(ssrc);
52 ssrcTransaction.setMediaServerId(mediaServerId); 58 ssrcTransaction.setMediaServerId(mediaServerId);
  59 + ssrcTransaction.setType(type);
53 60
54 redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetup.getServerId() 61 redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetup.getServerId()
55 + "_" + deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction); 62 + "_" + deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -115,7 +115,9 @@ public interface ISIPCommander { @@ -115,7 +115,9 @@ public interface ISIPCommander {
115 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss 115 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
116 * @param downloadSpeed 下载倍速参数 116 * @param downloadSpeed 下载倍速参数
117 */ 117 */
118 - void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String startTime, String endTime, String downloadSpeed, InviteStreamCallback event, SipSubscribe.Event errorEvent); 118 + void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
  119 + String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
  120 + SipSubscribe.Event errorEvent);
119 121
120 /** 122 /**
121 * 视频流停止 123 * 视频流停止
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -428,7 +428,7 @@ public class SIPCommander implements ISIPCommander { @@ -428,7 +428,7 @@ public class SIPCommander implements ISIPCommander {
428 errorEvent.response(e); 428 errorEvent.response(e);
429 }), e ->{ 429 }), e ->{
430 // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 430 // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
431 - streamSession.put(device.getDeviceId(), channelId ,"play", streamId, ssrcInfo.getSsrc(), mediaServerItem.getId(), ((ResponseEvent)e.event).getClientTransaction()); 431 + streamSession.put(device.getDeviceId(), channelId ,"play", streamId, ssrcInfo.getSsrc(), mediaServerItem.getId(), ((ResponseEvent)e.event).getClientTransaction(), VideoStreamSessionManager.SessionType.play);
432 streamSession.put(device.getDeviceId(), channelId ,"play", e.dialog); 432 streamSession.put(device.getDeviceId(), channelId ,"play", e.dialog);
433 }); 433 });
434 434
@@ -537,7 +537,7 @@ public class SIPCommander implements ISIPCommander { @@ -537,7 +537,7 @@ public class SIPCommander implements ISIPCommander {
537 537
538 transmitRequest(device, request, errorEvent, okEvent -> { 538 transmitRequest(device, request, errorEvent, okEvent -> {
539 ResponseEvent responseEvent = (ResponseEvent) okEvent.event; 539 ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
540 - streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction()); 540 + streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.playback);
541 streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog); 541 streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog);
542 }); 542 });
543 if (inviteStreamCallback != null) { 543 if (inviteStreamCallback != null) {
@@ -558,8 +558,9 @@ public class SIPCommander implements ISIPCommander { @@ -558,8 +558,9 @@ public class SIPCommander implements ISIPCommander {
558 * @param downloadSpeed 下载倍速参数 558 * @param downloadSpeed 下载倍速参数
559 */ 559 */
560 @Override 560 @Override
561 - public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String startTime, String endTime, String downloadSpeed, InviteStreamCallback event  
562 - , SipSubscribe.Event errorEvent) { 561 + public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
  562 + String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
  563 + SipSubscribe.Event errorEvent) {
563 try { 564 try {
564 logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort()); 565 logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
565 566
@@ -572,8 +573,6 @@ public class SIPCommander implements ISIPCommander { @@ -572,8 +573,6 @@ public class SIPCommander implements ISIPCommander {
572 content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" " 573 content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" "
573 +DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n"); 574 +DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n");
574 575
575 -  
576 -  
577 String streamMode = device.getStreamMode().toUpperCase(); 576 String streamMode = device.getStreamMode().toUpperCase();
578 577
579 if (userSetup.isSeniorSdp()) { 578 if (userSetup.isSeniorSdp()) {
@@ -639,15 +638,20 @@ public class SIPCommander implements ISIPCommander { @@ -639,15 +638,20 @@ public class SIPCommander implements ISIPCommander {
639 logger.debug("录像回放添加订阅,订阅内容:" + subscribeKey.toString()); 638 logger.debug("录像回放添加订阅,订阅内容:" + subscribeKey.toString());
640 subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, 639 subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
641 (MediaServerItem mediaServerItemInUse, JSONObject json)->{ 640 (MediaServerItem mediaServerItemInUse, JSONObject json)->{
642 - event.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream())); 641 + hookEvent.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
643 subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey); 642 subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey);
644 }); 643 });
645 644
646 Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader, ssrcInfo.getSsrc()); 645 Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader, ssrcInfo.getSsrc());
  646 + if (inviteStreamCallback != null) {
  647 + inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
  648 + }
  649 + transmitRequest(device, request, errorEvent, okEvent->{
  650 + ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
  651 + streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.download);
  652 + streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog);
  653 + });
647 654
648 - ClientTransaction transaction = transmitRequest(device, request, errorEvent);  
649 - streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), transaction);  
650 - streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), transaction);  
651 655
652 } catch ( SipException | ParseException | InvalidArgumentException e) { 656 } catch ( SipException | ParseException | InvalidArgumentException e) {
653 e.printStackTrace(); 657 e.printStackTrace();
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java
@@ -104,6 +104,7 @@ public class CatalogNotifyMessageHandler extends SIPRequestProcessorParent imple @@ -104,6 +104,7 @@ public class CatalogNotifyMessageHandler extends SIPRequestProcessorParent imple
104 DeviceChannel deviceChannel = storager.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId()); 104 DeviceChannel deviceChannel = storager.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId());
105 deviceChannel.setParental(0); 105 deviceChannel.setParental(0);
106 deviceChannel.setParentId(channelReduce.getCatalogId()); 106 deviceChannel.setParentId(channelReduce.getCatalogId());
  107 + deviceChannel.setCivilCode(parentPlatform.getDeviceGBId().substring(0, 6));
107 cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size); 108 cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size);
108 // 防止发送过快 109 // 防止发送过快
109 Thread.sleep(100); 110 Thread.sleep(100);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java
@@ -62,7 +62,12 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i @@ -62,7 +62,12 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
62 if (NotifyType.equals("121")){ 62 if (NotifyType.equals("121")){
63 logger.info("媒体播放完毕,通知关流"); 63 logger.info("媒体播放完毕,通知关流");
64 String channelId =getText(rootElement, "DeviceID"); 64 String channelId =getText(rootElement, "DeviceID");
65 - redisCatchStorage.stopPlayback(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); 65 +// redisCatchStorage.stopPlayback(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
  66 +// redisCatchStorage.stopDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
  67 + StreamInfo streamInfo = redisCatchStorage.queryDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
  68 + // 设置进度100%
  69 + streamInfo.setProgress(1);
  70 + redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId());
66 cmder.streamByeCmd(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); 71 cmder.streamByeCmd(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
67 // TODO 如果级联播放,需要给上级发送此通知 72 // TODO 如果级联播放,需要给上级发送此通知
68 73
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java
@@ -107,6 +107,7 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem @@ -107,6 +107,7 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem
107 DeviceChannel deviceChannel = storager.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId()); 107 DeviceChannel deviceChannel = storager.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId());
108 deviceChannel.setParental(0); 108 deviceChannel.setParental(0);
109 deviceChannel.setParentId(channelReduce.getCatalogId()); 109 deviceChannel.setParentId(channelReduce.getCatalogId());
  110 + deviceChannel.setCivilCode(parentPlatform.getDeviceGBId().substring(0, 6));
110 cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size); 111 cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size);
111 // 防止发送过快 112 // 防止发送过快
112 Thread.sleep(100); 113 Thread.sleep(100);
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java 0 → 100644
  1 +package com.genersoft.iot.vmp.media.zlm;
  2 +
  3 +import com.alibaba.fastjson.JSON;
  4 +import com.alibaba.fastjson.JSONObject;
  5 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  6 +import okhttp3.*;
  7 +import okhttp3.logging.HttpLoggingInterceptor;
  8 +import org.jetbrains.annotations.NotNull;
  9 +import org.slf4j.Logger;
  10 +import org.slf4j.LoggerFactory;
  11 +import org.springframework.stereotype.Component;
  12 +import org.springframework.util.StringUtils;
  13 +
  14 +import java.io.File;
  15 +import java.io.FileOutputStream;
  16 +import java.io.IOException;
  17 +import java.net.ConnectException;
  18 +import java.util.HashMap;
  19 +import java.util.Map;
  20 +import java.util.Objects;
  21 +import java.util.concurrent.TimeUnit;
  22 +
  23 +@Component
  24 +public class AssistRESTfulUtils {
  25 +
  26 + private final static Logger logger = LoggerFactory.getLogger(AssistRESTfulUtils.class);
  27 +
  28 + public interface RequestCallback{
  29 + void run(JSONObject response);
  30 + }
  31 +
  32 + private OkHttpClient getClient(){
  33 + OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
  34 + if (logger.isDebugEnabled()) {
  35 + HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> {
  36 + logger.debug("http请求参数:" + message);
  37 + });
  38 + logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
  39 + // OkHttp進行添加攔截器loggingInterceptor
  40 + httpClientBuilder.addInterceptor(logging);
  41 + }
  42 + return httpClientBuilder.build();
  43 + }
  44 +
  45 +
  46 + public JSONObject sendGet(MediaServerItem mediaServerItem, String api, Map<String, Object> param, RequestCallback callback) {
  47 + OkHttpClient client = getClient();
  48 +
  49 + if (mediaServerItem == null) {
  50 + return null;
  51 + }
  52 + if (StringUtils.isEmpty(mediaServerItem.getRecordAssistPort())) {
  53 + logger.warn("未启用Assist服务");
  54 + return null;
  55 + }
  56 + StringBuffer stringBuffer = new StringBuffer();
  57 + stringBuffer.append(String.format("http://%s:%s/%s", mediaServerItem.getIp(), mediaServerItem.getRecordAssistPort(), api));
  58 + JSONObject responseJSON = null;
  59 +
  60 + if (param != null && param.keySet().size() > 0) {
  61 + stringBuffer.append("?");
  62 + int index = 1;
  63 + for (String key : param.keySet()){
  64 + if (param.get(key) != null) {
  65 + stringBuffer.append(key + "=" + param.get(key));
  66 + if (index < param.size()) {
  67 + stringBuffer.append("&");
  68 + }
  69 + }
  70 + index++;
  71 + }
  72 + }
  73 +
  74 + String url = stringBuffer.toString();
  75 + Request request = new Request.Builder()
  76 + .get()
  77 + .url(url)
  78 + .build();
  79 + if (callback == null) {
  80 + try {
  81 + Response response = client.newCall(request).execute();
  82 + if (response.isSuccessful()) {
  83 + ResponseBody responseBody = response.body();
  84 + if (responseBody != null) {
  85 + String responseStr = responseBody.string();
  86 + responseJSON = JSON.parseObject(responseStr);
  87 + }
  88 + }else {
  89 + response.close();
  90 + Objects.requireNonNull(response.body()).close();
  91 + }
  92 + } catch (ConnectException e) {
  93 + logger.error(String.format("连接Assist失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
  94 + logger.info("请检查media配置并确认Assist已启动...");
  95 + }catch (IOException e) {
  96 + logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
  97 + }
  98 + }else {
  99 + client.newCall(request).enqueue(new Callback(){
  100 +
  101 + @Override
  102 + public void onResponse(@NotNull Call call, @NotNull Response response){
  103 + if (response.isSuccessful()) {
  104 + try {
  105 + String responseStr = Objects.requireNonNull(response.body()).string();
  106 + callback.run(JSON.parseObject(responseStr));
  107 + } catch (IOException e) {
  108 + logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
  109 + }
  110 +
  111 + }else {
  112 + response.close();
  113 + Objects.requireNonNull(response.body()).close();
  114 + }
  115 + }
  116 +
  117 + @Override
  118 + public void onFailure(@NotNull Call call, @NotNull IOException e) {
  119 + logger.error(String.format("连接Assist失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
  120 + logger.info("请检查media配置并确认Assist已启动...");
  121 + }
  122 + });
  123 + }
  124 +
  125 +
  126 +
  127 + return responseJSON;
  128 + }
  129 +
  130 +
  131 + public JSONObject fileDuration(MediaServerItem mediaServerItem, String app, String stream, RequestCallback callback){
  132 + Map<String, Object> param = new HashMap<>();
  133 + param.put("app",app);
  134 + param.put("stream",stream);
  135 + param.put("recordIng",true);
  136 + return sendGet(mediaServerItem, "api/record/file/duration",param, callback);
  137 + }
  138 +
  139 +}
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -215,7 +215,16 @@ public class ZLMHttpHookListener { @@ -215,7 +215,16 @@ public class ZLMHttpHookListener {
215 if (deviceChannel != null) { 215 if (deviceChannel != null) {
216 ret.put("enable_audio", deviceChannel.isHasAudio()); 216 ret.put("enable_audio", deviceChannel.isHasAudio());
217 } 217 }
  218 + // 如果是录像下载就设置视频间隔十秒
  219 + if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.download) {
  220 + ret.put("mp4_max_second", 10);
  221 + ret.put("enable_mp4", true);
  222 + ret.put("enable_audio", true);
  223 + }
  224 +
218 } 225 }
  226 +
  227 +
219 return new ResponseEntity<String>(ret.toString(), HttpStatus.OK); 228 return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
220 } 229 }
221 230
@@ -324,7 +333,6 @@ public class ZLMHttpHookListener { @@ -324,7 +333,6 @@ public class ZLMHttpHookListener {
324 if (mediaInfo != null) { 333 if (mediaInfo != null) {
325 subscribe.response(mediaInfo, json); 334 subscribe.response(mediaInfo, json);
326 } 335 }
327 -  
328 } 336 }
329 // 流消失移除redis play 337 // 流消失移除redis play
330 String app = item.getApp(); 338 String app = item.getApp();
@@ -441,6 +449,7 @@ public class ZLMHttpHookListener { @@ -441,6 +449,7 @@ public class ZLMHttpHookListener {
441 if ("rtp".equals(app)){ 449 if ("rtp".equals(app)){
442 ret.put("close", true); 450 ret.put("close", true);
443 StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(streamId); 451 StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(streamId);
  452 + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, null, streamId);
444 if (streamInfoForPlayCatch != null) { 453 if (streamInfoForPlayCatch != null) {
445 // 如果在给上级推流,也不停止。 454 // 如果在给上级推流,也不停止。
446 if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) { 455 if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) {
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java
@@ -31,6 +31,7 @@ public class ZLMHttpHookSubscribe { @@ -31,6 +31,7 @@ public class ZLMHttpHookSubscribe {
31 on_server_keepalive 31 on_server_keepalive
32 } 32 }
33 33
  34 + @FunctionalInterface
34 public interface Event{ 35 public interface Event{
35 void response(MediaServerItem mediaServerItem, JSONObject response); 36 void response(MediaServerItem mediaServerItem, JSONObject response);
36 } 37 }
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
1 package com.genersoft.iot.vmp.service; 1 package com.genersoft.iot.vmp.service;
2 2
3 import com.alibaba.fastjson.JSONObject; 3 import com.alibaba.fastjson.JSONObject;
  4 +import com.genersoft.iot.vmp.common.StreamInfo;
4 import com.genersoft.iot.vmp.gb28181.bean.Device; 5 import com.genersoft.iot.vmp.gb28181.bean.Device;
5 import com.genersoft.iot.vmp.gb28181.bean.InviteStreamCallback; 6 import com.genersoft.iot.vmp.gb28181.bean.InviteStreamCallback;
6 import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo; 7 import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo;
@@ -34,4 +35,9 @@ public interface IPlayService { @@ -34,4 +35,9 @@ public interface IPlayService {
34 DeferredResult<ResponseEntity<String>> playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack); 35 DeferredResult<ResponseEntity<String>> playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack);
35 36
36 void zlmServerOffline(String mediaServerId); 37 void zlmServerOffline(String mediaServerId);
  38 +
  39 + DeferredResult<ResponseEntity<String>> download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack);
  40 + DeferredResult<ResponseEntity<String>> download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack);
  41 +
  42 + StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream);
37 } 43 }
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -12,6 +12,8 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; @@ -12,6 +12,8 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
12 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; 12 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
13 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; 13 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
14 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; 14 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
  15 +import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
  16 +import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
15 import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; 17 import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
16 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; 18 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
17 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; 19 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
@@ -28,7 +30,6 @@ import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult; @@ -28,7 +30,6 @@ import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
28 import com.genersoft.iot.vmp.service.IMediaService; 30 import com.genersoft.iot.vmp.service.IMediaService;
29 import com.genersoft.iot.vmp.service.IPlayService; 31 import com.genersoft.iot.vmp.service.IPlayService;
30 import gov.nist.javax.sip.stack.SIPDialog; 32 import gov.nist.javax.sip.stack.SIPDialog;
31 -import jdk.nashorn.internal.ir.RuntimeNode;  
32 import org.slf4j.Logger; 33 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory; 34 import org.slf4j.LoggerFactory;
34 import org.springframework.beans.factory.annotation.Autowired; 35 import org.springframework.beans.factory.annotation.Autowired;
@@ -38,10 +39,8 @@ import org.springframework.stereotype.Service; @@ -38,10 +39,8 @@ import org.springframework.stereotype.Service;
38 import org.springframework.util.ResourceUtils; 39 import org.springframework.util.ResourceUtils;
39 import org.springframework.web.context.request.async.DeferredResult; 40 import org.springframework.web.context.request.async.DeferredResult;
40 41
41 -import javax.sip.header.CallIdHeader;  
42 -import javax.sip.header.Header;  
43 -import javax.sip.message.Request;  
44 import java.io.FileNotFoundException; 42 import java.io.FileNotFoundException;
  43 +import java.math.BigDecimal;
45 import java.util.*; 44 import java.util.*;
46 45
47 @SuppressWarnings(value = {"rawtypes", "unchecked"}) 46 @SuppressWarnings(value = {"rawtypes", "unchecked"})
@@ -72,6 +71,9 @@ public class PlayServiceImpl implements IPlayService { @@ -72,6 +71,9 @@ public class PlayServiceImpl implements IPlayService {
72 private ZLMRESTfulUtils zlmresTfulUtils; 71 private ZLMRESTfulUtils zlmresTfulUtils;
73 72
74 @Autowired 73 @Autowired
  74 + private AssistRESTfulUtils assistRESTfulUtils;
  75 +
  76 + @Autowired
75 private IMediaService mediaService; 77 private IMediaService mediaService;
76 78
77 @Autowired 79 @Autowired
@@ -344,7 +346,7 @@ public class PlayServiceImpl implements IPlayService { @@ -344,7 +346,7 @@ public class PlayServiceImpl implements IPlayService {
344 return result; 346 return result;
345 } 347 }
346 348
347 - resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId, uuid, result); 349 + resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId, uuid, result);
348 RequestMessage msg = new RequestMessage(); 350 RequestMessage msg = new RequestMessage();
349 msg.setId(uuid); 351 msg.setId(uuid);
350 msg.setKey(key); 352 msg.setKey(key);
@@ -406,6 +408,136 @@ public class PlayServiceImpl implements IPlayService { @@ -406,6 +408,136 @@ public class PlayServiceImpl implements IPlayService {
406 } 408 }
407 409
408 @Override 410 @Override
  411 + public DeferredResult<ResponseEntity<String>> download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack) {
  412 + Device device = storager.queryVideoDevice(deviceId);
  413 + if (device == null) return null;
  414 + MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
  415 + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true);
  416 +
  417 + return download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed,infoCallBack, hookCallBack);
  418 + }
  419 +
  420 + @Override
  421 + public DeferredResult<ResponseEntity<String>> download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack) {
  422 + if (mediaServerItem == null || ssrcInfo == null) return null;
  423 + String uuid = UUID.randomUUID().toString();
  424 + String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId;
  425 + DeferredResult<ResponseEntity<String>> result = new DeferredResult<>(30000L);
  426 + Device device = storager.queryVideoDevice(deviceId);
  427 + if (device == null) {
  428 + result.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
  429 + return result;
  430 + }
  431 +
  432 + resultHolder.put(key, uuid, result);
  433 + RequestMessage msg = new RequestMessage();
  434 + msg.setId(uuid);
  435 + msg.setKey(key);
  436 + WVPResult<StreamInfo> wvpResult = new WVPResult<>();
  437 + msg.setData(wvpResult);
  438 + PlayBackResult<RequestMessage> downloadResult = new PlayBackResult<>();
  439 + downloadResult.setData(msg);
  440 +
  441 + Timer timer = new Timer();
  442 + timer.schedule(new TimerTask() {
  443 + @Override
  444 + public void run() {
  445 + logger.warn(String.format("录像下载请求超时,deviceId:%s ,channelId:%s", deviceId, channelId));
  446 + wvpResult.setCode(-1);
  447 + wvpResult.setMsg("录像下载请求超时");
  448 + downloadResult.setCode(-1);
  449 + hookCallBack.call(downloadResult);
  450 + SIPDialog dialog = streamSession.getDialogByStream(deviceId, channelId, ssrcInfo.getStream());
  451 + // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  452 + if (dialog != null) {
  453 + // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  454 + cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null);
  455 + }else {
  456 + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  457 + mediaServerService.closeRTPServer(deviceId, channelId, ssrcInfo.getStream());
  458 + streamSession.remove(deviceId, channelId, ssrcInfo.getStream());
  459 + }
  460 + cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null);
  461 + // 回复之前所有的点播请求
  462 + hookCallBack.call(downloadResult);
  463 + }
  464 + }, userSetup.getPlayTimeout());
  465 + cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack,
  466 + inviteStreamInfo -> {
  467 + logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
  468 + timer.cancel();
  469 + StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
  470 + streamInfo.setStartTime(startTime);
  471 + streamInfo.setEndTime(endTime);
  472 + if (streamInfo == null) {
  473 + logger.warn("录像下载API调用失败!");
  474 + wvpResult.setCode(-1);
  475 + wvpResult.setMsg("录像下载API调用失败");
  476 + downloadResult.setCode(-1);
  477 + hookCallBack.call(downloadResult);
  478 + return ;
  479 + }
  480 + redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
  481 + wvpResult.setCode(0);
  482 + wvpResult.setMsg("success");
  483 + wvpResult.setData(streamInfo);
  484 + downloadResult.setCode(0);
  485 + downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
  486 + downloadResult.setResponse(inviteStreamInfo.getResponse());
  487 + hookCallBack.call(downloadResult);
  488 + }, event -> {
  489 + timer.cancel();
  490 + downloadResult.setCode(-1);
  491 + wvpResult.setCode(-1);
  492 + wvpResult.setMsg(String.format("录像下载失败, 错误码: %s, %s", event.statusCode, event.msg));
  493 + downloadResult.setEvent(event);
  494 + hookCallBack.call(downloadResult);
  495 + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  496 + });
  497 + return result;
  498 + }
  499 +
  500 + @Override
  501 + public StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream) {
  502 + StreamInfo streamInfo = redisCatchStorage.queryDownload(deviceId, channelId, stream, null);
  503 + if (streamInfo != null) {
  504 + if (streamInfo.getProgress() == 1) {
  505 + return streamInfo;
  506 + }
  507 +
  508 + // 获取当前已下载时长
  509 + String mediaServerId = streamInfo.getMediaServerId();
  510 + MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
  511 + if (mediaServerItem == null) {
  512 + logger.warn("查询录像信息时发现节点已离线");
  513 + return null;
  514 + }
  515 + if (mediaServerItem.getRecordAssistPort() != 0) {
  516 + JSONObject jsonObject = assistRESTfulUtils.fileDuration(mediaServerItem, streamInfo.getApp(), streamInfo.getStream(), null);
  517 + if (jsonObject != null && jsonObject.getInteger("code") == 0) {
  518 + long duration = jsonObject.getLong("data");
  519 +
  520 + if (duration == 0) {
  521 + streamInfo.setProgress(0);
  522 + }else {
  523 + String startTime = streamInfo.getStartTime();
  524 + String endTime = streamInfo.getEndTime();
  525 + long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
  526 + long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
  527 +
  528 + BigDecimal currentCount = new BigDecimal(duration/1000);
  529 + BigDecimal totalCount = new BigDecimal(end-start);
  530 + BigDecimal divide = currentCount.divide(totalCount,2, BigDecimal.ROUND_HALF_UP);
  531 + double process = divide.doubleValue();
  532 + streamInfo.setProgress(process);
  533 + }
  534 + }
  535 + }
  536 + }
  537 + return streamInfo;
  538 + }
  539 +
  540 + @Override
409 public void onPublishHandlerForDownload(InviteStreamInfo inviteStreamInfo, String deviceId, String channelId, String uuid) { 541 public void onPublishHandlerForDownload(InviteStreamInfo inviteStreamInfo, String deviceId, String channelId, String uuid) {
410 RequestMessage msg = new RequestMessage(); 542 RequestMessage msg = new RequestMessage();
411 msg.setKey(DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId); 543 msg.setKey(DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId);
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java
@@ -91,7 +91,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService { @@ -91,7 +91,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
91 MediaServerItem mediaInfo; 91 MediaServerItem mediaInfo;
92 WVPResult<StreamInfo> wvpResult = new WVPResult<>(); 92 WVPResult<StreamInfo> wvpResult = new WVPResult<>();
93 wvpResult.setCode(0); 93 wvpResult.setCode(0);
94 - if ("auto".equals(param.getMediaServerId())){ 94 + if (param.getMediaServerId() == null || "auto".equals(param.getMediaServerId())){
95 mediaInfo = mediaServerService.getMediaServerForMinimumLoad(); 95 mediaInfo = mediaServerService.getMediaServerForMinimumLoad();
96 }else { 96 }else {
97 mediaInfo = mediaServerService.getOne(param.getMediaServerId()); 97 mediaInfo = mediaServerService.getOne(param.getMediaServerId());
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
@@ -169,6 +169,8 @@ public interface IRedisCatchStorage { @@ -169,6 +169,8 @@ public interface IRedisCatchStorage {
169 169
170 StreamInfo queryDownload(String deviceId, String channelId, String stream, String callId); 170 StreamInfo queryDownload(String deviceId, String channelId, String stream, String callId);
171 171
  172 + boolean stopDownload(String deviceId, String channelId, String stream, String callId);
  173 +
172 /** 174 /**
173 * 查找第三方系统留下的国标预设值 175 * 查找第三方系统留下的国标预设值
174 * @param queryKey 176 * @param queryKey
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
@@ -166,8 +166,42 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @@ -166,8 +166,42 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
166 166
167 @Override 167 @Override
168 public boolean startDownload(StreamInfo stream, String callId) { 168 public boolean startDownload(StreamInfo stream, String callId) {
169 - return redis.set(String.format("%S_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,  
170 - userSetup.getServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream); 169 + boolean result;
  170 + if (stream.getProgress() == 1) {
  171 + result = redis.set(String.format("%S_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,
  172 + userSetup.getServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream);
  173 + }else {
  174 + result = redis.set(String.format("%S_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,
  175 + userSetup.getServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream, 60*60);
  176 + }
  177 + return result;
  178 + }
  179 + @Override
  180 + public boolean stopDownload(String deviceId, String channelId, String stream, String callId) {
  181 + DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(deviceId, channelId);
  182 + if (deviceChannel != null) {
  183 + deviceChannel.setStreamId(null);
  184 + deviceChannel.setDeviceId(deviceId);
  185 + deviceChannelMapper.update(deviceChannel);
  186 + }
  187 + if (deviceId == null) deviceId = "*";
  188 + if (channelId == null) channelId = "*";
  189 + if (stream == null) stream = "*";
  190 + if (callId == null) callId = "*";
  191 + String key = String.format("%S_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,
  192 + userSetup.getServerId(),
  193 + deviceId,
  194 + channelId,
  195 + stream,
  196 + callId
  197 + );
  198 + List<Object> scan = redis.scan(key);
  199 + if (scan.size() > 0) {
  200 + for (Object keyObj : scan) {
  201 + redis.del((String) keyObj);
  202 + }
  203 + }
  204 + return true;
171 } 205 }
172 206
173 @Override 207 @Override
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java deleted 100644 → 0
1 -package com.genersoft.iot.vmp.vmanager.gb28181.playback;  
2 -  
3 -import com.genersoft.iot.vmp.common.StreamInfo;  
4 -import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo;  
5 -import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;  
6 -import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;  
7 -import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;  
8 -import com.genersoft.iot.vmp.service.IMediaServerService;  
9 -import com.genersoft.iot.vmp.service.bean.SSRCInfo;  
10 -import com.genersoft.iot.vmp.storager.IRedisCatchStorage;  
11 -import com.genersoft.iot.vmp.service.IPlayService;  
12 -import io.swagger.annotations.Api;  
13 -import io.swagger.annotations.ApiImplicitParam;  
14 -import io.swagger.annotations.ApiImplicitParams;  
15 -import io.swagger.annotations.ApiOperation;  
16 -import org.slf4j.Logger;  
17 -import org.slf4j.LoggerFactory;  
18 -import org.springframework.beans.factory.annotation.Autowired;  
19 -import org.springframework.http.HttpStatus;  
20 -import org.springframework.http.ResponseEntity;  
21 -import org.springframework.web.bind.annotation.CrossOrigin;  
22 -import org.springframework.web.bind.annotation.GetMapping;  
23 -import org.springframework.web.bind.annotation.PathVariable;  
24 -import org.springframework.web.bind.annotation.RequestMapping;  
25 -import org.springframework.web.bind.annotation.RestController;  
26 -  
27 -import com.alibaba.fastjson.JSONObject;  
28 -import com.genersoft.iot.vmp.gb28181.bean.Device;  
29 -import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;  
30 -import com.genersoft.iot.vmp.storager.IVideoManagerStorager;  
31 -import org.springframework.web.context.request.async.DeferredResult;  
32 -  
33 -import javax.sip.message.Response;  
34 -import java.util.UUID;  
35 -  
36 -@Api(tags = "历史媒体下载")  
37 -@CrossOrigin  
38 -@RestController  
39 -@RequestMapping("/api/download")  
40 -public class DownloadController {  
41 -  
42 - private final static Logger logger = LoggerFactory.getLogger(DownloadController.class);  
43 -  
44 - @Autowired  
45 - private SIPCommander cmder;  
46 -  
47 - @Autowired  
48 - private IVideoManagerStorager storager;  
49 -  
50 - @Autowired  
51 - private IRedisCatchStorage redisCatchStorage;  
52 -  
53 - // @Autowired  
54 - // private ZLMRESTfulUtils zlmresTfulUtils;  
55 -  
56 - @Autowired  
57 - private IPlayService playService;  
58 -  
59 - @Autowired  
60 - private DeferredResultHolder resultHolder;  
61 -  
62 - @Autowired  
63 - private IMediaServerService mediaServerService;  
64 -  
65 - @ApiOperation("开始历史媒体下载")  
66 - @ApiImplicitParams({  
67 - @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),  
68 - @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class),  
69 - @ApiImplicitParam(name = "startTime", value = "开始时间", dataTypeClass = String.class),  
70 - @ApiImplicitParam(name = "endTime", value = "结束时间", dataTypeClass = String.class),  
71 - @ApiImplicitParam(name = "downloadSpeed", value = "下载倍速", dataTypeClass = String.class),  
72 - })  
73 - @GetMapping("/start/{deviceId}/{channelId}")  
74 - public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId, @PathVariable String channelId,  
75 - String startTime, String endTime, String downloadSpeed) {  
76 -  
77 - if (logger.isDebugEnabled()) {  
78 - logger.debug(String.format("历史媒体下载 API调用,deviceId:%s,channelId:%s,downloadSpeed:%s", deviceId, channelId, downloadSpeed));  
79 - }  
80 - String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId;  
81 - String uuid = UUID.randomUUID().toString();  
82 - DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(30000L);  
83 - // 超时处理  
84 - result.onTimeout(()->{  
85 - logger.warn(String.format("设备下载响应超时,deviceId:%s ,channelId:%s", deviceId, channelId));  
86 - RequestMessage msg = new RequestMessage();  
87 - msg.setId(uuid);  
88 - msg.setKey(key);  
89 - msg.setData("Timeout");  
90 - resultHolder.invokeAllResult(msg);  
91 - });  
92 - if(resultHolder.exist(key, null)) {  
93 - return result;  
94 - }  
95 - resultHolder.put(key, uuid, result);  
96 - Device device = storager.queryVideoDevice(deviceId);  
97 -  
98 - MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);  
99 - if (newMediaServerItem == null) {  
100 - logger.warn(String.format("设备下载响应超时,deviceId:%s ,channelId:%s", deviceId, channelId));  
101 - RequestMessage msg = new RequestMessage();  
102 - msg.setId(uuid);  
103 - msg.setKey(key);  
104 - msg.setData("Timeout");  
105 - resultHolder.invokeAllResult(msg);  
106 - return result;  
107 - }  
108 -  
109 - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true);  
110 -  
111 - cmder.downloadStreamCmd(newMediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, (InviteStreamInfo inviteStreamInfo) -> {  
112 - logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());  
113 - playService.onPublishHandlerForDownload(inviteStreamInfo, deviceId, channelId, uuid);  
114 - }, event -> {  
115 - RequestMessage msg = new RequestMessage();  
116 - msg.setId(uuid);  
117 - msg.setKey(key);  
118 - msg.setData(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg));  
119 - resultHolder.invokeAllResult(msg);  
120 - });  
121 -  
122 - return result;  
123 - }  
124 -  
125 - @ApiOperation("停止历史媒体下载")  
126 - @ApiImplicitParams({  
127 - @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),  
128 - @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class),  
129 - @ApiImplicitParam(name = "stream", value = "流ID", dataTypeClass = String.class),  
130 - })  
131 - @GetMapping("/stop/{deviceId}/{channelId}/{stream}")  
132 - public ResponseEntity<String> playStop(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) {  
133 -  
134 - cmder.streamByeCmd(deviceId, channelId, stream, null);  
135 -  
136 - if (logger.isDebugEnabled()) {  
137 - logger.debug(String.format("设备历史媒体下载停止 API调用,deviceId/channelId:%s_%s", deviceId, channelId));  
138 - }  
139 -  
140 - if (deviceId != null && channelId != null) {  
141 - JSONObject json = new JSONObject();  
142 - json.put("deviceId", deviceId);  
143 - json.put("channelId", channelId);  
144 - return new ResponseEntity<String>(json.toString(), HttpStatus.OK);  
145 - } else {  
146 - logger.warn("设备历史媒体下载停止API调用失败!");  
147 - return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);  
148 - }  
149 - }  
150 -}  
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java
1 package com.genersoft.iot.vmp.vmanager.gb28181.record; 1 package com.genersoft.iot.vmp.vmanager.gb28181.record;
2 2
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.genersoft.iot.vmp.common.StreamInfo;
  5 +import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo;
3 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; 6 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
  7 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  8 +import com.genersoft.iot.vmp.service.IMediaServerService;
  9 +import com.genersoft.iot.vmp.service.IPlayService;
  10 +import com.genersoft.iot.vmp.service.bean.SSRCInfo;
4 import io.swagger.annotations.Api; 11 import io.swagger.annotations.Api;
5 import io.swagger.annotations.ApiImplicitParam; 12 import io.swagger.annotations.ApiImplicitParam;
6 import io.swagger.annotations.ApiImplicitParams; 13 import io.swagger.annotations.ApiImplicitParams;
@@ -8,6 +15,7 @@ import io.swagger.annotations.ApiOperation; @@ -8,6 +15,7 @@ import io.swagger.annotations.ApiOperation;
8 import org.slf4j.Logger; 15 import org.slf4j.Logger;
9 import org.slf4j.LoggerFactory; 16 import org.slf4j.LoggerFactory;
10 import org.springframework.beans.factory.annotation.Autowired; 17 import org.springframework.beans.factory.annotation.Autowired;
  18 +import org.springframework.http.HttpStatus;
11 import org.springframework.http.ResponseEntity; 19 import org.springframework.http.ResponseEntity;
12 import org.springframework.web.bind.annotation.CrossOrigin; 20 import org.springframework.web.bind.annotation.CrossOrigin;
13 import org.springframework.web.bind.annotation.GetMapping; 21 import org.springframework.web.bind.annotation.GetMapping;
@@ -41,6 +49,12 @@ public class GBRecordController { @@ -41,6 +49,12 @@ public class GBRecordController {
41 @Autowired 49 @Autowired
42 private DeferredResultHolder resultHolder; 50 private DeferredResultHolder resultHolder;
43 51
  52 + @Autowired
  53 + private IPlayService playService;
  54 +
  55 + @Autowired
  56 + private IMediaServerService mediaServerService;
  57 +
44 @ApiOperation("录像查询") 58 @ApiOperation("录像查询")
45 @ApiImplicitParams({ 59 @ApiImplicitParams({
46 @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class), 60 @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
@@ -77,4 +91,111 @@ public class GBRecordController { @@ -77,4 +91,111 @@ public class GBRecordController {
77 }); 91 });
78 return result; 92 return result;
79 } 93 }
  94 +
  95 + @ApiOperation("开始历史媒体下载")
  96 + @ApiImplicitParams({
  97 + @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
  98 + @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class),
  99 + @ApiImplicitParam(name = "startTime", value = "开始时间", dataTypeClass = String.class),
  100 + @ApiImplicitParam(name = "endTime", value = "结束时间", dataTypeClass = String.class),
  101 + @ApiImplicitParam(name = "downloadSpeed", value = "下载倍速", dataTypeClass = String.class),
  102 + })
  103 + @GetMapping("/download/start/{deviceId}/{channelId}")
  104 + public DeferredResult<ResponseEntity<String>> download(@PathVariable String deviceId, @PathVariable String channelId,
  105 + String startTime, String endTime, String downloadSpeed) {
  106 +
  107 + if (logger.isDebugEnabled()) {
  108 + logger.debug(String.format("历史媒体下载 API调用,deviceId:%s,channelId:%s,downloadSpeed:%s", deviceId, channelId, downloadSpeed));
  109 + }
  110 +// String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId;
  111 +// String uuid = UUID.randomUUID().toString();
  112 +// DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(30000L);
  113 +// // 超时处理
  114 +// result.onTimeout(()->{
  115 +// logger.warn(String.format("设备下载响应超时,deviceId:%s ,channelId:%s", deviceId, channelId));
  116 +// RequestMessage msg = new RequestMessage();
  117 +// msg.setId(uuid);
  118 +// msg.setKey(key);
  119 +// msg.setData("Timeout");
  120 +// resultHolder.invokeAllResult(msg);
  121 +// });
  122 +// if(resultHolder.exist(key, null)) {
  123 +// return result;
  124 +// }
  125 +// resultHolder.put(key, uuid, result);
  126 +// Device device = storager.queryVideoDevice(deviceId);
  127 +//
  128 +// MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
  129 +// if (newMediaServerItem == null) {
  130 +// logger.warn(String.format("设备下载响应超时,deviceId:%s ,channelId:%s", deviceId, channelId));
  131 +// RequestMessage msg = new RequestMessage();
  132 +// msg.setId(uuid);
  133 +// msg.setKey(key);
  134 +// msg.setData("Timeout");
  135 +// resultHolder.invokeAllResult(msg);
  136 +// return result;
  137 +// }
  138 +//
  139 +// SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true);
  140 +//
  141 +// cmder.downloadStreamCmd(newMediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, (InviteStreamInfo inviteStreamInfo) -> {
  142 +// logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
  143 +// playService.onPublishHandlerForDownload(inviteStreamInfo, deviceId, channelId, uuid);
  144 +// }, event -> {
  145 +// RequestMessage msg = new RequestMessage();
  146 +// msg.setId(uuid);
  147 +// msg.setKey(key);
  148 +// msg.setData(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg));
  149 +// resultHolder.invokeAllResult(msg);
  150 +// });
  151 +
  152 + if (logger.isDebugEnabled()) {
  153 + logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId));
  154 + }
  155 +
  156 + DeferredResult<ResponseEntity<String>> result = playService.download(deviceId, channelId, startTime, endTime, Integer.parseInt(downloadSpeed), null, hookCallBack->{
  157 + resultHolder.invokeResult(hookCallBack.getData());
  158 + });
  159 +
  160 + return result;
  161 + }
  162 +
  163 + @ApiOperation("停止历史媒体下载")
  164 + @ApiImplicitParams({
  165 + @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
  166 + @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class),
  167 + @ApiImplicitParam(name = "stream", value = "流ID", dataTypeClass = String.class),
  168 + })
  169 + @GetMapping("/download/stop/{deviceId}/{channelId}/{stream}")
  170 + public ResponseEntity<String> playStop(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) {
  171 +
  172 + cmder.streamByeCmd(deviceId, channelId, stream, null);
  173 +
  174 + if (logger.isDebugEnabled()) {
  175 + logger.debug(String.format("设备历史媒体下载停止 API调用,deviceId/channelId:%s_%s", deviceId, channelId));
  176 + }
  177 +
  178 + if (deviceId != null && channelId != null) {
  179 + JSONObject json = new JSONObject();
  180 + json.put("deviceId", deviceId);
  181 + json.put("channelId", channelId);
  182 + return new ResponseEntity<String>(json.toString(), HttpStatus.OK);
  183 + } else {
  184 + logger.warn("设备历史媒体下载停止API调用失败!");
  185 + return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
  186 + }
  187 + }
  188 +
  189 + @ApiOperation("获取历史媒体下载进度")
  190 + @ApiImplicitParams({
  191 + @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
  192 + @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class),
  193 + @ApiImplicitParam(name = "stream", value = "流ID", dataTypeClass = String.class),
  194 + })
  195 + @GetMapping("/download/progress/{deviceId}/{channelId}/{stream}")
  196 + public ResponseEntity<StreamInfo> getProgress(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) {
  197 +
  198 + StreamInfo streamInfo = playService.getDownLoadInfo(deviceId, channelId, stream);
  199 + return new ResponseEntity<>(streamInfo, HttpStatus.OK);
  200 + }
80 } 201 }
web_src/build/webpack.dev.conf.js
@@ -32,6 +32,7 @@ const devWebpackConfig = merge(baseWebpackConfig, { @@ -32,6 +32,7 @@ const devWebpackConfig = merge(baseWebpackConfig, {
32 contentBase: false, // since we use CopyWebpackPlugin. 32 contentBase: false, // since we use CopyWebpackPlugin.
33 compress: true, 33 compress: true,
34 host: HOST || config.dev.host, 34 host: HOST || config.dev.host,
  35 + // host:'127.0.0.1',
35 port: PORT || config.dev.port, 36 port: PORT || config.dev.port,
36 open: config.dev.autoOpenBrowser, 37 open: config.dev.autoOpenBrowser,
37 overlay: config.dev.errorOverlay 38 overlay: config.dev.errorOverlay
web_src/config/index.js
@@ -29,11 +29,13 @@ module.exports = { @@ -29,11 +29,13 @@ module.exports = {
29 }, 29 },
30 30
31 // Various Dev Server settings 31 // Various Dev Server settings
32 - host: 'localhost', // can be overwritten by process.env.HOST 32 + host:"127.0.0.1",
  33 + useLocalIp: false, // can be overwritten by process.env.HOST
33 port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 34 port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
34 autoOpenBrowser: false, 35 autoOpenBrowser: false,
35 errorOverlay: true, 36 errorOverlay: true,
36 notifyOnErrors: true, 37 notifyOnErrors: true,
  38 + hot: true,//自动保存
37 poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 39 poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
38 40
39 41
web_src/package-lock.json
@@ -57,7 +57,7 @@ @@ -57,7 +57,7 @@
57 "vue-template-compiler": "^2.5.2", 57 "vue-template-compiler": "^2.5.2",
58 "webpack": "^3.6.0", 58 "webpack": "^3.6.0",
59 "webpack-bundle-analyzer": "^2.9.0", 59 "webpack-bundle-analyzer": "^2.9.0",
60 - "webpack-dev-server": "^2.9.1", 60 + "webpack-dev-server": "^2.11.5",
61 "webpack-merge": "^4.1.0" 61 "webpack-merge": "^4.1.0"
62 }, 62 },
63 "engines": { 63 "engines": {
@@ -13382,7 +13382,7 @@ @@ -13382,7 +13382,7 @@
13382 }, 13382 },
13383 "node_modules/webpack-dev-server": { 13383 "node_modules/webpack-dev-server": {
13384 "version": "2.11.5", 13384 "version": "2.11.5",
13385 - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.5.tgz", 13385 + "resolved": "https://registry.npmmirror.com/webpack-dev-server/-/webpack-dev-server-2.11.5.tgz",
13386 "integrity": "sha512-7TdOKKt7G3sWEhPKV0zP+nD0c4V9YKUJ3wDdBwQsZNo58oZIRoVIu66pg7PYkBW8A74msP9C2kLwmxGHndz/pw==", 13386 "integrity": "sha512-7TdOKKt7G3sWEhPKV0zP+nD0c4V9YKUJ3wDdBwQsZNo58oZIRoVIu66pg7PYkBW8A74msP9C2kLwmxGHndz/pw==",
13387 "dev": true, 13387 "dev": true,
13388 "dependencies": { 13388 "dependencies": {
@@ -25569,7 +25569,7 @@ @@ -25569,7 +25569,7 @@
25569 }, 25569 },
25570 "webpack-dev-server": { 25570 "webpack-dev-server": {
25571 "version": "2.11.5", 25571 "version": "2.11.5",
25572 - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.5.tgz", 25572 + "resolved": "https://registry.npmmirror.com/webpack-dev-server/-/webpack-dev-server-2.11.5.tgz",
25573 "integrity": "sha512-7TdOKKt7G3sWEhPKV0zP+nD0c4V9YKUJ3wDdBwQsZNo58oZIRoVIu66pg7PYkBW8A74msP9C2kLwmxGHndz/pw==", 25573 "integrity": "sha512-7TdOKKt7G3sWEhPKV0zP+nD0c4V9YKUJ3wDdBwQsZNo58oZIRoVIu66pg7PYkBW8A74msP9C2kLwmxGHndz/pw==",
25574 "dev": true, 25574 "dev": true,
25575 "requires": { 25575 "requires": {
web_src/src/components/dialog/devicePlayer.vue
@@ -175,6 +175,7 @@ @@ -175,6 +175,7 @@
175 </el-tabs> 175 </el-tabs>
176 </div> 176 </div>
177 </el-dialog> 177 </el-dialog>
  178 + <recordDownload ref="recordDownload"></recordDownload>
178 </div> 179 </div>
179 </template> 180 </template>
180 181
@@ -183,15 +184,15 @@ @@ -183,15 +184,15 @@
183 // import LivePlayer from '@liveqing/liveplayer' 184 // import LivePlayer from '@liveqing/liveplayer'
184 // import player from '../dialog/easyPlayer.vue' 185 // import player from '../dialog/easyPlayer.vue'
185 import player from '../dialog/jessibuca.vue' 186 import player from '../dialog/jessibuca.vue'
  187 +import recordDownload from '../dialog/recordDownload.vue'
186 export default { 188 export default {
187 name: 'devicePlayer', 189 name: 'devicePlayer',
188 props: {}, 190 props: {},
189 components: { 191 components: {
190 - player, 192 + player,recordDownload,
191 }, 193 },
192 computed: { 194 computed: {
193 getPlayerShared: function () { 195 getPlayerShared: function () {
194 -  
195 return { 196 return {
196 sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl), 197 sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl),
197 sharedIframe: '<iframe src="' + window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl) + '"></iframe>', 198 sharedIframe: '<iframe src="' + window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl) + '"></iframe>',
@@ -250,7 +251,7 @@ export default { @@ -250,7 +251,7 @@ export default {
250 that.tracks = []; 251 that.tracks = [];
251 that.tracksLoading = true; 252 that.tracksLoading = true;
252 that.tracksNotLoaded = false; 253 that.tracksNotLoaded = false;
253 - if (tab.name == "codec") { 254 + if (tab.name === "codec") {
254 this.$axios({ 255 this.$axios({
255 method: 'get', 256 method: 'get',
256 url: '/zlm/' +this.mediaServerId+ '/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app='+ this.app +'&stream='+ this.streamId 257 url: '/zlm/' +this.mediaServerId+ '/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app='+ this.app +'&stream='+ this.streamId
@@ -340,7 +341,7 @@ export default { @@ -340,7 +341,7 @@ export default {
340 this.$refs.videoPlayer.pause() 341 this.$refs.videoPlayer.pause()
341 that.$axios({ 342 that.$axios({
342 method: 'post', 343 method: 'post',
343 - url: '/api/play/convert/' + that.streamId 344 + url: '/api/gb_record/convert/' + that.streamId
344 }).then(function (res) { 345 }).then(function (res) {
345 if (res.data.code == 0) { 346 if (res.data.code == 0) {
346 that.convertKey = res.data.key; 347 that.convertKey = res.data.key;
@@ -474,8 +475,8 @@ export default { @@ -474,8 +475,8 @@ export default {
474 console.log(this.seekTime) 475 console.log(this.seekTime)
475 if (that.streamId != "") { 476 if (that.streamId != "") {
476 that.stopPlayRecord(function () { 477 that.stopPlayRecord(function () {
477 - that.streamId = "",  
478 - that.playRecord(row); 478 + that.streamId = "";
  479 + that.playRecord(row);
479 }) 480 })
480 } else { 481 } else {
481 this.$axios({ 482 this.$axios({
@@ -506,22 +507,36 @@ export default { @@ -506,22 +507,36 @@ export default {
506 downloadRecord: function (row) { 507 downloadRecord: function (row) {
507 let that = this; 508 let that = this;
508 if (that.streamId != "") { 509 if (that.streamId != "") {
509 - that.stopDownloadRecord(function () {  
510 - that.streamId = "",  
511 - that.downloadRecord(row); 510 + that.stopDownloadRecord(function (res) {
  511 + if (res.code == 0) {
  512 + that.streamId = "";
  513 + that.downloadRecord(row);
  514 + }else {
  515 + this.$message({
  516 + showClose: true,
  517 + message: res.data.msg,
  518 + type: "error",
  519 + });
  520 + }
  521 +
512 }) 522 })
513 } else { 523 } else {
514 this.$axios({ 524 this.$axios({
515 method: 'get', 525 method: 'get',
516 - url: '/api/download/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' + 526 + url: '/api/gb_record/download/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' +
517 row.endTime + '&downloadSpeed=4' 527 row.endTime + '&downloadSpeed=4'
518 }).then(function (res) { 528 }).then(function (res) {
519 - var streamInfo = res.data;  
520 - that.app = streamInfo.app;  
521 - that.streamId = streamInfo.stream;  
522 - that.mediaServerId = streamInfo.mediaServerId;  
523 - that.videoUrl = that.getUrlByStreamInfo(streamInfo);  
524 - that.recordPlay = true; 529 + if (res.data.code == 0) {
  530 + let streamInfo = res.data.data;
  531 + that.recordPlay = false;
  532 + that.$refs.recordDownload.openDialog(that.deviceId, that.channelId, streamInfo.app, streamInfo.stream, streamInfo.mediaServerId);
  533 + }else {
  534 + that.$message({
  535 + showClose: true,
  536 + message: res.data.msg,
  537 + type: "error",
  538 + });
  539 + }
525 }); 540 });
526 } 541 }
527 }, 542 },
@@ -530,9 +545,9 @@ export default { @@ -530,9 +545,9 @@ export default {
530 this.videoUrl = ''; 545 this.videoUrl = '';
531 this.$axios({ 546 this.$axios({
532 method: 'get', 547 method: 'get',
533 - url: '/api/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.streamId  
534 - }).then(function (res) {  
535 - if (callback) callback() 548 + url: '/api/gb_record/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.streamId
  549 + }).then((res)=> {
  550 + if (callback) callback(res)
536 }); 551 });
537 }, 552 },
538 ptzCamera: function (command) { 553 ptzCamera: function (command) {
web_src/src/components/dialog/recordDownload.vue 0 → 100644
  1 +<template>
  2 +<div id="recordDownload" >
  3 + <el-dialog :title="title" v-if="showDialog" width="45rem" :append-to-body="true" :close-on-click-modal="false" :visible.sync="showDialog" :destroy-on-close="true" @close="close()" center>
  4 + <el-row>
  5 + <el-col :span="18" style="padding-top: 7px;">
  6 + <el-progress :percentage="percentage"></el-progress>
  7 + </el-col>
  8 + <el-col :span="6" >
  9 +<!-- <el-dropdown size="mini" title="播放倍速" style="margin-left: 1px;" @command="gbScale">-->
  10 +<!-- <el-button-group>-->
  11 +<!-- <el-button size="mini" style="width: 100%">-->
  12 +<!-- {{scale}}倍速 <i class="el-icon-arrow-down el-icon&#45;&#45;right"></i>-->
  13 +<!-- </el-button>-->
  14 +<!-- </el-button-group>-->
  15 +<!-- <el-dropdown-menu slot="dropdown">-->
  16 +<!-- <el-dropdown-item command="1">1倍速</el-dropdown-item>-->
  17 +<!-- <el-dropdown-item command="2">2倍速</el-dropdown-item>-->
  18 +<!-- <el-dropdown-item command="4">4倍速</el-dropdown-item>-->
  19 +<!-- </el-dropdown-menu>-->
  20 +<!-- </el-dropdown>-->
  21 + <el-button icon="el-icon-download" v-if="percentage < 100" size="mini" title="点击下载可将以缓存部分下载到本地" @click="download()">停止缓存并下载</el-button>
  22 + </el-col>
  23 + </el-row>
  24 + </el-dialog>
  25 +</div>
  26 +</template>
  27 +
  28 +
  29 +<script>
  30 +
  31 +import moment from "moment";
  32 +
  33 +export default {
  34 + name: 'recordDownload',
  35 + created() {
  36 +
  37 +
  38 + },
  39 + data() {
  40 + return {
  41 + title: "四倍速下载中...",
  42 + deviceId: "",
  43 + channelId: "",
  44 + app: "",
  45 + stream: "",
  46 + mediaServerId: "",
  47 + showDialog: false,
  48 + scale: 1,
  49 + percentage: 0.00,
  50 + streamInfo: null,
  51 + taskId: null,
  52 + getProgressRun: false,
  53 + getProgressForFileRun: false,
  54 +
  55 + };
  56 + },
  57 + methods: {
  58 + openDialog: function (deviceId, channelId, app, stream, mediaServerId) {
  59 + this.deviceId = deviceId;
  60 + this.channelId = channelId;
  61 + this.app = app;
  62 + this.stream = stream;
  63 + this.mediaServerId = mediaServerId;
  64 + this.showDialog = true;
  65 + this.getProgressRun = true;
  66 + this.percentage = 0.0;
  67 + this.getProgressTimer()
  68 + },
  69 + getProgressTimer(){
  70 + if (!this.getProgressRun) {
  71 + return;
  72 + }
  73 + if (this.percentage == 100 ) {
  74 + this.getFileDownload();
  75 + return;
  76 + }
  77 + setTimeout( ()=>{
  78 + if (!this.showDialog) return;
  79 + this.getProgress(this.getProgressTimer())
  80 + }, 5000)
  81 + },
  82 + getProgress: function (callback){
  83 + this.$axios({
  84 + method: 'get',
  85 + url: `/api/gb_record/download/progress/${this.deviceId}/${this.channelId}/${this.stream}`
  86 + }).then((res)=> {
  87 + console.log(res)
  88 + console.log(res.data.progress)
  89 + this.streamInfo = res.data;
  90 + if (parseFloat(res.data.progress) == 1) {
  91 + this.percentage = 100;
  92 + }else {
  93 + this.percentage = (res.data.progress*100).toFixed(1);
  94 + }
  95 + if (callback)callback();
  96 + }).catch((e) =>{
  97 +
  98 + });
  99 + },
  100 + close: function (){
  101 + if (this.streamInfo.progress < 100) {
  102 + this.stopDownloadRecord();
  103 + }
  104 + this.showDialog=false;
  105 + this.getProgressRun = false;
  106 + this.getProgressForFileRun = false;
  107 + },
  108 + gbScale: function (scale){
  109 + this.scale = scale;
  110 + },
  111 + download: function (){
  112 + this.getProgressRun = false;
  113 + if (this.streamInfo != null ) {
  114 + if (this.streamInfo.progress < 1) {
  115 + // 发送停止缓存
  116 + this.stopDownloadRecord((res)=>{
  117 + this.getFileDownload()
  118 + })
  119 + }else {
  120 + this.getFileDownload()
  121 + }
  122 + }
  123 + },
  124 + stopDownloadRecord: function (callback) {
  125 + this.$axios({
  126 + method: 'get',
  127 + url: '/api/gb_record/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.stream
  128 + }).then((res)=> {
  129 + if (callback) callback(res)
  130 + });
  131 + },
  132 + getFileDownload: function (){
  133 + this.$axios({
  134 + method: 'get',
  135 + url:`/record_proxy/${this.mediaServerId}/api/record/file/download/task/add`,
  136 + params: {
  137 + app: this.app,
  138 + stream: this.stream,
  139 + startTime: null,
  140 + endTime: null,
  141 + }
  142 + }).then((res) =>{
  143 + if (res.data.code === 0 && res.data.msg === "success") {
  144 + // 查询进度
  145 + this.title = "录像文件处理中..."
  146 + this.taskId = res.data.data;
  147 + this.percentage = 0.0;
  148 + this.getProgressForFileRun = true;
  149 + this.getProgressForFileTimer();
  150 + }
  151 + }).catch(function (error) {
  152 + console.log(error);
  153 + });
  154 + },
  155 + getProgressForFileTimer: function (){
  156 + if (!this.getProgressForFileRun || this.percentage == 100) {
  157 + return;
  158 + }
  159 + setTimeout( ()=>{
  160 + if (!this.showDialog) return;
  161 + this.getProgressForFile(this.getProgressForFileTimer())
  162 + }, 1000)
  163 + },
  164 + getProgressForFile: function (callback){
  165 + this.$axios({
  166 + method: 'get',
  167 + url:`/record_proxy/${this.mediaServerId}/api/record/file/download/task/list`,
  168 + params: {
  169 + app: this.app,
  170 + stream: this.stream,
  171 + taskId: this.taskId,
  172 + isEnd: true,
  173 + }
  174 + }).then((res) => {
  175 + if (res.data.code == 0) {
  176 + this.percentage = parseFloat(res.data.data.percentage)*100
  177 + if (res.data.data[0].percentage === '1') {
  178 + this.getProgressForFileRun = false;
  179 + window.open(res.data.data[0].downloadFile)
  180 + this.close();
  181 + }else {
  182 + if (callback)callback()
  183 + }
  184 + }
  185 + }).catch(function (error) {
  186 + console.log(error);
  187 + });
  188 + }
  189 + }
  190 +};
  191 +</script>
  192 +
  193 +<style>
  194 +
  195 +</style>
web_src/src/components/test.vue deleted 100644 → 0
1 -<template>  
2 -<div id="test">  
3 - <div class="timeQuery" id="timeQuery">  
4 - <div class="timeQuery-background" ></div>  
5 - <div class="timeQuery-pointer">  
6 - <div class="timeQuery-pointer-content" id="timeQueryPointer">  
7 - <div class="timeQuery-pointer-handle" @mousedown.left="mousedownHandler" ></div>  
8 - </div>  
9 - </div>  
10 -  
11 - <div class="timeQuery-data" >  
12 -  
13 - <div class="timeQuery-data-cell" v-for="item of recordData" :style="'width:' + getDataWidth(item) + '%; left:' + getDataLeft(item) + '%'" ></div>  
14 - <!-- <div class="timeQuery-data-cell" style="width: 30%; left: 20%" @click="timeChoose"></div>-->  
15 - <!-- <div class="timeQuery-data-cell" style="width: 60%; left: 20%" @click="timeChoose"></div>-->  
16 - </div>  
17 -  
18 - <div class="timeQuery-label" >  
19 - <div class="timeQuery-label-cell" style="left: 0%">  
20 - <div class="timeQuery-label-cell-label">0</div>  
21 - </div>  
22 - <div v-for="index of timeNode" class="timeQuery-label-cell" :style="'left:' + (100.0/timeNode*index).toFixed(4) + '%'">  
23 - <div class="timeQuery-label-cell-label">{{24/timeNode * index}}</div>  
24 - </div>  
25 - </div>  
26 - </div>  
27 -  
28 -</div>  
29 -</template>  
30 -  
31 -<script>  
32 -export default {  
33 - name: "test",  
34 - data() {  
35 - return {  
36 - mouseDown: false,  
37 - timeNode: 24,  
38 - recordData:[  
39 - {  
40 - startTime: "2021-04-18 00:00:00",  
41 - endTime: "2021-04-18 00:00:09",  
42 - },  
43 - {  
44 - startTime: "2021-04-18 00:00:09",  
45 - endTime: "2021-04-18 01:00:05",  
46 - },  
47 - {  
48 - startTime: "2021-04-18 02:00:01",  
49 - endTime: "2021-04-18 04:25:05",  
50 - },  
51 - {  
52 - startTime: "2021-04-18 05:00:01",  
53 - endTime: "2021-04-18 20:00:05",  
54 - },  
55 - ]  
56 - };  
57 - },  
58 - mounted() {  
59 - document.body.addEventListener("mouseup", this.mouseupHandler, false)  
60 - document.body.addEventListener("mousemove", this.mousemoveHandler, false)  
61 - },  
62 - methods:{  
63 - getTimeNode(){  
64 - let mine = 20  
65 - let width = document.getElementById("timeQuery").offsetWidth  
66 - if (width/20 > 24){  
67 - return 24  
68 - }else if (width/20 > 12) {  
69 - return 12  
70 - }else if (width/20 > 6) {  
71 - return 6  
72 - }  
73 - },  
74 - timeChoose(event){  
75 - console.log(event)  
76 - },  
77 - getDataWidth(item){  
78 - let startTime = new Date(item.startTime);  
79 - let endTime = new Date(item.endTime);  
80 - let result = parseFloat((endTime.getTime() - startTime.getTime())/(24*60*60*10))  
81 - // console.log(result)  
82 - return parseFloat((endTime.getTime() - startTime.getTime())/(24*60*60*10))  
83 - },  
84 - getDataLeft(item){  
85 - let startTime = new Date(item.startTime);  
86 - let differenceTime = startTime.getTime() - new Date(item.startTime.substr(0,10) + " 00:00:00").getTime()  
87 - let result = differenceTime/(24*60*60*10)  
88 - console.log(differenceTime)  
89 - console.log(result)  
90 - return parseFloat(differenceTime/(24*60*60*10));  
91 - },  
92 - mousedownHandler(event){  
93 - this.mouseDown = true  
94 - },  
95 - mousemoveHandler(event){  
96 - if (this.mouseDown){  
97 - document.getElementById("timeQueryPointer").style.left = (event.clientX - 20)+ "px"  
98 - }  
99 - },  
100 - mouseupHandler(event){  
101 - this.mouseDown = false  
102 - }  
103 - }  
104 -}  
105 -</script>  
106 -  
107 -<style scoped>  
108 - .timeQuery{  
109 - width: 96%;  
110 - margin-left: 2%;  
111 - margin-right: 2%;  
112 - margin-top: 20%;  
113 - position: absolute;  
114 - }  
115 - .timeQuery-background{  
116 - height: 16px;  
117 - width: 100%;  
118 - background-color: #ececec;  
119 - position: absolute;  
120 - left: 0;  
121 - top: 0;  
122 - z-index: 10;  
123 - box-shadow: #9d9d9d 0px 0px 10px inset;  
124 - }  
125 - .timeQuery-data{  
126 - height: 16px;  
127 - width: 100%;  
128 - position: absolute;  
129 - left: 0;  
130 - top: 0;  
131 - z-index: 11;  
132 - }  
133 - .timeQuery-data-cell{  
134 - height: 10px;  
135 - background-color: #888787;  
136 - position: absolute;  
137 - z-index: 11;  
138 - -webkit-box-shadow: #9d9d9d 0px 0px 10px inset;  
139 - margin-top: 3px;  
140 - top: 100%;  
141 - }  
142 - .timeQuery-label{  
143 - height: 16px;  
144 - width: 100%;  
145 - position: absolute;  
146 - pointer-events: none;  
147 - left: 0;  
148 - top: 0;  
149 - z-index: 11;  
150 - }  
151 - .timeQuery-label-cell{  
152 - height: 16px;  
153 - position: absolute;  
154 - z-index: 12;  
155 - width: 0px;  
156 - border-right: 1px solid #b7b7b7;  
157 - }  
158 - .timeQuery-label-cell-label {  
159 - width: 23px;  
160 - text-align: center;  
161 - height: 18px;  
162 - margin-left: -10px;  
163 - margin-top: -30px;  
164 - color: #444;  
165 - }  
166 - .timeQuery-pointer{  
167 - width: 0px;  
168 - height: 18px;  
169 - position: absolute;  
170 - left: 0;  
171 - }  
172 - .timeQuery-pointer-content{  
173 - width: 0px;  
174 - height: 70px;  
175 - position: absolute;  
176 - border-right: 2px solid #f60303;  
177 - z-index: 14;  
178 - top: -30px;  
179 - }  
180 - .timeQuery-pointer-handle {  
181 - width: 0;  
182 - height: 0;  
183 - border-top: 12px solid transparent;  
184 - border-right: 12px solid transparent;  
185 - border-bottom: 20px solid #ff0909;  
186 - border-left: 12px solid transparent;  
187 - cursor: no-drop;  
188 - position: absolute;  
189 - left: -11px;  
190 - top: 50px;  
191 - }  
192 - /*.timeQuery-cell:after{*/  
193 - /* content: "";*/  
194 - /* height: 14px;*/  
195 - /* border: 1px solid #e70303;*/  
196 - /* position: absolute;*/  
197 - /*}*/  
198 -</style>  
web_src/src/components/test2.vue deleted 100644 → 0
1 -<template>  
2 -<div id="test2">  
3 - <div class="timeQuery" style="width: 100%; height: 300px" id="timeQuery">  
4 - </div>  
5 -</div>  
6 -</template>  
7 -  
8 -<script>  
9 -  
10 -import * as echarts from 'echarts';  
11 -  
12 -export default {  
13 - name: "test2",  
14 - data() {  
15 - return {  
16 - };  
17 - },  
18 - mounted() {  
19 - var base = +new Date("2021-02-02 00:00:00");  
20 - var oneDay = 24 * 3600 * 1000;  
21 -  
22 - var data = [[base, 10]];  
23 -  
24 - for (var i = 1; i < 24; i++) {  
25 - var now = new Date(base += oneDay);  
26 - data.push([  
27 - new Date("2021-02-02 " + i+":00:00"), 10  
28 - ]);  
29 - }  
30 - // 基于准备好的dom,初始化echarts实例  
31 - var myChart = echarts.init(document.getElementById('timeQuery'));  
32 - let option = {  
33 -  
34 - toolbox: {  
35 - feature: {  
36 - dataZoom: {  
37 - yAxisIndex: 'none'  
38 - },  
39 - restore: {},  
40 - saveAsImage: {}  
41 - }  
42 - },  
43 - xAxis: {  
44 - type: 'time',  
45 - boundaryGap: false  
46 - },  
47 - yAxis: {  
48 - type: 'value',  
49 - show: false,  
50 - splitLine:{show: false}, //去除网格线  
51 - boundaryGap: [0, '100%']  
52 - },  
53 - dataZoom: [{  
54 - type: 'inside',  
55 - start: 0,  
56 - end: 20  
57 - }, {  
58 - start: 0,  
59 - end: 20  
60 - }],  
61 - series: [  
62 - {  
63 - name: '模拟数据',  
64 - type: 'line',  
65 - smooth: false,  
66 - symbol: 'none',  
67 - areaStyle: {},  
68 - data: data  
69 - }  
70 - ]  
71 - };  
72 - // 绘制图表  
73 - myChart.setOption(option);  
74 - },  
75 - methods:{  
76 - getTimeNode(){  
77 - let mine = 20  
78 - let width = document.getElementById("timeQuery").offsetWidth  
79 - if (width/20 > 24){  
80 - return 24  
81 - }else if (width/20 > 12) {  
82 - return 12  
83 - }else if (width/20 > 6) {  
84 - return 6  
85 - }  
86 - },  
87 - hoveEvent(event){  
88 - console.log(2222222)  
89 - console.log(event)  
90 - },  
91 - timeChoose(event){  
92 - console.log(event)  
93 - },  
94 - getDataWidth(item){  
95 - let startTime = new Date(item.startTime);  
96 - let endTime = new Date(item.endTime);  
97 - let result = parseFloat((endTime.getTime() - startTime.getTime())/(24*60*60*10))  
98 - // console.log(result)  
99 - return parseFloat((endTime.getTime() - startTime.getTime())/(24*60*60*10))  
100 - },  
101 - getDataLeft(item){  
102 - let startTime = new Date(item.startTime);  
103 - let differenceTime = startTime.getTime() - new Date(item.startTime.substr(0,10) + " 00:00:00").getTime()  
104 - let result = differenceTime/(24*60*60*10)  
105 - console.log(differenceTime)  
106 - console.log(result)  
107 - return parseFloat(differenceTime/(24*60*60*10));  
108 - }  
109 - }  
110 -}  
111 -</script>  
112 -  
113 -<style scoped>  
114 - .timeQuery{  
115 - width: 96%;  
116 - margin-left: 2%;  
117 - margin-right: 2%;  
118 - margin-top: 20%;  
119 - position: absolute;  
120 - }  
121 - .timeQuery-background{  
122 - height: 16px;  
123 - width: 100%;  
124 - background-color: #ececec;  
125 - position: absolute;  
126 - left: 0;  
127 - top: 0;  
128 - z-index: 10;  
129 - box-shadow: #9d9d9d 0px 0px 10px inset;  
130 - }  
131 - .timeQuery-data{  
132 - height: 16px;  
133 - width: 100%;  
134 - position: absolute;  
135 - left: 0;  
136 - top: 0;  
137 - z-index: 11;  
138 - }  
139 - .timeQuery-data-cell{  
140 - height: 10px;  
141 - background-color: #888787;  
142 - position: absolute;  
143 - z-index: 11;  
144 - -webkit-box-shadow: #9d9d9d 0px 0px 10px inset;  
145 - margin-top: 3px;  
146 - }  
147 - .timeQuery-label{  
148 - height: 16px;  
149 - width: 100%;  
150 - position: absolute;  
151 - pointer-events: none;  
152 - left: 0;  
153 - top: 0;  
154 - z-index: 11;  
155 - }  
156 - .timeQuery-label-cell{  
157 - height: 16px;  
158 - position: absolute;  
159 - z-index: 12;  
160 - width: 0px;  
161 - border-right: 1px solid #b7b7b7;  
162 - }  
163 - .timeQuery-label-cell-label {  
164 - width: 23px;  
165 - text-align: center;  
166 - height: 18px;  
167 - margin-left: -10px;  
168 - margin-top: -30px;  
169 - color: #444;  
170 - }  
171 - .timeQuery-pointer{  
172 - width: 0px;  
173 - height: 18px;  
174 - position: absolute;  
175 - left: 0;  
176 - }  
177 - .timeQuery-pointer-content{  
178 - width: 0px;  
179 - height: 16px;  
180 - position: absolute;  
181 - border-right: 3px solid #f60303;  
182 - z-index: 14;  
183 - }  
184 - /*.timeQuery-cell:after{*/  
185 - /* content: "";*/  
186 - /* height: 14px;*/  
187 - /* border: 1px solid #e70303;*/  
188 - /* position: absolute;*/  
189 - /*}*/  
190 -</style>  
web_src/src/router/index.js
@@ -11,7 +11,6 @@ import login from &#39;../components/Login.vue&#39; @@ -11,7 +11,6 @@ import login from &#39;../components/Login.vue&#39;
11 import parentPlatformList from '../components/ParentPlatformList.vue' 11 import parentPlatformList from '../components/ParentPlatformList.vue'
12 import cloudRecord from '../components/CloudRecord.vue' 12 import cloudRecord from '../components/CloudRecord.vue'
13 import mediaServerManger from '../components/MediaServerManger.vue' 13 import mediaServerManger from '../components/MediaServerManger.vue'
14 -import test from '../components/test.vue'  
15 import web from '../components/setting/Web.vue' 14 import web from '../components/setting/Web.vue'
16 import sip from '../components/setting/Sip.vue' 15 import sip from '../components/setting/Sip.vue'
17 import media from '../components/setting/Media.vue' 16 import media from '../components/setting/Media.vue'
@@ -97,11 +96,6 @@ export default new VueRouter({ @@ -97,11 +96,6 @@ export default new VueRouter({
97 component: media, 96 component: media,
98 }, 97 },
99 { 98 {
100 - path: '/test',  
101 - name: 'test',  
102 - component: test,  
103 - },  
104 - {  
105 path: '/play/wasm/:url', 99 path: '/play/wasm/:url',
106 name: 'wasmPlayer', 100 name: 'wasmPlayer',
107 component: wasmPlayer, 101 component: wasmPlayer,