From 7e136c9ac7265bedfdb79b4bca465965486e0541 Mon Sep 17 00:00:00 2001 From: 648540858 <648540858@qq.com> Date: Wed, 6 Dec 2023 15:11:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=B8=8B=E8=BD=BD=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E5=89=8D=E5=90=8E=E8=B0=83=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vmp/media/zlm/ZLMHttpHookListener.java | 9 +- .../iot/vmp/service/IPlayService.java | 1 - .../iot/vmp/service/impl/PlayServiceImpl.java | 91 +++++---------- .../iot/vmp/vmanager/bean/StreamContent.java | 16 +++ .../gb28181/record/GBRecordController.java | 28 ----- web_src/build/webpack.dev.conf.js | 6 +- .../src/components/dialog/recordDownload.vue | 108 ++++-------------- 7 files changed, 77 insertions(+), 182 deletions(-) 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 3bf95052..67fea3ea 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -761,7 +761,7 @@ public class ZLMHttpHookListener { taskExecutor.execute(() -> { JSONObject json = (JSONObject) JSON.toJSON(param); List 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 @@ public class ZLMHttpHookListener { logger.info("[ZLM HOOK] 录像完成事件:{}->{}", param.getMediaServerId(), param.getFile_path()); taskExecutor.execute(() -> { + List 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(); 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 bee7f1ef..e8def54e 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java @@ -45,5 +45,4 @@ public interface IPlayService { void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback); - void getFilePath(String deviceId, String channelId, String stream, ErrorCallback callback); } 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 1e8dee77..7ca07eee 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java @@ -49,9 +49,11 @@ import java.io.File; 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 @@ public class PlayServiceImpl implements IPlayService { // 处理收到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,76 +815,15 @@ public class PlayServiceImpl implements IPlayService { 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 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 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) { DownloadFileInfo downloadFileInfo = new DownloadFileInfo(); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java index abf6df44..4974209a 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java +++ b/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 @@ public class StreamContent { @Schema(description = "结束时间") private String endTime; + @Schema(description = "文件下载地址(录像下载使用)") + private DownloadFileInfo downLoadFilePath; + private double progress; public StreamContent(StreamInfo streamInfo) { @@ -170,6 +174,10 @@ public class StreamContent { 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 class StreamContent { public void setProgress(double progress) { this.progress = progress; } + + public DownloadFileInfo getDownLoadFilePath() { + return downLoadFilePath; + } + + public void setDownLoadFilePath(DownloadFileInfo downLoadFilePath) { + this.downLoadFilePath = downLoadFilePath; + } } 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 bf8f78bc..7c33708f 100755 --- 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 @@ -212,32 +212,4 @@ public class GBRecordController { } 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> getDownloadFilePath(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) { - - DeferredResult> result = new DeferredResult<>(); - - result.onTimeout(()->{ - WVPResult wvpResult = new WVPResult<>(); - wvpResult.setCode(ErrorCode.ERROR100.getCode()); - wvpResult.setMsg("timeout"); - result.setResult(wvpResult); - }); - - playService.getFilePath(deviceId, channelId, stream, (code, msg, data)->{ - WVPResult wvpResult = new WVPResult<>(); - wvpResult.setCode(code); - wvpResult.setMsg(msg); - wvpResult.setData(data); - result.setResult(wvpResult); - }); - - - return result; - } } diff --git a/web_src/build/webpack.dev.conf.js b/web_src/build/webpack.dev.conf.js index ad7c5306..1bebcce2 100755 --- a/web_src/build/webpack.dev.conf.js +++ b/web_src/build/webpack.dev.conf.js @@ -10,7 +10,6 @@ const HtmlWebpackPlugin = require('html-webpack-plugin') 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 @@ const devWebpackConfig = merge(baseWebpackConfig, { 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 } diff --git a/web_src/src/components/dialog/recordDownload.vue b/web_src/src/components/dialog/recordDownload.vue index dcecc280..c78cd1ce 100755 --- a/web_src/src/components/dialog/recordDownload.vue +++ b/web_src/src/components/dialog/recordDownload.vue @@ -38,7 +38,6 @@ export default { streamInfo: null, taskId: null, getProgressRun: false, - getProgressForFileRun: false, timer: null, downloadFile: null, @@ -61,7 +60,7 @@ export default { return; } if (this.percentage == 100 ) { - this.getFileDownload(); + return; } setTimeout( ()=>{ @@ -74,7 +73,6 @@ export default { 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) { @@ -82,6 +80,15 @@ export default { }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 { this.$message({ @@ -107,24 +114,11 @@ export default { } 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 @@ export default { 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() {