Commit 9361943e47a09ea46f76adf06fa0d24a07ac711d

Authored by 648540858
1 parent ef19b4f8

修复ui录象播放

优化录象查询接口
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
@@ -18,6 +18,8 @@ public class VideoManagerConstants { @@ -18,6 +18,8 @@ public class VideoManagerConstants {
18 18
19 public static final String PLAYER_PREFIX = "VMP_player_"; 19 public static final String PLAYER_PREFIX = "VMP_player_";
20 20
  21 + public static final String PLAY_BLACK_PREFIX = "VMP_playback_";
  22 +
21 public static final String EVENT_ONLINE_REGISTER = "1"; 23 public static final String EVENT_ONLINE_REGISTER = "1";
22 24
23 public static final String EVENT_ONLINE_KEEPLIVE = "2"; 25 public static final String EVENT_ONLINE_KEEPLIVE = "2";
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
1 package com.genersoft.iot.vmp.gb28181.bean; 1 package com.genersoft.iot.vmp.gb28181.bean;
2 2
3 3
  4 +import org.jetbrains.annotations.NotNull;
  5 +
  6 +import java.text.ParseException;
  7 +import java.text.SimpleDateFormat;
  8 +import java.util.Date;
  9 +
4 /** 10 /**
5 * @Description:设备录像bean 11 * @Description:设备录像bean
6 * @author: swwheihei 12 * @author: swwheihei
7 * @date: 2020年5月8日 下午2:06:54 13 * @date: 2020年5月8日 下午2:06:54
8 */ 14 */
9 -public class RecordItem { 15 +public class RecordItem implements Comparable<RecordItem>{
10 16
11 private String deviceId; 17 private String deviceId;
12 18
@@ -97,4 +103,21 @@ public class RecordItem { @@ -97,4 +103,21 @@ public class RecordItem {
97 public void setRecorderId(String recorderId) { 103 public void setRecorderId(String recorderId) {
98 this.recorderId = recorderId; 104 this.recorderId = recorderId;
99 } 105 }
  106 +
  107 + @Override
  108 + public int compareTo(@NotNull RecordItem recordItem) {
  109 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  110 + try {
  111 + Date startTime_now = sdf.parse(startTime);
  112 + Date startTime_param = sdf.parse(recordItem.getStartTime());
  113 + if (startTime_param.compareTo(startTime_now) > 0) {
  114 + return -1;
  115 + }else {
  116 + return 1;
  117 + }
  118 + } catch (ParseException e) {
  119 + e.printStackTrace();
  120 + }
  121 + return 0;
  122 + }
100 } 123 }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -80,7 +80,7 @@ public interface ISIPCommander { @@ -80,7 +80,7 @@ public interface ISIPCommander {
80 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss 80 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
81 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss 81 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
82 */ 82 */
83 - public String playbackStreamCmd(Device device,String channelId, String startTime, String endTime); 83 + public StreamInfo playbackStreamCmd(Device device,String channelId, String startTime, String endTime);
84 84
85 /** 85 /**
86 * 视频流停止 86 * 视频流停止
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -279,7 +279,7 @@ public class SIPCommander implements ISIPCommander { @@ -279,7 +279,7 @@ public class SIPCommander implements ISIPCommander {
279 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss 279 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
280 */ 280 */
281 @Override 281 @Override
282 - public String playbackStreamCmd(Device device, String channelId, String startTime, String endTime) { 282 + public StreamInfo playbackStreamCmd(Device device, String channelId, String startTime, String endTime) {
283 try { 283 try {
284 MediaServerConfig mediaInfo = storager.getMediaInfo(); 284 MediaServerConfig mediaInfo = storager.getMediaInfo();
285 String ssrc = streamSession.createPlayBackSsrc(); 285 String ssrc = streamSession.createPlayBackSsrc();
@@ -324,7 +324,13 @@ public class SIPCommander implements ISIPCommander { @@ -324,7 +324,13 @@ public class SIPCommander implements ISIPCommander {
324 324
325 ClientTransaction transaction = transmitRequest(device, request); 325 ClientTransaction transaction = transmitRequest(device, request);
326 streamSession.put(ssrc, transaction); 326 streamSession.put(ssrc, transaction);
327 - return ssrc; 327 +
  328 + StreamInfo streamInfo = new StreamInfo();
  329 + streamInfo.setSsrc(ssrc);
  330 + streamInfo.setCahnnelId(channelId);
  331 + streamInfo.setDeviceID(device.getDeviceId());
  332 + boolean b = storager.startPlayBlack(streamInfo);
  333 + return streamInfo;
328 334
329 } catch ( SipException | ParseException | InvalidArgumentException e) { 335 } catch ( SipException | ParseException | InvalidArgumentException e) {
330 e.printStackTrace(); 336 e.printStackTrace();
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
@@ -2,11 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.request.impl; @@ -2,11 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.request.impl;
2 2
3 import java.io.ByteArrayInputStream; 3 import java.io.ByteArrayInputStream;
4 import java.text.ParseException; 4 import java.text.ParseException;
5 -import java.util.ArrayList;  
6 -import java.util.HashMap;  
7 -import java.util.Iterator;  
8 -import java.util.List;  
9 -import java.util.Map; 5 +import java.util.*;
10 6
11 import javax.sip.InvalidArgumentException; 7 import javax.sip.InvalidArgumentException;
12 import javax.sip.RequestEvent; 8 import javax.sip.RequestEvent;
@@ -316,6 +312,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { @@ -316,6 +312,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
316 record.setRecorderId(XmlUtil.getText(itemRecord,"RecorderID")); 312 record.setRecorderId(XmlUtil.getText(itemRecord,"RecorderID"));
317 recordList.add(record); 313 recordList.add(record);
318 } 314 }
  315 +// recordList.sort(Comparator.naturalOrder());
319 recordInfo.setRecordList(recordList); 316 recordInfo.setRecordList(recordList);
320 } 317 }
321 318
@@ -349,9 +346,13 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { @@ -349,9 +346,13 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
349 // 走到这里,有以下可能:1、没有录像信息,第一次收到recordinfo的消息即返回响应数据,无redis操作 346 // 走到这里,有以下可能:1、没有录像信息,第一次收到recordinfo的消息即返回响应数据,无redis操作
350 // 2、有录像数据,且第一次即收到完整数据,返回响应数据,无redis操作 347 // 2、有录像数据,且第一次即收到完整数据,返回响应数据,无redis操作
351 // 3、有录像数据,在超时时间内收到多次包组装后数量足够,返回数据 348 // 3、有录像数据,在超时时间内收到多次包组装后数量足够,返回数据
  349 +
  350 + // 对记录进行排序
352 RequestMessage msg = new RequestMessage(); 351 RequestMessage msg = new RequestMessage();
353 msg.setDeviceId(deviceId); 352 msg.setDeviceId(deviceId);
354 msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO); 353 msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);
  354 + // 自然顺序排序, 元素进行升序排列
  355 + recordInfo.getRecordList().sort(Comparator.naturalOrder());
355 msg.setData(recordInfo); 356 msg.setData(recordInfo);
356 deferredResultHolder.invokeResult(msg); 357 deferredResultHolder.invokeResult(msg);
357 } catch (DocumentException e) { 358 } catch (DocumentException e) {
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -130,17 +130,27 @@ public class ZLMHttpHookListener { @@ -130,17 +130,27 @@ public class ZLMHttpHookListener {
130 String streamId = json.getString("id"); 130 String streamId = json.getString("id");
131 131
132 132
133 -// String ssrc = String.format("%10d", Integer.parseInt(streamId, 16)); // ZLM 要求大写且首位补零  
134 String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16)); 133 String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16));
135 - StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc);  
136 - if ("rtp".equals(app) && streamInfo != null ) { 134 + StreamInfo streamInfoForPlay = storager.queryPlayBySSRC(ssrc);
  135 + if ("rtp".equals(app) && streamInfoForPlay != null ) {
  136 + MediaServerConfig mediaInfo = storager.getMediaInfo();
  137 + streamInfoForPlay.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId));
  138 + streamInfoForPlay.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId));
  139 + streamInfoForPlay.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getLocalIP(), mediaInfo.getRtmpPort(), streamId));
  140 + streamInfoForPlay.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId));
  141 + streamInfoForPlay.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getLocalIP(), mediaInfo.getRtspPort(), streamId));
  142 + storager.startPlay(streamInfoForPlay);
  143 + }
  144 +
  145 + StreamInfo streamInfoForPlayBack = storager.queryPlayBlackBySSRC(ssrc);
  146 + if ("rtp".equals(app) && streamInfoForPlayBack != null ) {
137 MediaServerConfig mediaInfo = storager.getMediaInfo(); 147 MediaServerConfig mediaInfo = storager.getMediaInfo();
138 - streamInfo.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId));  
139 - streamInfo.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId));  
140 - streamInfo.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getLocalIP(), mediaInfo.getRtmpPort(), streamId));  
141 - streamInfo.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId));  
142 - streamInfo.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getLocalIP(), mediaInfo.getRtspPort(), streamId));  
143 - storager.startPlay(streamInfo); 148 + streamInfoForPlayBack.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId));
  149 + streamInfoForPlayBack.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId));
  150 + streamInfoForPlayBack.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getLocalIP(), mediaInfo.getRtmpPort(), streamId));
  151 + streamInfoForPlayBack.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId));
  152 + streamInfoForPlayBack.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getLocalIP(), mediaInfo.getRtspPort(), streamId));
  153 + storager.startPlayBlack(streamInfoForPlayBack);
144 } 154 }
145 155
146 // TODO Auto-generated method stub 156 // TODO Auto-generated method stub
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMUtils.java
@@ -30,11 +30,6 @@ public class ZLMUtils { @@ -30,11 +30,6 @@ public class ZLMUtils {
30 param.put("stream_id", streamId); 30 param.put("stream_id", streamId);
31 JSONObject jsonObject = zlmresTfulUtils.openRtpServer(param); 31 JSONObject jsonObject = zlmresTfulUtils.openRtpServer(param);
32 if (jsonObject.getInteger("code") == 0) { 32 if (jsonObject.getInteger("code") == 0) {
33 - System.out.println(11111111);  
34 - System.out.println(streamId);  
35 - System.out.println(ssrc);  
36 - System.out.println(newPort);  
37 - System.out.println(jsonObject.toJSONString());  
38 return newPort; 33 return newPort;
39 } else { 34 } else {
40 return getNewRTPPort(ssrc); 35 return getNewRTPPort(ssrc);
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
@@ -183,4 +183,12 @@ public interface IVideoManagerStorager { @@ -183,4 +183,12 @@ public interface IVideoManagerStorager {
183 StreamInfo queryPlayByDevice(String deviceId, String code); 183 StreamInfo queryPlayByDevice(String deviceId, String code);
184 184
185 Map<String, StreamInfo> queryPlayByDeviceId(String deviceId); 185 Map<String, StreamInfo> queryPlayByDeviceId(String deviceId);
  186 +
  187 + boolean startPlayBlack(StreamInfo streamInfo);
  188 +
  189 + boolean stopPlayBlack(StreamInfo streamInfo);
  190 +
  191 + StreamInfo queryPlayBlackByDevice(String deviceId, String channelId);
  192 +
  193 + StreamInfo queryPlayBlackBySSRC(String ssrc);
186 } 194 }
src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java
@@ -190,6 +190,27 @@ public class VideoManagerJdbcStoragerImpl implements IVideoManagerStorager { @@ -190,6 +190,27 @@ public class VideoManagerJdbcStoragerImpl implements IVideoManagerStorager {
190 190
191 @Override 191 @Override
192 public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) { 192 public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) {
  193 +
  194 + return null;
  195 + }
  196 +
  197 + @Override
  198 + public boolean startPlayBlack(StreamInfo streamInfo) {
  199 + return false;
  200 + }
  201 +
  202 + @Override
  203 + public boolean stopPlayBlack(StreamInfo streamInfo) {
  204 + return false;
  205 + }
  206 +
  207 + @Override
  208 + public StreamInfo queryPlayBlackByDevice(String deviceId, String channelId) {
  209 + return null;
  210 + }
  211 +
  212 + @Override
  213 + public StreamInfo queryPlayBlackBySSRC(String ssrc) {
193 return null; 214 return null;
194 } 215 }
195 } 216 }
src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
@@ -411,6 +411,14 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager { @@ -411,6 +411,14 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
411 } 411 }
412 412
413 @Override 413 @Override
  414 + public StreamInfo queryPlayBlackBySSRC(String ssrc) {
  415 +// List<Object> playLeys = redis.keys(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, ssrc));
  416 + List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, ssrc));
  417 + if (playLeys == null || playLeys.size() == 0) return null;
  418 + return (StreamInfo)redis.get(playLeys.get(0).toString());
  419 + }
  420 +
  421 + @Override
414 public StreamInfo queryPlayByDevice(String deviceId, String code) { 422 public StreamInfo queryPlayByDevice(String deviceId, String code) {
415 // List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, 423 // List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
416 List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, 424 List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
@@ -498,5 +506,37 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager { @@ -498,5 +506,37 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
498 } 506 }
499 507
500 508
  509 + @Override
  510 + public boolean startPlayBlack(StreamInfo stream) {
  511 + return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, stream.getSsrc(),stream.getDeviceID(), stream.getCahnnelId()),
  512 + stream);
  513 + }
  514 +
  515 +
  516 + @Override
  517 + public boolean stopPlayBlack(StreamInfo streamInfo) {
  518 + if (streamInfo == null) return false;
  519 + DeviceChannel deviceChannel = queryChannel(streamInfo.getDeviceID(), streamInfo.getCahnnelId());
  520 + if (deviceChannel != null) {
  521 + deviceChannel.setSsrc(null);
  522 + deviceChannel.setPlay(false);
  523 + updateChannel(streamInfo.getDeviceID(), deviceChannel);
  524 + }
  525 + return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
  526 + streamInfo.getSsrc(),
  527 + streamInfo.getDeviceID(),
  528 + streamInfo.getCahnnelId()));
  529 + }
501 530
  531 + @Override
  532 + public StreamInfo queryPlayBlackByDevice(String deviceId, String code) {
  533 + String format = String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
  534 + deviceId,
  535 + code);
  536 + List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
  537 + deviceId,
  538 + code));
  539 + if (playLeys == null || playLeys.size() == 0) return null;
  540 + return (StreamInfo)redis.get(playLeys.get(0).toString());
  541 + }
502 } 542 }
src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
@@ -70,7 +70,7 @@ public class PlayController { @@ -70,7 +70,7 @@ public class PlayController {
70 }else { 70 }else {
71 streamInfo = storager.queryPlayByDevice(deviceId, channelId); 71 streamInfo = storager.queryPlayByDevice(deviceId, channelId);
72 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); 72 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
73 - if (rtpInfo != null && rtpInfo.getBoolean("exist") && streamInfo.getFlv() != null){ 73 + if (rtpInfo != null && rtpInfo.getBoolean("exist") && streamInfo != null && streamInfo.getFlv() != null){
74 JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo("rtp", "rtmp", streamId); 74 JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo("rtp", "rtmp", streamId);
75 if (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online")) { 75 if (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online")) {
76 lockFlag = false; 76 lockFlag = false;
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java
1 package com.genersoft.iot.vmp.vmanager.playback; 1 package com.genersoft.iot.vmp.vmanager.playback;
2 2
  3 +import com.alibaba.fastjson.JSON;
  4 +import com.alibaba.fastjson.JSONArray;
  5 +import com.genersoft.iot.vmp.common.StreamInfo;
  6 +import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
3 import org.slf4j.Logger; 7 import org.slf4j.Logger;
4 import org.slf4j.LoggerFactory; 8 import org.slf4j.LoggerFactory;
5 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.beans.factory.annotation.Autowired;
@@ -30,7 +34,10 @@ public class PlaybackController { @@ -30,7 +34,10 @@ public class PlaybackController {
30 34
31 @Autowired 35 @Autowired
32 private IVideoManagerStorager storager; 36 private IVideoManagerStorager storager;
33 - 37 +
  38 + @Autowired
  39 + private ZLMRESTfulUtils zlmresTfulUtils;
  40 +
34 @GetMapping("/playback/{deviceId}/{channelId}") 41 @GetMapping("/playback/{deviceId}/{channelId}")
35 public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId, String startTime, String endTime){ 42 public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId, String startTime, String endTime){
36 43
@@ -43,25 +50,70 @@ public class PlaybackController { @@ -43,25 +50,70 @@ public class PlaybackController {
43 logger.warn(log); 50 logger.warn(log);
44 return new ResponseEntity<String>(log,HttpStatus.BAD_REQUEST); 51 return new ResponseEntity<String>(log,HttpStatus.BAD_REQUEST);
45 } 52 }
46 - 53 +
47 Device device = storager.queryVideoDevice(deviceId); 54 Device device = storager.queryVideoDevice(deviceId);
48 - String ssrc = cmder.playbackStreamCmd(device, channelId, startTime, endTime); 55 + StreamInfo streamInfo = storager.queryPlayBlackByDevice(deviceId, channelId);
  56 +
  57 + if (streamInfo != null) {
  58 + cmder.streamByeCmd(streamInfo.getSsrc());
  59 + }
  60 +
  61 +// }else {
  62 +// String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
  63 +// JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
  64 +// if (rtpInfo.getBoolean("exist")) {
  65 +// return new ResponseEntity<String>(JSON.toJSONString(streamInfo),HttpStatus.OK);
  66 +// }else {
  67 +// storager.stopPlayBlack(streamInfo);
  68 +// streamInfo = cmder.playbackStreamCmd(device, channelId, startTime, endTime);
  69 +// }
  70 +// }
  71 + streamInfo = cmder.playbackStreamCmd(device, channelId, startTime, endTime);
  72 +
  73 + String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
49 74
50 if (logger.isDebugEnabled()) { 75 if (logger.isDebugEnabled()) {
51 - logger.debug("设备回放 API调用,ssrc:"+ssrc+",ZLMedia streamId:"+Integer.toHexString(Integer.parseInt(ssrc))); 76 + logger.debug("设备回放 API调用,ssrc:" + streamInfo.getSsrc() + ",ZLMedia streamId:" + streamId);
52 } 77 }
53 -  
54 - if(ssrc!=null) {  
55 - JSONObject json = new JSONObject();  
56 - json.put("ssrc", ssrc);  
57 - return new ResponseEntity<String>(json.toString(),HttpStatus.OK); 78 + // 等待推流, TODO 默认超时15s
  79 + boolean lockFlag = true;
  80 + long lockStartTime = System.currentTimeMillis();
  81 + while (lockFlag) {
  82 + try {
  83 + if (System.currentTimeMillis() - lockStartTime > 75 * 1000) {
  84 + storager.stopPlayBlack(streamInfo);
  85 + return new ResponseEntity<String>("timeout",HttpStatus.OK);
  86 + }else {
  87 + streamInfo = storager.queryPlayBlackByDevice(deviceId, channelId);
  88 + JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
  89 + if (rtpInfo != null && rtpInfo.getBoolean("exist") && streamInfo.getFlv() != null){
  90 + JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo("rtp", "rtmp", streamId);
  91 + if (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online")) {
  92 + lockFlag = false;
  93 + JSONArray tracks = mediaInfo.getJSONArray("tracks");
  94 + streamInfo.setTracks(tracks);
  95 + storager.startPlayBlack(streamInfo);
  96 + }else {
  97 +
  98 + }
  99 + }else {
  100 + Thread.sleep(2000);
  101 + continue;
  102 + };
  103 + }
  104 + } catch (InterruptedException e) {
  105 + e.printStackTrace();
  106 + }
  107 + }
  108 + if(streamInfo!=null) {
  109 + return new ResponseEntity<String>(JSON.toJSONString(streamInfo),HttpStatus.OK);
58 } else { 110 } else {
59 logger.warn("设备回放API调用失败!"); 111 logger.warn("设备回放API调用失败!");
60 return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR); 112 return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
61 } 113 }
62 } 114 }
63 115
64 - @PostMapping("/playback/{ssrc}/stop") 116 + @RequestMapping("/playback/{ssrc}/stop")
65 public ResponseEntity<String> playStop(@PathVariable String ssrc){ 117 public ResponseEntity<String> playStop(@PathVariable String ssrc){
66 118
67 cmder.streamByeCmd(ssrc); 119 cmder.streamByeCmd(ssrc);
web_src/index.html
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 <head> 3 <head>
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width,initial-scale=1.0"> 5 <meta name="viewport" content="width=device-width,initial-scale=1.0">
6 - <title>GB28181·þÎñÆ÷</title> 6 + <title>国标28181</title>
7 </head> 7 </head>
8 <body> 8 <body>
9 <script type="text/javascript" src="./js/liveplayer-lib.min.js"></script> 9 <script type="text/javascript" src="./js/liveplayer-lib.min.js"></script>
web_src/package-lock.json
@@ -5843,6 +5843,11 @@ @@ -5843,6 +5843,11 @@
5843 "minimist": "^1.2.5" 5843 "minimist": "^1.2.5"
5844 } 5844 }
5845 }, 5845 },
  5846 + "moment": {
  5847 + "version": "2.29.1",
  5848 + "resolved": "https://registry.npm.taobao.org/moment/download/moment-2.29.1.tgz?cache=0&sync_timestamp=1601983423917&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmoment%2Fdownload%2Fmoment-2.29.1.tgz",
  5849 + "integrity": "sha1-sr52n6MZQL6e7qZGnAdeNQBvo9M="
  5850 + },
5846 "move-concurrently": { 5851 "move-concurrently": {
5847 "version": "1.0.1", 5852 "version": "1.0.1",
5848 "resolved": "https://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz", 5853 "resolved": "https://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz",
web_src/package.json
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 "core-js": "^2.6.5", 15 "core-js": "^2.6.5",
16 "echarts": "^4.7.0", 16 "echarts": "^4.7.0",
17 "element-ui": "2.10.1", 17 "element-ui": "2.10.1",
  18 + "moment": "^2.29.1",
18 "vue": "^2.6.11", 19 "vue": "^2.6.11",
19 "vue-clipboard2": "^0.3.1", 20 "vue-clipboard2": "^0.3.1",
20 "vue-cookies": "^1.7.4", 21 "vue-cookies": "^1.7.4",
web_src/src/components/Loading.vue deleted 100644 → 0
1 -//loading效果组件  
2 -  
3 -<template>  
4 -<div class="loadEffect" :style="{marginTop: marginTop? marginTop : '50%'}">  
5 - <span class="ld-span"></span>  
6 - <span class="ld-span"></span>  
7 - <span class="ld-span"></span>  
8 - <span class="ld-span"></span>  
9 - <span class="ld-span"></span>  
10 - <span class="ld-span"></span>  
11 - <span class="ld-span"></span>  
12 - <span class="ld-span"></span>  
13 -</div>  
14 -</template>  
15 -  
16 -<script>  
17 -export default {  
18 - name: 'Loading',  
19 - props: ["marginTop"]  
20 -  
21 -}  
22 -</script>  
23 -  
24 -<style scoped>  
25 -.loadEffect{  
26 - width: 100px;  
27 - height: 100px;  
28 - position: relative;  
29 - margin: 0 auto;  
30 - position: relative;  
31 - top:-50px;  
32 - margin-top:50%;  
33 - transform: scale(.5)  
34 -}  
35 -.loadEffect .ld-span{  
36 - display: inline-block;  
37 - width: 20px;  
38 - height: 20px;  
39 - border-radius: 50%;  
40 - background: #67e7d5;  
41 - position: absolute;  
42 - -webkit-animation: load 1.04s ease infinite;  
43 -}  
44 -@-webkit-keyframes load{  
45 - 0%{  
46 - -webkit-transform: scale(1.2);  
47 - opacity: 1;  
48 - }  
49 - 100%{  
50 - -webkit-transform: scale(.3);  
51 - opacity: 0.5;  
52 - }  
53 -}  
54 -.loadEffect .ld-span:nth-child(1){  
55 - left: 0;  
56 - top: 50%;  
57 - margin-top:-10px;  
58 - -webkit-animation-delay:0.13s;  
59 -}  
60 -.loadEffect .ld-span:nth-child(2){  
61 - left: 14px;  
62 - top: 14px;  
63 - -webkit-animation-delay:0.26s;  
64 -}  
65 -.loadEffect .ld-span:nth-child(3){  
66 - left: 50%;  
67 - top: 0;  
68 - margin-left: -10px;  
69 - -webkit-animation-delay:0.39s;  
70 -}  
71 -.loadEffect .ld-span:nth-child(4){  
72 - top: 14px;  
73 - right:14px;  
74 - -webkit-animation-delay:0.52s;  
75 -}  
76 -.loadEffect .ld-span:nth-child(5){  
77 - right: 0;  
78 - top: 50%;  
79 - margin-top:-10px;  
80 - -webkit-animation-delay:0.65s;  
81 -}  
82 -.loadEffect .ld-span:nth-child(6){  
83 - right: 14px;  
84 - bottom:14px;  
85 - -webkit-animation-delay:0.78s;  
86 -}  
87 -.loadEffect .ld-span:nth-child(7){  
88 - bottom: 0;  
89 - left: 50%;  
90 - margin-left: -10px;  
91 - -webkit-animation-delay:0.91s;  
92 -}  
93 -.loadEffect .ld-span:nth-child(8){  
94 - bottom: 14px;  
95 - left: 14px;  
96 - -webkit-animation-delay:1.04s;  
97 -}  
98 -</style>  
web_src/src/components/Login.vue
@@ -10,16 +10,14 @@ @@ -10,16 +10,14 @@
10 <div class="log-logo">Welcome!</div> 10 <div class="log-logo">Welcome!</div>
11 <div class="log-text"></div> 11 <div class="log-text"></div>
12 </div> 12 </div>
13 - <div class="log-email"> 13 + <div class="log-email" v-loading="isLoging" >
14 <input type="text" placeholder="用户名" :class="'log-input' + (username==''?' log-input-empty':'')" v-model="username"><input type="password" placeholder="密码" :class="'log-input' + (password==''?' log-input-empty':'')" v-model="password"> 14 <input type="text" placeholder="用户名" :class="'log-input' + (username==''?' log-input-empty':'')" v-model="username"><input type="password" placeholder="密码" :class="'log-input' + (password==''?' log-input-empty':'')" v-model="password">
15 <a href="javascript:;" class="log-btn" @click="login" >登录</a> 15 <a href="javascript:;" class="log-btn" @click="login" >登录</a>
16 </div> 16 </div>
17 - <Loading v-if="isLoging" marginTop="-30%"></Loading>  
18 </div> 17 </div>
19 </template> 18 </template>
20 19
21 <script> 20 <script>
22 -import Loading from './Loading.vue'  
23 import crypto from 'crypto' 21 import crypto from 'crypto'
24 export default { 22 export default {
25 name: 'Login', 23 name: 'Login',
@@ -30,9 +28,6 @@ export default { @@ -30,9 +28,6 @@ export default {
30 password: '' 28 password: ''
31 } 29 }
32 }, 30 },
33 - components:{  
34 - Loading  
35 - },  
36 created(){ 31 created(){
37 var that = this; 32 var that = this;
38 document.onkeydown = function(e) { 33 document.onkeydown = function(e) {
@@ -79,7 +74,8 @@ export default { @@ -79,7 +74,8 @@ export default {
79 } 74 }
80 }) 75 })
81 .catch(function (error) { 76 .catch(function (error) {
82 - console.log(error); 77 + that.$message.error(error.response.statusText);
  78 + that.isLoging = false;
83 }); 79 });
84 80
85 81
web_src/src/components/channelList.vue
@@ -26,7 +26,7 @@ @@ -26,7 +26,7 @@
26 </el-select> 26 </el-select>
27 27
28 </div> 28 </div>
29 - <devicePlayer ref="devicePlayer"></devicePlayer> 29 + <devicePlayer ref="devicePlayer" v-loading="isLoging"></devicePlayer>
30 <!--设备列表--> 30 <!--设备列表-->
31 <el-table ref="channelListTable" :data="deviceChannelList" :height="winHeight" border style="width: 100%"> 31 <el-table ref="channelListTable" :data="deviceChannelList" :height="winHeight" border style="width: 100%">
32 <el-table-column prop="channelId" label="通道编号" width="210"> 32 <el-table-column prop="channelId" label="通道编号" width="210">
@@ -58,8 +58,8 @@ @@ -58,8 +58,8 @@
58 <el-button size="mini" icon="el-icon-video-play" @click="sendDevicePush(scope.row)">播放</el-button> 58 <el-button size="mini" icon="el-icon-video-play" @click="sendDevicePush(scope.row)">播放</el-button>
59 <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="scope.row.play" @click="stopDevicePush(scope.row)">停止</el-button> 59 <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="scope.row.play" @click="stopDevicePush(scope.row)">停止</el-button>
60 <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.parental == 1" @click="changeSubchannel(scope.row)">查看</el-button> 60 <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.parental == 1" @click="changeSubchannel(scope.row)">查看</el-button>
61 - <!-- <el-button size="mini" icon="el-icon-video-camera" type="primary" >设备录象</el-button>-->  
62 - <!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> --> 61 + <el-button size="mini" icon="el-icon-video-camera" type="primary" @click="queryRecords(scope.row)">设备录象</el-button>
  62 +<!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> -->
63 </el-button-group> 63 </el-button-group>
64 </template> 64 </template>
65 </el-table-column> 65 </el-table-column>
@@ -69,20 +69,18 @@ @@ -69,20 +69,18 @@
69 69
70 </el-main> 70 </el-main>
71 </el-container> 71 </el-container>
72 - <Loading v-if="isLoging" marginTop="-50%"></Loading>  
73 </div> 72 </div>
74 </template> 73 </template>
75 74
76 <script> 75 <script>
77 import devicePlayer from './gb28181/devicePlayer.vue' 76 import devicePlayer from './gb28181/devicePlayer.vue'
78 import uiHeader from './UiHeader.vue' 77 import uiHeader from './UiHeader.vue'
79 -import Loading from './Loading.vue' 78 +import moment from "moment";
80 export default { 79 export default {
81 name: 'channelList', 80 name: 'channelList',
82 components: { 81 components: {
83 devicePlayer, 82 devicePlayer,
84 - uiHeader,  
85 - Loading 83 + uiHeader
86 }, 84 },
87 data() { 85 data() {
88 return { 86 return {
@@ -209,13 +207,23 @@ export default { @@ -209,13 +207,23 @@ export default {
209 let ssrc = res.data.ssrc; 207 let ssrc = res.data.ssrc;
210 that.isLoging = false; 208 that.isLoging = false;
211 if (!!ssrc) { 209 if (!!ssrc) {
212 - that.$refs.devicePlayer.play(res.data, deviceId, channelId, itemData.hasAudio); 210 + // that.$refs.devicePlayer.play(res.data, deviceId, channelId, itemData.hasAudio);
  211 + that.$refs.devicePlayer.openDialog("media", deviceId, channelId,{
  212 + streamInfo: res.data,
  213 + hasAudio: itemData.hasAudio
  214 + });
213 that.initData(); 215 that.initData();
214 } else { 216 } else {
215 that.$message.error(res.data); 217 that.$message.error(res.data);
216 } 218 }
217 }).catch(function (e) {}); 219 }).catch(function (e) {});
218 }, 220 },
  221 + queryRecords: function (itemData) {
  222 + var format = moment().format("YYYY-M-D");
  223 + let deviceId = this.deviceId;
  224 + let channelId = itemData.channelId;
  225 + this.$refs.devicePlayer.openDialog("record", deviceId, channelId, {date:format})
  226 + },
219 stopDevicePush: function (itemData) { 227 stopDevicePush: function (itemData) {
220 console.log(itemData) 228 console.log(itemData)
221 var that = this; 229 var that = this;
web_src/src/components/gb28181/devicePlayer.vue
1 <template> 1 <template>
2 -<div id="devicePlayer">  
3 - <el-dialog title="视频播放" top="0" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="close()">  
4 - <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :hasaudio="hasaudio" fluent autoplay live></LivePlayer>  
5 - <div id="shared" style="text-align: right; margin-top: 1rem;">  
6 - <el-tabs v-model="tabActiveName">  
7 - <el-tab-pane label="媒体流信息" name="media">  
8 - <div style="margin-bottom: 0.5rem;">  
9 - <el-button type="primary" size="small" @click="playRecord(true, '')">播放</el-button>  
10 - <el-button type="primary" size="small" @click="startRecord()">录制</el-button>  
11 - <el-button type="primary" size="small" @click="stopRecord()">停止录制</el-button>  
12 - </div>  
13 - <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">  
14 - <span style="width: 5rem; line-height: 2.5rem; text-align: right;">播放地址:</span>  
15 - <el-input v-model="getPlayerShared.sharedUrl" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedUrl)"></el-input>  
16 - </div>  
17 - <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">  
18 - <span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe:</span>  
19 - <el-input v-model="getPlayerShared.sharedIframe" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedIframe)"></el-input>  
20 - </div>  
21 - <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">  
22 - <span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址:</span>  
23 - <el-input v-model="getPlayerShared.sharedRtmp" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedRtmp)"></el-input>  
24 - </div>  
25 - </el-tab-pane>  
26 - <!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}-->  
27 - <el-tab-pane label="录像查询" name="second">  
28 - <el-date-picker v-model="videoHistory.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始时间" @change="recordList()"></el-date-picker>  
29 - <el-date-picker v-model="videoHistory.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束时间" @change="recordList()"></el-date-picker>  
30 - <el-table :data="videoHistory.searchHistoryResult" style="width: 100%">  
31 - <el-table-column label="名称" prop="name" width="150"></el-table-column>  
32 - <el-table-column label="文件" prop="filePath" width="300"></el-table-column>  
33 - <el-table-column label="开始时间" prop="startTime" width="160"></el-table-column>  
34 - <el-table-column label="结束时间" prop="endTime" width="160"></el-table-column> 2 + <div id="devicePlayer">
  3 + <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="close()">
  4 + <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :hasaudio="hasaudio" fluent autoplay live ></LivePlayer>
  5 + <div id="shared" style="text-align: right; margin-top: 1rem;">
  6 + <el-tabs v-model="tabActiveName">
  7 + <el-tab-pane label="实时视频" name="media">
  8 + <div style="margin-bottom: 0.5rem;">
  9 +<!-- <el-button type="primary" size="small" @click="playRecord(true, '')">播放</el-button>-->
  10 +<!-- <el-button type="primary" size="small" @click="startRecord()">录制</el-button>-->
  11 +<!-- <el-button type="primary" size="small" @click="stopRecord()">停止录制</el-button>-->
  12 + </div>
  13 + <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
  14 + <span style="width: 5rem; line-height: 2.5rem; text-align: right;">播放地址:</span>
  15 + <el-input v-model="getPlayerShared.sharedUrl" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedUrl)"></el-input>
  16 + </div>
  17 + <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
  18 + <span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe:</span>
  19 + <el-input v-model="getPlayerShared.sharedIframe" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedIframe)"></el-input>
  20 + </div>
  21 + <div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
  22 + <span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址:</span>
  23 + <el-input v-model="getPlayerShared.sharedRtmp" :disabled="true" v-on:click.native="copySharedInfo(getPlayerShared.sharedRtmp)"></el-input>
  24 + </div>
  25 + </el-tab-pane>
  26 + <!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}-->
  27 + <el-tab-pane label="录像查询" name="record">
  28 + <el-date-picker size="mini" v-model="videoHistory.date" type="date" value-format="yyyy-MM-dd" placeholder="日期"
  29 + @change="queryRecords()"></el-date-picker>
  30 +<!-- <el-slider style="margin: 0 1rem 1rem 1rem;"-->
  31 +<!-- v-model="timeVal"-->
  32 +<!-- :min="timeMin"-->
  33 +<!-- :max="timeMax"-->
  34 +<!-- :step="5"-->
  35 +<!-- :marks="getTimeMakrs()"-->
  36 +<!-- :format-tooltip="formatTooltip">-->
  37 +<!-- </el-slider>-->
  38 +<!-- <range-slider :min="timeMin"-->
  39 +<!-- :max="timeMax"-->
  40 +<!-- :step="5"></range-slider>-->
35 41
36 - <el-table-column label="操作">  
37 - <template slot-scope="scope">  
38 - <el-button type="primary" size="mini" @click="playRecord(false, scope.row)">播放</el-button>  
39 - </template>  
40 - </el-table-column>  
41 - </el-table>  
42 - </el-tab-pane>  
43 - <!--遥控界面-->  
44 - <el-tab-pane label="云台控制" name="third">  
45 - <div style="display: flex; justify-content: center;">  
46 - <div class="control-wrapper">  
47 - <div class="control-btn control-top" @mousedown="ptzCamera(0, 1, 0)" @mouseup="ptzCamera(0, 0, 0)">  
48 - <i class="el-icon-caret-top"></i>  
49 - <div class="control-inner-btn control-inner"></div>  
50 - </div>  
51 - <div class="control-btn control-left" @mousedown="ptzCamera(1, 0, 0)" @mouseup="ptzCamera(0, 0, 0)">  
52 - <i class="el-icon-caret-left"></i>  
53 - <div class="control-inner-btn control-inner"></div>  
54 - </div>  
55 - <div class="control-btn control-bottom" @mousedown="ptzCamera(0, 2, 0)" @mouseup="ptzCamera(0, 0, 0)">  
56 - <i class="el-icon-caret-bottom"></i>  
57 - <div class="control-inner-btn control-inner"></div>  
58 - </div>  
59 - <div class="control-btn control-right" @mousedown="ptzCamera(2, 0, 0)" @mouseup="ptzCamera(0, 0, 0)">  
60 - <i class="el-icon-caret-right"></i>  
61 - <div class="control-inner-btn control-inner"></div>  
62 - </div>  
63 - <div class="control-round">  
64 - <div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>  
65 - </div> 42 +<!-- <el-date-picker v-model="videoHistory.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束时间"-->
  43 +<!-- @change="recordList()"></el-date-picker>-->
  44 + <el-table :data="videoHistory.searchHistoryResult" height="150" v-load="recordsLoading">
  45 + <el-table-column label="名称" prop="name"></el-table-column>
  46 + <el-table-column label="文件" prop="filePath"></el-table-column>
  47 + <el-table-column label="开始时间" prop="startTime" :formatter="timeFormatter"></el-table-column>
  48 + <el-table-column label="结束时间" prop="endTime" :formatter="timeFormatter"></el-table-column>
66 49
67 - <div style="position: absolute; left: 7.25rem; top: 1.25rem" @mousedown="ptzCamera(0, 0, 2)" @mouseup="ptzCamera(0, 0, 0)"><i class="el-icon-zoom-in" style="font-size: 1.875rem;"></i></div>  
68 - <div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;" @mousedown="ptzCamera(0, 0, 1)" @mouseup="ptzCamera(0, 0, 0)"><i class="el-icon-zoom-out"></i></div>  
69 - </div>  
70 - </div> 50 + <el-table-column label="操作" >
  51 + <template slot-scope="scope">
  52 + <el-button icon="el-icon-video-play" size="mini" @click="playRecord(scope.row)">播放</el-button>
  53 + </template>
  54 + </el-table-column>
  55 + </el-table>
  56 + </el-tab-pane>
  57 + <!--遥控界面-->
  58 + <el-tab-pane label="云台控制" name="control">
  59 + <div style="display: flex; justify-content: center;">
  60 + <div class="control-wrapper">
  61 + <div class="control-btn control-top" @mousedown="ptzCamera(0, 1, 0)" @mouseup="ptzCamera(0, 0, 0)">
  62 + <i class="el-icon-caret-top"></i>
  63 + <div class="control-inner-btn control-inner"></div>
  64 + </div>
  65 + <div class="control-btn control-left" @mousedown="ptzCamera(1, 0, 0)" @mouseup="ptzCamera(0, 0, 0)">
  66 + <i class="el-icon-caret-left"></i>
  67 + <div class="control-inner-btn control-inner"></div>
  68 + </div>
  69 + <div class="control-btn control-bottom" @mousedown="ptzCamera(0, 2, 0)" @mouseup="ptzCamera(0, 0, 0)">
  70 + <i class="el-icon-caret-bottom"></i>
  71 + <div class="control-inner-btn control-inner"></div>
  72 + </div>
  73 + <div class="control-btn control-right" @mousedown="ptzCamera(2, 0, 0)" @mouseup="ptzCamera(0, 0, 0)">
  74 + <i class="el-icon-caret-right"></i>
  75 + <div class="control-inner-btn control-inner"></div>
  76 + </div>
  77 + <div class="control-round">
  78 + <div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
  79 + </div>
  80 + <div style="position: absolute; left: 7.25rem; top: 1.25rem" @mousedown="ptzCamera(0, 0, 2)" @mouseup="ptzCamera(0, 0, 0)"><i
  81 + class="el-icon-zoom-in" style="font-size: 1.875rem;"></i></div>
  82 + <div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;" @mousedown="ptzCamera(0, 0, 1)"
  83 + @mouseup="ptzCamera(0, 0, 0)"><i class="el-icon-zoom-out"></i></div>
  84 + </div>
  85 + </div>
71 86
72 - </el-tab-pane>  
73 - </el-tabs>  
74 - </div>  
75 - </el-dialog>  
76 -</div> 87 + </el-tab-pane>
  88 + </el-tabs>
  89 + </div>
  90 + </el-dialog>
  91 + </div>
77 </template> 92 </template>
78 93
79 <script> 94 <script>
80 -import LivePlayer from '@liveqing/liveplayer'  
81 -export default {  
82 - name: 'devicePlayer',  
83 - props: {},  
84 - components: {  
85 - LivePlayer  
86 - },  
87 - computed: {  
88 - getPlayerShared: function () {  
89 - return {  
90 - sharedUrl: window.location.host + '/' + this.videoUrl,  
91 - sharedIframe: '<iframe src="' + window.location.host + '/' + this.videoUrl + '"></iframe>',  
92 - sharedRtmp: this.videoUrl  
93 - };  
94 - }  
95 - },  
96 - created() {  
97 - // this.videoHistory.searchHistoryResult = falsificationData.recordData.recordList;  
98 - },  
99 - data() { 95 + import LivePlayer from '@liveqing/liveplayer'
  96 + export default {
  97 + name: 'devicePlayer',
  98 + props: {},
  99 + components: {
  100 + LivePlayer
  101 + },
  102 + computed: {
  103 + getPlayerShared: function() {
100 return { 104 return {
101 - video: 'http://lndxyj.iqilu.com/public/upload/2019/10/14/8c001ea0c09cdc59a57829dabc8010fa.mp4',  
102 - videoUrl: '',  
103 - videoHistory: {  
104 - startTime: '',  
105 - endTime: '',  
106 - searchHistoryResult: [] //媒体流历史记录搜索结果  
107 - },  
108 - showVideoDialog: false,  
109 - normalssrc: '',  
110 - ssrc: '',  
111 - deviceId: '',  
112 - channelId: '',  
113 - tabActiveName: 'media',  
114 - hasaudio: false 105 + sharedUrl: window.location.host + '/' + this.videoUrl,
  106 + sharedIframe: '<iframe src="' + window.location.host + '/' + this.videoUrl + '"></iframe>',
  107 + sharedRtmp: this.videoUrl
  108 + };
  109 + }
  110 + },
  111 + created() {
  112 + },
  113 + data() {
  114 + return {
  115 + video:'http://lndxyj.iqilu.com/public/upload/2019/10/14/8c001ea0c09cdc59a57829dabc8010fa.mp4',
  116 + videoUrl: '',
  117 + videoHistory: {
  118 + date: '',
  119 + searchHistoryResult: [] //媒体流历史记录搜索结果
  120 + },
  121 + timeMakrs:{
  122 + // 0 : "0:00",
  123 + // // 60 : "1:00",
  124 + // 120 : "2:00",
  125 + // // 180 : "3:00",
  126 + // 240 : "4:00",
  127 + // // 300 : "5:00",
  128 + // 360 : "6:00",
  129 + // // 420 : "7:00",
  130 + // 480 : "8:00",
  131 + // 540 : "9:00",
  132 + 600: "10:00",
  133 + // 660 : "11:00",
  134 + 720 : "12:00",
  135 + // 780 : "13:00",
  136 + 840 : "14:00",
  137 + // 900 : "15:00",
  138 + 960 : "16:00",
  139 + // 1020 : "17:00",
  140 + 1080 : "18:00",
  141 + // 1140 : "19:00",
  142 + // 1200 : "20:00",
  143 + // // 1260 : "21:00",
  144 + // 1320 : "22:00",
  145 + // // 1380 : "23:00",
  146 + // 1440 : "24:00"
  147 + },
  148 + showVideoDialog: false,
  149 + ssrc: '',
  150 + deviceId: '',
  151 + channelId: '',
  152 + tabActiveName: 'media',
  153 + hasaudio: false,
  154 + loadingRecords: false,
  155 + recordsLoading: false,
  156 + timeVal: 0,
  157 + timeMin: 0,
  158 + timeMax: 1440,
  159 +
  160 + };
  161 + },
  162 + methods: {
  163 + openDialog: function (tab, deviceId, channelId, param) {
  164 + this.tabActiveName = tab;
  165 + this.channelId = channelId;
  166 + this.deviceId = deviceId;
  167 + this.ssrc = "";
  168 + this.videoUrl = ""
  169 + if (!!this.$refs.videoPlayer) {
  170 + this.$refs.videoPlayer.pause();
  171 + }
115 172
116 - };  
117 - },  
118 - methods: { 173 + switch(tab) {
  174 + case "media":
  175 + this.play(param.streamInfo, param.hasAudio)
  176 + break;
  177 + case "record":
  178 + this.showVideoDialog = true;
119 179
120 - play: function (streamInfo, deviceId, channelId, hasAudio) { 180 + this.videoHistory.date = param.date;
  181 + this.queryRecords()
  182 + break;
  183 + case "control":
  184 + break;
  185 + }
  186 + },
  187 + timeAxisSelTime:function (val) {
  188 + console.log(val)
  189 + },
  190 + getTimeMakrs(){
  191 + return this.timeMakrs;
  192 + },
  193 + play: function (streamInfo, hasAudio) {
121 this.hasaudio = hasAudio; 194 this.hasaudio = hasAudio;
122 // 根据媒体流信息二次判断 195 // 根据媒体流信息二次判断
123 if (!!streamInfo.tracks && streamInfo.tracks.length > 0) { 196 if (!!streamInfo.tracks && streamInfo.tracks.length > 0) {
@@ -130,8 +203,6 @@ export default { @@ -130,8 +203,6 @@ export default {
130 this.hasaudio = realHasAudio && this.hasaudio; 203 this.hasaudio = realHasAudio && this.hasaudio;
131 } 204 }
132 this.ssrc = streamInfo.ssrc; 205 this.ssrc = streamInfo.ssrc;
133 - this.deviceId = deviceId;  
134 - this.channelId = channelId;  
135 // this.$refs.videoPlayer.hasaudio = hasAudio; 206 // this.$refs.videoPlayer.hasaudio = hasAudio;
136 // this.videoUrl = streamInfo.flv + "?" + new Date().getTime(); 207 // this.videoUrl = streamInfo.flv + "?" + new Date().getTime();
137 this.videoUrl = streamInfo.ws_flv; 208 this.videoUrl = streamInfo.ws_flv;
@@ -165,211 +236,276 @@ export default { @@ -165,211 +236,276 @@ export default {
165 ); 236 );
166 }, 237 },
167 238
168 - recordList: function () {  
169 - if (!this.videoHistory.startTime || !this.videoHistory.endTime) {  
170 - return;  
171 - }  
172 - let that = this;  
173 - this.$axios({  
174 - method: 'get',  
175 - url: '/api/record/' + this.deviceId + '/' + this.channelId + '?startTime=' + this.videoHistory  
176 - .startTime + '&endTime=' + this.videoHistory.endTime  
177 - }).then(function (res) {  
178 - console.log(JSON.stringify(res));  
179 - }).catch(function (e) {  
180 - // that.videoHistory.searchHistoryResult = falsificationData.recordData;  
181 - }); 239 + queryRecords: function() {
  240 + if (!this.videoHistory.date) {
  241 + return;
  242 + }
  243 + this.recordsLoading = true;
  244 + let that = this;
  245 + var startTime = this.videoHistory.date + " 00:00:00";
  246 + var endTime = this.videoHistory.date + " 23:59:59";
  247 + this.$axios({
  248 + method: 'get',
  249 + url: '/api/record/' + this.deviceId + '/' + this.channelId + '?startTime=' + startTime + '&endTime=' + endTime
  250 + }).then(function(res) {
  251 + // 处理时间信息
  252 + that.videoHistory.searchHistoryResult = res.data.recordList;
  253 + that.recordsLoading = false;
  254 + }).catch(function(e) {
  255 + // that.videoHistory.searchHistoryResult = falsificationData.recordData;
  256 + });
182 257
183 - },  
184 - playRecord: function (isBackLive, rowData) {  
185 - let that = this;  
186 - if (isBackLive) {  
187 - this.videoUrl = this.getVideoUrlBySsrc(this.normalssrc);  
188 - return; 258 + },
  259 + onTimeChange: function (video) {
  260 + // this.queryRecords()
  261 + },
  262 + playRecord: function(row) {
  263 + let that = this;
  264 + if (that.ssrc != "") {
  265 + that.stopPlayRecord(function (){
  266 + that.ssrc = "",
  267 + that.playRecord(row);
  268 + })
  269 + }else {
  270 + this.$axios({
  271 + method: 'get',
  272 + url: '/api/playback/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' +
  273 + row.endTime
  274 + }).then(function(res) {
  275 + var streamInfo = res.data;
  276 + that.ssrc = streamInfo.ssrc;
  277 + that.videoUrl = streamInfo.ws_flv;
  278 + });
  279 + }
  280 + },
  281 + stopPlayRecord: function (callback) {
  282 + this.$refs.videoPlayer.pause();
  283 + this.videoUrl = '';
  284 + this.$axios({
  285 + method: 'get',
  286 + url: '/api/playback/' + this.ssrc + '/stop'
  287 + }).then(function(res) {
  288 + if (callback) callback()
  289 + });
  290 + },
  291 + ptzCamera: function(leftRight, upDown, zoom) {
  292 + console.log('云台控制:' + leftRight + ' : ' + upDown + " : " + zoom);
  293 + let that = this;
  294 + this.$axios({
  295 + method: 'post',
  296 + url: '/api/ptz/' + this.deviceId + '/' + this.channelId + '?leftRight=' + leftRight + '&upDown=' + upDown +
  297 + '&inOut=' + zoom + '&moveSpeed=50&zoomSpeed=50'
  298 + }).then(function(res) {});
  299 + },
  300 + //////////////////////播放器事件处理//////////////////////////
  301 + videoError:function(e){
  302 + console.log("播放器错误:"+JSON.stringify(e));
  303 + },
  304 + formatTooltip:function(val) {
  305 + var h = parseInt(val/60);
  306 + var hStr = h< 10 ? ("0" + h):h;
  307 + var s = val%60;
  308 + var sStr = s< 10 ? ("0" + s):s;
  309 + return h + ":" + sStr;
  310 + },
  311 + timeFormatter: function (row, column, cellValue, index) {
  312 + return cellValue.split( " ")[1];
  313 + },
  314 + mergeTime: function (timeArray) {
  315 + var resultArray = [];
  316 + for (let i = 0; i < timeArray.length; i++) {
  317 + var startTime = new Date(timeArray[i].startTime);
  318 + var endTime = new Date(timeArray[i].endTime);
  319 + if (i ==0) {
  320 + resultArray[0] = {
  321 + startTime: startTime,
  322 + endTime: endTime
189 } 323 }
190 - this.$axios({  
191 - method: 'get',  
192 - url: '/api/playback/' + this.deviceId + '/' + this.channelId + '?startTime=' + rowData.startTime + '&endTime=' +  
193 - rowData.endTime  
194 - }).then(function (res) {  
195 - let ssrc = res.data.ssrc;  
196 - that.videoUrl = that.getVideoUrlBySsrc(ssrc);  
197 - //that.videoUrl='http://hls.cntv.kcdnvip.com/asp/hls/main/0303000a/3/default/f466089412c04a759c5515dbfcc3ac3d/main.m3u8?maxbr=2048';  
198 - });  
199 -  
200 - },  
201 - ptzCamera: function (leftRight, upDown, zoom) {  
202 - console.log('云台控制:' + leftRight + ' : ' + upDown + " : " + zoom);  
203 - let that = this;  
204 - this.$axios({  
205 - method: 'post',  
206 - url: '/api/ptz/' + this.deviceId + '/' + this.channelId + '?leftRight=' + leftRight + '&upDown=' + upDown +  
207 - '&inOut=' + zoom + '&moveSpeed=50&zoomSpeed=50'  
208 - }).then(function (res) {});  
209 - },  
210 - //////////////////////播放器事件处理//////////////////////////  
211 - videoError: function (e) {  
212 - console.log("播放器错误:" + JSON.stringify(e)); 324 + }
  325 + for (let j = 0; j < resultArray.length; j++) {
  326 + if (startTime > resultArray[j].endTime ) { // 合并
  327 + if (startTime - resultArray[j].endTime <= 1000){
  328 + resultArray[j].endTime = endTime;
  329 + }else {
  330 + resultArray[resultArray.length] = {
  331 + startTime: startTime,
  332 + endTime: endTime
  333 + }
  334 + }
  335 + }else if(resultArray[j].startTime > endTime ) { // 合并
  336 + if (resultArray[j].startTime - endTime <= 1000) {
  337 + resultArray[j].startTime = startTime;
  338 + }else {
  339 + resultArray[resultArray.length] = {
  340 + startTime: startTime,
  341 + endTime: endTime
  342 + }
  343 + }
  344 + }
  345 + }
213 } 346 }
214 - }  
215 -}; 347 + console.log(resultArray)
  348 + return resultArray;
  349 + }
  350 + }
  351 + };
216 </script> 352 </script>
217 353
218 <style> 354 <style>
219 -.control-wrapper {  
220 - position: relative;  
221 - width: 6.25rem;  
222 - height: 6.25rem;  
223 - max-width: 6.25rem;  
224 - max-height: 6.25rem;  
225 - margin: 0 auto;  
226 - border-radius: 100%;  
227 - float: left;  
228 -} 355 + .control-wrapper {
  356 + position: relative;
  357 + width: 6.25rem;
  358 + height: 6.25rem;
  359 + max-width: 6.25rem;
  360 + max-height: 6.25rem;
  361 + margin: 0 auto;
  362 + border-radius: 100%;
  363 + float: left;
  364 + }
229 365
230 -.control-btn {  
231 - display: flex;  
232 - justify-content: center;  
233 - position: absolute;  
234 - width: 44%;  
235 - height: 44%;  
236 - border-radius: 5px;  
237 - border: 1px solid #78aee4;  
238 - box-sizing: border-box;  
239 - transition: all 0.3s linear;  
240 -} 366 + .control-btn {
  367 + display: flex;
  368 + justify-content: center;
  369 + position: absolute;
  370 + width: 44%;
  371 + height: 44%;
  372 + border-radius: 5px;
  373 + border: 1px solid #78aee4;
  374 + box-sizing: border-box;
  375 + transition: all 0.3s linear;
  376 + }
241 377
242 -.control-btn i {  
243 - font-size: 20px;  
244 - color: #78aee4;  
245 - display: flex;  
246 - justify-content: center;  
247 - align-items: center;  
248 -} 378 + .control-btn i {
  379 + font-size: 20px;
  380 + color: #78aee4;
  381 + display: flex;
  382 + justify-content: center;
  383 + align-items: center;
  384 + }
249 385
250 -.control-round {  
251 - position: absolute;  
252 - top: 21%;  
253 - left: 21%;  
254 - width: 58%;  
255 - height: 58%;  
256 - background: #fff;  
257 - border-radius: 100%;  
258 -} 386 + .control-round {
  387 + position: absolute;
  388 + top: 21%;
  389 + left: 21%;
  390 + width: 58%;
  391 + height: 58%;
  392 + background: #fff;
  393 + border-radius: 100%;
  394 + }
259 395
260 -.control-round-inner {  
261 - position: absolute;  
262 - left: 15%;  
263 - top: 15%;  
264 - display: flex;  
265 - justify-content: center;  
266 - align-items: center;  
267 - width: 70%;  
268 - height: 70%;  
269 - font-size: 40px;  
270 - color: #78aee4;  
271 - border: 1px solid #78aee4;  
272 - border-radius: 100%;  
273 - transition: all 0.3s linear;  
274 -} 396 + .control-round-inner {
  397 + position: absolute;
  398 + left: 15%;
  399 + top: 15%;
  400 + display: flex;
  401 + justify-content: center;
  402 + align-items: center;
  403 + width: 70%;
  404 + height: 70%;
  405 + font-size: 40px;
  406 + color: #78aee4;
  407 + border: 1px solid #78aee4;
  408 + border-radius: 100%;
  409 + transition: all 0.3s linear;
  410 + }
275 411
276 -.control-inner-btn {  
277 - position: absolute;  
278 - width: 60%;  
279 - height: 60%;  
280 - background: #fafafa;  
281 -} 412 + .control-inner-btn {
  413 + position: absolute;
  414 + width: 60%;
  415 + height: 60%;
  416 + background: #fafafa;
  417 + }
282 418
283 -.control-top {  
284 - top: -8%;  
285 - left: 27%;  
286 - transform: rotate(-45deg);  
287 - border-radius: 5px 100% 5px 0;  
288 -} 419 + .control-top {
  420 + top: -8%;
  421 + left: 27%;
  422 + transform: rotate(-45deg);
  423 + border-radius: 5px 100% 5px 0;
  424 + }
289 425
290 -.control-top i {  
291 - transform: rotate(45deg);  
292 - border-radius: 5px 100% 5px 0;  
293 -} 426 + .control-top i {
  427 + transform: rotate(45deg);
  428 + border-radius: 5px 100% 5px 0;
  429 + }
294 430
295 -.control-top .control-inner {  
296 - left: -1px;  
297 - bottom: 0;  
298 - border-top: 1px solid #78aee4;  
299 - border-right: 1px solid #78aee4;  
300 - border-radius: 0 100% 0 0;  
301 -} 431 + .control-top .control-inner {
  432 + left: -1px;
  433 + bottom: 0;
  434 + border-top: 1px solid #78aee4;
  435 + border-right: 1px solid #78aee4;
  436 + border-radius: 0 100% 0 0;
  437 + }
302 438
303 -.control-top .fa {  
304 - transform: rotate(45deg) translateY(-7px);  
305 -} 439 + .control-top .fa {
  440 + transform: rotate(45deg) translateY(-7px);
  441 + }
306 442
307 -.control-left {  
308 - top: 27%;  
309 - left: -8%;  
310 - transform: rotate(45deg);  
311 - border-radius: 5px 0 5px 100%;  
312 -} 443 + .control-left {
  444 + top: 27%;
  445 + left: -8%;
  446 + transform: rotate(45deg);
  447 + border-radius: 5px 0 5px 100%;
  448 + }
313 449
314 -.control-left i {  
315 - transform: rotate(-45deg);  
316 -} 450 + .control-left i {
  451 + transform: rotate(-45deg);
  452 + }
317 453
318 -.control-left .control-inner {  
319 - right: -1px;  
320 - top: -1px;  
321 - border-bottom: 1px solid #78aee4;  
322 - border-left: 1px solid #78aee4;  
323 - border-radius: 0 0 0 100%;  
324 -} 454 + .control-left .control-inner {
  455 + right: -1px;
  456 + top: -1px;
  457 + border-bottom: 1px solid #78aee4;
  458 + border-left: 1px solid #78aee4;
  459 + border-radius: 0 0 0 100%;
  460 + }
325 461
326 -.control-left .fa {  
327 - transform: rotate(-45deg) translateX(-7px);  
328 -} 462 + .control-left .fa {
  463 + transform: rotate(-45deg) translateX(-7px);
  464 + }
329 465
330 -.control-right {  
331 - top: 27%;  
332 - right: -8%;  
333 - transform: rotate(45deg);  
334 - border-radius: 5px 100% 5px 0;  
335 -} 466 + .control-right {
  467 + top: 27%;
  468 + right: -8%;
  469 + transform: rotate(45deg);
  470 + border-radius: 5px 100% 5px 0;
  471 + }
336 472
337 -.control-right i {  
338 - transform: rotate(-45deg);  
339 -} 473 + .control-right i {
  474 + transform: rotate(-45deg);
  475 + }
340 476
341 -.control-right .control-inner {  
342 - left: -1px;  
343 - bottom: -1px;  
344 - border-top: 1px solid #78aee4;  
345 - border-right: 1px solid #78aee4;  
346 - border-radius: 0 100% 0 0;  
347 -} 477 + .control-right .control-inner {
  478 + left: -1px;
  479 + bottom: -1px;
  480 + border-top: 1px solid #78aee4;
  481 + border-right: 1px solid #78aee4;
  482 + border-radius: 0 100% 0 0;
  483 + }
348 484
349 -.control-right .fa {  
350 - transform: rotate(-45deg) translateX(7px);  
351 -} 485 + .control-right .fa {
  486 + transform: rotate(-45deg) translateX(7px);
  487 + }
352 488
353 -.control-bottom {  
354 - left: 27%;  
355 - bottom: -8%;  
356 - transform: rotate(45deg);  
357 - border-radius: 0 5px 100% 5px;  
358 -} 489 + .control-bottom {
  490 + left: 27%;
  491 + bottom: -8%;
  492 + transform: rotate(45deg);
  493 + border-radius: 0 5px 100% 5px;
  494 + }
359 495
360 -.control-bottom i {  
361 - transform: rotate(-45deg);  
362 -} 496 + .control-bottom i {
  497 + transform: rotate(-45deg);
  498 + }
363 499
364 -.control-bottom .control-inner {  
365 - top: -1px;  
366 - left: -1px;  
367 - border-bottom: 1px solid #78aee4;  
368 - border-right: 1px solid #78aee4;  
369 - border-radius: 0 0 100% 0;  
370 -} 500 + .control-bottom .control-inner {
  501 + top: -1px;
  502 + left: -1px;
  503 + border-bottom: 1px solid #78aee4;
  504 + border-right: 1px solid #78aee4;
  505 + border-radius: 0 0 100% 0;
  506 + }
371 507
372 -.control-bottom .fa {  
373 - transform: rotate(-45deg) translateY(7px);  
374 -} 508 + .control-bottom .fa {
  509 + transform: rotate(-45deg) translateY(7px);
  510 + }
375 </style> 511 </style>
web_src/src/main.js
@@ -6,8 +6,8 @@ import &#39;element-ui/lib/theme-chalk/index.css&#39;; @@ -6,8 +6,8 @@ import &#39;element-ui/lib/theme-chalk/index.css&#39;;
6 import router from './router/index.js'; 6 import router from './router/index.js';
7 import axios from 'axios'; 7 import axios from 'axios';
8 import VueCookies from 'vue-cookies'; 8 import VueCookies from 'vue-cookies';
9 -  
10 import echarts from 'echarts'; 9 import echarts from 'echarts';
  10 +
11 import VueClipboard from 'vue-clipboard2' 11 import VueClipboard from 'vue-clipboard2'
12 Vue.use(VueClipboard) 12 Vue.use(VueClipboard)
13 Vue.use(ElementUI); 13 Vue.use(ElementUI);