Browse Source

修复点播判断错误导致的15s超长延时

增加默认不关闭推流, 无人观看超时或点击停止按钮关闭流
修复点播其他bug
pull/1/head
648540858 4 years ago
parent
commit
3ec3b88456
  1. 13
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
  2. 9
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
  3. 3
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
  4. 6
      src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java
  5. 40
      src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
  6. 29
      src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
  7. 1
      src/main/resources/application.yml
  8. 25
      web_src/src/components/channelList.vue
  9. 17
      web_src/src/components/gb28181/devicePlayer.vue

13
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java

@ -148,6 +148,11 @@ public class DeviceChannel {
*/ */
private boolean hasAudio; private boolean hasAudio;
/**
* 是否正在播放
*/
private boolean play;
public String getChannelId() { public String getChannelId() {
return channelId; return channelId;
} }
@ -388,4 +393,12 @@ public class DeviceChannel {
public void setHasAudio(boolean hasAudio) { public void setHasAudio(boolean hasAudio) {
this.hasAudio = hasAudio; this.hasAudio = hasAudio;
} }
public boolean isPlay() {
return play;
}
public void setPlay(boolean play) {
this.play = play;
}
} }

9
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java

@ -38,6 +38,9 @@ public class ZLMRunner implements CommandLineRunner {
@Value("${media.secret}") @Value("${media.secret}")
private String mediaSecret; private String mediaSecret;
@Value("${media.streamNoneReaderDelayMS}")
private String streamNoneReaderDelayMS;
@Value("${sip.ip}") @Value("${sip.ip}")
private String sipIP; private String sipIP;
@ -54,9 +57,10 @@ public class ZLMRunner implements CommandLineRunner {
MediaServerConfig mediaServerConfig = getMediaServerConfig(); MediaServerConfig mediaServerConfig = getMediaServerConfig();
if (mediaServerConfig != null) { if (mediaServerConfig != null) {
logger.info("zlm接入成功..."); logger.info("zlm接入成功...");
storager.updateMediaInfo(mediaServerConfig);
logger.info("设置zlm..."); logger.info("设置zlm...");
saveZLMConfig(); saveZLMConfig();
mediaServerConfig = getMediaServerConfig();
storager.updateMediaInfo(mediaServerConfig);
} }
} }
@ -79,7 +83,7 @@ public class ZLMRunner implements CommandLineRunner {
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
getMediaServerConfig(); mediaServerConfig = getMediaServerConfig();
} }
return mediaServerConfig; 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_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.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex));
param.put("hook.timeoutSec","20"); param.put("hook.timeoutSec","20");
param.put("general.streamNoneReaderDelayMS",streamNoneReaderDelayMS);
JSONObject responseJSON = zlmresTfulUtils.setServerConfig(param); JSONObject responseJSON = zlmresTfulUtils.setServerConfig(param);

3
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java

@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.storager; package com.genersoft.iot.vmp.storager;
import java.util.List; import java.util.List;
import java.util.Map;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.PageResult; import com.genersoft.iot.vmp.common.PageResult;
@ -180,4 +181,6 @@ public interface IVideoManagerStorager {
StreamInfo queryPlayBySSRC(String ssrc); StreamInfo queryPlayBySSRC(String ssrc);
StreamInfo queryPlayByDevice(String deviceId, String code); StreamInfo queryPlayByDevice(String deviceId, String code);
Map<String, StreamInfo> queryPlayByDeviceId(String deviceId);
} }

6
src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java

@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.storager.jdbc; package com.genersoft.iot.vmp.storager.jdbc;
import java.util.List; import java.util.List;
import java.util.Map;
import com.genersoft.iot.vmp.common.PageResult; import com.genersoft.iot.vmp.common.PageResult;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
@ -186,4 +187,9 @@ public class VideoManagerJdbcStoragerImpl implements IVideoManagerStorager {
public StreamInfo queryPlayByDevice(String deviceId, String code) { public StreamInfo queryPlayByDevice(String deviceId, String code) {
return null; return null;
} }
@Override
public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) {
return null;
}
} }

40
src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java

@ -134,6 +134,8 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
@Override @Override
public PageResult queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, String online, int page, int count) { 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<>(); List<DeviceChannel> result = new ArrayList<>();
PageResult pageResult = new PageResult<DeviceChannel>(); PageResult pageResult = new PageResult<DeviceChannel>();
String queryContent = "*"; String queryContent = "*";
@ -154,7 +156,11 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
int maxCount = (page + 1 ) * count; int maxCount = (page + 1 ) * count;
if (deviceChannelList != null && deviceChannelList.size() > 0 ) { if (deviceChannelList != null && deviceChannelList.size() > 0 ) {
for (int i = page * count; i < (pageResult.getTotal() > maxCount ? maxCount : pageResult.getTotal() ); i++) { 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); pageResult.setData(result);
} }
@ -162,6 +168,8 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
return pageResult; return pageResult;
} }
@Override @Override
public List<DeviceChannel> queryChannelsByDeviceId(String deviceId) { public List<DeviceChannel> queryChannelsByDeviceId(String deviceId) {
List<DeviceChannel> result = new ArrayList<>(); List<DeviceChannel> result = new ArrayList<>();
@ -231,7 +239,13 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
@Override @Override
public DeviceChannel queryChannel(String deviceId, String channelId) { 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 @Override
public boolean stopPlay(StreamInfo streamInfo) { public boolean stopPlay(StreamInfo streamInfo) {
if (streamInfo == null) return false; 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, return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
streamInfo.getSsrc(), streamInfo.getSsrc(),
streamInfo.getDeviceID(), streamInfo.getDeviceID(),
@ -366,7 +386,7 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager {
@Override @Override
public StreamInfo queryPlayBySSRC(String ssrc) { public StreamInfo queryPlayBySSRC(String ssrc) {
List<Object> playLeys = redis.keys(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, 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()); 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, List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
deviceId, deviceId,
code)); code));
if (playLeys == null || playLeys.size() == 0) return null;
return (StreamInfo)redis.get(playLeys.get(0).toString()); 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;
}
} }

29
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){ public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId){
Device device = storager.queryVideoDevice(deviceId); 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 // 等待推流, TODO 默认超时15s
boolean lockFlag = true; boolean lockFlag = true;
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase();
// 判断推流是否存在
while (lockFlag) { while (lockFlag) {
try { try {
if (System.currentTimeMillis() - startTime > 15 * 1000) { if (System.currentTimeMillis() - startTime > 15 * 1000) {
storager.stopPlay(streamInfo);
return new ResponseEntity<String>("timeout",HttpStatus.OK);
}else {
JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
if (rtpInfo == null){ Boolean exist = rtpInfo.getBoolean("exist");
if (rtpInfo == null || !rtpInfo.getBoolean("exist") || streamInfo.getFlv() != null){
continue; continue;
}else { }else {
lockFlag = false; lockFlag = false;
@ -72,10 +90,9 @@ public class PlayController {
} }
} }
}; };
} }
Thread.sleep(200); Thread.sleep(200);
streamInfo = storager.queryPlayByDevice(deviceId, channelId);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }

1
src/main/resources/application.yml

@ -43,4 +43,5 @@ media: #zlm服务器的ip与http端口, 重点: 这是http端口
ip: 192.168.1.20 ip: 192.168.1.20
port: 9080 port: 9080
secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
streamNoneReaderDelayMS: 1800000 # 无人观看多久关闭流

25
web_src/src/components/channelList.vue

@ -56,7 +56,8 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" width="240" align="center" fixed="right"> <el-table-column label="操作" width="240" align="center" fixed="right">
<template slot-scope="scope"> <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" 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> --> <!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> -->
</template> </template>
@ -198,7 +199,7 @@
message: '请求成功', message: '请求成功',
type: 'success' type: 'success'
}); });
});; });
}, },
// //
sendDevicePush: function(itemData) { sendDevicePush: function(itemData) {
@ -212,12 +213,30 @@
method: 'get', method: 'get',
url: '/api/play/' + deviceId + '/' + channelId url: '/api/play/' + deviceId + '/' + channelId
}).then(function(res) { }).then(function(res) {
console.log(res.data)
let ssrc = res.data.ssrc; let ssrc = res.data.ssrc;
that.isLoging = false 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) { }).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(){ showDevice: function(){
this.$router.push(this.beforeUrl).then(()=>{ this.$router.push(this.beforeUrl).then(()=>{
this.initParam(); this.initParam();

17
web_src/src/components/gb28181/devicePlayer.vue

@ -1,6 +1,6 @@
<template> <template>
<div id="devicePlayer"> <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> <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;"> <div id="shared" style="text-align: right; margin-top: 1rem;">
<el-tabs v-model="tabActiveName"> <el-tabs v-model="tabActiveName">
@ -145,24 +145,11 @@
this.showVideoDialog = true; this.showVideoDialog = true;
console.log(this.ssrc); console.log(this.ssrc);
}, },
stop: function() { close: function() {
console.log('关闭视频'); console.log('关闭视频');
this.$refs.videoPlayer.pause(); this.$refs.videoPlayer.pause();
this.videoUrl = ''; this.videoUrl = '';
this.showVideoDialog = false; 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) { copySharedInfo: function(data) {
console.log('复制内容:' + data); console.log('复制内容:' + data);

Loading…
Cancel
Save