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 | package com.genersoft.iot.vmp.common; | 1 | package com.genersoft.iot.vmp.common; |
| 2 | 2 | ||
| 3 | +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; | ||
| 3 | import io.swagger.v3.oas.annotations.media.Schema; | 4 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 5 | ||
| 5 | import java.io.Serializable; | 6 | import java.io.Serializable; |
| @@ -76,6 +77,8 @@ public class StreamInfo implements Serializable, Cloneable{ | @@ -76,6 +77,8 @@ public class StreamInfo implements Serializable, Cloneable{ | ||
| 76 | private String endTime; | 77 | private String endTime; |
| 77 | @Schema(description = "进度(录像下载使用)") | 78 | @Schema(description = "进度(录像下载使用)") |
| 78 | private double progress; | 79 | private double progress; |
| 80 | + @Schema(description = "文件下载地址(录像下载使用)") | ||
| 81 | + private DownloadFileInfo downLoadFilePath; | ||
| 79 | 82 | ||
| 80 | @Schema(description = "是否暂停(录像回放使用)") | 83 | @Schema(description = "是否暂停(录像回放使用)") |
| 81 | private boolean pause; | 84 | private boolean pause; |
| @@ -605,5 +608,11 @@ public class StreamInfo implements Serializable, Cloneable{ | @@ -605,5 +608,11 @@ public class StreamInfo implements Serializable, Cloneable{ | ||
| 605 | this.subStream = subStream; | 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,7 +275,7 @@ public class ZLMHttpHookListener { | ||
| 275 | List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream()); | 275 | List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream()); |
| 276 | if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) { | 276 | if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) { |
| 277 | 277 | ||
| 278 | - // 为录制国标模拟一个鉴权信息 | 278 | + // 为录制国标模拟一个鉴权信息, 方便后续写入录像文件时使用 |
| 279 | StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); | 279 | StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); |
| 280 | streamAuthorityInfo.setApp(param.getApp()); | 280 | streamAuthorityInfo.setApp(param.getApp()); |
| 281 | streamAuthorityInfo.setStream(ssrcTransactionForAll.get(0).getStream()); | 281 | streamAuthorityInfo.setStream(ssrcTransactionForAll.get(0).getStream()); |
| @@ -291,8 +291,18 @@ public class ZLMHttpHookListener { | @@ -291,8 +291,18 @@ public class ZLMHttpHookListener { | ||
| 291 | } | 291 | } |
| 292 | // 如果是录像下载就设置视频间隔十秒 | 292 | // 如果是录像下载就设置视频间隔十秒 |
| 293 | if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) { | 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 | if (param.getApp().equalsIgnoreCase("rtp")) { | 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,4 +41,15 @@ public class HookSubscribeFactory { | ||
| 41 | 41 | ||
| 42 | return hookSubscribe; | 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,6 +7,7 @@ public class HookResultForOnPublish extends HookResult{ | ||
| 7 | private int mp4_max_second; | 7 | private int mp4_max_second; |
| 8 | private String mp4_save_path; | 8 | private String mp4_save_path; |
| 9 | private String stream_replace; | 9 | private String stream_replace; |
| 10 | + private Integer modify_stamp; | ||
| 10 | 11 | ||
| 11 | public HookResultForOnPublish() { | 12 | public HookResultForOnPublish() { |
| 12 | } | 13 | } |
| @@ -60,14 +61,23 @@ public class HookResultForOnPublish extends HookResult{ | @@ -60,14 +61,23 @@ public class HookResultForOnPublish extends HookResult{ | ||
| 60 | this.stream_replace = stream_replace; | 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 | @Override | 72 | @Override |
| 64 | public String toString() { | 73 | public String toString() { |
| 65 | return "HookResultForOnPublish{" + | 74 | return "HookResultForOnPublish{" + |
| 66 | "enable_audio=" + enable_audio + | 75 | "enable_audio=" + enable_audio + |
| 67 | ", enable_mp4=" + enable_mp4 + | 76 | ", enable_mp4=" + enable_mp4 + |
| 68 | ", mp4_max_second=" + mp4_max_second + | 77 | ", mp4_max_second=" + mp4_max_second + |
| 69 | - ", stream_replace=" + stream_replace + | ||
| 70 | ", mp4_save_path='" + mp4_save_path + '\'' + | 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,6 +4,7 @@ import com.genersoft.iot.vmp.common.StreamInfo; | ||
| 4 | import com.genersoft.iot.vmp.conf.exception.ServiceException; | 4 | import com.genersoft.iot.vmp.conf.exception.ServiceException; |
| 5 | import com.genersoft.iot.vmp.gb28181.bean.Device; | 5 | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| 6 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | 6 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 7 | +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; | ||
| 7 | import com.genersoft.iot.vmp.service.bean.ErrorCallback; | 8 | import com.genersoft.iot.vmp.service.bean.ErrorCallback; |
| 8 | import com.genersoft.iot.vmp.service.bean.SSRCInfo; | 9 | import com.genersoft.iot.vmp.service.bean.SSRCInfo; |
| 9 | 10 | ||
| @@ -44,5 +45,5 @@ public interface IPlayService { | @@ -44,5 +45,5 @@ public interface IPlayService { | ||
| 44 | 45 | ||
| 45 | void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback); | 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,6 +592,7 @@ public class MediaServerServiceImpl implements IMediaServerService { | ||
| 592 | if (mediaServerItem.getRecordPath() != null) { | 592 | if (mediaServerItem.getRecordPath() != null) { |
| 593 | File recordPathFile = new File(mediaServerItem.getRecordPath()); | 593 | File recordPathFile = new File(mediaServerItem.getRecordPath()); |
| 594 | param.put("protocol.mp4_save_path", recordPathFile.getParentFile().getPath()); | 594 | param.put("protocol.mp4_save_path", recordPathFile.getParentFile().getPath()); |
| 595 | + param.put("protocol.downloadRoot", recordPathFile.getParentFile().getPath()); | ||
| 595 | param.put("record.appName", recordPathFile.getName()); | 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,7 +67,7 @@ public class MediaServiceImpl implements IMediaService { | ||
| 67 | if (data == null) { | 67 | if (data == null) { |
| 68 | return null; | 68 | return null; |
| 69 | } | 69 | } |
| 70 | - JSONObject mediaJSON = JSON.parseObject(JSON.toJSONString(data.get(0)), JSONObject.class); | 70 | + JSONObject mediaJSON = data.getJSONObject(0); |
| 71 | JSONArray tracks = mediaJSON.getJSONArray("tracks"); | 71 | JSONArray tracks = mediaJSON.getJSONArray("tracks"); |
| 72 | if (authority) { | 72 | if (authority) { |
| 73 | streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld); | 73 | streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld); |
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
| 1 | package com.genersoft.iot.vmp.service.impl; | 1 | package com.genersoft.iot.vmp.service.impl; |
| 2 | 2 | ||
| 3 | +import com.alibaba.fastjson2.JSON; | ||
| 4 | +import com.alibaba.fastjson2.JSONArray; | ||
| 3 | import com.alibaba.fastjson2.JSONObject; | 5 | import com.alibaba.fastjson2.JSONObject; |
| 4 | import com.genersoft.iot.vmp.common.InviteInfo; | 6 | import com.genersoft.iot.vmp.common.InviteInfo; |
| 5 | import com.genersoft.iot.vmp.common.InviteSessionStatus; | 7 | import com.genersoft.iot.vmp.common.InviteSessionStatus; |
| @@ -21,16 +23,12 @@ import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; | @@ -21,16 +23,12 @@ import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; | ||
| 21 | import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; | 23 | import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; |
| 22 | import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; | 24 | import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; |
| 23 | import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; | 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 | import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam; | 27 | import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam; |
| 28 | +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam; | ||
| 28 | import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; | 29 | import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; |
| 29 | import com.genersoft.iot.vmp.service.*; | 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 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | 32 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 35 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | 33 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| 36 | import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; | 34 | import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; |
| @@ -77,7 +75,7 @@ public class PlayServiceImpl implements IPlayService { | @@ -77,7 +75,7 @@ public class PlayServiceImpl implements IPlayService { | ||
| 77 | private IInviteStreamService inviteStreamService; | 75 | private IInviteStreamService inviteStreamService; |
| 78 | 76 | ||
| 79 | @Autowired | 77 | @Autowired |
| 80 | - private DeferredResultHolder resultHolder; | 78 | + private ZlmHttpHookSubscribe subscribe; |
| 81 | 79 | ||
| 82 | @Autowired | 80 | @Autowired |
| 83 | private ZLMRESTfulUtils zlmresTfulUtils; | 81 | private ZLMRESTfulUtils zlmresTfulUtils; |
| @@ -86,9 +84,6 @@ public class PlayServiceImpl implements IPlayService { | @@ -86,9 +84,6 @@ public class PlayServiceImpl implements IPlayService { | ||
| 86 | private ZLMServerFactory zlmServerFactory; | 84 | private ZLMServerFactory zlmServerFactory; |
| 87 | 85 | ||
| 88 | @Autowired | 86 | @Autowired |
| 89 | - private AssistRESTfulUtils assistRESTfulUtils; | ||
| 90 | - | ||
| 91 | - @Autowired | ||
| 92 | private IMediaService mediaService; | 87 | private IMediaService mediaService; |
| 93 | 88 | ||
| 94 | @Autowired | 89 | @Autowired |
| @@ -107,9 +102,6 @@ public class PlayServiceImpl implements IPlayService { | @@ -107,9 +102,6 @@ public class PlayServiceImpl implements IPlayService { | ||
| 107 | private DynamicTask dynamicTask; | 102 | private DynamicTask dynamicTask; |
| 108 | 103 | ||
| 109 | @Autowired | 104 | @Autowired |
| 110 | - private ZlmHttpHookSubscribe subscribe; | ||
| 111 | - | ||
| 112 | - @Autowired | ||
| 113 | private CloudRecordServiceMapper cloudRecordServiceMapper; | 105 | private CloudRecordServiceMapper cloudRecordServiceMapper; |
| 114 | 106 | ||
| 115 | 107 | ||
| @@ -741,60 +733,147 @@ public class PlayServiceImpl implements IPlayService { | @@ -741,60 +733,147 @@ public class PlayServiceImpl implements IPlayService { | ||
| 741 | @Override | 733 | @Override |
| 742 | public StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream) { | 734 | public StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream) { |
| 743 | InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, stream); | 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 | inviteStreamService.updateInviteInfo(inviteInfo); | 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 | private StreamInfo onPublishHandlerForDownload(MediaServerItem mediaServerItemInuse, HookParam hookParam, String deviceId, String channelId, String startTime, String endTime) { | 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,4 +106,10 @@ public interface CloudRecordServiceMapper { | ||
| 106 | " </script>") | 106 | " </script>") |
| 107 | int deleteList(List<CloudRecordItem> cloudRecordItemIdList); | 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,4 +139,13 @@ public class DateUtil { | ||
| 139 | Instant beforeInstant = Instant.from(formatter.parse(keepaliveTime)); | 139 | Instant beforeInstant = Instant.from(formatter.parse(keepaliveTime)); |
| 140 | return ChronoUnit.MILLIS.between(beforeInstant, Instant.now()); | 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 | package com.genersoft.iot.vmp.vmanager.gb28181.record; | 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 | import com.genersoft.iot.vmp.common.StreamInfo; | 5 | import com.genersoft.iot.vmp.common.StreamInfo; |
| 4 | import com.genersoft.iot.vmp.conf.UserSetting; | 6 | import com.genersoft.iot.vmp.conf.UserSetting; |
| 5 | import com.genersoft.iot.vmp.conf.exception.ControllerException; | 7 | import com.genersoft.iot.vmp.conf.exception.ControllerException; |
| @@ -10,7 +12,9 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; | @@ -10,7 +12,9 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; | ||
| 10 | import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; | 12 | import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; |
| 11 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; | 13 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; |
| 12 | import com.genersoft.iot.vmp.service.IDeviceService; | 14 | import com.genersoft.iot.vmp.service.IDeviceService; |
| 15 | +import com.genersoft.iot.vmp.service.IInviteStreamService; | ||
| 13 | import com.genersoft.iot.vmp.service.IPlayService; | 16 | import com.genersoft.iot.vmp.service.IPlayService; |
| 17 | +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; | ||
| 14 | import com.genersoft.iot.vmp.service.bean.InviteErrorCode; | 18 | import com.genersoft.iot.vmp.service.bean.InviteErrorCode; |
| 15 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | 19 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| 16 | import com.genersoft.iot.vmp.utils.DateUtil; | 20 | import com.genersoft.iot.vmp.utils.DateUtil; |
| @@ -23,6 +27,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; | @@ -23,6 +27,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; | ||
| 23 | import org.slf4j.Logger; | 27 | import org.slf4j.Logger; |
| 24 | import org.slf4j.LoggerFactory; | 28 | import org.slf4j.LoggerFactory; |
| 25 | import org.springframework.beans.factory.annotation.Autowired; | 29 | import org.springframework.beans.factory.annotation.Autowired; |
| 30 | +import org.springframework.util.ObjectUtils; | ||
| 26 | import org.springframework.web.bind.annotation.GetMapping; | 31 | import org.springframework.web.bind.annotation.GetMapping; |
| 27 | import org.springframework.web.bind.annotation.PathVariable; | 32 | import org.springframework.web.bind.annotation.PathVariable; |
| 28 | import org.springframework.web.bind.annotation.RequestMapping; | 33 | import org.springframework.web.bind.annotation.RequestMapping; |
| @@ -56,6 +61,9 @@ public class GBRecordController { | @@ -56,6 +61,9 @@ public class GBRecordController { | ||
| 56 | private IPlayService playService; | 61 | private IPlayService playService; |
| 57 | 62 | ||
| 58 | @Autowired | 63 | @Autowired |
| 64 | + private IInviteStreamService inviteStreamService; | ||
| 65 | + | ||
| 66 | + @Autowired | ||
| 59 | private IDeviceService deviceService; | 67 | private IDeviceService deviceService; |
| 60 | 68 | ||
| 61 | @Autowired | 69 | @Autowired |
| @@ -204,4 +212,32 @@ public class GBRecordController { | @@ -204,4 +212,32 @@ public class GBRecordController { | ||
| 204 | } | 212 | } |
| 205 | return new StreamContent(downLoadInfo); | 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,8 +6,7 @@ | ||
| 6 | <el-progress :percentage="percentage"></el-progress> | 6 | <el-progress :percentage="percentage"></el-progress> |
| 7 | </el-col> | 7 | </el-col> |
| 8 | <el-col :span="6" > | 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 | </el-col> | 10 | </el-col> |
| 12 | </el-row> | 11 | </el-row> |
| 13 | </el-dialog> | 12 | </el-dialog> |