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 18  
19 19 public static final String PLAYER_PREFIX = "VMP_player_";
20 20  
  21 + public static final String PLAY_BLACK_PREFIX = "VMP_playback_";
  22 +
21 23 public static final String EVENT_ONLINE_REGISTER = "1";
22 24  
23 25 public static final String EVENT_ONLINE_KEEPLIVE = "2";
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
1 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 11 * @Description:设备录像bean
6 12 * @author: swwheihei
7 13 * @date: 2020年5月8日 下午2:06:54
8 14 */
9   -public class RecordItem {
  15 +public class RecordItem implements Comparable<RecordItem>{
10 16  
11 17 private String deviceId;
12 18  
... ... @@ -97,4 +103,21 @@ public class RecordItem {
97 103 public void setRecorderId(String recorderId) {
98 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 80 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
81 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 279 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
280 280 */
281 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 283 try {
284 284 MediaServerConfig mediaInfo = storager.getMediaInfo();
285 285 String ssrc = streamSession.createPlayBackSsrc();
... ... @@ -324,7 +324,13 @@ public class SIPCommander implements ISIPCommander {
324 324  
325 325 ClientTransaction transaction = transmitRequest(device, request);
326 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 335 } catch ( SipException | ParseException | InvalidArgumentException e) {
330 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 2  
3 3 import java.io.ByteArrayInputStream;
4 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 7 import javax.sip.InvalidArgumentException;
12 8 import javax.sip.RequestEvent;
... ... @@ -316,6 +312,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
316 312 record.setRecorderId(XmlUtil.getText(itemRecord,"RecorderID"));
317 313 recordList.add(record);
318 314 }
  315 +// recordList.sort(Comparator.naturalOrder());
319 316 recordInfo.setRecordList(recordList);
320 317 }
321 318  
... ... @@ -349,9 +346,13 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
349 346 // 走到这里,有以下可能:1、没有录像信息,第一次收到recordinfo的消息即返回响应数据,无redis操作
350 347 // 2、有录像数据,且第一次即收到完整数据,返回响应数据,无redis操作
351 348 // 3、有录像数据,在超时时间内收到多次包组装后数量足够,返回数据
  349 +
  350 + // 对记录进行排序
352 351 RequestMessage msg = new RequestMessage();
353 352 msg.setDeviceId(deviceId);
354 353 msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO);
  354 + // 自然顺序排序, 元素进行升序排列
  355 + recordInfo.getRecordList().sort(Comparator.naturalOrder());
355 356 msg.setData(recordInfo);
356 357 deferredResultHolder.invokeResult(msg);
357 358 } catch (DocumentException e) {
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
... ... @@ -130,17 +130,27 @@ public class ZLMHttpHookListener {
130 130 String streamId = json.getString("id");
131 131  
132 132  
133   -// String ssrc = String.format("%10d", Integer.parseInt(streamId, 16)); // ZLM 要求大写且首位补零
134 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 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 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 30 param.put("stream_id", streamId);
31 31 JSONObject jsonObject = zlmresTfulUtils.openRtpServer(param);
32 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 33 return newPort;
39 34 } else {
40 35 return getNewRTPPort(ssrc);
... ...
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
... ... @@ -183,4 +183,12 @@ public interface IVideoManagerStorager {
183 183 StreamInfo queryPlayByDevice(String deviceId, String code);
184 184  
185 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 190  
191 191 @Override
192 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 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 411 }
412 412  
413 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 422 public StreamInfo queryPlayByDevice(String deviceId, String code) {
415 423 // List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
416 424 List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
... ... @@ -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 70 }else {
71 71 streamInfo = storager.queryPlayByDevice(deviceId, channelId);
72 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 74 JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo("rtp", "rtmp", streamId);
75 75 if (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online")) {
76 76 lockFlag = false;
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java
1 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 7 import org.slf4j.Logger;
4 8 import org.slf4j.LoggerFactory;
5 9 import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -30,7 +34,10 @@ public class PlaybackController {
30 34  
31 35 @Autowired
32 36 private IVideoManagerStorager storager;
33   -
  37 +
  38 + @Autowired
  39 + private ZLMRESTfulUtils zlmresTfulUtils;
  40 +
34 41 @GetMapping("/playback/{deviceId}/{channelId}")
35 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 50 logger.warn(log);
44 51 return new ResponseEntity<String>(log,HttpStatus.BAD_REQUEST);
45 52 }
46   -
  53 +
47 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 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 110 } else {
59 111 logger.warn("设备回放API调用失败!");
60 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 117 public ResponseEntity<String> playStop(@PathVariable String ssrc){
66 118  
67 119 cmder.streamByeCmd(ssrc);
... ...
web_src/index.html
... ... @@ -3,7 +3,7 @@
3 3 <head>
4 4 <meta charset="utf-8">
5 5 <meta name="viewport" content="width=device-width,initial-scale=1.0">
6   - <title>GB28181·þÎñÆ÷</title>
  6 + <title>国标28181</title>
7 7 </head>
8 8 <body>
9 9 <script type="text/javascript" src="./js/liveplayer-lib.min.js"></script>
... ...
web_src/package-lock.json
... ... @@ -5843,6 +5843,11 @@
5843 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 5851 "move-concurrently": {
5847 5852 "version": "1.0.1",
5848 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 15 "core-js": "^2.6.5",
16 16 "echarts": "^4.7.0",
17 17 "element-ui": "2.10.1",
  18 + "moment": "^2.29.1",
18 19 "vue": "^2.6.11",
19 20 "vue-clipboard2": "^0.3.1",
20 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 10 <div class="log-logo">Welcome!</div>
11 11 <div class="log-text"></div>
12 12 </div>
13   - <div class="log-email">
  13 + <div class="log-email" v-loading="isLoging" >
14 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 15 <a href="javascript:;" class="log-btn" @click="login" >登录</a>
16 16 </div>
17   - <Loading v-if="isLoging" marginTop="-30%"></Loading>
18 17 </div>
19 18 </template>
20 19  
21 20 <script>
22   -import Loading from './Loading.vue'
23 21 import crypto from 'crypto'
24 22 export default {
25 23 name: 'Login',
... ... @@ -30,9 +28,6 @@ export default {
30 28 password: ''
31 29 }
32 30 },
33   - components:{
34   - Loading
35   - },
36 31 created(){
37 32 var that = this;
38 33 document.onkeydown = function(e) {
... ... @@ -79,7 +74,8 @@ export default {
79 74 }
80 75 })
81 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 26 </el-select>
27 27  
28 28 </div>
29   - <devicePlayer ref="devicePlayer"></devicePlayer>
  29 + <devicePlayer ref="devicePlayer" v-loading="isLoging"></devicePlayer>
30 30 <!--设备列表-->
31 31 <el-table ref="channelListTable" :data="deviceChannelList" :height="winHeight" border style="width: 100%">
32 32 <el-table-column prop="channelId" label="通道编号" width="210">
... ... @@ -58,8 +58,8 @@
58 58 <el-button size="mini" icon="el-icon-video-play" @click="sendDevicePush(scope.row)">播放</el-button>
59 59 <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="scope.row.play" @click="stopDevicePush(scope.row)">停止</el-button>
60 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 63 </el-button-group>
64 64 </template>
65 65 </el-table-column>
... ... @@ -69,20 +69,18 @@
69 69  
70 70 </el-main>
71 71 </el-container>
72   - <Loading v-if="isLoging" marginTop="-50%"></Loading>
73 72 </div>
74 73 </template>
75 74  
76 75 <script>
77 76 import devicePlayer from './gb28181/devicePlayer.vue'
78 77 import uiHeader from './UiHeader.vue'
79   -import Loading from './Loading.vue'
  78 +import moment from "moment";
80 79 export default {
81 80 name: 'channelList',
82 81 components: {
83 82 devicePlayer,
84   - uiHeader,
85   - Loading
  83 + uiHeader
86 84 },
87 85 data() {
88 86 return {
... ... @@ -209,13 +207,23 @@ export default {
209 207 let ssrc = res.data.ssrc;
210 208 that.isLoging = false;
211 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 215 that.initData();
214 216 } else {
215 217 that.$message.error(res.data);
216 218 }
217 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 227 stopDevicePush: function (itemData) {
220 228 console.log(itemData)
221 229 var that = this;
... ...
web_src/src/components/gb28181/devicePlayer.vue
1 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 92 </template>
78 93  
79 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 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 194 this.hasaudio = hasAudio;
122 195 // 根据媒体流信息二次判断
123 196 if (!!streamInfo.tracks && streamInfo.tracks.length > 0) {
... ... @@ -130,8 +203,6 @@ export default {
130 203 this.hasaudio = realHasAudio && this.hasaudio;
131 204 }
132 205 this.ssrc = streamInfo.ssrc;
133   - this.deviceId = deviceId;
134   - this.channelId = channelId;
135 206 // this.$refs.videoPlayer.hasaudio = hasAudio;
136 207 // this.videoUrl = streamInfo.flv + "?" + new Date().getTime();
137 208 this.videoUrl = streamInfo.ws_flv;
... ... @@ -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 352 </script>
217 353  
218 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 511 </style>
... ...
web_src/src/main.js
... ... @@ -6,8 +6,8 @@ import &#39;element-ui/lib/theme-chalk/index.css&#39;;
6 6 import router from './router/index.js';
7 7 import axios from 'axios';
8 8 import VueCookies from 'vue-cookies';
9   -
10 9 import echarts from 'echarts';
  10 +
11 11 import VueClipboard from 'vue-clipboard2'
12 12 Vue.use(VueClipboard)
13 13 Vue.use(ElementUI);
... ...