优化国标录像下载,添加进度条以及自动合并文件下载,需要结合新版assist服务使用。
22个文件已修改
2个文件已添加
3个文件已删除
1346 ■■■■■ 已修改文件
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/build/webpack.dev.conf.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/config/index.js 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/package-lock.json 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/devicePlayer.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/recordDownload.vue 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/test.vue 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/test2.vue 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/router/index.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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;
    }
}
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;
    }
}
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);
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
    }
    /**
     * 添加一个点播/回放的事务信息
     * 后续可以通过流Id/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);
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -115,7 +115,9 @@
     * @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);
    /**
     * 视频流停止
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -428,7 +428,7 @@
                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 @@
            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("{} 分配的ZLM为: {} [{}:{}]", 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();
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);
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 如果级联播放,需要给上级发送此通知
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);
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java
New file
@@ -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<String, Object> 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<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);
    }
}
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);
            }
        }
        // 流消失移除redis 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())) {
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);
    }
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);
}
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("录像下载请求超时,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();
        msg.setKey(DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId);
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());
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
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,
        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
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java
File was deleted
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调用,deviceId:%s,channelId:%s,downloadSpeed:%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("设备下载响应超时,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<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 = "流ID", 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调用,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<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 = "流ID", 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);
    }
}
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
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-
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": {
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,7 +475,7 @@
            console.log(this.seekTime)
            if (that.streamId != "") {
                that.stopPlayRecord(function () {
                    that.streamId = "",
                    that.streamId = "";
                        that.playRecord(row);
                })
            } else {
@@ -506,22 +507,36 @@
        downloadRecord: function (row) {
            let that = this;
            if (that.streamId != "") {
                that.stopDownloadRecord(function () {
                    that.streamId = "",
                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) {
web_src/src/components/dialog/recordDownload.vue
New file
@@ -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>
web_src/src/components/test.vue
File was deleted
web_src/src/components/test2.vue
File was deleted
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',