diff --git a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java index 06263840..4e4ba15e 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java @@ -31,6 +31,9 @@ public class StreamInfo { private String rtc; private String mediaServerId; private Object tracks; + private String startTime; + private String endTime; + private double progress; public static class TransactionInfo{ public String callId; @@ -264,4 +267,29 @@ public class StreamInfo { public void setHttps_ts(String https_ts) { this.https_ts = https_ts; } + + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public double getProgress() { + return progress; + } + + public void setProgress(double progress) { + this.progress = progress; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java index c2dedec1..87d26359 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java @@ -1,5 +1,7 @@ package com.genersoft.iot.vmp.gb28181.bean; +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; + public class SsrcTransaction { private String deviceId; @@ -10,6 +12,7 @@ public class SsrcTransaction { private byte[] dialog; private String mediaServerId; private String ssrc; + private VideoStreamSessionManager.SessionType type; public String getDeviceId() { return deviceId; @@ -74,4 +77,12 @@ public class SsrcTransaction { public void setSsrc(String ssrc) { this.ssrc = ssrc; } + + public VideoStreamSessionManager.SessionType getType() { + return type; + } + + public void setType(VideoStreamSessionManager.SessionType type) { + this.type = type; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java index d511f421..50957f6d 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java @@ -156,8 +156,6 @@ public class CatalogEventLister implements ApplicationListener { List parentPlatforms = parentPlatformMap.get(gbId); if (parentPlatforms != null && parentPlatforms.size() > 0) { for (ParentPlatform platform : parentPlatforms) { -// String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetup.getServerId() + "_Catalog_" + platform.getServerGBId(); -// SubscribeInfo subscribeInfo = redisCatchStorage.getSubscribe(key); SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(event.getPlatformId()); if (subscribeInfo == null) continue; logger.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), gbId); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java index ba8f24c1..6eed17eb 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java @@ -30,6 +30,12 @@ public class VideoStreamSessionManager { @Autowired private UserSetup userSetup; + public enum SessionType { + play, + playback, + download + } + /** * 添加一个点播/回放的事务信息 * 后续可以通过流Id/callID @@ -40,7 +46,7 @@ public class VideoStreamSessionManager { * @param mediaServerId 所使用的流媒体ID * @param transaction 事务 */ - public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, ClientTransaction transaction){ + public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, ClientTransaction transaction, SessionType type){ SsrcTransaction ssrcTransaction = new SsrcTransaction(); ssrcTransaction.setDeviceId(deviceId); ssrcTransaction.setChannelId(channelId); @@ -50,6 +56,7 @@ public class VideoStreamSessionManager { ssrcTransaction.setCallId(callId); ssrcTransaction.setSsrc(ssrc); ssrcTransaction.setMediaServerId(mediaServerId); + ssrcTransaction.setType(type); redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetup.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java index 409eedbf..9cd89e0e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java @@ -115,7 +115,9 @@ public interface ISIPCommander { * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss * @param downloadSpeed 下载倍速参数 */ - void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String startTime, String endTime, String downloadSpeed, InviteStreamCallback event, SipSubscribe.Event errorEvent); + void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, + String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent, + SipSubscribe.Event errorEvent); /** * 视频流停止 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java index 48bffd77..0f2242cc 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java @@ -428,7 +428,7 @@ public class SIPCommander implements ISIPCommander { errorEvent.response(e); }), e ->{ // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 - streamSession.put(device.getDeviceId(), channelId ,"play", streamId, ssrcInfo.getSsrc(), mediaServerItem.getId(), ((ResponseEvent)e.event).getClientTransaction()); + streamSession.put(device.getDeviceId(), channelId ,"play", streamId, ssrcInfo.getSsrc(), mediaServerItem.getId(), ((ResponseEvent)e.event).getClientTransaction(), VideoStreamSessionManager.SessionType.play); streamSession.put(device.getDeviceId(), channelId ,"play", e.dialog); }); @@ -537,7 +537,7 @@ public class SIPCommander implements ISIPCommander { transmitRequest(device, request, errorEvent, okEvent -> { ResponseEvent responseEvent = (ResponseEvent) okEvent.event; - streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction()); + streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.playback); streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog); }); if (inviteStreamCallback != null) { @@ -558,8 +558,9 @@ public class SIPCommander implements ISIPCommander { * @param downloadSpeed 下载倍速参数 */ @Override - public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String startTime, String endTime, String downloadSpeed, InviteStreamCallback event - , SipSubscribe.Event errorEvent) { + public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, + String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent, + SipSubscribe.Event errorEvent) { try { logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort()); @@ -572,8 +573,6 @@ public class SIPCommander implements ISIPCommander { content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" " +DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n"); - - String streamMode = device.getStreamMode().toUpperCase(); if (userSetup.isSeniorSdp()) { @@ -639,15 +638,20 @@ public class SIPCommander implements ISIPCommander { logger.debug("录像回放添加订阅,订阅内容:" + subscribeKey.toString()); subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, (MediaServerItem mediaServerItemInUse, JSONObject json)->{ - event.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream())); + hookEvent.call(new InviteStreamInfo(mediaServerItem, json, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream())); subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey); }); Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader, ssrcInfo.getSsrc()); + if (inviteStreamCallback != null) { + inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream())); + } + transmitRequest(device, request, errorEvent, okEvent->{ + ResponseEvent responseEvent = (ResponseEvent) okEvent.event; + streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.download); + streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog); + }); - ClientTransaction transaction = transmitRequest(device, request, errorEvent); - streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), transaction); - streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), transaction); } catch ( SipException | ParseException | InvalidArgumentException e) { e.printStackTrace(); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java index f21dfc0a..9550266b 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java +++ b/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 DeviceChannel deviceChannel = storager.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId()); deviceChannel.setParental(0); deviceChannel.setParentId(channelReduce.getCatalogId()); + deviceChannel.setCivilCode(parentPlatform.getDeviceGBId().substring(0, 6)); cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size); // 防止发送过快 Thread.sleep(100); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java index 8235ade1..e36a705a 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java +++ b/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 if (NotifyType.equals("121")){ logger.info("媒体播放完毕,通知关流"); String channelId =getText(rootElement, "DeviceID"); - redisCatchStorage.stopPlayback(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); +// redisCatchStorage.stopPlayback(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); +// redisCatchStorage.stopDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); + StreamInfo streamInfo = redisCatchStorage.queryDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); + // 设置进度100% + streamInfo.setProgress(1); + redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId()); cmder.streamByeCmd(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); // TODO 如果级联播放,需要给上级发送此通知 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java index d8c72506..959432ce 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java +++ b/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 DeviceChannel deviceChannel = storager.queryChannel(channelReduce.getDeviceId(), channelReduce.getChannelId()); deviceChannel.setParental(0); deviceChannel.setParentId(channelReduce.getCatalogId()); + deviceChannel.setCivilCode(parentPlatform.getDeviceGBId().substring(0, 6)); cmderFroPlatform.catalogQuery(deviceChannel, parentPlatform, sn, fromHeader.getTag(), size); // 防止发送过快 Thread.sleep(100); diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java new file mode 100644 index 00000000..249ec03c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java @@ -0,0 +1,139 @@ +package com.genersoft.iot.vmp.media.zlm; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import okhttp3.*; +import okhttp3.logging.HttpLoggingInterceptor; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.ConnectException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@Component +public class AssistRESTfulUtils { + + private final static Logger logger = LoggerFactory.getLogger(AssistRESTfulUtils.class); + + public interface RequestCallback{ + void run(JSONObject response); + } + + private OkHttpClient getClient(){ + OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); + if (logger.isDebugEnabled()) { + HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> { + logger.debug("http请求参数:" + message); + }); + logging.setLevel(HttpLoggingInterceptor.Level.BASIC); + // OkHttp進行添加攔截器loggingInterceptor + httpClientBuilder.addInterceptor(logging); + } + return httpClientBuilder.build(); + } + + + public JSONObject sendGet(MediaServerItem mediaServerItem, String api, Map param, RequestCallback callback) { + OkHttpClient client = getClient(); + + if (mediaServerItem == null) { + return null; + } + if (StringUtils.isEmpty(mediaServerItem.getRecordAssistPort())) { + logger.warn("未启用Assist服务"); + return null; + } + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append(String.format("http://%s:%s/%s", mediaServerItem.getIp(), mediaServerItem.getRecordAssistPort(), api)); + JSONObject responseJSON = null; + + if (param != null && param.keySet().size() > 0) { + stringBuffer.append("?"); + int index = 1; + for (String key : param.keySet()){ + if (param.get(key) != null) { + stringBuffer.append(key + "=" + param.get(key)); + if (index < param.size()) { + stringBuffer.append("&"); + } + } + index++; + } + } + + String url = stringBuffer.toString(); + Request request = new Request.Builder() + .get() + .url(url) + .build(); + if (callback == null) { + try { + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + ResponseBody responseBody = response.body(); + if (responseBody != null) { + String responseStr = responseBody.string(); + responseJSON = JSON.parseObject(responseStr); + } + }else { + response.close(); + Objects.requireNonNull(response.body()).close(); + } + } catch (ConnectException e) { + logger.error(String.format("连接Assist失败: %s, %s", e.getCause().getMessage(), e.getMessage())); + logger.info("请检查media配置并确认Assist已启动..."); + }catch (IOException e) { + logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + } + }else { + client.newCall(request).enqueue(new Callback(){ + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response){ + if (response.isSuccessful()) { + try { + String responseStr = Objects.requireNonNull(response.body()).string(); + callback.run(JSON.parseObject(responseStr)); + } catch (IOException e) { + logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + } + + }else { + response.close(); + Objects.requireNonNull(response.body()).close(); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + logger.error(String.format("连接Assist失败: %s, %s", e.getCause().getMessage(), e.getMessage())); + logger.info("请检查media配置并确认Assist已启动..."); + } + }); + } + + + + return responseJSON; + } + + + public JSONObject fileDuration(MediaServerItem mediaServerItem, String app, String stream, RequestCallback callback){ + Map param = new HashMap<>(); + param.put("app",app); + param.put("stream",stream); + param.put("recordIng",true); + return sendGet(mediaServerItem, "api/record/file/duration",param, callback); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java index bd39a0ce..c3c30a6a 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -215,7 +215,16 @@ public class ZLMHttpHookListener { if (deviceChannel != null) { ret.put("enable_audio", deviceChannel.isHasAudio()); } + // 如果是录像下载就设置视频间隔十秒 + if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.download) { + ret.put("mp4_max_second", 10); + ret.put("enable_mp4", true); + ret.put("enable_audio", true); + } + } + + return new ResponseEntity(ret.toString(), HttpStatus.OK); } @@ -324,7 +333,6 @@ public class ZLMHttpHookListener { if (mediaInfo != null) { subscribe.response(mediaInfo, json); } - } // 流消失移除redis play String app = item.getApp(); @@ -441,6 +449,7 @@ public class ZLMHttpHookListener { if ("rtp".equals(app)){ ret.put("close", true); StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(streamId); + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, null, streamId); if (streamInfoForPlayCatch != null) { // 如果在给上级推流,也不停止。 if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) { diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java index 84b36e3e..35052253 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java @@ -31,6 +31,7 @@ public class ZLMHttpHookSubscribe { on_server_keepalive } + @FunctionalInterface public interface Event{ void response(MediaServerItem mediaServerItem, JSONObject response); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java index 4cff4a68..9e764931 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.service; import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.InviteStreamCallback; import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo; @@ -34,4 +35,9 @@ public interface IPlayService { DeferredResult> playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack); void zlmServerOffline(String mediaServerId); + + DeferredResult> download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack); + DeferredResult> download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack); + + StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java index 9ee58673..7334184f 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java @@ -12,6 +12,8 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; +import com.genersoft.iot.vmp.gb28181.utils.DateUtil; +import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; @@ -28,7 +30,6 @@ import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult; import com.genersoft.iot.vmp.service.IMediaService; import com.genersoft.iot.vmp.service.IPlayService; import gov.nist.javax.sip.stack.SIPDialog; -import jdk.nashorn.internal.ir.RuntimeNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -38,10 +39,8 @@ import org.springframework.stereotype.Service; import org.springframework.util.ResourceUtils; import org.springframework.web.context.request.async.DeferredResult; -import javax.sip.header.CallIdHeader; -import javax.sip.header.Header; -import javax.sip.message.Request; import java.io.FileNotFoundException; +import java.math.BigDecimal; import java.util.*; @SuppressWarnings(value = {"rawtypes", "unchecked"}) @@ -71,6 +70,9 @@ public class PlayServiceImpl implements IPlayService { @Autowired private ZLMRESTfulUtils zlmresTfulUtils; + @Autowired + private AssistRESTfulUtils assistRESTfulUtils; + @Autowired private IMediaService mediaService; @@ -344,7 +346,7 @@ public class PlayServiceImpl implements IPlayService { return result; } - resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId, uuid, result); + resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId, uuid, result); RequestMessage msg = new RequestMessage(); msg.setId(uuid); msg.setKey(key); @@ -405,6 +407,136 @@ public class PlayServiceImpl implements IPlayService { return result; } + @Override + public DeferredResult> download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack) { + Device device = storager.queryVideoDevice(deviceId); + if (device == null) return null; + MediaServerItem newMediaServerItem = getNewMediaServerItem(device); + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true); + + return download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed,infoCallBack, hookCallBack); + } + + @Override + public DeferredResult> download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack) { + if (mediaServerItem == null || ssrcInfo == null) return null; + String uuid = UUID.randomUUID().toString(); + String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId; + DeferredResult> result = new DeferredResult<>(30000L); + Device device = storager.queryVideoDevice(deviceId); + if (device == null) { + result.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); + return result; + } + + resultHolder.put(key, uuid, result); + RequestMessage msg = new RequestMessage(); + msg.setId(uuid); + msg.setKey(key); + WVPResult wvpResult = new WVPResult<>(); + msg.setData(wvpResult); + PlayBackResult downloadResult = new PlayBackResult<>(); + downloadResult.setData(msg); + + Timer timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + logger.warn(String.format("录像下载请求超时,deviceId:%s ,channelId:%s", deviceId, channelId)); + wvpResult.setCode(-1); + wvpResult.setMsg("录像下载请求超时"); + downloadResult.setCode(-1); + hookCallBack.call(downloadResult); + SIPDialog dialog = streamSession.getDialogByStream(deviceId, channelId, ssrcInfo.getStream()); + // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 + if (dialog != null) { + // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 + cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null); + }else { + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); + mediaServerService.closeRTPServer(deviceId, channelId, ssrcInfo.getStream()); + streamSession.remove(deviceId, channelId, ssrcInfo.getStream()); + } + cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null); + // 回复之前所有的点播请求 + hookCallBack.call(downloadResult); + } + }, userSetup.getPlayTimeout()); + cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack, + inviteStreamInfo -> { + logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString()); + timer.cancel(); + StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId); + streamInfo.setStartTime(startTime); + streamInfo.setEndTime(endTime); + if (streamInfo == null) { + logger.warn("录像下载API调用失败!"); + wvpResult.setCode(-1); + wvpResult.setMsg("录像下载API调用失败"); + downloadResult.setCode(-1); + hookCallBack.call(downloadResult); + return ; + } + redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId()); + wvpResult.setCode(0); + wvpResult.setMsg("success"); + wvpResult.setData(streamInfo); + downloadResult.setCode(0); + downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem()); + downloadResult.setResponse(inviteStreamInfo.getResponse()); + hookCallBack.call(downloadResult); + }, event -> { + timer.cancel(); + downloadResult.setCode(-1); + wvpResult.setCode(-1); + wvpResult.setMsg(String.format("录像下载失败, 错误码: %s, %s", event.statusCode, event.msg)); + downloadResult.setEvent(event); + hookCallBack.call(downloadResult); + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + }); + return result; + } + + @Override + public StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream) { + StreamInfo streamInfo = redisCatchStorage.queryDownload(deviceId, channelId, stream, null); + if (streamInfo != null) { + if (streamInfo.getProgress() == 1) { + return streamInfo; + } + + // 获取当前已下载时长 + String mediaServerId = streamInfo.getMediaServerId(); + MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); + if (mediaServerItem == null) { + logger.warn("查询录像信息时发现节点已离线"); + return null; + } + if (mediaServerItem.getRecordAssistPort() != 0) { + JSONObject jsonObject = assistRESTfulUtils.fileDuration(mediaServerItem, streamInfo.getApp(), streamInfo.getStream(), null); + if (jsonObject != null && jsonObject.getInteger("code") == 0) { + long duration = jsonObject.getLong("data"); + + if (duration == 0) { + streamInfo.setProgress(0); + }else { + String startTime = streamInfo.getStartTime(); + String endTime = streamInfo.getEndTime(); + long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); + long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); + + BigDecimal currentCount = new BigDecimal(duration/1000); + BigDecimal totalCount = new BigDecimal(end-start); + BigDecimal divide = currentCount.divide(totalCount,2, BigDecimal.ROUND_HALF_UP); + double process = divide.doubleValue(); + streamInfo.setProgress(process); + } + } + } + } + return streamInfo; + } + @Override public void onPublishHandlerForDownload(InviteStreamInfo inviteStreamInfo, String deviceId, String channelId, String uuid) { RequestMessage msg = new RequestMessage(); diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java index 564deb55..3e82e8d8 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java @@ -91,7 +91,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService { MediaServerItem mediaInfo; WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(0); - if ("auto".equals(param.getMediaServerId())){ + if (param.getMediaServerId() == null || "auto".equals(param.getMediaServerId())){ mediaInfo = mediaServerService.getMediaServerForMinimumLoad(); }else { mediaInfo = mediaServerService.getOne(param.getMediaServerId()); diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java index 50948533..e669ab45 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java @@ -169,6 +169,8 @@ public interface IRedisCatchStorage { StreamInfo queryDownload(String deviceId, String channelId, String stream, String callId); + boolean stopDownload(String deviceId, String channelId, String stream, String callId); + /** * 查找第三方系统留下的国标预设值 * @param queryKey diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java index 6ad654e6..4840446f 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java @@ -166,8 +166,42 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Override public boolean startDownload(StreamInfo stream, String callId) { - return redis.set(String.format("%S_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX, - userSetup.getServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream); + boolean result; + if (stream.getProgress() == 1) { + result = redis.set(String.format("%S_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX, + userSetup.getServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream); + }else { + result = redis.set(String.format("%S_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX, + userSetup.getServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream, 60*60); + } + return result; + } + @Override + public boolean stopDownload(String deviceId, String channelId, String stream, String callId) { + DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(deviceId, channelId); + if (deviceChannel != null) { + deviceChannel.setStreamId(null); + deviceChannel.setDeviceId(deviceId); + deviceChannelMapper.update(deviceChannel); + } + if (deviceId == null) deviceId = "*"; + if (channelId == null) channelId = "*"; + if (stream == null) stream = "*"; + if (callId == null) callId = "*"; + String key = String.format("%S_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX, + userSetup.getServerId(), + deviceId, + channelId, + stream, + callId + ); + List scan = redis.scan(key); + if (scan.size() > 0) { + for (Object keyObj : scan) { + redis.del((String) keyObj); + } + } + return true; } @Override diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java deleted file mode 100644 index 4ffbd4b0..00000000 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.genersoft.iot.vmp.vmanager.gb28181.playback; - -import com.genersoft.iot.vmp.common.StreamInfo; -import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo; -import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; -import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; -import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; -import com.genersoft.iot.vmp.service.IMediaServerService; -import com.genersoft.iot.vmp.service.bean.SSRCInfo; -import com.genersoft.iot.vmp.storager.IRedisCatchStorage; -import com.genersoft.iot.vmp.service.IPlayService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiImplicitParam; -import io.swagger.annotations.ApiImplicitParams; -import io.swagger.annotations.ApiOperation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.alibaba.fastjson.JSONObject; -import com.genersoft.iot.vmp.gb28181.bean.Device; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; -import com.genersoft.iot.vmp.storager.IVideoManagerStorager; -import org.springframework.web.context.request.async.DeferredResult; - -import javax.sip.message.Response; -import java.util.UUID; - -@Api(tags = "历史媒体下载") -@CrossOrigin -@RestController -@RequestMapping("/api/download") -public class DownloadController { - - private final static Logger logger = LoggerFactory.getLogger(DownloadController.class); - - @Autowired - private SIPCommander cmder; - - @Autowired - private IVideoManagerStorager storager; - - @Autowired - private IRedisCatchStorage redisCatchStorage; - - // @Autowired - // private ZLMRESTfulUtils zlmresTfulUtils; - - @Autowired - private IPlayService playService; - - @Autowired - private DeferredResultHolder resultHolder; - - @Autowired - private IMediaServerService mediaServerService; - - @ApiOperation("开始历史媒体下载") - @ApiImplicitParams({ - @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class), - @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class), - @ApiImplicitParam(name = "startTime", value = "开始时间", dataTypeClass = String.class), - @ApiImplicitParam(name = "endTime", value = "结束时间", dataTypeClass = String.class), - @ApiImplicitParam(name = "downloadSpeed", value = "下载倍速", dataTypeClass = String.class), - }) - @GetMapping("/start/{deviceId}/{channelId}") - public DeferredResult> play(@PathVariable String deviceId, @PathVariable String channelId, - String startTime, String endTime, String downloadSpeed) { - - if (logger.isDebugEnabled()) { - logger.debug(String.format("历史媒体下载 API调用,deviceId:%s,channelId:%s,downloadSpeed:%s", deviceId, channelId, downloadSpeed)); - } - String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId; - String uuid = UUID.randomUUID().toString(); - DeferredResult> result = new DeferredResult>(30000L); - // 超时处理 - result.onTimeout(()->{ - logger.warn(String.format("设备下载响应超时,deviceId:%s ,channelId:%s", deviceId, channelId)); - RequestMessage msg = new RequestMessage(); - msg.setId(uuid); - msg.setKey(key); - msg.setData("Timeout"); - resultHolder.invokeAllResult(msg); - }); - if(resultHolder.exist(key, null)) { - return result; - } - resultHolder.put(key, uuid, result); - Device device = storager.queryVideoDevice(deviceId); - - MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); - if (newMediaServerItem == null) { - logger.warn(String.format("设备下载响应超时,deviceId:%s ,channelId:%s", deviceId, channelId)); - RequestMessage msg = new RequestMessage(); - msg.setId(uuid); - msg.setKey(key); - msg.setData("Timeout"); - resultHolder.invokeAllResult(msg); - return result; - } - - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true); - - cmder.downloadStreamCmd(newMediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, (InviteStreamInfo inviteStreamInfo) -> { - logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString()); - playService.onPublishHandlerForDownload(inviteStreamInfo, deviceId, channelId, uuid); - }, event -> { - RequestMessage msg = new RequestMessage(); - msg.setId(uuid); - msg.setKey(key); - msg.setData(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)); - resultHolder.invokeAllResult(msg); - }); - - return result; - } - - @ApiOperation("停止历史媒体下载") - @ApiImplicitParams({ - @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class), - @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class), - @ApiImplicitParam(name = "stream", value = "流ID", dataTypeClass = String.class), - }) - @GetMapping("/stop/{deviceId}/{channelId}/{stream}") - public ResponseEntity playStop(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) { - - cmder.streamByeCmd(deviceId, channelId, stream, null); - - if (logger.isDebugEnabled()) { - logger.debug(String.format("设备历史媒体下载停止 API调用,deviceId/channelId:%s_%s", deviceId, channelId)); - } - - if (deviceId != null && channelId != null) { - JSONObject json = new JSONObject(); - json.put("deviceId", deviceId); - json.put("channelId", channelId); - return new ResponseEntity(json.toString(), HttpStatus.OK); - } else { - logger.warn("设备历史媒体下载停止API调用失败!"); - return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); - } - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java index e5659814..d33dd2ac 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java @@ -1,6 +1,13 @@ package com.genersoft.iot.vmp.vmanager.gb28181.record; +import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.gb28181.bean.InviteStreamInfo; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.service.IMediaServerService; +import com.genersoft.iot.vmp.service.IPlayService; +import com.genersoft.iot.vmp.service.bean.SSRCInfo; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; @@ -8,6 +15,7 @@ import io.swagger.annotations.ApiOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; @@ -41,6 +49,12 @@ public class GBRecordController { @Autowired private DeferredResultHolder resultHolder; + @Autowired + private IPlayService playService; + + @Autowired + private IMediaServerService mediaServerService; + @ApiOperation("录像查询") @ApiImplicitParams({ @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class), @@ -77,4 +91,111 @@ public class GBRecordController { }); return result; } + + @ApiOperation("开始历史媒体下载") + @ApiImplicitParams({ + @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class), + @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class), + @ApiImplicitParam(name = "startTime", value = "开始时间", dataTypeClass = String.class), + @ApiImplicitParam(name = "endTime", value = "结束时间", dataTypeClass = String.class), + @ApiImplicitParam(name = "downloadSpeed", value = "下载倍速", dataTypeClass = String.class), + }) + @GetMapping("/download/start/{deviceId}/{channelId}") + public DeferredResult> download(@PathVariable String deviceId, @PathVariable String channelId, + String startTime, String endTime, String downloadSpeed) { + + if (logger.isDebugEnabled()) { + logger.debug(String.format("历史媒体下载 API调用,deviceId:%s,channelId:%s,downloadSpeed:%s", deviceId, channelId, downloadSpeed)); + } +// String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId; +// String uuid = UUID.randomUUID().toString(); +// DeferredResult> result = new DeferredResult>(30000L); +// // 超时处理 +// result.onTimeout(()->{ +// logger.warn(String.format("设备下载响应超时,deviceId:%s ,channelId:%s", deviceId, channelId)); +// RequestMessage msg = new RequestMessage(); +// msg.setId(uuid); +// msg.setKey(key); +// msg.setData("Timeout"); +// resultHolder.invokeAllResult(msg); +// }); +// if(resultHolder.exist(key, null)) { +// return result; +// } +// resultHolder.put(key, uuid, result); +// Device device = storager.queryVideoDevice(deviceId); +// +// MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); +// if (newMediaServerItem == null) { +// logger.warn(String.format("设备下载响应超时,deviceId:%s ,channelId:%s", deviceId, channelId)); +// RequestMessage msg = new RequestMessage(); +// msg.setId(uuid); +// msg.setKey(key); +// msg.setData("Timeout"); +// resultHolder.invokeAllResult(msg); +// return result; +// } +// +// SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, true); +// +// cmder.downloadStreamCmd(newMediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, (InviteStreamInfo inviteStreamInfo) -> { +// logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString()); +// playService.onPublishHandlerForDownload(inviteStreamInfo, deviceId, channelId, uuid); +// }, event -> { +// RequestMessage msg = new RequestMessage(); +// msg.setId(uuid); +// msg.setKey(key); +// msg.setData(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)); +// resultHolder.invokeAllResult(msg); +// }); + + if (logger.isDebugEnabled()) { + logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId)); + } + + DeferredResult> result = playService.download(deviceId, channelId, startTime, endTime, Integer.parseInt(downloadSpeed), null, hookCallBack->{ + resultHolder.invokeResult(hookCallBack.getData()); + }); + + return result; + } + + @ApiOperation("停止历史媒体下载") + @ApiImplicitParams({ + @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class), + @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class), + @ApiImplicitParam(name = "stream", value = "流ID", dataTypeClass = String.class), + }) + @GetMapping("/download/stop/{deviceId}/{channelId}/{stream}") + public ResponseEntity playStop(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) { + + cmder.streamByeCmd(deviceId, channelId, stream, null); + + if (logger.isDebugEnabled()) { + logger.debug(String.format("设备历史媒体下载停止 API调用,deviceId/channelId:%s_%s", deviceId, channelId)); + } + + if (deviceId != null && channelId != null) { + JSONObject json = new JSONObject(); + json.put("deviceId", deviceId); + json.put("channelId", channelId); + return new ResponseEntity(json.toString(), HttpStatus.OK); + } else { + logger.warn("设备历史媒体下载停止API调用失败!"); + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @ApiOperation("获取历史媒体下载进度") + @ApiImplicitParams({ + @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class), + @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class), + @ApiImplicitParam(name = "stream", value = "流ID", dataTypeClass = String.class), + }) + @GetMapping("/download/progress/{deviceId}/{channelId}/{stream}") + public ResponseEntity getProgress(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) { + + StreamInfo streamInfo = playService.getDownLoadInfo(deviceId, channelId, stream); + return new ResponseEntity<>(streamInfo, HttpStatus.OK); + } } diff --git a/web_src/build/webpack.dev.conf.js b/web_src/build/webpack.dev.conf.js index 070ae221..55efd304 100755 --- a/web_src/build/webpack.dev.conf.js +++ b/web_src/build/webpack.dev.conf.js @@ -32,6 +32,7 @@ const devWebpackConfig = merge(baseWebpackConfig, { contentBase: false, // since we use CopyWebpackPlugin. compress: true, host: HOST || config.dev.host, + // host:'127.0.0.1', port: PORT || config.dev.port, open: config.dev.autoOpenBrowser, overlay: config.dev.errorOverlay diff --git a/web_src/config/index.js b/web_src/config/index.js index cec91b87..b1e1cbe1 100644 --- a/web_src/config/index.js +++ b/web_src/config/index.js @@ -29,11 +29,13 @@ module.exports = { }, // Various Dev Server settings - host: 'localhost', // can be overwritten by process.env.HOST + host:"127.0.0.1", + useLocalIp: false, // can be overwritten by process.env.HOST port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined autoOpenBrowser: false, errorOverlay: true, notifyOnErrors: true, + hot: true,//自动保存 poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- diff --git a/web_src/package-lock.json b/web_src/package-lock.json index 356bbccb..b1da39e2 100644 --- a/web_src/package-lock.json +++ b/web_src/package-lock.json @@ -57,7 +57,7 @@ "vue-template-compiler": "^2.5.2", "webpack": "^3.6.0", "webpack-bundle-analyzer": "^2.9.0", - "webpack-dev-server": "^2.9.1", + "webpack-dev-server": "^2.11.5", "webpack-merge": "^4.1.0" }, "engines": { @@ -13382,7 +13382,7 @@ }, "node_modules/webpack-dev-server": { "version": "2.11.5", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.5.tgz", + "resolved": "https://registry.npmmirror.com/webpack-dev-server/-/webpack-dev-server-2.11.5.tgz", "integrity": "sha512-7TdOKKt7G3sWEhPKV0zP+nD0c4V9YKUJ3wDdBwQsZNo58oZIRoVIu66pg7PYkBW8A74msP9C2kLwmxGHndz/pw==", "dev": true, "dependencies": { @@ -25569,7 +25569,7 @@ }, "webpack-dev-server": { "version": "2.11.5", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.5.tgz", + "resolved": "https://registry.npmmirror.com/webpack-dev-server/-/webpack-dev-server-2.11.5.tgz", "integrity": "sha512-7TdOKKt7G3sWEhPKV0zP+nD0c4V9YKUJ3wDdBwQsZNo58oZIRoVIu66pg7PYkBW8A74msP9C2kLwmxGHndz/pw==", "dev": true, "requires": { diff --git a/web_src/src/components/dialog/devicePlayer.vue b/web_src/src/components/dialog/devicePlayer.vue index 5a080060..effd389d 100644 --- a/web_src/src/components/dialog/devicePlayer.vue +++ b/web_src/src/components/dialog/devicePlayer.vue @@ -175,6 +175,7 @@ + @@ -183,15 +184,15 @@ // import LivePlayer from '@liveqing/liveplayer' // import player from '../dialog/easyPlayer.vue' import player from '../dialog/jessibuca.vue' +import recordDownload from '../dialog/recordDownload.vue' export default { name: 'devicePlayer', props: {}, components: { - player, + player,recordDownload, }, computed: { getPlayerShared: function () { - return { sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl), sharedIframe: '', @@ -250,7 +251,7 @@ export default { that.tracks = []; that.tracksLoading = true; that.tracksNotLoaded = false; - if (tab.name == "codec") { + if (tab.name === "codec") { this.$axios({ method: 'get', url: '/zlm/' +this.mediaServerId+ '/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app='+ this.app +'&stream='+ this.streamId @@ -340,7 +341,7 @@ export default { this.$refs.videoPlayer.pause() that.$axios({ method: 'post', - url: '/api/play/convert/' + that.streamId + url: '/api/gb_record/convert/' + that.streamId }).then(function (res) { if (res.data.code == 0) { that.convertKey = res.data.key; @@ -474,8 +475,8 @@ export default { console.log(this.seekTime) if (that.streamId != "") { that.stopPlayRecord(function () { - that.streamId = "", - that.playRecord(row); + that.streamId = ""; + that.playRecord(row); }) } else { this.$axios({ @@ -506,22 +507,36 @@ export default { downloadRecord: function (row) { let that = this; if (that.streamId != "") { - that.stopDownloadRecord(function () { - that.streamId = "", - that.downloadRecord(row); + that.stopDownloadRecord(function (res) { + if (res.code == 0) { + that.streamId = ""; + that.downloadRecord(row); + }else { + this.$message({ + showClose: true, + message: res.data.msg, + type: "error", + }); + } + }) } else { this.$axios({ method: 'get', - url: '/api/download/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' + + url: '/api/gb_record/download/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' + row.endTime + '&downloadSpeed=4' }).then(function (res) { - var streamInfo = res.data; - that.app = streamInfo.app; - that.streamId = streamInfo.stream; - that.mediaServerId = streamInfo.mediaServerId; - that.videoUrl = that.getUrlByStreamInfo(streamInfo); - that.recordPlay = true; + if (res.data.code == 0) { + let streamInfo = res.data.data; + that.recordPlay = false; + that.$refs.recordDownload.openDialog(that.deviceId, that.channelId, streamInfo.app, streamInfo.stream, streamInfo.mediaServerId); + }else { + that.$message({ + showClose: true, + message: res.data.msg, + type: "error", + }); + } }); } }, @@ -530,9 +545,9 @@ export default { this.videoUrl = ''; this.$axios({ method: 'get', - url: '/api/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.streamId - }).then(function (res) { - if (callback) callback() + url: '/api/gb_record/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.streamId + }).then((res)=> { + if (callback) callback(res) }); }, ptzCamera: function (command) { diff --git a/web_src/src/components/dialog/recordDownload.vue b/web_src/src/components/dialog/recordDownload.vue new file mode 100644 index 00000000..6b7ca1f6 --- /dev/null +++ b/web_src/src/components/dialog/recordDownload.vue @@ -0,0 +1,195 @@ + + + + + + diff --git a/web_src/src/components/test.vue b/web_src/src/components/test.vue deleted file mode 100644 index d7801259..00000000 --- a/web_src/src/components/test.vue +++ /dev/null @@ -1,198 +0,0 @@ - - - - - diff --git a/web_src/src/components/test2.vue b/web_src/src/components/test2.vue deleted file mode 100644 index 75f182eb..00000000 --- a/web_src/src/components/test2.vue +++ /dev/null @@ -1,190 +0,0 @@ - - - - - diff --git a/web_src/src/router/index.js b/web_src/src/router/index.js index ad573cfa..05bb1ae6 100644 --- a/web_src/src/router/index.js +++ b/web_src/src/router/index.js @@ -11,7 +11,6 @@ import login from '../components/Login.vue' import parentPlatformList from '../components/ParentPlatformList.vue' import cloudRecord from '../components/CloudRecord.vue' import mediaServerManger from '../components/MediaServerManger.vue' -import test from '../components/test.vue' import web from '../components/setting/Web.vue' import sip from '../components/setting/Sip.vue' import media from '../components/setting/Media.vue' @@ -96,11 +95,6 @@ export default new VueRouter({ name: 'media', component: media, }, - { - path: '/test', - name: 'test', - component: test, - }, { path: '/play/wasm/:url', name: 'wasmPlayer',