From 7d9cc96ef54399795deb5b7fc7682e6323dc1202 Mon Sep 17 00:00:00 2001
From: 648540858 <456panlinlin>
Date: 星期五, 25 三月 2022 16:05:14 +0800
Subject: [PATCH] 优化国标录像下载,添加进度条以及自动合并文件下载,需要结合新版assist服务使用。

---
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java                                                 |   24 
 src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java                                                                      |   28 +
 src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java                                                           |  139 ++++++++
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java     |    1 
 web_src/package-lock.json                                                                                                       |    6 
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java                                                     |    4 
 src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java                                                         |    1 
 src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java                                                    |   38 ++
 web_src/src/components/dialog/devicePlayer.vue                                                                                  |   53 ++-
 src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java                                                            |    2 
 web_src/build/webpack.dev.conf.js                                                                                               |    1 
 src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java                                                           |   11 
 src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java                                     |    2 
 src/main/java/com/genersoft/iot/vmp/service/IPlayService.java                                                                   |    6 
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java       |    1 
 web_src/config/index.js                                                                                                         |    4 
 src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java                                                           |  142 ++++++++
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java |    7 
 /dev/null                                                                                                                       |  190 -----------
 src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java                                              |    9 
 src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java                                                          |   11 
 web_src/src/components/dialog/recordDownload.vue                                                                                |  195 +++++++++++
 web_src/src/router/index.js                                                                                                     |    6 
 src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java                                             |  121 +++++++
 src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java                                                    |    2 
 25 files changed, 761 insertions(+), 243 deletions(-)

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 0626384..4e4ba15 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 @@
     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 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 c2dedec..87d2635 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 @@
     private byte[] dialog;
     private String mediaServerId;
     private String ssrc;
+    private VideoStreamSessionManager.SessionType type;
 
     public String getDeviceId() {
         return deviceId;
@@ -74,4 +77,12 @@
     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 d511f42..50957f6 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 @@
                         List<ParentPlatform> 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 ba8f24c..6eed17e 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 @@
 	@Autowired
 	private UserSetup userSetup;
 
+	public enum SessionType {
+		play,
+		playback,
+		download
+	}
+
 	/**
 	 * 娣诲姞涓�涓偣鎾�/鍥炴斁鐨勪簨鍔′俊鎭�
 	 * 鍚庣画鍙互閫氳繃娴両d/callID
@@ -40,7 +46,7 @@
 	 * @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 @@
 		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 409eedb..9cd89e0 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 @@
 	 * @param endTime 缁撴潫鏃堕棿,鏍煎紡瑕佹眰锛歽yyy-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 48bffd7..0f2242c 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 @@
 				errorEvent.response(e);
 			}), e ->{
 				// 杩欓噷涓轰緥閬垮厤涓�涓�氶亾鐨勭偣鎾彧鏈変竴涓猚allID杩欎釜鍙傛暟浣跨敤涓�涓浐瀹氬��
-				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 @@
 
 	        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 @@
 	 * @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("{} 鍒嗛厤鐨刏LM涓�: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
 
@@ -571,8 +572,6 @@
 	        content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
 	        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();
 
@@ -639,15 +638,20 @@
 			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 f21dfc0..9550266 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 @@
                     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 8235ade..e36a705 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 @@
         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 d8c7250..959432c 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 @@
                     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 0000000..249ec03
--- /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閫茶娣诲姞鏀旀埅鍣╨oggingInterceptor
+            httpClientBuilder.addInterceptor(logging);
+        }
+        return httpClientBuilder.build();
+    }
+
+
+    public JSONObject sendGet(MediaServerItem mediaServerItem, String api, Map<String, Object> param, RequestCallback callback) {
+        OkHttpClient client = getClient();
+
+        if (mediaServerItem == null) {
+            return null;
+        }
+        if (StringUtils.isEmpty(mediaServerItem.getRecordAssistPort())) {
+            logger.warn("鏈惎鐢ˋssist鏈嶅姟");
+            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("璇锋鏌edia閰嶇疆骞剁‘璁ssist宸插惎鍔�...");
+                }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("璇锋鏌edia閰嶇疆骞剁‘璁ssist宸插惎鍔�...");
+                    }
+                });
+            }
+
+
+
+        return responseJSON;
+    }
+
+
+    public JSONObject fileDuration(MediaServerItem mediaServerItem, String app, String stream, RequestCallback callback){
+        Map<String, Object> 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 bd39a0c..c3c30a6 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 @@
 			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<String>(ret.toString(), HttpStatus.OK);
 	}
 	
@@ -324,7 +333,6 @@
 			if (mediaInfo != null) {
 				subscribe.response(mediaInfo, json);
 			}
-
 		}
 		// 娴佹秷澶辩Щ闄edis play
 		String app = item.getApp();
@@ -441,6 +449,7 @@
 		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 84b36e3..3505225 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 @@
         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 4cff4a6..9e76493 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 @@
     DeferredResult<ResponseEntity<String>> playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack);
 
     void zlmServerOffline(String mediaServerId);
+
+    DeferredResult<ResponseEntity<String>> download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack);
+    DeferredResult<ResponseEntity<String>> 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 9ee5867..7334184 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.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.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.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"})
@@ -70,6 +69,9 @@
 
     @Autowired
     private ZLMRESTfulUtils zlmresTfulUtils;
+
+    @Autowired
+    private AssistRESTfulUtils assistRESTfulUtils;
 
     @Autowired
     private IMediaService mediaService;
@@ -344,7 +346,7 @@
             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);
@@ -406,6 +408,136 @@
     }
 
     @Override
+    public DeferredResult<ResponseEntity<String>> 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<ResponseEntity<String>> 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<ResponseEntity<String>> 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<StreamInfo> wvpResult = new WVPResult<>();
+        msg.setData(wvpResult);
+        PlayBackResult<RequestMessage> downloadResult = new PlayBackResult<>();
+        downloadResult.setData(msg);
+
+        Timer timer = new Timer();
+        timer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                logger.warn(String.format("褰曞儚涓嬭浇璇锋眰瓒呮椂锛宒eviceId锛�%s 锛宑hannelId锛�%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();
         msg.setKey(DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId);
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 564deb5..3e82e8d 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 @@
         MediaServerItem mediaInfo;
         WVPResult<StreamInfo> 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 5094853..e669ab4 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 @@
 
     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 6ad654e..4840446 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 @@
 
     @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<Object> 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 4ffbd4b..0000000
--- 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<ResponseEntity<String>> play(@PathVariable String deviceId, @PathVariable String channelId,
-													   String startTime, String endTime, String downloadSpeed) {
-
-		if (logger.isDebugEnabled()) {
-			logger.debug(String.format("鍘嗗彶濯掍綋涓嬭浇 API璋冪敤锛宒eviceId锛�%s锛宑hannelId锛�%s锛宒ownloadSpeed锛�%s", deviceId, channelId, downloadSpeed));
-		}
-		String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId;
-		String uuid = UUID.randomUUID().toString();
-		DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(30000L);
-		// 瓒呮椂澶勭悊
-		result.onTimeout(()->{
-			logger.warn(String.format("璁惧涓嬭浇鍝嶅簲瓒呮椂锛宒eviceId锛�%s 锛宑hannelId锛�%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("璁惧涓嬭浇鍝嶅簲瓒呮椂锛宒eviceId锛�%s 锛宑hannelId锛�%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 = "娴両D", dataTypeClass = String.class),
-	})
-	@GetMapping("/stop/{deviceId}/{channelId}/{stream}")
-	public ResponseEntity<String> playStop(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) {
-
-		cmder.streamByeCmd(deviceId, channelId, stream, null);
-
-		if (logger.isDebugEnabled()) {
-			logger.debug(String.format("璁惧鍘嗗彶濯掍綋涓嬭浇鍋滄 API璋冪敤锛宒eviceId/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<String>(json.toString(), HttpStatus.OK);
-		} else {
-			logger.warn("璁惧鍘嗗彶濯掍綋涓嬭浇鍋滄API璋冪敤澶辫触锛�");
-			return new ResponseEntity<String>(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 e565981..d33dd2a 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 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;
@@ -40,6 +48,12 @@
 
 	@Autowired
 	private DeferredResultHolder resultHolder;
+
+	@Autowired
+	private IPlayService playService;
+
+	@Autowired
+	private IMediaServerService mediaServerService;
 
 	@ApiOperation("褰曞儚鏌ヨ")
 	@ApiImplicitParams({
@@ -77,4 +91,111 @@
 		});
         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<ResponseEntity<String>> download(@PathVariable String deviceId, @PathVariable String channelId,
+													   String startTime, String endTime, String downloadSpeed) {
+
+		if (logger.isDebugEnabled()) {
+			logger.debug(String.format("鍘嗗彶濯掍綋涓嬭浇 API璋冪敤锛宒eviceId锛�%s锛宑hannelId锛�%s锛宒ownloadSpeed锛�%s", deviceId, channelId, downloadSpeed));
+		}
+//		String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId;
+//		String uuid = UUID.randomUUID().toString();
+//		DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(30000L);
+//		// 瓒呮椂澶勭悊
+//		result.onTimeout(()->{
+//			logger.warn(String.format("璁惧涓嬭浇鍝嶅簲瓒呮椂锛宒eviceId锛�%s 锛宑hannelId锛�%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("璁惧涓嬭浇鍝嶅簲瓒呮椂锛宒eviceId锛�%s 锛宑hannelId锛�%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璋冪敤锛宒eviceId锛�%s 锛宑hannelId锛�%s", deviceId, channelId));
+		}
+
+		DeferredResult<ResponseEntity<String>> 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 = "娴両D", dataTypeClass = String.class),
+	})
+	@GetMapping("/download/stop/{deviceId}/{channelId}/{stream}")
+	public ResponseEntity<String> playStop(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) {
+
+		cmder.streamByeCmd(deviceId, channelId, stream, null);
+
+		if (logger.isDebugEnabled()) {
+			logger.debug(String.format("璁惧鍘嗗彶濯掍綋涓嬭浇鍋滄 API璋冪敤锛宒eviceId/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<String>(json.toString(), HttpStatus.OK);
+		} else {
+			logger.warn("璁惧鍘嗗彶濯掍綋涓嬭浇鍋滄API璋冪敤澶辫触锛�");
+			return new ResponseEntity<String>(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 = "娴両D", dataTypeClass = String.class),
+	})
+	@GetMapping("/download/progress/{deviceId}/{channelId}/{stream}")
+	public ResponseEntity<StreamInfo> 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 070ae22..55efd30 100755
--- a/web_src/build/webpack.dev.conf.js
+++ b/web_src/build/webpack.dev.conf.js
@@ -32,6 +32,7 @@
     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 cec91b8..b1e1cbe 100644
--- a/web_src/config/index.js
+++ b/web_src/config/index.js
@@ -29,11 +29,13 @@
     },
 
     // 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 356bbcc..b1da39e 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 5a08006..effd389 100644
--- a/web_src/src/components/dialog/devicePlayer.vue
+++ b/web_src/src/components/dialog/devicePlayer.vue
@@ -175,6 +175,7 @@
             </el-tabs>
         </div>
     </el-dialog>
+    <recordDownload ref="recordDownload"></recordDownload>
 </div>
 </template>
 
@@ -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: '<iframe src="' + window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl) + '"></iframe>',
@@ -250,7 +251,7 @@
             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 @@
             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 @@
             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 @@
         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 @@
             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 0000000..6b7ca1f
--- /dev/null
+++ b/web_src/src/components/dialog/recordDownload.vue
@@ -0,0 +1,195 @@
+<template>
+<div id="recordDownload" >
+  <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>
+    <el-row>
+      <el-col :span="18" style="padding-top: 7px;">
+        <el-progress :percentage="percentage"></el-progress>
+      </el-col>
+      <el-col :span="6" >
+<!--       <el-dropdown size="mini" title="鎾斁鍊嶉��" style="margin-left: 1px;" @command="gbScale">-->
+<!--         <el-button-group>-->
+<!--           <el-button size="mini" style="width: 100%">-->
+<!--             {{scale}}鍊嶉�� <i class="el-icon-arrow-down el-icon&#45;&#45;right"></i>-->
+<!--           </el-button>-->
+<!--         </el-button-group>-->
+<!--        <el-dropdown-menu  slot="dropdown">-->
+<!--          <el-dropdown-item command="1">1鍊嶉��</el-dropdown-item>-->
+<!--          <el-dropdown-item command="2">2鍊嶉��</el-dropdown-item>-->
+<!--          <el-dropdown-item command="4">4鍊嶉��</el-dropdown-item>-->
+<!--        </el-dropdown-menu>-->
+<!--      </el-dropdown>-->
+        <el-button icon="el-icon-download" v-if="percentage < 100" size="mini" title="鐐瑰嚮涓嬭浇鍙皢浠ョ紦瀛橀儴鍒嗕笅杞藉埌鏈湴" @click="download()">鍋滄缂撳瓨骞朵笅杞�</el-button>
+      </el-col>
+    </el-row>
+  </el-dialog>
+</div>
+</template>
+
+
+<script>
+
+import moment from "moment";
+
+export default {
+    name: 'recordDownload',
+    created() {
+
+
+    },
+    data() {
+        return {
+          title: "鍥涘�嶉�熶笅杞戒腑...",
+          deviceId: "",
+          channelId: "",
+          app: "",
+          stream: "",
+          mediaServerId: "",
+          showDialog: false,
+          scale: 1,
+          percentage: 0.00,
+          streamInfo: null,
+          taskId: null,
+          getProgressRun: false,
+          getProgressForFileRun: false,
+
+        };
+    },
+    methods: {
+        openDialog: function (deviceId, channelId, app, stream, mediaServerId) {
+            this.deviceId = deviceId;
+            this.channelId = channelId;
+            this.app = app;
+            this.stream = stream;
+            this.mediaServerId = mediaServerId;
+            this.showDialog = true;
+            this.getProgressRun = true;
+            this.percentage = 0.0;
+            this.getProgressTimer()
+        },
+        getProgressTimer(){
+          if (!this.getProgressRun) {
+            return;
+          }
+          if (this.percentage == 100 ) {
+            this.getFileDownload();
+            return;
+          }
+          setTimeout( ()=>{
+            if (!this.showDialog) return;
+            this.getProgress(this.getProgressTimer())
+          }, 5000)
+        },
+        getProgress: function (callback){
+          this.$axios({
+            method: 'get',
+            url: `/api/gb_record/download/progress/${this.deviceId}/${this.channelId}/${this.stream}`
+          }).then((res)=> {
+              console.log(res)
+              console.log(res.data.progress)
+              this.streamInfo = res.data;
+              if (parseFloat(res.data.progress) == 1) {
+                this.percentage = 100;
+              }else {
+                this.percentage = (res.data.progress*100).toFixed(1);
+              }
+              if (callback)callback();
+          }).catch((e) =>{
+
+          });
+        },
+        close: function (){
+          if (this.streamInfo.progress < 100) {
+            this.stopDownloadRecord();
+          }
+          this.showDialog=false;
+          this.getProgressRun = false;
+          this.getProgressForFileRun = false;
+        },
+        gbScale: function (scale){
+          this.scale = scale;
+        },
+        download: function (){
+          this.getProgressRun = false;
+          if (this.streamInfo != null ) {
+            if (this.streamInfo.progress < 1) {
+              // 鍙戦�佸仠姝㈢紦瀛�
+              this.stopDownloadRecord((res)=>{
+                  this.getFileDownload()
+              })
+            }else {
+              this.getFileDownload()
+            }
+          }
+        },
+        stopDownloadRecord: function (callback) {
+          this.$axios({
+            method: 'get',
+            url: '/api/gb_record/download/stop/' + this.deviceId + "/" + this.channelId+ "/" + this.stream
+          }).then((res)=> {
+            if (callback) callback(res)
+          });
+        },
+        getFileDownload: function (){
+          this.$axios({
+            method: 'get',
+            url:`/record_proxy/${this.mediaServerId}/api/record/file/download/task/add`,
+            params: {
+              app: this.app,
+              stream: this.stream,
+              startTime: null,
+              endTime: null,
+            }
+          }).then((res) =>{
+            if (res.data.code === 0 && res.data.msg === "success") {
+              // 鏌ヨ杩涘害
+              this.title = "褰曞儚鏂囦欢澶勭悊涓�..."
+              this.taskId = res.data.data;
+              this.percentage = 0.0;
+              this.getProgressForFileRun = true;
+              this.getProgressForFileTimer();
+            }
+          }).catch(function (error) {
+            console.log(error);
+          });
+        },
+        getProgressForFileTimer: function (){
+          if (!this.getProgressForFileRun || this.percentage == 100) {
+            return;
+          }
+          setTimeout( ()=>{
+            if (!this.showDialog) return;
+            this.getProgressForFile(this.getProgressForFileTimer())
+          }, 1000)
+        },
+        getProgressForFile: function (callback){
+          this.$axios({
+            method: 'get',
+            url:`/record_proxy/${this.mediaServerId}/api/record/file/download/task/list`,
+            params: {
+              app: this.app,
+              stream: this.stream,
+              taskId: this.taskId,
+              isEnd: true,
+            }
+          }).then((res) => {
+            if (res.data.code == 0) {
+                this.percentage = parseFloat(res.data.data.percentage)*100
+                 if (res.data.data[0].percentage === '1') {
+                   this.getProgressForFileRun = false;
+                   window.open(res.data.data[0].downloadFile)
+                   this.close();
+                 }else {
+                   if (callback)callback()
+                 }
+            }
+          }).catch(function (error) {
+            console.log(error);
+          });
+        }
+    }
+};
+</script>
+
+<style>
+
+</style>
diff --git a/web_src/src/components/test.vue b/web_src/src/components/test.vue
deleted file mode 100644
index d780125..0000000
--- a/web_src/src/components/test.vue
+++ /dev/null
@@ -1,198 +0,0 @@
-<template>
-<div id="test">
-  <div class="timeQuery" id="timeQuery">
-      <div class="timeQuery-background"  ></div>
-      <div class="timeQuery-pointer">
-        <div class="timeQuery-pointer-content" id="timeQueryPointer">
-          <div class="timeQuery-pointer-handle"  @mousedown.left="mousedownHandler" ></div>
-        </div>
-      </div>
-
-      <div class="timeQuery-data" >
-
-        <div class="timeQuery-data-cell" v-for="item of recordData" :style="'width:'  +  getDataWidth(item) + '%; left:' + getDataLeft(item) + '%'"  ></div>
-        <!--          <div class="timeQuery-data-cell" style="width: 30%; left: 20%" @click="timeChoose"></div>-->
-        <!--          <div class="timeQuery-data-cell" style="width: 60%; left: 20%" @click="timeChoose"></div>-->
-      </div>
-
-      <div class="timeQuery-label" >
-        <div class="timeQuery-label-cell" style="left: 0%">
-          <div class="timeQuery-label-cell-label">0</div>
-        </div>
-        <div v-for="index of timeNode" class="timeQuery-label-cell" :style="'left:' + (100.0/timeNode*index).toFixed(4) + '%'">
-          <div class="timeQuery-label-cell-label">{{24/timeNode * index}}</div>
-        </div>
-      </div>
-  </div>
-
-</div>
-</template>
-
-<script>
-export default {
-  name: "test",
-  data() {
-    return {
-      mouseDown: false,
-      timeNode: 24,
-      recordData:[
-        {
-          startTime: "2021-04-18 00:00:00",
-          endTime: "2021-04-18 00:00:09",
-        },
-        {
-          startTime: "2021-04-18 00:00:09",
-          endTime: "2021-04-18 01:00:05",
-        },
-        {
-          startTime: "2021-04-18 02:00:01",
-          endTime: "2021-04-18 04:25:05",
-        },
-        {
-          startTime: "2021-04-18 05:00:01",
-          endTime: "2021-04-18 20:00:05",
-        },
-      ]
-    };
-  },
-  mounted() {
-    document.body.addEventListener("mouseup", this.mouseupHandler, false)
-    document.body.addEventListener("mousemove", this.mousemoveHandler, false)
-  },
-  methods:{
-    getTimeNode(){
-      let mine = 20
-      let width = document.getElementById("timeQuery").offsetWidth
-      if (width/20 > 24){
-        return 24
-      }else if (width/20 > 12) {
-        return 12
-      }else if (width/20 > 6) {
-        return 6
-      }
-    },
-    timeChoose(event){
-      console.log(event)
-    },
-    getDataWidth(item){
-      let startTime = new Date(item.startTime);
-      let endTime = new Date(item.endTime);
-      let result = parseFloat((endTime.getTime() - startTime.getTime())/(24*60*60*10))
-      // console.log(result)
-      return parseFloat((endTime.getTime() - startTime.getTime())/(24*60*60*10))
-    },
-    getDataLeft(item){
-      let startTime = new Date(item.startTime);
-      let differenceTime = startTime.getTime() - new Date(item.startTime.substr(0,10) + " 00:00:00").getTime()
-      let result = differenceTime/(24*60*60*10)
-      console.log(differenceTime)
-      console.log(result)
-      return parseFloat(differenceTime/(24*60*60*10));
-    },
-    mousedownHandler(event){
-      this.mouseDown = true
-    },
-    mousemoveHandler(event){
-      if (this.mouseDown){
-        document.getElementById("timeQueryPointer").style.left = (event.clientX - 20)+ "px"
-      }
-    },
-    mouseupHandler(event){
-      this.mouseDown = false
-    }
-  }
-}
-</script>
-
-<style scoped>
-  .timeQuery{
-    width: 96%;
-    margin-left: 2%;
-    margin-right: 2%;
-    margin-top: 20%;
-    position: absolute;
-  }
-  .timeQuery-background{
-    height: 16px;
-    width: 100%;
-    background-color: #ececec;
-    position: absolute;
-    left: 0;
-    top: 0;
-    z-index: 10;
-    box-shadow: #9d9d9d 0px 0px 10px inset;
-  }
-  .timeQuery-data{
-    height: 16px;
-    width: 100%;
-    position: absolute;
-    left: 0;
-    top: 0;
-    z-index: 11;
-  }
-  .timeQuery-data-cell{
-    height: 10px;
-    background-color: #888787;
-    position: absolute;
-    z-index: 11;
-    -webkit-box-shadow: #9d9d9d 0px 0px 10px inset;
-    margin-top: 3px;
-    top: 100%;
-  }
-  .timeQuery-label{
-    height: 16px;
-    width: 100%;
-    position: absolute;
-    pointer-events: none;
-    left: 0;
-    top: 0;
-    z-index: 11;
-  }
-  .timeQuery-label-cell{
-    height: 16px;
-    position: absolute;
-    z-index: 12;
-    width: 0px;
-    border-right: 1px solid #b7b7b7;
-  }
-  .timeQuery-label-cell-label {
-    width: 23px;
-    text-align: center;
-    height: 18px;
-    margin-left: -10px;
-    margin-top: -30px;
-    color: #444;
-  }
-  .timeQuery-pointer{
-    width: 0px;
-    height: 18px;
-    position: absolute;
-    left: 0;
-  }
-  .timeQuery-pointer-content{
-    width: 0px;
-    height: 70px;
-    position: absolute;
-    border-right: 2px solid #f60303;
-    z-index: 14;
-    top: -30px;
-  }
-  .timeQuery-pointer-handle {
-    width: 0;
-    height: 0;
-    border-top: 12px solid transparent;
-    border-right: 12px solid transparent;
-    border-bottom: 20px solid #ff0909;
-    border-left: 12px solid transparent;
-    cursor: no-drop;
-    position: absolute;
-    left: -11px;
-    top: 50px;
-  }
-  /*.timeQuery-cell:after{*/
-  /*  content: "";*/
-  /*  height: 14px;*/
-  /*  border: 1px solid #e70303;*/
-  /*  position: absolute;*/
-  /*}*/
-</style>
diff --git a/web_src/src/components/test2.vue b/web_src/src/components/test2.vue
deleted file mode 100644
index 75f182e..0000000
--- a/web_src/src/components/test2.vue
+++ /dev/null
@@ -1,190 +0,0 @@
-<template>
-<div id="test2">
-  <div class="timeQuery" style="width: 100%; height: 300px" id="timeQuery">
-  </div>
-</div>
-</template>
-
-<script>
-
-import * as echarts from 'echarts';
-
-export default {
-  name: "test2",
-  data() {
-    return {
-    };
-  },
-  mounted() {
-    var base = +new Date("2021-02-02 00:00:00");
-    var oneDay = 24 * 3600 * 1000;
-
-    var data = [[base, 10]];
-
-    for (var i = 1; i < 24; i++) {
-      var now = new Date(base += oneDay);
-      data.push([
-        new Date("2021-02-02 " + i+":00:00"), 10
-      ]);
-    }
-    // 鍩轰簬鍑嗗濂界殑dom锛屽垵濮嬪寲echarts瀹炰緥
-    var myChart = echarts.init(document.getElementById('timeQuery'));
-    let option = {
-
-      toolbox: {
-        feature: {
-          dataZoom: {
-            yAxisIndex: 'none'
-          },
-          restore: {},
-          saveAsImage: {}
-        }
-      },
-      xAxis: {
-        type: 'time',
-        boundaryGap: false
-      },
-      yAxis: {
-        type: 'value',
-        show: false,
-        splitLine:{show: false},   //鍘婚櫎缃戞牸绾�
-        boundaryGap: [0, '100%']
-      },
-      dataZoom: [{
-        type: 'inside',
-        start: 0,
-        end: 20
-      }, {
-        start: 0,
-        end: 20
-      }],
-      series: [
-        {
-          name: '妯℃嫙鏁版嵁',
-          type: 'line',
-          smooth: false,
-          symbol: 'none',
-          areaStyle: {},
-          data: data
-        }
-      ]
-    };
-    // 缁樺埗鍥捐〃
-      myChart.setOption(option);
-  },
-  methods:{
-    getTimeNode(){
-      let mine = 20
-      let width = document.getElementById("timeQuery").offsetWidth
-      if (width/20 > 24){
-        return 24
-      }else if (width/20 > 12) {
-        return 12
-      }else if (width/20 > 6) {
-        return 6
-      }
-    },
-    hoveEvent(event){
-      console.log(2222222)
-      console.log(event)
-    },
-    timeChoose(event){
-      console.log(event)
-    },
-    getDataWidth(item){
-      let startTime = new Date(item.startTime);
-      let endTime = new Date(item.endTime);
-      let result = parseFloat((endTime.getTime() - startTime.getTime())/(24*60*60*10))
-      // console.log(result)
-      return parseFloat((endTime.getTime() - startTime.getTime())/(24*60*60*10))
-    },
-    getDataLeft(item){
-      let startTime = new Date(item.startTime);
-      let differenceTime = startTime.getTime() - new Date(item.startTime.substr(0,10) + " 00:00:00").getTime()
-      let result = differenceTime/(24*60*60*10)
-      console.log(differenceTime)
-      console.log(result)
-      return parseFloat(differenceTime/(24*60*60*10));
-    }
-  }
-}
-</script>
-
-<style scoped>
-  .timeQuery{
-    width: 96%;
-    margin-left: 2%;
-    margin-right: 2%;
-    margin-top: 20%;
-    position: absolute;
-  }
-  .timeQuery-background{
-    height: 16px;
-    width: 100%;
-    background-color: #ececec;
-    position: absolute;
-    left: 0;
-    top: 0;
-    z-index: 10;
-    box-shadow: #9d9d9d 0px 0px 10px inset;
-  }
-  .timeQuery-data{
-    height: 16px;
-    width: 100%;
-    position: absolute;
-    left: 0;
-    top: 0;
-    z-index: 11;
-  }
-  .timeQuery-data-cell{
-    height: 10px;
-    background-color: #888787;
-    position: absolute;
-    z-index: 11;
-    -webkit-box-shadow: #9d9d9d 0px 0px 10px inset;
-    margin-top: 3px;
-  }
-  .timeQuery-label{
-    height: 16px;
-    width: 100%;
-    position: absolute;
-    pointer-events: none;
-    left: 0;
-    top: 0;
-    z-index: 11;
-  }
-  .timeQuery-label-cell{
-    height: 16px;
-    position: absolute;
-    z-index: 12;
-    width: 0px;
-    border-right: 1px solid #b7b7b7;
-  }
-  .timeQuery-label-cell-label {
-    width: 23px;
-    text-align: center;
-    height: 18px;
-    margin-left: -10px;
-    margin-top: -30px;
-    color: #444;
-  }
-  .timeQuery-pointer{
-    width: 0px;
-    height: 18px;
-    position: absolute;
-    left: 0;
-  }
-  .timeQuery-pointer-content{
-    width: 0px;
-    height: 16px;
-    position: absolute;
-    border-right: 3px solid #f60303;
-    z-index: 14;
-  }
-  /*.timeQuery-cell:after{*/
-  /*  content: "";*/
-  /*  height: 14px;*/
-  /*  border: 1px solid #e70303;*/
-  /*  position: absolute;*/
-  /*}*/
-</style>
diff --git a/web_src/src/router/index.js b/web_src/src/router/index.js
index ad573cf..05bb1ae 100644
--- a/web_src/src/router/index.js
+++ b/web_src/src/router/index.js
@@ -11,7 +11,6 @@
 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'
@@ -95,11 +94,6 @@
       path: '/setting/media',
       name: 'media',
       component: media,
-    },
-    {
-      path: '/test',
-      name: 'test',
-      component: test,
     },
     {
       path: '/play/wasm/:url',

--
Gitblit v1.8.0