From ad36354ef46a31f24b2583263f575d6736c0ad28 Mon Sep 17 00:00:00 2001 From: 648540858 <648540858@qq.com> Date: Tue, 5 Dec 2023 11:29:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9B=BD=E6=A0=87=E5=BD=95?= =?UTF-8?q?=E5=83=8F=E6=9C=8D=E5=8A=A1=E7=AB=AF=EF=BC=8C=E4=BD=BF=E7=94=A8?= =?UTF-8?q?zlm=E6=96=B0=E6=8E=A5=E5=8F=A3=E5=AE=9E=E7=8E=B0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../genersoft/iot/vmp/common/StreamInfo.java | 9 + .../vmp/media/zlm/ZLMHttpHookListener.java | 16 +- .../media/zlm/dto/HookSubscribeFactory.java | 11 + .../zlm/dto/HookSubscribeForRecordMp4.java | 44 ++++ .../zlm/dto/hook/HookResultForOnPublish.java | 12 +- .../iot/vmp/service/IPlayService.java | 3 +- .../vmp/service/bean/DownloadFileInfo.java | 41 ++++ .../service/impl/MediaServerServiceImpl.java | 1 + .../vmp/service/impl/MediaServiceImpl.java | 2 +- .../iot/vmp/service/impl/PlayServiceImpl.java | 209 ++++++++++++------ .../dao/CloudRecordServiceMapper.java | 6 + .../com/genersoft/iot/vmp/utils/DateUtil.java | 9 + .../gb28181/record/GBRecordController.java | 36 +++ .../src/components/dialog/recordDownload.vue | 3 +- 14 files changed, 329 insertions(+), 73 deletions(-) create mode 100755 src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForRecordMp4.java create mode 100644 src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java diff --git a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java index 70718c7a..4ceaaecc 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.common; +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import io.swagger.v3.oas.annotations.media.Schema; import java.io.Serializable; @@ -76,6 +77,8 @@ public class StreamInfo implements Serializable, Cloneable{ private String endTime; @Schema(description = "进度(录像下载使用)") private double progress; + @Schema(description = "文件下载地址(录像下载使用)") + private DownloadFileInfo downLoadFilePath; @Schema(description = "是否暂停(录像回放使用)") private boolean pause; @@ -605,5 +608,11 @@ public class StreamInfo implements Serializable, Cloneable{ this.subStream = subStream; } + public DownloadFileInfo getDownLoadFilePath() { + return downLoadFilePath; + } + public void setDownLoadFilePath(DownloadFileInfo downLoadFilePath) { + this.downLoadFilePath = downLoadFilePath; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java index bb15414f..3bf95052 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -275,7 +275,7 @@ public class ZLMHttpHookListener { List ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream()); if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) { - // 为录制国标模拟一个鉴权信息 + // 为录制国标模拟一个鉴权信息, 方便后续写入录像文件时使用 StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); streamAuthorityInfo.setApp(param.getApp()); streamAuthorityInfo.setStream(ssrcTransactionForAll.get(0).getStream()); @@ -291,8 +291,18 @@ public class ZLMHttpHookListener { } // 如果是录像下载就设置视频间隔十秒 if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) { - result.setMp4_max_second(30); - result.setEnable_mp4(true); + // 获取录像的总时长,然后设置为这个视频的时长 + InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, param.getStream()); + if (inviteInfo.getStreamInfo() != null ) { + String startTime = inviteInfo.getStreamInfo().getStartTime(); + String endTime = inviteInfo.getStreamInfo().getEndTime(); + long difference = DateUtil.getDifference(startTime, endTime)/1000; + result.setMp4_max_second((int)difference); + result.setEnable_mp4(true); + // 设置为2保证得到的mp4的时长是正常的 + result.setModify_stamp(2); + } + } } if (param.getApp().equalsIgnoreCase("rtp")) { diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java index f3f389a7..6d6554bc 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java @@ -41,4 +41,15 @@ public class HookSubscribeFactory { return hookSubscribe; } + + public static HookSubscribeForRecordMp4 on_record_mp4(String mediaServerId, String app, String stream) { + HookSubscribeForRecordMp4 hookSubscribe = new HookSubscribeForRecordMp4(); + JSONObject subscribeKey = new com.alibaba.fastjson2.JSONObject(); + subscribeKey.put("app", app); + subscribeKey.put("stream", stream); + subscribeKey.put("mediaServerId", mediaServerId); + hookSubscribe.setContent(subscribeKey); + + return hookSubscribe; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForRecordMp4.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForRecordMp4.java new file mode 100755 index 00000000..34c467c5 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForRecordMp4.java @@ -0,0 +1,44 @@ +package com.genersoft.iot.vmp.media.zlm.dto; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.annotation.JSONField; + +import java.time.Instant; + +/** + * hook订阅-录像完成 + * @author lin + */ +public class HookSubscribeForRecordMp4 implements IHookSubscribe{ + + private HookType hookType = HookType.on_record_mp4; + + private JSONObject content; + + @JSONField(format="yyyy-MM-dd HH:mm:ss") + private Instant expires; + + @Override + public HookType getHookType() { + return hookType; + } + + @Override + public JSONObject getContent() { + return content; + } + + public void setContent(JSONObject content) { + this.content = content; + } + + @Override + public Instant getExpires() { + return expires; + } + + @Override + public void setExpires(Instant expires) { + this.expires = expires; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java index 12d83627..55369e5b 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java @@ -7,6 +7,7 @@ public class HookResultForOnPublish extends HookResult{ private int mp4_max_second; private String mp4_save_path; private String stream_replace; + private Integer modify_stamp; public HookResultForOnPublish() { } @@ -60,14 +61,23 @@ public class HookResultForOnPublish extends HookResult{ this.stream_replace = stream_replace; } + public Integer getModify_stamp() { + return modify_stamp; + } + + public void setModify_stamp(Integer modify_stamp) { + this.modify_stamp = modify_stamp; + } + @Override public String toString() { return "HookResultForOnPublish{" + "enable_audio=" + enable_audio + ", enable_mp4=" + enable_mp4 + ", mp4_max_second=" + mp4_max_second + - ", stream_replace=" + stream_replace + ", mp4_save_path='" + mp4_save_path + '\'' + + ", stream_replace='" + stream_replace + '\'' + + ", modify_stamp='" + modify_stamp + '\'' + '}'; } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java index 1effe96c..bee7f1ef 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java @@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.exception.ServiceException; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.SSRCInfo; @@ -44,5 +45,5 @@ public interface IPlayService { void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback); - + void getFilePath(String deviceId, String channelId, String stream, ErrorCallback callback); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java b/src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java new file mode 100644 index 00000000..602e1846 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java @@ -0,0 +1,41 @@ +package com.genersoft.iot.vmp.service.bean; + +public class DownloadFileInfo { + + private String httpPath; + private String httpsPath; + private String httpDomainPath; + private String httpsDomainPath; + + public String getHttpPath() { + return httpPath; + } + + public void setHttpPath(String httpPath) { + this.httpPath = httpPath; + } + + public String getHttpsPath() { + return httpsPath; + } + + public void setHttpsPath(String httpsPath) { + this.httpsPath = httpsPath; + } + + public String getHttpDomainPath() { + return httpDomainPath; + } + + public void setHttpDomainPath(String httpDomainPath) { + this.httpDomainPath = httpDomainPath; + } + + public String getHttpsDomainPath() { + return httpsDomainPath; + } + + public void setHttpsDomainPath(String httpsDomainPath) { + this.httpsDomainPath = httpsDomainPath; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java index 7c4965ef..3ecd7542 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java @@ -592,6 +592,7 @@ public class MediaServerServiceImpl implements IMediaServerService { if (mediaServerItem.getRecordPath() != null) { File recordPathFile = new File(mediaServerItem.getRecordPath()); param.put("protocol.mp4_save_path", recordPathFile.getParentFile().getPath()); + param.put("protocol.downloadRoot", recordPathFile.getParentFile().getPath()); param.put("record.appName", recordPathFile.getName()); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java index b8241d7a..f9aec2ea 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java @@ -67,7 +67,7 @@ public class MediaServiceImpl implements IMediaService { if (data == null) { return null; } - JSONObject mediaJSON = JSON.parseObject(JSON.toJSONString(data.get(0)), JSONObject.class); + JSONObject mediaJSON = data.getJSONObject(0); JSONArray tracks = mediaJSON.getJSONArray("tracks"); if (authority) { streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld); diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java index 0d1bad69..1e8dee77 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java @@ -1,5 +1,7 @@ package com.genersoft.iot.vmp.service.impl; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionStatus; @@ -21,16 +23,12 @@ import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; -import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; -import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; -import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.media.zlm.dto.*; import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam; +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; import com.genersoft.iot.vmp.service.*; -import com.genersoft.iot.vmp.service.bean.CloudRecordItem; -import com.genersoft.iot.vmp.service.bean.ErrorCallback; -import com.genersoft.iot.vmp.service.bean.InviteErrorCode; -import com.genersoft.iot.vmp.service.bean.SSRCInfo; +import com.genersoft.iot.vmp.service.bean.*; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; @@ -77,7 +75,7 @@ public class PlayServiceImpl implements IPlayService { private IInviteStreamService inviteStreamService; @Autowired - private DeferredResultHolder resultHolder; + private ZlmHttpHookSubscribe subscribe; @Autowired private ZLMRESTfulUtils zlmresTfulUtils; @@ -85,9 +83,6 @@ public class PlayServiceImpl implements IPlayService { @Autowired private ZLMServerFactory zlmServerFactory; - @Autowired - private AssistRESTfulUtils assistRESTfulUtils; - @Autowired private IMediaService mediaService; @@ -106,9 +101,6 @@ public class PlayServiceImpl implements IPlayService { @Autowired private DynamicTask dynamicTask; - @Autowired - private ZlmHttpHookSubscribe subscribe; - @Autowired private CloudRecordServiceMapper cloudRecordServiceMapper; @@ -741,60 +733,147 @@ public class PlayServiceImpl implements IPlayService { @Override public StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream) { InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, stream); + if (inviteInfo == null || inviteInfo.getStreamInfo() == null) { + logger.warn("[获取下载进度] 未查询到录像下载的信息"); + return null; + } - if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { - if (inviteInfo.getStreamInfo().getProgress() == 1) { - return inviteInfo.getStreamInfo(); - } - - // 获取当前已下载时长 - String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId(); - MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); - if (mediaServerItem == null) { - logger.warn("查询录像信息时发现节点已离线"); - return null; - } - if (mediaServerItem.getRecordAssistPort() == 0) { - throw new ControllerException(ErrorCode.ERROR100.getCode(), "未配置Assist服务,无法完成录像下载"); - } - SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, null, stream); - - if (ssrcTransaction == null) { - logger.warn("[获取下载进度],未找到下载事务信息"); - return null; - } - - // 为了支持多个数据库,这里不能使用求和函数来直接获取总数了 - List cloudRecordItemList = cloudRecordServiceMapper.getList(null, "rtp", inviteInfo.getStream(), null, null, ssrcTransaction.getCallId(), null); - - if (cloudRecordItemList.isEmpty()) { - logger.warn("[获取下载进度],未找到下载视频信息"); - return null; - } - long duration = 0; - for (CloudRecordItem cloudRecordItem : cloudRecordItemList) { - duration += cloudRecordItem.getTimeLen(); - } - if (duration == 0) { - inviteInfo.getStreamInfo().setProgress(0); - } else { - String startTime = inviteInfo.getStreamInfo().getStartTime(); - String endTime = inviteInfo.getStreamInfo().getEndTime(); - // 此时start和end单位是秒 - long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); - long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); - - BigDecimal currentCount = new BigDecimal(duration); - BigDecimal totalCount = new BigDecimal((end - start) * 1000); - BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP); - double process = divide.doubleValue(); - inviteInfo.getStreamInfo().setProgress(process); - } - inviteStreamService.updateInviteInfo(inviteInfo); - + if (inviteInfo.getStreamInfo().getProgress() == 1) { return inviteInfo.getStreamInfo(); } - return null; + + // 获取当前已下载时长 + String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId(); + MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); + if (mediaServerItem == null) { + logger.warn("[获取下载进度] 查询录像信息时发现节点不存在"); + return null; + } + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, null, stream); + + if (ssrcTransaction == null) { + logger.warn("[获取下载进度] 下载已结束"); + return null; + } + + JSONObject mediaListJson= zlmresTfulUtils.getMediaList(mediaServerItem, "rtp", stream); + if (mediaListJson == null) { + logger.warn("[获取下载进度] 从zlm查询进度失败"); + return null; + } + if (mediaListJson.getInteger("code") != 0) { + logger.warn("[获取下载进度] 从zlm查询进度出现错误: {}", mediaListJson.getString("msg")); + return null; + } + JSONArray data = mediaListJson.getJSONArray("data"); + if (data == null) { + logger.warn("[获取下载进度] 从zlm查询进度时未返回数据"); + return null; + } + JSONObject mediaJSON = data.getJSONObject(0); + JSONArray tracks = mediaJSON.getJSONArray("tracks"); + if (tracks.isEmpty()) { + logger.warn("[获取下载进度] 从zlm查询进度时未返回数据"); + return null; + } + JSONObject jsonObject = tracks.getJSONObject(0); + long duration = jsonObject.getLongValue("duration"); + if (duration == 0) { + inviteInfo.getStreamInfo().setProgress(0); + } else { + String startTime = inviteInfo.getStreamInfo().getStartTime(); + String endTime = inviteInfo.getStreamInfo().getEndTime(); + // 此时start和end单位是秒 + long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); + long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); + + BigDecimal currentCount = new BigDecimal(duration); + BigDecimal totalCount = new BigDecimal((end - start) * 1000); + BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP); + double process = divide.doubleValue(); + inviteInfo.getStreamInfo().setProgress(process); + } + inviteStreamService.updateInviteInfo(inviteInfo); + return inviteInfo.getStreamInfo(); + } + + @Override + public void getFilePath(String deviceId, String channelId, String stream, ErrorCallback callback) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, stream); + if (inviteInfo == null || inviteInfo.getStreamInfo() == null) { + logger.warn("[获取录像下载文件地址] 未查询到录像下载的信息, {}/{}-{}", deviceId, channelId, stream); + callback.run(ErrorCode.ERROR100.getCode(), "未查询到录像下载的信息", null); + return ; + } + + if (!ObjectUtils.isEmpty(inviteInfo.getStreamInfo().getDownLoadFilePath())) { + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), + inviteInfo.getStreamInfo().getDownLoadFilePath()); + return; + } + + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo("rtp", stream); + if (streamAuthorityInfo == null) { + logger.warn("[获取录像下载文件地址] 未查询到录像的视频信息, {}/{}-{}", deviceId, channelId, stream); + callback.run(ErrorCode.ERROR100.getCode(), "未查询到录像的视频信息", null); + return ; + } + + // 获取当前已下载时长 + String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId(); + MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); + if (mediaServerItem == null) { + logger.warn("[获取录像下载文件地址] 查询录像信息时发现节点不存在, {}/{}-{}", deviceId, channelId, stream); + callback.run(ErrorCode.ERROR100.getCode(), "查询录像信息时发现节点不存在", null); + return ; + } + + List cloudRecordItemList = cloudRecordServiceMapper.getListByCallId(streamAuthorityInfo.getCallId()); + if (!cloudRecordItemList.isEmpty()) { + String filePath = cloudRecordItemList.get(0).getFilePath(); + + DownloadFileInfo downloadFileInfo = getDownloadFilePath(mediaServerItem, filePath); + inviteInfo.getStreamInfo().setDownLoadFilePath(downloadFileInfo); + inviteStreamService.updateInviteInfo(inviteInfo); + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), downloadFileInfo); + }else { + // 可能尚未生成,那就监听hook等着收到对应的录像通知 + ZlmHttpHookSubscribe.Event hookEvent = (mediaServerItemInuse, hookParam) -> { + logger.info("[录像下载]收到订阅消息: , {}/{}-{}", deviceId, channelId, stream); + logger.info("[录像下载]收到订阅消息内容: " + hookParam); + dynamicTask.stop(streamAuthorityInfo.getCallId()); + OnRecordMp4HookParam recordMp4HookParam = (OnRecordMp4HookParam)hookParam; + String filePath = recordMp4HookParam.getFile_path(); + DownloadFileInfo downloadFileInfo = getDownloadFilePath(mediaServerItem, filePath); + inviteInfo.getStreamInfo().setDownLoadFilePath(downloadFileInfo); + inviteStreamService.updateInviteInfo(inviteInfo); + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), downloadFileInfo); + }; + HookSubscribeForRecordMp4 hookSubscribe = HookSubscribeFactory.on_record_mp4(mediaServerId, "rtp", stream); + subscribe.addSubscribe(hookSubscribe, hookEvent); + + // 设置超时,超时结束监听 + dynamicTask.startDelay(streamAuthorityInfo.getCallId(), ()->{ + logger.info("[录像下载] 接收hook超时, {}/{}-{}", deviceId, channelId, stream); + subscribe.removeSubscribe(hookSubscribe); + callback.run(ErrorCode.ERROR100.getCode(), "接收hook超时", null); + }, 10000); + } + } + + private DownloadFileInfo getDownloadFilePath(MediaServerItem mediaServerItem, String filePath) { + DownloadFileInfo downloadFileInfo = new DownloadFileInfo(); + + String pathTemplate = "%s://%s:%s/index/api/downloadFile?file_path=" + filePath; + + downloadFileInfo.setHttpPath(String.format(pathTemplate, "http", mediaServerItem.getStreamIp(), + mediaServerItem.getHttpPort())); + + if (mediaServerItem.getHttpSSlPort() > 0) { + downloadFileInfo.setHttpsPath(String.format(pathTemplate, "https", mediaServerItem.getStreamIp(), + mediaServerItem.getHttpSSlPort())); + } + return downloadFileInfo; } private StreamInfo onPublishHandlerForDownload(MediaServerItem mediaServerItemInuse, HookParam hookParam, String deviceId, String channelId, String startTime, String endTime) { diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java index d51f3f91..3724fecb 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java @@ -106,4 +106,10 @@ public interface CloudRecordServiceMapper { " ") int deleteList(List cloudRecordItemIdList); + @Select(" ") + List getListByCallId(@Param("callId") String callId); } diff --git a/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java b/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java index 617d9f24..e5f9fe43 100755 --- a/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java +++ b/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java @@ -139,4 +139,13 @@ public class DateUtil { Instant beforeInstant = Instant.from(formatter.parse(keepaliveTime)); return ChronoUnit.MILLIS.between(beforeInstant, Instant.now()); } + + public static long getDifference(String startTime, String endTime) { + if (ObjectUtils.isEmpty(startTime) || ObjectUtils.isEmpty(endTime)) { + return 0; + } + Instant startInstant = Instant.from(formatter.parse(startTime)); + Instant endInstant = Instant.from(formatter.parse(endTime)); + return ChronoUnit.MILLIS.between(endInstant, startInstant); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java index 910e4360..bf8f78bc 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java @@ -1,5 +1,7 @@ package com.genersoft.iot.vmp.vmanager.gb28181.record; +import com.genersoft.iot.vmp.common.InviteInfo; +import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; @@ -10,7 +12,9 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.service.IDeviceService; +import com.genersoft.iot.vmp.service.IInviteStreamService; import com.genersoft.iot.vmp.service.IPlayService; +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.utils.DateUtil; @@ -23,6 +27,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -55,6 +60,9 @@ public class GBRecordController { @Autowired private IPlayService playService; + @Autowired + private IInviteStreamService inviteStreamService; + @Autowired private IDeviceService deviceService; @@ -204,4 +212,32 @@ public class GBRecordController { } return new StreamContent(downLoadInfo); } + + @Operation(summary = "获取历史媒体下载文件地址") + @Parameter(name = "deviceId", description = "设备国标编号", required = true) + @Parameter(name = "channelId", description = "通道国标编号", required = true) + @Parameter(name = "stream", description = "流ID", required = true) + @GetMapping("/download/file/path/{deviceId}/{channelId}/{stream}") + public DeferredResult> getDownloadFilePath(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) { + + DeferredResult> result = new DeferredResult<>(); + + result.onTimeout(()->{ + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(ErrorCode.ERROR100.getCode()); + wvpResult.setMsg("timeout"); + result.setResult(wvpResult); + }); + + playService.getFilePath(deviceId, channelId, stream, (code, msg, data)->{ + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(code); + wvpResult.setMsg(msg); + wvpResult.setData(data); + result.setResult(wvpResult); + }); + + + return result; + } } diff --git a/web_src/src/components/dialog/recordDownload.vue b/web_src/src/components/dialog/recordDownload.vue index 220da8bf..dcecc280 100755 --- a/web_src/src/components/dialog/recordDownload.vue +++ b/web_src/src/components/dialog/recordDownload.vue @@ -6,8 +6,7 @@ - 停止缓存并下载 - 点击下载 + 下载