Commit ad36354ef46a31f24b2583263f575d6736c0ad28
1 parent
fc77b3f8
优化国标录像服务端,使用zlm新接口实现功能
Showing
14 changed files
with
324 additions
and
68 deletions
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> | ... | ... |