Commit ad36354ef46a31f24b2583263f575d6736c0ad28

Authored by 648540858
1 parent fc77b3f8

优化国标录像服务端,使用zlm新接口实现功能

src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
1 1 package com.genersoft.iot.vmp.common;
2 2  
  3 +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
3 4 import io.swagger.v3.oas.annotations.media.Schema;
4 5  
5 6 import java.io.Serializable;
... ... @@ -76,6 +77,8 @@ public class StreamInfo implements Serializable, Cloneable{
76 77 private String endTime;
77 78 @Schema(description = "进度(录像下载使用)")
78 79 private double progress;
  80 + @Schema(description = "文件下载地址(录像下载使用)")
  81 + private DownloadFileInfo downLoadFilePath;
79 82  
80 83 @Schema(description = "是否暂停(录像回放使用)")
81 84 private boolean pause;
... ... @@ -605,5 +608,11 @@ public class StreamInfo implements Serializable, Cloneable{
605 608 this.subStream = subStream;
606 609 }
607 610  
  611 + public DownloadFileInfo getDownLoadFilePath() {
  612 + return downLoadFilePath;
  613 + }
608 614  
  615 + public void setDownLoadFilePath(DownloadFileInfo downLoadFilePath) {
  616 + this.downLoadFilePath = downLoadFilePath;
  617 + }
609 618 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
... ... @@ -275,7 +275,7 @@ public class ZLMHttpHookListener {
275 275 List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream());
276 276 if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
277 277  
278   - // 为录制国标模拟一个鉴权信息
  278 + // 为录制国标模拟一个鉴权信息, 方便后续写入录像文件时使用
279 279 StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
280 280 streamAuthorityInfo.setApp(param.getApp());
281 281 streamAuthorityInfo.setStream(ssrcTransactionForAll.get(0).getStream());
... ... @@ -291,8 +291,18 @@ public class ZLMHttpHookListener {
291 291 }
292 292 // 如果是录像下载就设置视频间隔十秒
293 293 if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) {
294   - result.setMp4_max_second(30);
295   - result.setEnable_mp4(true);
  294 + // 获取录像的总时长,然后设置为这个视频的时长
  295 + InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, param.getStream());
  296 + if (inviteInfo.getStreamInfo() != null ) {
  297 + String startTime = inviteInfo.getStreamInfo().getStartTime();
  298 + String endTime = inviteInfo.getStreamInfo().getEndTime();
  299 + long difference = DateUtil.getDifference(startTime, endTime)/1000;
  300 + result.setMp4_max_second((int)difference);
  301 + result.setEnable_mp4(true);
  302 + // 设置为2保证得到的mp4的时长是正常的
  303 + result.setModify_stamp(2);
  304 + }
  305 +
296 306 }
297 307 }
298 308 if (param.getApp().equalsIgnoreCase("rtp")) {
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java
... ... @@ -41,4 +41,15 @@ public class HookSubscribeFactory {
41 41  
42 42 return hookSubscribe;
43 43 }
  44 +
  45 + public static HookSubscribeForRecordMp4 on_record_mp4(String mediaServerId, String app, String stream) {
  46 + HookSubscribeForRecordMp4 hookSubscribe = new HookSubscribeForRecordMp4();
  47 + JSONObject subscribeKey = new com.alibaba.fastjson2.JSONObject();
  48 + subscribeKey.put("app", app);
  49 + subscribeKey.put("stream", stream);
  50 + subscribeKey.put("mediaServerId", mediaServerId);
  51 + hookSubscribe.setContent(subscribeKey);
  52 +
  53 + return hookSubscribe;
  54 + }
44 55 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForRecordMp4.java 0 → 100755
  1 +package com.genersoft.iot.vmp.media.zlm.dto;
  2 +
  3 +import com.alibaba.fastjson2.JSONObject;
  4 +import com.alibaba.fastjson2.annotation.JSONField;
  5 +
  6 +import java.time.Instant;
  7 +
  8 +/**
  9 + * hook订阅-录像完成
  10 + * @author lin
  11 + */
  12 +public class HookSubscribeForRecordMp4 implements IHookSubscribe{
  13 +
  14 + private HookType hookType = HookType.on_record_mp4;
  15 +
  16 + private JSONObject content;
  17 +
  18 + @JSONField(format="yyyy-MM-dd HH:mm:ss")
  19 + private Instant expires;
  20 +
  21 + @Override
  22 + public HookType getHookType() {
  23 + return hookType;
  24 + }
  25 +
  26 + @Override
  27 + public JSONObject getContent() {
  28 + return content;
  29 + }
  30 +
  31 + public void setContent(JSONObject content) {
  32 + this.content = content;
  33 + }
  34 +
  35 + @Override
  36 + public Instant getExpires() {
  37 + return expires;
  38 + }
  39 +
  40 + @Override
  41 + public void setExpires(Instant expires) {
  42 + this.expires = expires;
  43 + }
  44 +}
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java
... ... @@ -7,6 +7,7 @@ public class HookResultForOnPublish extends HookResult{
7 7 private int mp4_max_second;
8 8 private String mp4_save_path;
9 9 private String stream_replace;
  10 + private Integer modify_stamp;
10 11  
11 12 public HookResultForOnPublish() {
12 13 }
... ... @@ -60,14 +61,23 @@ public class HookResultForOnPublish extends HookResult{
60 61 this.stream_replace = stream_replace;
61 62 }
62 63  
  64 + public Integer getModify_stamp() {
  65 + return modify_stamp;
  66 + }
  67 +
  68 + public void setModify_stamp(Integer modify_stamp) {
  69 + this.modify_stamp = modify_stamp;
  70 + }
  71 +
63 72 @Override
64 73 public String toString() {
65 74 return "HookResultForOnPublish{" +
66 75 "enable_audio=" + enable_audio +
67 76 ", enable_mp4=" + enable_mp4 +
68 77 ", mp4_max_second=" + mp4_max_second +
69   - ", stream_replace=" + stream_replace +
70 78 ", mp4_save_path='" + mp4_save_path + '\'' +
  79 + ", stream_replace='" + stream_replace + '\'' +
  80 + ", modify_stamp='" + modify_stamp + '\'' +
71 81 '}';
72 82 }
73 83 }
... ...
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
... ... @@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.common.StreamInfo;
4 4 import com.genersoft.iot.vmp.conf.exception.ServiceException;
5 5 import com.genersoft.iot.vmp.gb28181.bean.Device;
6 6 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  7 +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
7 8 import com.genersoft.iot.vmp.service.bean.ErrorCallback;
8 9 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
9 10  
... ... @@ -44,5 +45,5 @@ public interface IPlayService {
44 45  
45 46 void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback);
46 47  
47   -
  48 + void getFilePath(String deviceId, String channelId, String stream, ErrorCallback<DownloadFileInfo> callback);
48 49 }
... ...
src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java 0 → 100644
  1 +package com.genersoft.iot.vmp.service.bean;
  2 +
  3 +public class DownloadFileInfo {
  4 +
  5 + private String httpPath;
  6 + private String httpsPath;
  7 + private String httpDomainPath;
  8 + private String httpsDomainPath;
  9 +
  10 + public String getHttpPath() {
  11 + return httpPath;
  12 + }
  13 +
  14 + public void setHttpPath(String httpPath) {
  15 + this.httpPath = httpPath;
  16 + }
  17 +
  18 + public String getHttpsPath() {
  19 + return httpsPath;
  20 + }
  21 +
  22 + public void setHttpsPath(String httpsPath) {
  23 + this.httpsPath = httpsPath;
  24 + }
  25 +
  26 + public String getHttpDomainPath() {
  27 + return httpDomainPath;
  28 + }
  29 +
  30 + public void setHttpDomainPath(String httpDomainPath) {
  31 + this.httpDomainPath = httpDomainPath;
  32 + }
  33 +
  34 + public String getHttpsDomainPath() {
  35 + return httpsDomainPath;
  36 + }
  37 +
  38 + public void setHttpsDomainPath(String httpsDomainPath) {
  39 + this.httpsDomainPath = httpsDomainPath;
  40 + }
  41 +}
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
... ... @@ -592,6 +592,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
592 592 if (mediaServerItem.getRecordPath() != null) {
593 593 File recordPathFile = new File(mediaServerItem.getRecordPath());
594 594 param.put("protocol.mp4_save_path", recordPathFile.getParentFile().getPath());
  595 + param.put("protocol.downloadRoot", recordPathFile.getParentFile().getPath());
595 596 param.put("record.appName", recordPathFile.getName());
596 597 }
597 598  
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
... ... @@ -67,7 +67,7 @@ public class MediaServiceImpl implements IMediaService {
67 67 if (data == null) {
68 68 return null;
69 69 }
70   - JSONObject mediaJSON = JSON.parseObject(JSON.toJSONString(data.get(0)), JSONObject.class);
  70 + JSONObject mediaJSON = data.getJSONObject(0);
71 71 JSONArray tracks = mediaJSON.getJSONArray("tracks");
72 72 if (authority) {
73 73 streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld);
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.alibaba.fastjson2.JSON;
  4 +import com.alibaba.fastjson2.JSONArray;
3 5 import com.alibaba.fastjson2.JSONObject;
4 6 import com.genersoft.iot.vmp.common.InviteInfo;
5 7 import com.genersoft.iot.vmp.common.InviteSessionStatus;
... ... @@ -21,16 +23,12 @@ import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
21 23 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
22 24 import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
23 25 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
24   -import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
25   -import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
26   -import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  26 +import com.genersoft.iot.vmp.media.zlm.dto.*;
27 27 import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
  28 +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
28 29 import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
29 30 import com.genersoft.iot.vmp.service.*;
30   -import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
31   -import com.genersoft.iot.vmp.service.bean.ErrorCallback;
32   -import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
33   -import com.genersoft.iot.vmp.service.bean.SSRCInfo;
  31 +import com.genersoft.iot.vmp.service.bean.*;
34 32 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
35 33 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
36 34 import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
... ... @@ -77,7 +75,7 @@ public class PlayServiceImpl implements IPlayService {
77 75 private IInviteStreamService inviteStreamService;
78 76  
79 77 @Autowired
80   - private DeferredResultHolder resultHolder;
  78 + private ZlmHttpHookSubscribe subscribe;
81 79  
82 80 @Autowired
83 81 private ZLMRESTfulUtils zlmresTfulUtils;
... ... @@ -86,9 +84,6 @@ public class PlayServiceImpl implements IPlayService {
86 84 private ZLMServerFactory zlmServerFactory;
87 85  
88 86 @Autowired
89   - private AssistRESTfulUtils assistRESTfulUtils;
90   -
91   - @Autowired
92 87 private IMediaService mediaService;
93 88  
94 89 @Autowired
... ... @@ -107,9 +102,6 @@ public class PlayServiceImpl implements IPlayService {
107 102 private DynamicTask dynamicTask;
108 103  
109 104 @Autowired
110   - private ZlmHttpHookSubscribe subscribe;
111   -
112   - @Autowired
113 105 private CloudRecordServiceMapper cloudRecordServiceMapper;
114 106  
115 107  
... ... @@ -741,60 +733,147 @@ public class PlayServiceImpl implements IPlayService {
741 733 @Override
742 734 public StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream) {
743 735 InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, stream);
  736 + if (inviteInfo == null || inviteInfo.getStreamInfo() == null) {
  737 + logger.warn("[获取下载进度] 未查询到录像下载的信息");
  738 + return null;
  739 + }
744 740  
745   - if (inviteInfo != null && inviteInfo.getStreamInfo() != null) {
746   - if (inviteInfo.getStreamInfo().getProgress() == 1) {
747   - return inviteInfo.getStreamInfo();
748   - }
  741 + if (inviteInfo.getStreamInfo().getProgress() == 1) {
  742 + return inviteInfo.getStreamInfo();
  743 + }
749 744  
750   - // 获取当前已下载时长
751   - String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId();
752   - MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
753   - if (mediaServerItem == null) {
754   - logger.warn("查询录像信息时发现节点已离线");
755   - return null;
756   - }
757   - if (mediaServerItem.getRecordAssistPort() == 0) {
758   - throw new ControllerException(ErrorCode.ERROR100.getCode(), "未配置Assist服务,无法完成录像下载");
759   - }
760   - SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, null, stream);
  745 + // 获取当前已下载时长
  746 + String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId();
  747 + MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
  748 + if (mediaServerItem == null) {
  749 + logger.warn("[获取下载进度] 查询录像信息时发现节点不存在");
  750 + return null;
  751 + }
  752 + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, null, stream);
761 753  
762   - if (ssrcTransaction == null) {
763   - logger.warn("[获取下载进度],未找到下载事务信息");
764   - return null;
765   - }
  754 + if (ssrcTransaction == null) {
  755 + logger.warn("[获取下载进度] 下载已结束");
  756 + return null;
  757 + }
766 758  
767   - // 为了支持多个数据库,这里不能使用求和函数来直接获取总数了
768   - List<CloudRecordItem> cloudRecordItemList = cloudRecordServiceMapper.getList(null, "rtp", inviteInfo.getStream(), null, null, ssrcTransaction.getCallId(), null);
  759 + JSONObject mediaListJson= zlmresTfulUtils.getMediaList(mediaServerItem, "rtp", stream);
  760 + if (mediaListJson == null) {
  761 + logger.warn("[获取下载进度] 从zlm查询进度失败");
  762 + return null;
  763 + }
  764 + if (mediaListJson.getInteger("code") != 0) {
  765 + logger.warn("[获取下载进度] 从zlm查询进度出现错误: {}", mediaListJson.getString("msg"));
  766 + return null;
  767 + }
  768 + JSONArray data = mediaListJson.getJSONArray("data");
  769 + if (data == null) {
  770 + logger.warn("[获取下载进度] 从zlm查询进度时未返回数据");
  771 + return null;
  772 + }
  773 + JSONObject mediaJSON = data.getJSONObject(0);
  774 + JSONArray tracks = mediaJSON.getJSONArray("tracks");
  775 + if (tracks.isEmpty()) {
  776 + logger.warn("[获取下载进度] 从zlm查询进度时未返回数据");
  777 + return null;
  778 + }
  779 + JSONObject jsonObject = tracks.getJSONObject(0);
  780 + long duration = jsonObject.getLongValue("duration");
  781 + if (duration == 0) {
  782 + inviteInfo.getStreamInfo().setProgress(0);
  783 + } else {
  784 + String startTime = inviteInfo.getStreamInfo().getStartTime();
  785 + String endTime = inviteInfo.getStreamInfo().getEndTime();
  786 + // 此时start和end单位是秒
  787 + long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
  788 + long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
  789 +
  790 + BigDecimal currentCount = new BigDecimal(duration);
  791 + BigDecimal totalCount = new BigDecimal((end - start) * 1000);
  792 + BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP);
  793 + double process = divide.doubleValue();
  794 + inviteInfo.getStreamInfo().setProgress(process);
  795 + }
  796 + inviteStreamService.updateInviteInfo(inviteInfo);
  797 + return inviteInfo.getStreamInfo();
  798 + }
769 799  
770   - if (cloudRecordItemList.isEmpty()) {
771   - logger.warn("[获取下载进度],未找到下载视频信息");
772   - return null;
773   - }
774   - long duration = 0;
775   - for (CloudRecordItem cloudRecordItem : cloudRecordItemList) {
776   - duration += cloudRecordItem.getTimeLen();
777   - }
778   - if (duration == 0) {
779   - inviteInfo.getStreamInfo().setProgress(0);
780   - } else {
781   - String startTime = inviteInfo.getStreamInfo().getStartTime();
782   - String endTime = inviteInfo.getStreamInfo().getEndTime();
783   - // 此时start和end单位是秒
784   - long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
785   - long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
786   -
787   - BigDecimal currentCount = new BigDecimal(duration);
788   - BigDecimal totalCount = new BigDecimal((end - start) * 1000);
789   - BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP);
790   - double process = divide.doubleValue();
791   - inviteInfo.getStreamInfo().setProgress(process);
792   - }
  800 + @Override
  801 + public void getFilePath(String deviceId, String channelId, String stream, ErrorCallback<DownloadFileInfo> callback) {
  802 + InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, stream);
  803 + if (inviteInfo == null || inviteInfo.getStreamInfo() == null) {
  804 + logger.warn("[获取录像下载文件地址] 未查询到录像下载的信息, {}/{}-{}", deviceId, channelId, stream);
  805 + callback.run(ErrorCode.ERROR100.getCode(), "未查询到录像下载的信息", null);
  806 + return ;
  807 + }
  808 +
  809 + if (!ObjectUtils.isEmpty(inviteInfo.getStreamInfo().getDownLoadFilePath())) {
  810 + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(),
  811 + inviteInfo.getStreamInfo().getDownLoadFilePath());
  812 + return;
  813 + }
  814 +
  815 + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo("rtp", stream);
  816 + if (streamAuthorityInfo == null) {
  817 + logger.warn("[获取录像下载文件地址] 未查询到录像的视频信息, {}/{}-{}", deviceId, channelId, stream);
  818 + callback.run(ErrorCode.ERROR100.getCode(), "未查询到录像的视频信息", null);
  819 + return ;
  820 + }
  821 +
  822 + // 获取当前已下载时长
  823 + String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId();
  824 + MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
  825 + if (mediaServerItem == null) {
  826 + logger.warn("[获取录像下载文件地址] 查询录像信息时发现节点不存在, {}/{}-{}", deviceId, channelId, stream);
  827 + callback.run(ErrorCode.ERROR100.getCode(), "查询录像信息时发现节点不存在", null);
  828 + return ;
  829 + }
  830 +
  831 + List<CloudRecordItem> cloudRecordItemList = cloudRecordServiceMapper.getListByCallId(streamAuthorityInfo.getCallId());
  832 + if (!cloudRecordItemList.isEmpty()) {
  833 + String filePath = cloudRecordItemList.get(0).getFilePath();
  834 +
  835 + DownloadFileInfo downloadFileInfo = getDownloadFilePath(mediaServerItem, filePath);
  836 + inviteInfo.getStreamInfo().setDownLoadFilePath(downloadFileInfo);
793 837 inviteStreamService.updateInviteInfo(inviteInfo);
  838 + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), downloadFileInfo);
  839 + }else {
  840 + // 可能尚未生成,那就监听hook等着收到对应的录像通知
  841 + ZlmHttpHookSubscribe.Event hookEvent = (mediaServerItemInuse, hookParam) -> {
  842 + logger.info("[录像下载]收到订阅消息: , {}/{}-{}", deviceId, channelId, stream);
  843 + logger.info("[录像下载]收到订阅消息内容: " + hookParam);
  844 + dynamicTask.stop(streamAuthorityInfo.getCallId());
  845 + OnRecordMp4HookParam recordMp4HookParam = (OnRecordMp4HookParam)hookParam;
  846 + String filePath = recordMp4HookParam.getFile_path();
  847 + DownloadFileInfo downloadFileInfo = getDownloadFilePath(mediaServerItem, filePath);
  848 + inviteInfo.getStreamInfo().setDownLoadFilePath(downloadFileInfo);
  849 + inviteStreamService.updateInviteInfo(inviteInfo);
  850 + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), downloadFileInfo);
  851 + };
  852 + HookSubscribeForRecordMp4 hookSubscribe = HookSubscribeFactory.on_record_mp4(mediaServerId, "rtp", stream);
  853 + subscribe.addSubscribe(hookSubscribe, hookEvent);
  854 +
  855 + // 设置超时,超时结束监听
  856 + dynamicTask.startDelay(streamAuthorityInfo.getCallId(), ()->{
  857 + logger.info("[录像下载] 接收hook超时, {}/{}-{}", deviceId, channelId, stream);
  858 + subscribe.removeSubscribe(hookSubscribe);
  859 + callback.run(ErrorCode.ERROR100.getCode(), "接收hook超时", null);
  860 + }, 10000);
  861 + }
  862 + }
794 863  
795   - return inviteInfo.getStreamInfo();
  864 + private DownloadFileInfo getDownloadFilePath(MediaServerItem mediaServerItem, String filePath) {
  865 + DownloadFileInfo downloadFileInfo = new DownloadFileInfo();
  866 +
  867 + String pathTemplate = "%s://%s:%s/index/api/downloadFile?file_path=" + filePath;
  868 +
  869 + downloadFileInfo.setHttpPath(String.format(pathTemplate, "http", mediaServerItem.getStreamIp(),
  870 + mediaServerItem.getHttpPort()));
  871 +
  872 + if (mediaServerItem.getHttpSSlPort() > 0) {
  873 + downloadFileInfo.setHttpsPath(String.format(pathTemplate, "https", mediaServerItem.getStreamIp(),
  874 + mediaServerItem.getHttpSSlPort()));
796 875 }
797   - return null;
  876 + return downloadFileInfo;
798 877 }
799 878  
800 879 private StreamInfo onPublishHandlerForDownload(MediaServerItem mediaServerItemInuse, HookParam hookParam, String deviceId, String channelId, String startTime, String endTime) {
... ...
src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java
... ... @@ -106,4 +106,10 @@ public interface CloudRecordServiceMapper {
106 106 " </script>")
107 107 int deleteList(List<CloudRecordItem> cloudRecordItemIdList);
108 108  
  109 + @Select(" <script>" +
  110 + "select *" +
  111 + " from wvp_cloud_record " +
  112 + "where call_id = #{callId}" +
  113 + " </script>")
  114 + List<CloudRecordItem> getListByCallId(@Param("callId") String callId);
109 115 }
... ...
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java
... ... @@ -139,4 +139,13 @@ public class DateUtil {
139 139 Instant beforeInstant = Instant.from(formatter.parse(keepaliveTime));
140 140 return ChronoUnit.MILLIS.between(beforeInstant, Instant.now());
141 141 }
  142 +
  143 + public static long getDifference(String startTime, String endTime) {
  144 + if (ObjectUtils.isEmpty(startTime) || ObjectUtils.isEmpty(endTime)) {
  145 + return 0;
  146 + }
  147 + Instant startInstant = Instant.from(formatter.parse(startTime));
  148 + Instant endInstant = Instant.from(formatter.parse(endTime));
  149 + return ChronoUnit.MILLIS.between(endInstant, startInstant);
  150 + }
142 151 }
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java
1 1 package com.genersoft.iot.vmp.vmanager.gb28181.record;
2 2  
  3 +import com.genersoft.iot.vmp.common.InviteInfo;
  4 +import com.genersoft.iot.vmp.common.InviteSessionType;
3 5 import com.genersoft.iot.vmp.common.StreamInfo;
4 6 import com.genersoft.iot.vmp.conf.UserSetting;
5 7 import com.genersoft.iot.vmp.conf.exception.ControllerException;
... ... @@ -10,7 +12,9 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
10 12 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
11 13 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
12 14 import com.genersoft.iot.vmp.service.IDeviceService;
  15 +import com.genersoft.iot.vmp.service.IInviteStreamService;
13 16 import com.genersoft.iot.vmp.service.IPlayService;
  17 +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
14 18 import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
15 19 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
16 20 import com.genersoft.iot.vmp.utils.DateUtil;
... ... @@ -23,6 +27,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
23 27 import org.slf4j.Logger;
24 28 import org.slf4j.LoggerFactory;
25 29 import org.springframework.beans.factory.annotation.Autowired;
  30 +import org.springframework.util.ObjectUtils;
26 31 import org.springframework.web.bind.annotation.GetMapping;
27 32 import org.springframework.web.bind.annotation.PathVariable;
28 33 import org.springframework.web.bind.annotation.RequestMapping;
... ... @@ -56,6 +61,9 @@ public class GBRecordController {
56 61 private IPlayService playService;
57 62  
58 63 @Autowired
  64 + private IInviteStreamService inviteStreamService;
  65 +
  66 + @Autowired
59 67 private IDeviceService deviceService;
60 68  
61 69 @Autowired
... ... @@ -204,4 +212,32 @@ public class GBRecordController {
204 212 }
205 213 return new StreamContent(downLoadInfo);
206 214 }
  215 +
  216 + @Operation(summary = "获取历史媒体下载文件地址")
  217 + @Parameter(name = "deviceId", description = "设备国标编号", required = true)
  218 + @Parameter(name = "channelId", description = "通道国标编号", required = true)
  219 + @Parameter(name = "stream", description = "流ID", required = true)
  220 + @GetMapping("/download/file/path/{deviceId}/{channelId}/{stream}")
  221 + public DeferredResult<WVPResult<DownloadFileInfo>> getDownloadFilePath(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) {
  222 +
  223 + DeferredResult<WVPResult<DownloadFileInfo>> result = new DeferredResult<>();
  224 +
  225 + result.onTimeout(()->{
  226 + WVPResult<DownloadFileInfo> wvpResult = new WVPResult<>();
  227 + wvpResult.setCode(ErrorCode.ERROR100.getCode());
  228 + wvpResult.setMsg("timeout");
  229 + result.setResult(wvpResult);
  230 + });
  231 +
  232 + playService.getFilePath(deviceId, channelId, stream, (code, msg, data)->{
  233 + WVPResult<DownloadFileInfo> wvpResult = new WVPResult<>();
  234 + wvpResult.setCode(code);
  235 + wvpResult.setMsg(msg);
  236 + wvpResult.setData(data);
  237 + result.setResult(wvpResult);
  238 + });
  239 +
  240 +
  241 + return result;
  242 + }
207 243 }
... ...
web_src/src/components/dialog/recordDownload.vue
... ... @@ -6,8 +6,7 @@
6 6 <el-progress :percentage="percentage"></el-progress>
7 7 </el-col>
8 8 <el-col :span="6" >
9   - <el-button icon="el-icon-download" v-if="percentage < 100" size="mini" title="点击下载可将以缓存部分下载到本地" @click="download()">停止缓存并下载</el-button>
10   - <el-button icon="el-icon-download" v-if="downloadFile" size="mini" title="点击下载" @click="downloadFileClientEvent()">点击下载</el-button>
  9 + <el-button icon="el-icon-download" v-if="downloadFile" size="mini" title="点击下载" @click="downloadFileClientEvent()">下载</el-button>
11 10 </el-col>
12 11 </el-row>
13 12 </el-dialog>
... ...