Commit 7e136c9ac7265bedfdb79b4bca465965486e0541

Authored by 648540858
1 parent ad36354e

完成下载文件的前后调试

src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -761,7 +761,7 @@ public class ZLMHttpHookListener { @@ -761,7 +761,7 @@ public class ZLMHttpHookListener {
761 taskExecutor.execute(() -> { 761 taskExecutor.execute(() -> {
762 JSONObject json = (JSONObject) JSON.toJSON(param); 762 JSONObject json = (JSONObject) JSON.toJSON(param);
763 List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout); 763 List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout);
764 - if (subscribes != null && subscribes.size() > 0) { 764 + if (subscribes != null && !subscribes.isEmpty()) {
765 for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { 765 for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
766 subscribe.response(null, param); 766 subscribe.response(null, param);
767 } 767 }
@@ -780,7 +780,14 @@ public class ZLMHttpHookListener { @@ -780,7 +780,14 @@ public class ZLMHttpHookListener {
780 logger.info("[ZLM HOOK] 录像完成事件:{}->{}", param.getMediaServerId(), param.getFile_path()); 780 logger.info("[ZLM HOOK] 录像完成事件:{}->{}", param.getMediaServerId(), param.getFile_path());
781 781
782 taskExecutor.execute(() -> { 782 taskExecutor.execute(() -> {
  783 + List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_record_mp4);
  784 + if (subscribes != null && !subscribes.isEmpty()) {
  785 + for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
  786 + subscribe.response(null, param);
  787 + }
  788 + }
783 cloudRecordService.addRecord(param); 789 cloudRecordService.addRecord(param);
  790 +
784 }); 791 });
785 792
786 return HookResult.SUCCESS(); 793 return HookResult.SUCCESS();
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
@@ -45,5 +45,4 @@ public interface IPlayService { @@ -45,5 +45,4 @@ public interface IPlayService {
45 45
46 void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback); 46 void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback);
47 47
48 - void getFilePath(String deviceId, String channelId, String stream, ErrorCallback<DownloadFileInfo> callback);  
49 } 48 }
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -49,9 +49,11 @@ import java.io.File; @@ -49,9 +49,11 @@ import java.io.File;
49 import java.math.BigDecimal; 49 import java.math.BigDecimal;
50 import java.math.RoundingMode; 50 import java.math.RoundingMode;
51 import java.text.ParseException; 51 import java.text.ParseException;
  52 +import java.time.Instant;
52 import java.util.List; 53 import java.util.List;
53 import java.util.UUID; 54 import java.util.UUID;
54 import java.util.Vector; 55 import java.util.Vector;
  56 +import java.util.concurrent.TimeUnit;
55 57
56 @SuppressWarnings(value = {"rawtypes", "unchecked"}) 58 @SuppressWarnings(value = {"rawtypes", "unchecked"})
57 @Service 59 @Service
@@ -718,6 +720,28 @@ public class PlayServiceImpl implements IPlayService { @@ -718,6 +720,28 @@ public class PlayServiceImpl implements IPlayService {
718 // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题 720 // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
719 InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId, 721 InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId,
720 downLoadTimeOutTaskKey, callback, inviteInfo, InviteSessionType.DOWNLOAD); 722 downLoadTimeOutTaskKey, callback, inviteInfo, InviteSessionType.DOWNLOAD);
  723 +
  724 + // 注册录像回调事件,录像下载结束后写入下载地址
  725 + ZlmHttpHookSubscribe.Event hookEventForRecord = (mediaServerItemInuse, hookParam) -> {
  726 + logger.info("[录像下载] 收到录像写入磁盘消息: , {}/{}-{}",
  727 + inviteInfo.getDeviceId(), inviteInfo.getChannelId(), ssrcInfo.getStream());
  728 + logger.info("[录像下载] 收到录像写入磁盘消息内容: " + hookParam);
  729 + OnRecordMp4HookParam recordMp4HookParam = (OnRecordMp4HookParam)hookParam;
  730 + String filePath = recordMp4HookParam.getFile_path();
  731 + DownloadFileInfo downloadFileInfo = getDownloadFilePath(mediaServerItem, filePath);
  732 + InviteInfo inviteInfoForNew = inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId()
  733 + , inviteInfo.getChannelId(), inviteInfo.getStream());
  734 + inviteInfoForNew.getStreamInfo().setDownLoadFilePath(downloadFileInfo);
  735 + inviteStreamService.updateInviteInfo(inviteInfoForNew);
  736 + };
  737 + HookSubscribeForRecordMp4 hookSubscribe = HookSubscribeFactory.on_record_mp4(
  738 + mediaServerItem.getId(), "rtp", ssrcInfo.getStream());
  739 +
  740 + // 设置过期时间,下载失败时自动处理订阅数据
  741 +// long difference = DateUtil.getDifference(startTime, endTime)/1000;
  742 +// Instant expiresInstant = Instant.now().plusSeconds(TimeUnit.MINUTES.toSeconds(difference * 2));
  743 +// hookSubscribe.setExpires(expiresInstant);
  744 + subscribe.addSubscribe(hookSubscribe, hookEventForRecord);
721 }); 745 });
722 } catch (InvalidArgumentException | SipException | ParseException e) { 746 } catch (InvalidArgumentException | SipException | ParseException e) {
723 logger.error("[命令发送失败] 录像下载: {}", e.getMessage()); 747 logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
@@ -791,76 +815,15 @@ public class PlayServiceImpl implements IPlayService { @@ -791,76 +815,15 @@ public class PlayServiceImpl implements IPlayService {
791 BigDecimal totalCount = new BigDecimal((end - start) * 1000); 815 BigDecimal totalCount = new BigDecimal((end - start) * 1000);
792 BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP); 816 BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP);
793 double process = divide.doubleValue(); 817 double process = divide.doubleValue();
  818 + if (process > 0.999) {
  819 + process = 1.0;
  820 + }
794 inviteInfo.getStreamInfo().setProgress(process); 821 inviteInfo.getStreamInfo().setProgress(process);
795 } 822 }
796 inviteStreamService.updateInviteInfo(inviteInfo); 823 inviteStreamService.updateInviteInfo(inviteInfo);
797 return inviteInfo.getStreamInfo(); 824 return inviteInfo.getStreamInfo();
798 } 825 }
799 826
800 - @Override  
801 - public void getFilePath(String deviceId, String channelId, String stream, ErrorCallback<DownloadFileInfo> callback) {  
802 - InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, stream);  
803 - if (inviteInfo == null || inviteInfo.getStreamInfo() == null) {  
804 - logger.warn("[获取录像下载文件地址] 未查询到录像下载的信息, {}/{}-{}", deviceId, channelId, stream);  
805 - callback.run(ErrorCode.ERROR100.getCode(), "未查询到录像下载的信息", null);  
806 - return ;  
807 - }  
808 -  
809 - if (!ObjectUtils.isEmpty(inviteInfo.getStreamInfo().getDownLoadFilePath())) {  
810 - callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(),  
811 - inviteInfo.getStreamInfo().getDownLoadFilePath());  
812 - return;  
813 - }  
814 -  
815 - StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo("rtp", stream);  
816 - if (streamAuthorityInfo == null) {  
817 - logger.warn("[获取录像下载文件地址] 未查询到录像的视频信息, {}/{}-{}", deviceId, channelId, stream);  
818 - callback.run(ErrorCode.ERROR100.getCode(), "未查询到录像的视频信息", null);  
819 - return ;  
820 - }  
821 -  
822 - // 获取当前已下载时长  
823 - String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId();  
824 - MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);  
825 - if (mediaServerItem == null) {  
826 - logger.warn("[获取录像下载文件地址] 查询录像信息时发现节点不存在, {}/{}-{}", deviceId, channelId, stream);  
827 - callback.run(ErrorCode.ERROR100.getCode(), "查询录像信息时发现节点不存在", null);  
828 - return ;  
829 - }  
830 -  
831 - List<CloudRecordItem> cloudRecordItemList = cloudRecordServiceMapper.getListByCallId(streamAuthorityInfo.getCallId());  
832 - if (!cloudRecordItemList.isEmpty()) {  
833 - String filePath = cloudRecordItemList.get(0).getFilePath();  
834 -  
835 - DownloadFileInfo downloadFileInfo = getDownloadFilePath(mediaServerItem, filePath);  
836 - inviteInfo.getStreamInfo().setDownLoadFilePath(downloadFileInfo);  
837 - inviteStreamService.updateInviteInfo(inviteInfo);  
838 - callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), downloadFileInfo);  
839 - }else {  
840 - // 可能尚未生成,那就监听hook等着收到对应的录像通知  
841 - ZlmHttpHookSubscribe.Event hookEvent = (mediaServerItemInuse, hookParam) -> {  
842 - logger.info("[录像下载]收到订阅消息: , {}/{}-{}", deviceId, channelId, stream);  
843 - logger.info("[录像下载]收到订阅消息内容: " + hookParam);  
844 - dynamicTask.stop(streamAuthorityInfo.getCallId());  
845 - OnRecordMp4HookParam recordMp4HookParam = (OnRecordMp4HookParam)hookParam;  
846 - String filePath = recordMp4HookParam.getFile_path();  
847 - DownloadFileInfo downloadFileInfo = getDownloadFilePath(mediaServerItem, filePath);  
848 - inviteInfo.getStreamInfo().setDownLoadFilePath(downloadFileInfo);  
849 - inviteStreamService.updateInviteInfo(inviteInfo);  
850 - callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), downloadFileInfo);  
851 - };  
852 - HookSubscribeForRecordMp4 hookSubscribe = HookSubscribeFactory.on_record_mp4(mediaServerId, "rtp", stream);  
853 - subscribe.addSubscribe(hookSubscribe, hookEvent);  
854 -  
855 - // 设置超时,超时结束监听  
856 - dynamicTask.startDelay(streamAuthorityInfo.getCallId(), ()->{  
857 - logger.info("[录像下载] 接收hook超时, {}/{}-{}", deviceId, channelId, stream);  
858 - subscribe.removeSubscribe(hookSubscribe);  
859 - callback.run(ErrorCode.ERROR100.getCode(), "接收hook超时", null);  
860 - }, 10000);  
861 - }  
862 - }  
863 -  
864 private DownloadFileInfo getDownloadFilePath(MediaServerItem mediaServerItem, String filePath) { 827 private DownloadFileInfo getDownloadFilePath(MediaServerItem mediaServerItem, String filePath) {
865 DownloadFileInfo downloadFileInfo = new DownloadFileInfo(); 828 DownloadFileInfo downloadFileInfo = new DownloadFileInfo();
866 829
src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java
1 package com.genersoft.iot.vmp.vmanager.bean; 1 package com.genersoft.iot.vmp.vmanager.bean;
2 2
3 import com.genersoft.iot.vmp.common.StreamInfo; 3 import com.genersoft.iot.vmp.common.StreamInfo;
  4 +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
4 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
5 6
6 @Schema(description = "流信息") 7 @Schema(description = "流信息")
@@ -93,6 +94,9 @@ public class StreamContent { @@ -93,6 +94,9 @@ public class StreamContent {
93 @Schema(description = "结束时间") 94 @Schema(description = "结束时间")
94 private String endTime; 95 private String endTime;
95 96
  97 + @Schema(description = "文件下载地址(录像下载使用)")
  98 + private DownloadFileInfo downLoadFilePath;
  99 +
96 private double progress; 100 private double progress;
97 101
98 public StreamContent(StreamInfo streamInfo) { 102 public StreamContent(StreamInfo streamInfo) {
@@ -170,6 +174,10 @@ public class StreamContent { @@ -170,6 +174,10 @@ public class StreamContent {
170 this.startTime = streamInfo.getStartTime(); 174 this.startTime = streamInfo.getStartTime();
171 this.endTime = streamInfo.getEndTime(); 175 this.endTime = streamInfo.getEndTime();
172 this.progress = streamInfo.getProgress(); 176 this.progress = streamInfo.getProgress();
  177 +
  178 + if (streamInfo.getDownLoadFilePath() != null) {
  179 + this.downLoadFilePath = streamInfo.getDownLoadFilePath();
  180 + }
173 } 181 }
174 182
175 public String getApp() { 183 public String getApp() {
@@ -411,4 +419,12 @@ public class StreamContent { @@ -411,4 +419,12 @@ public class StreamContent {
411 public void setProgress(double progress) { 419 public void setProgress(double progress) {
412 this.progress = progress; 420 this.progress = progress;
413 } 421 }
  422 +
  423 + public DownloadFileInfo getDownLoadFilePath() {
  424 + return downLoadFilePath;
  425 + }
  426 +
  427 + public void setDownLoadFilePath(DownloadFileInfo downLoadFilePath) {
  428 + this.downLoadFilePath = downLoadFilePath;
  429 + }
414 } 430 }
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java
@@ -212,32 +212,4 @@ public class GBRecordController { @@ -212,32 +212,4 @@ public class GBRecordController {
212 } 212 }
213 return new StreamContent(downLoadInfo); 213 return new StreamContent(downLoadInfo);
214 } 214 }
215 -  
216 - @Operation(summary = "获取历史媒体下载文件地址")  
217 - @Parameter(name = "deviceId", description = "设备国标编号", required = true)  
218 - @Parameter(name = "channelId", description = "通道国标编号", required = true)  
219 - @Parameter(name = "stream", description = "流ID", required = true)  
220 - @GetMapping("/download/file/path/{deviceId}/{channelId}/{stream}")  
221 - public DeferredResult<WVPResult<DownloadFileInfo>> getDownloadFilePath(@PathVariable String deviceId, @PathVariable String channelId, @PathVariable String stream) {  
222 -  
223 - DeferredResult<WVPResult<DownloadFileInfo>> result = new DeferredResult<>();  
224 -  
225 - result.onTimeout(()->{  
226 - WVPResult<DownloadFileInfo> wvpResult = new WVPResult<>();  
227 - wvpResult.setCode(ErrorCode.ERROR100.getCode());  
228 - wvpResult.setMsg("timeout");  
229 - result.setResult(wvpResult);  
230 - });  
231 -  
232 - playService.getFilePath(deviceId, channelId, stream, (code, msg, data)->{  
233 - WVPResult<DownloadFileInfo> wvpResult = new WVPResult<>();  
234 - wvpResult.setCode(code);  
235 - wvpResult.setMsg(msg);  
236 - wvpResult.setData(data);  
237 - result.setResult(wvpResult);  
238 - });  
239 -  
240 -  
241 - return result;  
242 - }  
243 } 215 }
web_src/build/webpack.dev.conf.js
@@ -10,7 +10,6 @@ const HtmlWebpackPlugin = require(&#39;html-webpack-plugin&#39;) @@ -10,7 +10,6 @@ const HtmlWebpackPlugin = require(&#39;html-webpack-plugin&#39;)
10 const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 10 const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
11 const portfinder = require('portfinder') 11 const portfinder = require('portfinder')
12 12
13 -const HOST = process.env.HOST  
14 const PORT = process.env.PORT && Number(process.env.PORT) 13 const PORT = process.env.PORT && Number(process.env.PORT)
15 14
16 const devWebpackConfig = merge(baseWebpackConfig, { 15 const devWebpackConfig = merge(baseWebpackConfig, {
@@ -31,9 +30,8 @@ const devWebpackConfig = merge(baseWebpackConfig, { @@ -31,9 +30,8 @@ const devWebpackConfig = merge(baseWebpackConfig, {
31 hot: true, 30 hot: true,
32 contentBase: false, // since we use CopyWebpackPlugin. 31 contentBase: false, // since we use CopyWebpackPlugin.
33 compress: true, 32 compress: true,
34 - host: HOST || config.dev.host,  
35 - // host:'127.0.0.1',  
36 - port: PORT || config.dev.port, 33 + host: config.dev.host,
  34 + port: config.dev.port,
37 open: config.dev.autoOpenBrowser, 35 open: config.dev.autoOpenBrowser,
38 overlay: config.dev.errorOverlay 36 overlay: config.dev.errorOverlay
39 ? { warnings: false, errors: true } 37 ? { warnings: false, errors: true }
web_src/src/components/dialog/recordDownload.vue
@@ -38,7 +38,6 @@ export default { @@ -38,7 +38,6 @@ export default {
38 streamInfo: null, 38 streamInfo: null,
39 taskId: null, 39 taskId: null,
40 getProgressRun: false, 40 getProgressRun: false,
41 - getProgressForFileRun: false,  
42 timer: null, 41 timer: null,
43 downloadFile: null, 42 downloadFile: null,
44 43
@@ -61,7 +60,7 @@ export default { @@ -61,7 +60,7 @@ export default {
61 return; 60 return;
62 } 61 }
63 if (this.percentage == 100 ) { 62 if (this.percentage == 100 ) {
64 - this.getFileDownload(); 63 +
65 return; 64 return;
66 } 65 }
67 setTimeout( ()=>{ 66 setTimeout( ()=>{
@@ -74,7 +73,6 @@ export default { @@ -74,7 +73,6 @@ export default {
74 method: 'get', 73 method: 'get',
75 url: `/api/gb_record/download/progress/${this.deviceId}/${this.channelId}/${this.stream}` 74 url: `/api/gb_record/download/progress/${this.deviceId}/${this.channelId}/${this.stream}`
76 }).then((res)=> { 75 }).then((res)=> {
77 - console.log(res)  
78 if (res.data.code === 0) { 76 if (res.data.code === 0) {
79 this.streamInfo = res.data.data; 77 this.streamInfo = res.data.data;
80 if (parseFloat(res.data.progress) == 1) { 78 if (parseFloat(res.data.progress) == 1) {
@@ -82,6 +80,15 @@ export default { @@ -82,6 +80,15 @@ export default {
82 }else { 80 }else {
83 this.percentage = (parseFloat(res.data.data.progress)*100).toFixed(1); 81 this.percentage = (parseFloat(res.data.data.progress)*100).toFixed(1);
84 } 82 }
  83 + if (this.streamInfo.downLoadFilePath) {
  84 + if (location.protocol === "https:") {
  85 + this.downloadFile = this.streamInfo.downLoadFilePath.httpsPath;
  86 + }else {
  87 + this.downloadFile = this.streamInfo.downLoadFilePath.httpPath;
  88 + }
  89 + this.getProgressRun = false;
  90 + this.downloadFileClientEvent()
  91 + }
85 if (callback)callback(); 92 if (callback)callback();
86 }else { 93 }else {
87 this.$message({ 94 this.$message({
@@ -107,24 +114,11 @@ export default { @@ -107,24 +114,11 @@ export default {
107 } 114 }
108 this.showDialog=false; 115 this.showDialog=false;
109 this.getProgressRun = false; 116 this.getProgressRun = false;
110 - this.getProgressForFileRun = false;  
111 }, 117 },
112 gbScale: function (scale){ 118 gbScale: function (scale){
113 this.scale = scale; 119 this.scale = scale;
114 }, 120 },
115 - download: function (){  
116 - this.getProgressRun = false;  
117 - if (this.streamInfo != null ) {  
118 - if (this.streamInfo.progress < 1) {  
119 - // 发送停止缓存  
120 - this.stopDownloadRecord((res)=>{  
121 - this.getFileDownload()  
122 - })  
123 - }else {  
124 - this.getFileDownload()  
125 - }  
126 - }  
127 - }, 121 +
128 stopDownloadRecord: function (callback) { 122 stopDownloadRecord: function (callback) {
129 this.$axios({ 123 this.$axios({
130 method: 'get', 124 method: 'get',
@@ -133,74 +127,20 @@ export default { @@ -133,74 +127,20 @@ export default {
133 if (callback) callback(res) 127 if (callback) callback(res)
134 }); 128 });
135 }, 129 },
136 - getFileDownload: function (){  
137 - this.$axios({  
138 - method: 'get',  
139 - url:`/api/cloud/record/task/add`,  
140 - params: {  
141 - app: this.app,  
142 - stream: this.stream,  
143 - mediaServerId: this.mediaServerId,  
144 - startTime: null,  
145 - endTime: null,  
146 - }  
147 - }).then((res) =>{  
148 - if (res.data.code === 0 ) {  
149 - // 查询进度  
150 - this.title = "录像文件处理中..."  
151 - this.taskId = res.data.data;  
152 - this.percentage = 0.0;  
153 - this.getProgressForFileRun = true;  
154 - this.getProgressForFileTimer();  
155 - }  
156 - }).catch(function (error) {  
157 - console.log(error);  
158 - });  
159 - },  
160 - getProgressForFileTimer: function (){  
161 - if (!this.getProgressForFileRun || this.percentage == 100) {  
162 - return;  
163 - }  
164 - setTimeout( ()=>{  
165 - if (!this.showDialog) return;  
166 - this.getProgressForFile(this.getProgressForFileTimer)  
167 - }, 1000)  
168 - },  
169 - getProgressForFile: function (callback){  
170 - this.$axios({  
171 - method: 'get',  
172 - url:`/api/cloud/record/task/list`,  
173 - params: {  
174 - mediaServerId: this.mediaServerId,  
175 - taskId: this.taskId,  
176 - isEnd: true,  
177 - }  
178 - }).then((res) => {  
179 - console.log(res)  
180 - if (res.data.code === 0) {  
181 - if (res.data.data.length === 0){  
182 - this.percentage = 0  
183 - // 往往在多次请求后(实验五分钟的视频是三次请求),才会返回数据,第一次请求通常是返回空数组  
184 - if (callback)callback()  
185 - return  
186 - }  
187 - // res.data.data应是数组类型  
188 - this.percentage = parseFloat(res.data.data[0].percentage)*100  
189 - if (res.data.data[0].percentage === '1') {  
190 - this.getProgressForFileRun = false;  
191 - this.downloadFile = res.data.data[0].downloadFile  
192 - this.title = "文件处理完成,点击按扭下载"  
193 - // window.open(res.data.data[0].downloadFile)  
194 - }else {  
195 - if (callback)callback()  
196 - }  
197 - }  
198 - }).catch(function (error) {  
199 - console.log(error);  
200 - });  
201 - },  
202 downloadFileClientEvent: function (){ 130 downloadFileClientEvent: function (){
203 - window.open(this.downloadFile ) 131 + // window.open(this.downloadFile )
  132 +
  133 + let x = new XMLHttpRequest();
  134 + x.open("GET", this.downloadFile, true);
  135 + x.responseType = 'blob';
  136 + x.onload=(e)=> {
  137 + let url = window.URL.createObjectURL(x.response)
  138 + let a = document.createElement('a');
  139 + a.href = url
  140 + a.download = this.deviceId + "-" + this.channelId + ".mp4";
  141 + a.click()
  142 + }
  143 + x.send();
204 } 144 }
205 }, 145 },
206 destroyed() { 146 destroyed() {