648540858
2023-12-06 7e136c9ac7265bedfdb79b4bca465965486e0541
完成下载文件的前后调试
7个文件已修改
259 ■■■■ 已修改文件
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/build/webpack.dev.conf.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/recordDownload.vue 108 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -761,7 +761,7 @@
        taskExecutor.execute(() -> {
            JSONObject json = (JSONObject) JSON.toJSON(param);
            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout);
            if (subscribes != null && subscribes.size() > 0) {
            if (subscribes != null && !subscribes.isEmpty()) {
                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
                    subscribe.response(null, param);
                }
@@ -780,7 +780,14 @@
        logger.info("[ZLM HOOK] 录像完成事件:{}->{}", param.getMediaServerId(), param.getFile_path());
        taskExecutor.execute(() -> {
            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_record_mp4);
            if (subscribes != null && !subscribes.isEmpty()) {
                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
                    subscribe.response(null, param);
                }
            }
            cloudRecordService.addRecord(param);
        });
        return HookResult.SUCCESS();
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
@@ -45,5 +45,4 @@
    void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback);
    void getFilePath(String deviceId, String channelId, String stream, ErrorCallback<DownloadFileInfo> callback);
}
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -49,9 +49,11 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
@SuppressWarnings(value = {"rawtypes", "unchecked"})
@Service
@@ -718,6 +720,28 @@
                        // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
                        InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId,
                                downLoadTimeOutTaskKey, callback, inviteInfo, InviteSessionType.DOWNLOAD);
                        // 注册录像回调事件,录像下载结束后写入下载地址
                        ZlmHttpHookSubscribe.Event hookEventForRecord = (mediaServerItemInuse, hookParam) -> {
                            logger.info("[录像下载] 收到录像写入磁盘消息: , {}/{}-{}",
                                    inviteInfo.getDeviceId(), inviteInfo.getChannelId(), ssrcInfo.getStream());
                            logger.info("[录像下载] 收到录像写入磁盘消息内容: " + hookParam);
                            OnRecordMp4HookParam recordMp4HookParam = (OnRecordMp4HookParam)hookParam;
                            String filePath = recordMp4HookParam.getFile_path();
                            DownloadFileInfo downloadFileInfo = getDownloadFilePath(mediaServerItem, filePath);
                            InviteInfo inviteInfoForNew = inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId()
                                    , inviteInfo.getChannelId(), inviteInfo.getStream());
                            inviteInfoForNew.getStreamInfo().setDownLoadFilePath(downloadFileInfo);
                            inviteStreamService.updateInviteInfo(inviteInfoForNew);
                        };
                        HookSubscribeForRecordMp4 hookSubscribe = HookSubscribeFactory.on_record_mp4(
                                mediaServerItem.getId(), "rtp", ssrcInfo.getStream());
                        // 设置过期时间,下载失败时自动处理订阅数据
//                        long difference = DateUtil.getDifference(startTime, endTime)/1000;
//                        Instant expiresInstant = Instant.now().plusSeconds(TimeUnit.MINUTES.toSeconds(difference * 2));
//                        hookSubscribe.setExpires(expiresInstant);
                        subscribe.addSubscribe(hookSubscribe, hookEventForRecord);
                    });
        } catch (InvalidArgumentException | SipException | ParseException e) {
            logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
@@ -791,74 +815,13 @@
            BigDecimal totalCount = new BigDecimal((end - start) * 1000);
            BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP);
            double process = divide.doubleValue();
            if (process > 0.999) {
                process = 1.0;
            }
            inviteInfo.getStreamInfo().setProgress(process);
        }
        inviteStreamService.updateInviteInfo(inviteInfo);
        return inviteInfo.getStreamInfo();
    }
    @Override
    public void getFilePath(String deviceId, String channelId, String stream, ErrorCallback<DownloadFileInfo> callback) {
        InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, stream);
        if (inviteInfo == null || inviteInfo.getStreamInfo() == null) {
            logger.warn("[获取录像下载文件地址] 未查询到录像下载的信息, {}/{}-{}", deviceId, channelId, stream);
            callback.run(ErrorCode.ERROR100.getCode(), "未查询到录像下载的信息", null);
            return ;
        }
        if (!ObjectUtils.isEmpty(inviteInfo.getStreamInfo().getDownLoadFilePath())) {
            callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(),
                    inviteInfo.getStreamInfo().getDownLoadFilePath());
            return;
        }
        StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo("rtp", stream);
        if (streamAuthorityInfo == null) {
            logger.warn("[获取录像下载文件地址] 未查询到录像的视频信息, {}/{}-{}", deviceId, channelId, stream);
            callback.run(ErrorCode.ERROR100.getCode(), "未查询到录像的视频信息", null);
            return ;
        }
        // 获取当前已下载时长
        String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId();
        MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
        if (mediaServerItem == null) {
            logger.warn("[获取录像下载文件地址] 查询录像信息时发现节点不存在, {}/{}-{}", deviceId, channelId, stream);
            callback.run(ErrorCode.ERROR100.getCode(), "查询录像信息时发现节点不存在", null);
            return ;
        }
        List<CloudRecordItem> cloudRecordItemList =  cloudRecordServiceMapper.getListByCallId(streamAuthorityInfo.getCallId());
        if (!cloudRecordItemList.isEmpty()) {
            String filePath = cloudRecordItemList.get(0).getFilePath();
            DownloadFileInfo downloadFileInfo = getDownloadFilePath(mediaServerItem, filePath);
            inviteInfo.getStreamInfo().setDownLoadFilePath(downloadFileInfo);
            inviteStreamService.updateInviteInfo(inviteInfo);
            callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), downloadFileInfo);
        }else {
            // 可能尚未生成,那就监听hook等着收到对应的录像通知
            ZlmHttpHookSubscribe.Event hookEvent = (mediaServerItemInuse, hookParam) -> {
                logger.info("[录像下载]收到订阅消息: , {}/{}-{}", deviceId, channelId, stream);
                logger.info("[录像下载]收到订阅消息内容: " + hookParam);
                dynamicTask.stop(streamAuthorityInfo.getCallId());
                OnRecordMp4HookParam recordMp4HookParam = (OnRecordMp4HookParam)hookParam;
                String filePath = recordMp4HookParam.getFile_path();
                DownloadFileInfo downloadFileInfo = getDownloadFilePath(mediaServerItem, filePath);
                inviteInfo.getStreamInfo().setDownLoadFilePath(downloadFileInfo);
                inviteStreamService.updateInviteInfo(inviteInfo);
                callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), downloadFileInfo);
            };
            HookSubscribeForRecordMp4 hookSubscribe = HookSubscribeFactory.on_record_mp4(mediaServerId, "rtp", stream);
            subscribe.addSubscribe(hookSubscribe, hookEvent);
            // 设置超时,超时结束监听
            dynamicTask.startDelay(streamAuthorityInfo.getCallId(), ()->{
                logger.info("[录像下载] 接收hook超时, {}/{}-{}", deviceId, channelId, stream);
                subscribe.removeSubscribe(hookSubscribe);
                callback.run(ErrorCode.ERROR100.getCode(), "接收hook超时", null);
            }, 10000);
        }
    }
    private DownloadFileInfo getDownloadFilePath(MediaServerItem mediaServerItem, String filePath) {
src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java
@@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.vmanager.bean;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "流信息")
@@ -93,6 +94,9 @@
    @Schema(description = "结束时间")
    private String endTime;
    @Schema(description = "文件下载地址(录像下载使用)")
    private DownloadFileInfo downLoadFilePath;
    private double progress;
    public StreamContent(StreamInfo streamInfo) {
@@ -170,6 +174,10 @@
        this.startTime = streamInfo.getStartTime();
        this.endTime = streamInfo.getEndTime();
        this.progress = streamInfo.getProgress();
        if (streamInfo.getDownLoadFilePath() != null) {
            this.downLoadFilePath = streamInfo.getDownLoadFilePath();
        }
    }
    public String getApp() {
@@ -411,4 +419,12 @@
    public void setProgress(double progress) {
        this.progress = progress;
    }
    public DownloadFileInfo getDownLoadFilePath() {
        return downLoadFilePath;
    }
    public void setDownLoadFilePath(DownloadFileInfo downLoadFilePath) {
        this.downLoadFilePath = downLoadFilePath;
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java
@@ -212,32 +212,4 @@
        }
        return new StreamContent(downLoadInfo);
    }
    @Operation(summary = "获取历史媒体下载文件地址")
    @Parameter(name = "deviceId", description = "设备国标编号", required = true)
    @Parameter(name = "channelId", description = "通道国标编号", required = true)
    @Parameter(name = "stream", description = "流ID", required = true)
    @GetMapping("/download/file/path/{deviceId}/{channelId}/{stream}")
    public DeferredResult<WVPResult<DownloadFileInfo>> getDownloadFilePath(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) {
        DeferredResult<WVPResult<DownloadFileInfo>> result = new DeferredResult<>();
        result.onTimeout(()->{
            WVPResult<DownloadFileInfo> wvpResult = new WVPResult<>();
            wvpResult.setCode(ErrorCode.ERROR100.getCode());
            wvpResult.setMsg("timeout");
            result.setResult(wvpResult);
        });
        playService.getFilePath(deviceId, channelId, stream, (code, msg, data)->{
            WVPResult<DownloadFileInfo> wvpResult = new WVPResult<>();
            wvpResult.setCode(code);
            wvpResult.setMsg(msg);
            wvpResult.setData(data);
            result.setResult(wvpResult);
        });
        return result;
    }
}
web_src/build/webpack.dev.conf.js
@@ -10,7 +10,6 @@
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
const devWebpackConfig = merge(baseWebpackConfig, {
@@ -31,9 +30,8 @@
    hot: true,
    contentBase: false, // since we use CopyWebpackPlugin.
    compress: true,
    host: HOST || config.dev.host,
    // host:'127.0.0.1',
    port: PORT || config.dev.port,
    host: config.dev.host,
    port: config.dev.port,
    open: config.dev.autoOpenBrowser,
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
web_src/src/components/dialog/recordDownload.vue
@@ -38,7 +38,6 @@
          streamInfo: null,
          taskId: null,
          getProgressRun: false,
          getProgressForFileRun: false,
          timer: null,
          downloadFile: null,
@@ -61,7 +60,7 @@
            return;
          }
          if (this.percentage == 100 ) {
            this.getFileDownload();
            return;
          }
          setTimeout( ()=>{
@@ -74,13 +73,21 @@
            method: 'get',
            url: `/api/gb_record/download/progress/${this.deviceId}/${this.channelId}/${this.stream}`
          }).then((res)=> {
            console.log(res)
              if (res.data.code === 0) {
                this.streamInfo = res.data.data;
                if (parseFloat(res.data.progress) == 1) {
                  this.percentage = 100;
                }else {
                  this.percentage = (parseFloat(res.data.data.progress)*100).toFixed(1);
                }
                if (this.streamInfo.downLoadFilePath) {
                  if (location.protocol === "https:") {
                    this.downloadFile = this.streamInfo.downLoadFilePath.httpsPath;
                  }else {
                    this.downloadFile = this.streamInfo.downLoadFilePath.httpPath;
                  }
                  this.getProgressRun = false;
                  this.downloadFileClientEvent()
                }
                if (callback)callback();
              }else {
@@ -107,24 +114,11 @@
          }
          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',
@@ -133,74 +127,20 @@
            if (callback) callback(res)
          });
        },
        getFileDownload: function (){
          this.$axios({
            method: 'get',
            url:`/api/cloud/record/task/add`,
            params: {
              app: this.app,
              stream: this.stream,
              mediaServerId: this.mediaServerId,
              startTime: null,
              endTime: null,
            }
          }).then((res) =>{
            if (res.data.code === 0 ) {
              // 查询进度
              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:`/api/cloud/record/task/list`,
            params: {
              mediaServerId: this.mediaServerId,
              taskId: this.taskId,
              isEnd: true,
            }
          }).then((res) => {
            console.log(res)
            if (res.data.code === 0) {
              if (res.data.data.length === 0){
                this.percentage = 0
                // 往往在多次请求后(实验五分钟的视频是三次请求),才会返回数据,第一次请求通常是返回空数组
                if (callback)callback()
                return
              }
              // res.data.data应是数组类型
                this.percentage = parseFloat(res.data.data[0].percentage)*100
                 if (res.data.data[0].percentage === '1') {
                   this.getProgressForFileRun = false;
                   this.downloadFile = res.data.data[0].downloadFile
                   this.title = "文件处理完成,点击按扭下载"
                   // window.open(res.data.data[0].downloadFile)
                 }else {
                   if (callback)callback()
                 }
            }
          }).catch(function (error) {
            console.log(error);
          });
        },
      downloadFileClientEvent: function (){
        window.open(this.downloadFile )
        // window.open(this.downloadFile )
        let x = new XMLHttpRequest();
        x.open("GET", this.downloadFile, true);
        x.responseType = 'blob';
        x.onload=(e)=> {
          let url = window.URL.createObjectURL(x.response)
          let a = document.createElement('a');
          a.href = url
          a.download = this.deviceId + "-" + this.channelId + ".mp4";
          a.click()
        }
        x.send();
      }
    },
    destroyed() {