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