Commit e48fa711a3664bece9b3e58840a75fe7c05bc47c

Authored by panlinlin
1 parent bd570d16

添加截图(快照)功能

src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
... ... @@ -12,7 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
12 12 import org.springframework.beans.factory.annotation.Value;
13 13 import org.springframework.stereotype.Component;
14 14  
15   -import java.io.IOException;
  15 +import java.io.*;
16 16 import java.net.ConnectException;
17 17 import java.util.HashMap;
18 18 import java.util.Map;
... ... @@ -26,6 +26,8 @@ public class ZLMRESTfulUtils {
26 26 @Autowired
27 27 private MediaConfig mediaConfig;
28 28  
  29 +
  30 +
29 31 public interface RequestCallback{
30 32 void run(JSONObject response);
31 33 }
... ... @@ -95,6 +97,53 @@ public class ZLMRESTfulUtils {
95 97 return responseJSON;
96 98 }
97 99  
  100 +
  101 + public void sendPostForImg(String api, Map<String, Object> param, String targetPath, String fileName) {
  102 + OkHttpClient client = new OkHttpClient();
  103 + String url = String.format("http://%s:%s/index/api/%s", mediaConfig.getIp(), mediaConfig.getHttpPort(), api);
  104 + JSONObject responseJSON = null;
  105 + logger.debug(url);
  106 +
  107 + FormBody.Builder builder = new FormBody.Builder();
  108 + builder.add("secret",mediaConfig.getSecret());
  109 + if (param != null && param.keySet().size() > 0) {
  110 + for (String key : param.keySet()){
  111 + if (param.get(key) != null) {
  112 + builder.add(key, param.get(key).toString());
  113 + }
  114 + }
  115 + }
  116 +
  117 + FormBody body = builder.build();
  118 +
  119 + Request request = new Request.Builder()
  120 + .post(body)
  121 + .url(url)
  122 + .build();
  123 + try {
  124 + Response response = client.newCall(request).execute();
  125 + if (response.isSuccessful()) {
  126 + if (targetPath != null) {
  127 + File snapFolder = new File(targetPath);
  128 + if (!snapFolder.exists()) {
  129 + snapFolder.mkdirs();
  130 + }
  131 + File snapFile = new File(targetPath + "/" + fileName);
  132 + FileOutputStream outStream = new FileOutputStream(snapFile);
  133 + outStream.write(response.body().bytes());
  134 + outStream.close();
  135 + }
  136 + }
  137 + } catch (ConnectException e) {
  138 + logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
  139 + logger.info("请检查media配置并确认ZLM已启动...");
  140 + }catch (IOException e) {
  141 + logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
  142 + }
  143 +
  144 + }
  145 +
  146 +
98 147 public JSONObject getMediaList(String app, String stream, String schema, RequestCallback callback){
99 148 Map<String, Object> param = new HashMap<>();
100 149 if (app != null) param.put("app",app);
... ... @@ -201,4 +250,12 @@ public class ZLMRESTfulUtils {
201 250 param.put("local_port", localPortSStr);
202 251 sendPost("kick_sessions",param, null);
203 252 }
  253 +
  254 + public void getSnap(String flvUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) {
  255 + Map<String, Object> param = new HashMap<>();
  256 + param.put("url", flvUrl);
  257 + param.put("timeout_sec", timeout_sec);
  258 + param.put("expire_sec", expire_sec);
  259 + sendPostForImg("getSnap",param, targetPath, fileName);
  260 + }
204 261 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
... ... @@ -15,6 +15,7 @@ import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
15 15 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
16 16 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
17 17 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
  18 +import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
18 19 import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
19 20 import com.genersoft.iot.vmp.service.IMediaService;
20 21 import com.genersoft.iot.vmp.service.IPlayService;
... ... @@ -23,14 +24,18 @@ import org.slf4j.Logger;
23 24 import org.slf4j.LoggerFactory;
24 25 import org.springframework.beans.factory.annotation.Autowired;
25 26 import org.springframework.beans.factory.annotation.Value;
  27 +import org.springframework.http.HttpStatus;
26 28 import org.springframework.http.ResponseEntity;
27 29 import org.springframework.stereotype.Service;
  30 +import org.springframework.util.ResourceUtils;
28 31 import org.springframework.web.context.request.async.DeferredResult;
29 32  
30 33 import javax.sip.ClientTransaction;
31 34 import javax.sip.Dialog;
32 35 import javax.sip.header.CallIdHeader;
33 36 import javax.sip.message.Response;
  37 +import java.io.File;
  38 +import java.io.FileNotFoundException;
34 39 import java.util.UUID;
35 40  
36 41 @Service
... ... @@ -82,9 +87,33 @@ public class PlayServiceImpl implements IPlayService {
82 87 cmder.closeRTPServer(playResult.getDevice(), channelId);
83 88 RequestMessage msg = new RequestMessage();
84 89 msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + playResult.getUuid());
85   - msg.setData("Timeout");
  90 + WVPResult wvpResult = new WVPResult();
  91 + wvpResult.setCode(-1);
  92 + wvpResult.setMsg("Timeout");
  93 + msg.setData(wvpResult);
86 94 resultHolder.invokeResult(msg);
87 95 });
  96 + result.onCompletion(()->{
  97 + // 点播结束时调用截图接口
  98 + try {
  99 + String path = ResourceUtils.getURL("classpath:").getPath()+"static/static/snap/";
  100 + String fileName = deviceId + "_" + channelId + ".jpg";
  101 + ResponseEntity responseEntity = (ResponseEntity)result.getResult();
  102 + if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
  103 + WVPResult wvpResult = (WVPResult)responseEntity.getBody();
  104 + if (wvpResult.getCode() == 0) {
  105 + StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
  106 + String flvUrl = streamInfoForSuccess.getFlv();
  107 + // 请求截图
  108 + zlmresTfulUtils.getSnap(flvUrl, 5, 1, path, fileName);
  109 + }
  110 + }
  111 +
  112 + System.out.println(path);
  113 + } catch (FileNotFoundException e) {
  114 + e.printStackTrace();
  115 + }
  116 + });
88 117 if (streamInfo == null) {
89 118 // 发送点播消息
90 119 cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
... ... @@ -98,7 +127,10 @@ public class PlayServiceImpl implements IPlayService {
98 127 msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
99 128 Response response = event.getResponse();
100 129 cmder.closeRTPServer(playResult.getDevice(), channelId);
101   - msg.setData(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
  130 + WVPResult wvpResult = new WVPResult();
  131 + wvpResult.setCode(-1);
  132 + wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
  133 + msg.setData(wvpResult);
102 134 resultHolder.invokeResult(msg);
103 135 if (errorEvent != null) {
104 136 errorEvent.response(event);
... ... @@ -109,7 +141,10 @@ public class PlayServiceImpl implements IPlayService {
109 141 if (streamId == null) {
110 142 RequestMessage msg = new RequestMessage();
111 143 msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
112   - msg.setData(String.format("点播失败, redis缓存streamId等于null"));
  144 + WVPResult wvpResult = new WVPResult();
  145 + wvpResult.setCode(-1);
  146 + wvpResult.setMsg(String.format("点播失败, redis缓存streamId等于null"));
  147 + msg.setData(wvpResult);
113 148 resultHolder.invokeResult(msg);
114 149 return playResult;
115 150 }
... ... @@ -117,7 +152,13 @@ public class PlayServiceImpl implements IPlayService {
117 152 if (rtpInfo != null && rtpInfo.getBoolean("exist")) {
118 153 RequestMessage msg = new RequestMessage();
119 154 msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
120   - msg.setData(JSON.toJSONString(streamInfo));
  155 +
  156 + WVPResult wvpResult = new WVPResult();
  157 + wvpResult.setCode(0);
  158 + wvpResult.setMsg("success");
  159 + wvpResult.setData(streamInfo);
  160 + msg.setData(wvpResult);
  161 +
121 162 resultHolder.invokeResult(msg);
122 163 if (hookEvent != null) {
123 164 hookEvent.response(JSONObject.parseObject(JSON.toJSONString(streamInfo)));
... ... @@ -133,7 +174,11 @@ public class PlayServiceImpl implements IPlayService {
133 174 RequestMessage msg = new RequestMessage();
134 175 msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
135 176 Response response = event.getResponse();
136   - msg.setData(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
  177 +
  178 + WVPResult wvpResult = new WVPResult();
  179 + wvpResult.setCode(-1);
  180 + wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
  181 + msg.setData(wvpResult);
137 182 resultHolder.invokeResult(msg);
138 183 });
139 184 }
... ... @@ -163,6 +208,13 @@ public class PlayServiceImpl implements IPlayService {
163 208 streamInfo.setTransactionInfo(transactionInfo);
164 209 redisCatchStorage.startPlay(streamInfo);
165 210 msg.setData(JSON.toJSONString(streamInfo));
  211 +
  212 + WVPResult wvpResult = new WVPResult();
  213 + wvpResult.setCode(0);
  214 + wvpResult.setMsg("sucess");
  215 + wvpResult.setData(streamInfo);
  216 + msg.setData(wvpResult);
  217 +
166 218 resultHolder.invokeResult(msg);
167 219 } else {
168 220 logger.warn("设备预览API调用失败!");
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
... ... @@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
19 19 import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.http.HttpStatus;
21 21 import org.springframework.http.ResponseEntity;
  22 +import org.springframework.util.ResourceUtils;
22 23 import org.springframework.web.bind.annotation.CrossOrigin;
23 24 import org.springframework.web.bind.annotation.GetMapping;
24 25 import org.springframework.web.bind.annotation.PathVariable;
... ... @@ -31,6 +32,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
31 32 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
32 33 import org.springframework.web.context.request.async.DeferredResult;
33 34  
  35 +import java.io.FileNotFoundException;
34 36 import java.util.UUID;
35 37  
36 38 import javax.sip.message.Response;
... ...
web_src/config/index.js
... ... @@ -18,6 +18,13 @@ module.exports = {
18 18 '^/debug': '/'
19 19 }
20 20 },
  21 + '/static/snap': {
  22 + target: 'http://localhost:18080',
  23 + changeOrigin: true,
  24 + // pathRewrite: {
  25 + // '^/static/snap': '/static/snap'
  26 + // }
  27 + },
21 28  
22 29 },
23 30  
... ...
web_src/src/components/Login.vue
... ... @@ -80,7 +80,7 @@ export default {
80 80  
81 81 this.$axios({
82 82 method: 'get',
83   - url:"/api/user/login",
  83 + url:"/api/user/login",
84 84 params: loginParam
85 85 }).then(function (res) {
86 86 console.log(JSON.stringify(res));
... ...
web_src/src/components/channelList.vue
... ... @@ -30,10 +30,28 @@
30 30 <el-table ref="channelListTable" :data="deviceChannelList" :height="winHeight" border style="width: 100%">
31 31 <el-table-column prop="channelId" label="通道编号" width="210">
32 32 </el-table-column>
33   - <el-table-column prop="channelId" label="设备编号" width="210">
  33 + <el-table-column prop="deviceId" label="设备编号" width="210">
34 34 </el-table-column>
35 35 <el-table-column prop="name" label="通道名称">
36 36 </el-table-column>
  37 + <el-table-column label="快照" width="80" align="center">
  38 + <template slot-scope="scope">
  39 + <img style="max-height: 3rem;max-width: 4rem;"
  40 + :id="scope.row.deviceId + '_' + scope.row.channelId"
  41 + :src="getSnap(scope.row)"
  42 + @error="getSnapErrorEvent($event.target.id)"
  43 + alt="">
  44 +<!-- <el-image-->
  45 +<!-- :id="'snapImg_' + scope.row.deviceId + '_' + scope.row.channelId"-->
  46 +<!-- :src="getSnap(scope.row)"-->
  47 +<!-- @error="getSnapErrorEvent($event, scope.row)"-->
  48 +<!-- :fit="'contain'">-->
  49 +<!-- <div slot="error" class="image-slot">-->
  50 +<!-- <i class="el-icon-picture-outline"></i>-->
  51 +<!-- </div>-->
  52 +<!-- </el-image>-->
  53 + </template>
  54 + </el-table-column>
37 55 <el-table-column prop="subCount" label="子节点数">
38 56 </el-table-column>
39 57 <el-table-column label="开启音频" align="center">
... ... @@ -100,7 +118,8 @@ export default {
100 118 total: 0,
101 119 beforeUrl: "/deviceList",
102 120 isLoging: false,
103   - autoList: true
  121 + autoList: true,
  122 + loadSnap:{}
104 123 };
105 124 },
106 125  
... ... @@ -122,7 +141,6 @@ export default {
122 141 } else {
123 142 this.showSubchannels();
124 143 }
125   -
126 144 },
127 145 initParam: function () {
128 146 this.deviceId = this.$route.params.deviceId;
... ... @@ -174,8 +192,6 @@ export default {
174 192 }).catch(function (error) {
175 193 console.log(error);
176 194 });
177   -
178   -
179 195 },
180 196  
181 197 //通知设备上传媒体流
... ... @@ -190,18 +206,22 @@ export default {
190 206 method: 'get',
191 207 url: '/api/play/start/' + deviceId + '/' + channelId
192 208 }).then(function (res) {
193   - console.log(res.data)
194   - let streamId = res.data.streamId;
195 209 that.isLoging = false;
196   - if (!!streamId) {
197   - // that.$refs.devicePlayer.play(res.data, deviceId, channelId, itemData.hasAudio);
198   - that.$refs.devicePlayer.openDialog("media", deviceId, channelId, {
199   - streamInfo: res.data,
200   - hasAudio: itemData.hasAudio
201   - });
202   - that.initData();
203   - } else {
204   - that.$message.error(res.data);
  210 + if (res.data.code == 0) {
  211 +
  212 + setTimeout(()=>{
  213 + console.log("下载截图")
  214 + let snapId = deviceId + "_" + channelId;
  215 + that.loadSnap[snapId] = 0;
  216 + that.getSnapErrorEvent(snapId)
  217 + },5000)
  218 + that.$refs.devicePlayer.openDialog("media", deviceId, channelId, {
  219 + streamInfo: res.data.data,
  220 + hasAudio: itemData.hasAudio
  221 + });
  222 + that.initData();
  223 + }else {
  224 + that.$message.error(res.data.msg);
205 225 }
206 226 }).catch(function (e) {});
207 227 },
... ... @@ -228,7 +248,24 @@ export default {
228 248 }
229 249 });
230 250 },
  251 + getSnap: function (row){
  252 + return '/static/snap/' + row.deviceId + '_' + row.channelId + '.jpg'
  253 + },
  254 + getSnapErrorEvent: function (id){
  255 +
  256 + if (typeof (this.loadSnap[id]) != "undefined") {
  257 + console.log("下载截图" + this.loadSnap[id])
  258 + if (this.loadSnap[id] > 5) {
  259 + delete this.loadSnap[id];
  260 + return;
  261 + }
  262 + setTimeout(()=>{
  263 + this.loadSnap[id] ++
  264 + document.getElementById(id).setAttribute("src", '/static/snap/' + id + '.jpg?' + new Date().getTime())
  265 + },1000)
231 266  
  267 + }
  268 + },
232 269 showDevice: function () {
233 270 this.$router.push(this.beforeUrl).then(() => {
234 271 this.initParam();
... ...
web_src/src/components/test.vue
... ... @@ -24,6 +24,9 @@
24 24 <div v-for="index of timeNode" class="timeQuery-label-cell" :style="'left:' + (100.0/timeNode*index).toFixed(4) + '%'">
25 25 <div class="timeQuery-label-cell-label">{{24/timeNode * index}}</div>
26 26 </div>
  27 + <ul>
  28 + <li v-for="item of allDataList" >{{!!item.name?item.name:item.dname}}</li>
  29 + </ul>
27 30 </div>
28 31 </el-col>
29 32 </el-row>
... ... @@ -36,6 +39,7 @@ export default {
36 39 name: "test",
37 40 data() {
38 41 return {
  42 + allDataList:[],
39 43 timeNode: 24,
40 44 recordData:[
41 45 {
... ... @@ -58,6 +62,32 @@ export default {
58 62 };
59 63 },
60 64 mounted() {
  65 + var list1 = [{
  66 + key: Math.random()*10,
  67 + name: "人1"
  68 + },{
  69 + key: Math.random()*10,
  70 + name: "人2"
  71 + },{
  72 + key: Math.random()*10,
  73 + name: "人3"
  74 + }]
  75 + var list2 = [{
  76 + key: Math.random()*10,
  77 + dname: "部门1"
  78 + },{
  79 + key: Math.random()*10,
  80 + dname: "部门2"
  81 + },{
  82 + key: Math.random()*10,
  83 + dname: "部门3"
  84 + }]
  85 +
  86 + var allData = list1.concat(list2)
  87 + allData.sort((a, b)=>{
  88 + return a.key-b.key;
  89 + })
  90 + this.allDataList = allData;
61 91 for (let i = 1; i <= 24; i++) {
62 92 console.log("<div class=\"timeQuery-label-cell\" style=\"left: " + (100.0/24*i).toFixed(4) + "%\"></div>")
63 93 }
... ...