From 3ec3b88456cf9ac455d93baba40f339bb284dd77 Mon Sep 17 00:00:00 2001
From: 648540858 <648540858@qq.com>
Date: Wed, 14 Oct 2020 14:39:10 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=82=B9=E6=92=AD=E5=88=A4?=
 =?UTF-8?q?=E6=96=AD=E9=94=99=E8=AF=AF=E5=AF=BC=E8=87=B4=E7=9A=8415s?=
 =?UTF-8?q?=E8=B6=85=E9=95=BF=E5=BB=B6=E6=97=B6=20=E5=A2=9E=E5=8A=A0?=
 =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=B8=8D=E5=85=B3=E9=97=AD=E6=8E=A8=E6=B5=81?=
 =?UTF-8?q?,=20=E6=97=A0=E4=BA=BA=E8=A7=82=E7=9C=8B=E8=B6=85=E6=97=B6?=
 =?UTF-8?q?=E6=88=96=E7=82=B9=E5=87=BB=E5=81=9C=E6=AD=A2=E6=8C=89=E9=92=AE?=
 =?UTF-8?q?=E5=85=B3=E9=97=AD=E6=B5=81=20=E4=BF=AE=E5=A4=8D=E7=82=B9?=
 =?UTF-8?q?=E6=92=AD=E5=85=B6=E4=BB=96bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../iot/vmp/gb28181/bean/DeviceChannel.java   | 13 ++++++
 .../iot/vmp/media/zlm/ZLMRunner.java          |  9 ++++-
 .../vmp/storager/IVideoManagerStorager.java   |  3 ++
 .../jdbc/VideoManagerJdbcStoragerImpl.java    |  6 +++
 .../redis/VideoManagerRedisStoragerImpl.java  | 40 +++++++++++++++++--
 .../iot/vmp/vmanager/play/PlayController.java | 29 +++++++++++---
 src/main/resources/application.yml            |  1 +
 web_src/src/components/channelList.vue        | 25 ++++++++++--
 .../src/components/gb28181/devicePlayer.vue   | 17 +-------
 9 files changed, 114 insertions(+), 29 deletions(-)

diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
index 00bd9951..13503419 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
@@ -148,6 +148,11 @@ public class DeviceChannel {
 	 */
 	private  boolean hasAudio;
 
+	/**
+	 *  是否正在播放
+	 */
+	private  boolean play;
+
 	public String getChannelId() {
 		return channelId;
 	}
@@ -388,4 +393,12 @@ public class DeviceChannel {
 	public void setHasAudio(boolean hasAudio) {
 		this.hasAudio = hasAudio;
 	}
+
+	public boolean isPlay() {
+		return play;
+	}
+
+	public void setPlay(boolean play) {
+		this.play = play;
+	}
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
index 81148144..7d7edf40 100644
--- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
+++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
@@ -38,6 +38,9 @@ public class ZLMRunner implements CommandLineRunner {
     @Value("${media.secret}")
     private String mediaSecret;
 
+    @Value("${media.streamNoneReaderDelayMS}")
+    private String streamNoneReaderDelayMS;
+
     @Value("${sip.ip}")
     private String sipIP;
 
@@ -54,9 +57,10 @@ public class ZLMRunner implements CommandLineRunner {
         MediaServerConfig mediaServerConfig = getMediaServerConfig();
         if (mediaServerConfig != null) {
             logger.info("zlm接入成功...");
-            storager.updateMediaInfo(mediaServerConfig);
             logger.info("设置zlm...");
             saveZLMConfig();
+            mediaServerConfig = getMediaServerConfig();
+            storager.updateMediaInfo(mediaServerConfig);
 
         }
     }
@@ -79,7 +83,7 @@ public class ZLMRunner implements CommandLineRunner {
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
-            getMediaServerConfig();
+            mediaServerConfig = getMediaServerConfig();
         }
         return mediaServerConfig;
     }
@@ -106,6 +110,7 @@ public class ZLMRunner implements CommandLineRunner {
         param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex));
         param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex));
         param.put("hook.timeoutSec","20");
+        param.put("general.streamNoneReaderDelayMS",streamNoneReaderDelayMS);
 
         JSONObject responseJSON = zlmresTfulUtils.setServerConfig(param);
 
diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
index 8381738c..0894fe2a 100644
--- a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
+++ b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.storager;
 
 import java.util.List;
+import java.util.Map;
 
 import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.common.PageResult;
@@ -180,4 +181,6 @@ public interface IVideoManagerStorager {
 	StreamInfo queryPlayBySSRC(String ssrc);
 
 	StreamInfo queryPlayByDevice(String deviceId, String code);
+
+	Map<String, StreamInfo> queryPlayByDeviceId(String deviceId);
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java
index c3a18b79..99f19ee2 100644
--- a/src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java
+++ b/src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java
@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.storager.jdbc;
 
 import java.util.List;
+import java.util.Map;
 
 import com.genersoft.iot.vmp.common.PageResult;
 import com.genersoft.iot.vmp.common.StreamInfo;
@@ -186,4 +187,9 @@ public class VideoManagerJdbcStoragerImpl implements IVideoManagerStorager {
 	public StreamInfo queryPlayByDevice(String deviceId, String code) {
 		return null;
 	}
+
+	@Override
+	public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) {
+		return null;
+	}
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
index 38a64762..91b60a1f 100644
--- a/src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
+++ b/src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
@@ -134,6 +134,8 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
 
 	@Override
 	public PageResult queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, String online, int page, int count) {
+		// 获取到所有正在播放的流
+		Map<String, StreamInfo> stringStreamInfoMap = queryPlayByDeviceId(deviceId);
 		List<DeviceChannel> result = new ArrayList<>();
 		PageResult pageResult = new PageResult<DeviceChannel>();
 		String queryContent = "*";
@@ -154,7 +156,11 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
 		int maxCount = (page + 1 ) * count;
 		if (deviceChannelList != null && deviceChannelList.size() > 0 ) {
 			for (int i = page * count; i < (pageResult.getTotal() > maxCount ? maxCount : pageResult.getTotal() ); i++) {
-				result.add((DeviceChannel)redis.get((String)deviceChannelList.get(i)));
+				DeviceChannel deviceChannel = (DeviceChannel)redis.get((String)deviceChannelList.get(i));
+				StreamInfo streamInfo = stringStreamInfoMap.get(deviceId + "_" + deviceChannel.getChannelId());
+				deviceChannel.setPlay(streamInfo != null);
+				if (streamInfo != null) deviceChannel.setSsrc(streamInfo.getSsrc());
+				result.add(deviceChannel);
 			}
 			pageResult.setData(result);
 		}
@@ -162,6 +168,8 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
 		return pageResult;
 	}
 
+
+
 	@Override
 	public List<DeviceChannel> queryChannelsByDeviceId(String deviceId) {
 		List<DeviceChannel> result = new ArrayList<>();
@@ -231,7 +239,13 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
 
 	@Override
 	public DeviceChannel queryChannel(String deviceId, String channelId) {
-		return (DeviceChannel)redis.get(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + "_" + channelId + "_");
+		DeviceChannel deviceChannel = null;
+		List<Object> deviceChannelList = redis.keys(VideoManagerConstants.CACHEKEY_PREFIX + deviceId +
+				"_" + channelId  + "*");
+		if (deviceChannelList != null && deviceChannelList.size() > 0 ) {
+			deviceChannel = (DeviceChannel)redis.get((String)deviceChannelList.get(0));
+		}
+		return deviceChannel;
 	}
 
 
@@ -345,6 +359,12 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
 	@Override
 	public boolean stopPlay(StreamInfo streamInfo) {
 		if (streamInfo == null) return false;
+		DeviceChannel deviceChannel = queryChannel(streamInfo.getDeviceID(), streamInfo.getCahnnelId());
+		if (deviceChannel != null) {
+			deviceChannel.setSsrc(null);
+			deviceChannel.setPlay(false);
+			updateChannel(streamInfo.getDeviceID(), deviceChannel);
+		}
 		return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
 				streamInfo.getSsrc(),
 				streamInfo.getDeviceID(),
@@ -366,7 +386,7 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
 	@Override
 	public StreamInfo queryPlayBySSRC(String ssrc) {
 		List<Object> playLeys = redis.keys(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, ssrc));
-		if (playLeys.size() == 0) return null;
+		if (playLeys == null || playLeys.size() == 0) return null;
 		return (StreamInfo)redis.get(playLeys.get(0).toString());
 	}
 
@@ -375,6 +395,7 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
 		List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
 				deviceId,
 				code));
+		if (playLeys == null || playLeys.size() == 0) return null;
 		return (StreamInfo)redis.get(playLeys.get(0).toString());
 	}
 
@@ -438,6 +459,19 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
 		}
 	}
 
+	@Override
+	public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) {
+		Map<String, StreamInfo> streamInfos = new HashMap<>();
+		List<Object> playLeys = redis.keys(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId));
+		if (playLeys.size() == 0) return streamInfos;
+		for (int i = 0; i < playLeys.size(); i++) {
+			String key = (String) playLeys.get(i);
+			StreamInfo streamInfo = (StreamInfo)redis.get(key);
+			streamInfos.put(streamInfo.getDeviceID() + "_" + streamInfo.getCahnnelId(), streamInfo);
+		}
+		return streamInfos;
+	}
+
 
 
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
index 996039ee..d5647fc6 100644
--- a/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
+++ b/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
@@ -41,18 +41,36 @@ public class PlayController {
 	public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId){
 		
 		Device device = storager.queryVideoDevice(deviceId);
-		StreamInfo streamInfo = cmder.playStreamCmd(device, channelId);
+		StreamInfo streamInfo = storager.queryPlayByDevice(deviceId, channelId);
+
+		if (streamInfo == null) {
+			streamInfo = cmder.playStreamCmd(device, channelId);
+		}else {
+			String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
+			JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
+			if (rtpInfo.getBoolean("exist")) {
+				return new ResponseEntity<String>(JSON.toJSONString(streamInfo),HttpStatus.OK);
+			}else {
+				storager.stopPlay(streamInfo);
+				streamInfo = cmder.playStreamCmd(device, channelId);
+			}
+
+		}
+		String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
 		// 等待推流, TODO 默认超时15s
 		boolean lockFlag = true;
 		long startTime = System.currentTimeMillis();
-		String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
 
-		// 判断推流是否存在
 		while (lockFlag) {
 			try {
+
 				if (System.currentTimeMillis() - startTime > 15 * 1000) {
+					storager.stopPlay(streamInfo);
+					return new ResponseEntity<String>("timeout",HttpStatus.OK);
+				}else {
 					JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
-					if (rtpInfo == null){
+					Boolean exist = rtpInfo.getBoolean("exist");
+					if (rtpInfo == null || !rtpInfo.getBoolean("exist") || streamInfo.getFlv() != null){
 						continue;
 					}else {
 						lockFlag = false;
@@ -72,10 +90,9 @@ public class PlayController {
 							}
 						}
 					};
-
 				}
-
 				Thread.sleep(200);
+				streamInfo = storager.queryPlayByDevice(deviceId, channelId);
 			} catch (InterruptedException e) {
 				e.printStackTrace();
 			}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 5b5f01eb..ad375486 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -43,4 +43,5 @@ media: #zlm服务器的ip与http端口, 重点: 这是http端口
     ip: 192.168.1.20
     port: 9080
     secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
+    streamNoneReaderDelayMS:  1800000  # 无人观看多久关闭流
 
diff --git a/web_src/src/components/channelList.vue b/web_src/src/components/channelList.vue
index 7c667baa..b3980c32 100644
--- a/web_src/src/components/channelList.vue
+++ b/web_src/src/components/channelList.vue
@@ -56,7 +56,8 @@
             </el-table-column>
 						<el-table-column label="操作" width="240" align="center" fixed="right">
 							<template slot-scope="scope">
-								<el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">预览视频</el-button>
+								<el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">播放</el-button>
+								<el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="scope.row.play" @click="stopDevicePush(scope.row)">停止</el-button>
 								<el-button size="mini" icon="el-icon-s-open"  type="primary" v-if="scope.row.parental == 1" @click="changeSubchannel(scope.row)">查看子目录</el-button>
 								<!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> -->
 							</template>
@@ -198,7 +199,7 @@
 						message: '请求成功',
 						type: 'success'
 					});
-				});;
+				});
 			},
 			//通知设备上传媒体流
 			sendDevicePush: function(itemData) {
@@ -212,12 +213,30 @@
 					method: 'get',
 					url: '/api/play/' + deviceId + '/' + channelId
 				}).then(function(res) {
+          console.log(res.data)
 					let ssrc = res.data.ssrc;
           that.isLoging = false
-					that.$refs.devicePlayer.play(res.data,deviceId,channelId,itemData.hasAudio);
+          if (!!ssrc) {
+            that.$refs.devicePlayer.play(res.data,deviceId,channelId,itemData.hasAudio);
+            that.initData();
+          }else {
+            that.$message.error(res.data);
+          }
 				}).catch(function(e) {
 				});
 			},
+      stopDevicePush: function(itemData) {
+			  console.log(itemData)
+        var that = this;
+        this.$axios({
+          method: 'post',
+          url: '/api/play/' + itemData.ssrc + '/stop'
+        }).then(function(res) {
+          console.log(JSON.stringify(res));
+          that.initData();
+        });
+      },
+
 			showDevice: function(){
 				this.$router.push(this.beforeUrl).then(()=>{
 					this.initParam();
diff --git a/web_src/src/components/gb28181/devicePlayer.vue b/web_src/src/components/gb28181/devicePlayer.vue
index 9e497e4c..ce36483a 100644
--- a/web_src/src/components/gb28181/devicePlayer.vue
+++ b/web_src/src/components/gb28181/devicePlayer.vue
@@ -1,6 +1,6 @@
 <template>
 	<div id="devicePlayer">
-		<el-dialog title="视频播放" top="0" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="stop()">
+		<el-dialog title="视频播放" top="0" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="close()">
       <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :hasaudio="hasaudio" fluent autoplay live ></LivePlayer>
 			<div id="shared" style="text-align: right; margin-top: 1rem;">
 				<el-tabs v-model="tabActiveName">
@@ -145,24 +145,11 @@
 				this.showVideoDialog = true;
 				console.log(this.ssrc);
 			},
-			stop: function() {
+			close: function() {
 				console.log('关闭视频');
 				this.$refs.videoPlayer.pause();
 				this.videoUrl = '';
 				this.showVideoDialog = false;
-				this.$axios({
-					method: 'post',
-					url: '/api/play/' + this.ssrc + '/stop'
-				}).then(function(res) {
-					console.log(JSON.stringify(res));
-				});
-
-				this.$axios({
-					method: 'post',
-					url: '/api/playback/' + this.ssrc + '/stop'
-				}).then(function(res) {
-					console.log(JSON.stringify(res));
-				});
 			},
 			copySharedInfo: function(data) {
 				console.log('复制内容:' + data);