Commit 3ec3b88456cf9ac455d93baba40f339bb284dd77
1 parent
fbdad00c
修复点播判断错误导致的15s超长延时
增加默认不关闭推流, 无人观看超时或点击停止按钮关闭流 修复点播其他bug
Showing
9 changed files
with
114 additions
and
29 deletions
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
| ... | ... | @@ -148,6 +148,11 @@ public class DeviceChannel { |
| 148 | 148 | */ |
| 149 | 149 | private boolean hasAudio; |
| 150 | 150 | |
| 151 | + /** | |
| 152 | + * 是否正在播放 | |
| 153 | + */ | |
| 154 | + private boolean play; | |
| 155 | + | |
| 151 | 156 | public String getChannelId() { |
| 152 | 157 | return channelId; |
| 153 | 158 | } |
| ... | ... | @@ -388,4 +393,12 @@ public class DeviceChannel { |
| 388 | 393 | public void setHasAudio(boolean hasAudio) { |
| 389 | 394 | this.hasAudio = hasAudio; |
| 390 | 395 | } |
| 396 | + | |
| 397 | + public boolean isPlay() { | |
| 398 | + return play; | |
| 399 | + } | |
| 400 | + | |
| 401 | + public void setPlay(boolean play) { | |
| 402 | + this.play = play; | |
| 403 | + } | |
| 391 | 404 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
| ... | ... | @@ -38,6 +38,9 @@ public class ZLMRunner implements CommandLineRunner { |
| 38 | 38 | @Value("${media.secret}") |
| 39 | 39 | private String mediaSecret; |
| 40 | 40 | |
| 41 | + @Value("${media.streamNoneReaderDelayMS}") | |
| 42 | + private String streamNoneReaderDelayMS; | |
| 43 | + | |
| 41 | 44 | @Value("${sip.ip}") |
| 42 | 45 | private String sipIP; |
| 43 | 46 | |
| ... | ... | @@ -54,9 +57,10 @@ public class ZLMRunner implements CommandLineRunner { |
| 54 | 57 | MediaServerConfig mediaServerConfig = getMediaServerConfig(); |
| 55 | 58 | if (mediaServerConfig != null) { |
| 56 | 59 | logger.info("zlm接入成功..."); |
| 57 | - storager.updateMediaInfo(mediaServerConfig); | |
| 58 | 60 | logger.info("设置zlm..."); |
| 59 | 61 | saveZLMConfig(); |
| 62 | + mediaServerConfig = getMediaServerConfig(); | |
| 63 | + storager.updateMediaInfo(mediaServerConfig); | |
| 60 | 64 | |
| 61 | 65 | } |
| 62 | 66 | } |
| ... | ... | @@ -79,7 +83,7 @@ public class ZLMRunner implements CommandLineRunner { |
| 79 | 83 | } catch (InterruptedException e) { |
| 80 | 84 | e.printStackTrace(); |
| 81 | 85 | } |
| 82 | - getMediaServerConfig(); | |
| 86 | + mediaServerConfig = getMediaServerConfig(); | |
| 83 | 87 | } |
| 84 | 88 | return mediaServerConfig; |
| 85 | 89 | } |
| ... | ... | @@ -106,6 +110,7 @@ public class ZLMRunner implements CommandLineRunner { |
| 106 | 110 | param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex)); |
| 107 | 111 | param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex)); |
| 108 | 112 | param.put("hook.timeoutSec","20"); |
| 113 | + param.put("general.streamNoneReaderDelayMS",streamNoneReaderDelayMS); | |
| 109 | 114 | |
| 110 | 115 | JSONObject responseJSON = zlmresTfulUtils.setServerConfig(param); |
| 111 | 116 | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
| 1 | 1 | package com.genersoft.iot.vmp.storager; |
| 2 | 2 | |
| 3 | 3 | import java.util.List; |
| 4 | +import java.util.Map; | |
| 4 | 5 | |
| 5 | 6 | import com.alibaba.fastjson.JSONObject; |
| 6 | 7 | import com.genersoft.iot.vmp.common.PageResult; |
| ... | ... | @@ -180,4 +181,6 @@ public interface IVideoManagerStorager { |
| 180 | 181 | StreamInfo queryPlayBySSRC(String ssrc); |
| 181 | 182 | |
| 182 | 183 | StreamInfo queryPlayByDevice(String deviceId, String code); |
| 184 | + | |
| 185 | + Map<String, StreamInfo> queryPlayByDeviceId(String deviceId); | |
| 183 | 186 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java
| 1 | 1 | package com.genersoft.iot.vmp.storager.jdbc; |
| 2 | 2 | |
| 3 | 3 | import java.util.List; |
| 4 | +import java.util.Map; | |
| 4 | 5 | |
| 5 | 6 | import com.genersoft.iot.vmp.common.PageResult; |
| 6 | 7 | import com.genersoft.iot.vmp.common.StreamInfo; |
| ... | ... | @@ -186,4 +187,9 @@ public class VideoManagerJdbcStoragerImpl implements IVideoManagerStorager { |
| 186 | 187 | public StreamInfo queryPlayByDevice(String deviceId, String code) { |
| 187 | 188 | return null; |
| 188 | 189 | } |
| 190 | + | |
| 191 | + @Override | |
| 192 | + public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) { | |
| 193 | + return null; | |
| 194 | + } | |
| 189 | 195 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
| ... | ... | @@ -134,6 +134,8 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager { |
| 134 | 134 | |
| 135 | 135 | @Override |
| 136 | 136 | public PageResult queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, String online, int page, int count) { |
| 137 | + // 获取到所有正在播放的流 | |
| 138 | + Map<String, StreamInfo> stringStreamInfoMap = queryPlayByDeviceId(deviceId); | |
| 137 | 139 | List<DeviceChannel> result = new ArrayList<>(); |
| 138 | 140 | PageResult pageResult = new PageResult<DeviceChannel>(); |
| 139 | 141 | String queryContent = "*"; |
| ... | ... | @@ -154,7 +156,11 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager { |
| 154 | 156 | int maxCount = (page + 1 ) * count; |
| 155 | 157 | if (deviceChannelList != null && deviceChannelList.size() > 0 ) { |
| 156 | 158 | for (int i = page * count; i < (pageResult.getTotal() > maxCount ? maxCount : pageResult.getTotal() ); i++) { |
| 157 | - result.add((DeviceChannel)redis.get((String)deviceChannelList.get(i))); | |
| 159 | + DeviceChannel deviceChannel = (DeviceChannel)redis.get((String)deviceChannelList.get(i)); | |
| 160 | + StreamInfo streamInfo = stringStreamInfoMap.get(deviceId + "_" + deviceChannel.getChannelId()); | |
| 161 | + deviceChannel.setPlay(streamInfo != null); | |
| 162 | + if (streamInfo != null) deviceChannel.setSsrc(streamInfo.getSsrc()); | |
| 163 | + result.add(deviceChannel); | |
| 158 | 164 | } |
| 159 | 165 | pageResult.setData(result); |
| 160 | 166 | } |
| ... | ... | @@ -162,6 +168,8 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager { |
| 162 | 168 | return pageResult; |
| 163 | 169 | } |
| 164 | 170 | |
| 171 | + | |
| 172 | + | |
| 165 | 173 | @Override |
| 166 | 174 | public List<DeviceChannel> queryChannelsByDeviceId(String deviceId) { |
| 167 | 175 | List<DeviceChannel> result = new ArrayList<>(); |
| ... | ... | @@ -231,7 +239,13 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager { |
| 231 | 239 | |
| 232 | 240 | @Override |
| 233 | 241 | public DeviceChannel queryChannel(String deviceId, String channelId) { |
| 234 | - return (DeviceChannel)redis.get(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + "_" + channelId + "_"); | |
| 242 | + DeviceChannel deviceChannel = null; | |
| 243 | + List<Object> deviceChannelList = redis.keys(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + | |
| 244 | + "_" + channelId + "*"); | |
| 245 | + if (deviceChannelList != null && deviceChannelList.size() > 0 ) { | |
| 246 | + deviceChannel = (DeviceChannel)redis.get((String)deviceChannelList.get(0)); | |
| 247 | + } | |
| 248 | + return deviceChannel; | |
| 235 | 249 | } |
| 236 | 250 | |
| 237 | 251 | |
| ... | ... | @@ -345,6 +359,12 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager { |
| 345 | 359 | @Override |
| 346 | 360 | public boolean stopPlay(StreamInfo streamInfo) { |
| 347 | 361 | if (streamInfo == null) return false; |
| 362 | + DeviceChannel deviceChannel = queryChannel(streamInfo.getDeviceID(), streamInfo.getCahnnelId()); | |
| 363 | + if (deviceChannel != null) { | |
| 364 | + deviceChannel.setSsrc(null); | |
| 365 | + deviceChannel.setPlay(false); | |
| 366 | + updateChannel(streamInfo.getDeviceID(), deviceChannel); | |
| 367 | + } | |
| 348 | 368 | return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, |
| 349 | 369 | streamInfo.getSsrc(), |
| 350 | 370 | streamInfo.getDeviceID(), |
| ... | ... | @@ -366,7 +386,7 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager { |
| 366 | 386 | @Override |
| 367 | 387 | public StreamInfo queryPlayBySSRC(String ssrc) { |
| 368 | 388 | List<Object> playLeys = redis.keys(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, ssrc)); |
| 369 | - if (playLeys.size() == 0) return null; | |
| 389 | + if (playLeys == null || playLeys.size() == 0) return null; | |
| 370 | 390 | return (StreamInfo)redis.get(playLeys.get(0).toString()); |
| 371 | 391 | } |
| 372 | 392 | |
| ... | ... | @@ -375,6 +395,7 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager { |
| 375 | 395 | List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, |
| 376 | 396 | deviceId, |
| 377 | 397 | code)); |
| 398 | + if (playLeys == null || playLeys.size() == 0) return null; | |
| 378 | 399 | return (StreamInfo)redis.get(playLeys.get(0).toString()); |
| 379 | 400 | } |
| 380 | 401 | |
| ... | ... | @@ -438,6 +459,19 @@ public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager { |
| 438 | 459 | } |
| 439 | 460 | } |
| 440 | 461 | |
| 462 | + @Override | |
| 463 | + public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) { | |
| 464 | + Map<String, StreamInfo> streamInfos = new HashMap<>(); | |
| 465 | + List<Object> playLeys = redis.keys(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId)); | |
| 466 | + if (playLeys.size() == 0) return streamInfos; | |
| 467 | + for (int i = 0; i < playLeys.size(); i++) { | |
| 468 | + String key = (String) playLeys.get(i); | |
| 469 | + StreamInfo streamInfo = (StreamInfo)redis.get(key); | |
| 470 | + streamInfos.put(streamInfo.getDeviceID() + "_" + streamInfo.getCahnnelId(), streamInfo); | |
| 471 | + } | |
| 472 | + return streamInfos; | |
| 473 | + } | |
| 474 | + | |
| 441 | 475 | |
| 442 | 476 | |
| 443 | 477 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
| ... | ... | @@ -41,18 +41,36 @@ public class PlayController { |
| 41 | 41 | public ResponseEntity<String> play(@PathVariable String deviceId,@PathVariable String channelId){ |
| 42 | 42 | |
| 43 | 43 | Device device = storager.queryVideoDevice(deviceId); |
| 44 | - StreamInfo streamInfo = cmder.playStreamCmd(device, channelId); | |
| 44 | + StreamInfo streamInfo = storager.queryPlayByDevice(deviceId, channelId); | |
| 45 | + | |
| 46 | + if (streamInfo == null) { | |
| 47 | + streamInfo = cmder.playStreamCmd(device, channelId); | |
| 48 | + }else { | |
| 49 | + String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase(); | |
| 50 | + JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); | |
| 51 | + if (rtpInfo.getBoolean("exist")) { | |
| 52 | + return new ResponseEntity<String>(JSON.toJSONString(streamInfo),HttpStatus.OK); | |
| 53 | + }else { | |
| 54 | + storager.stopPlay(streamInfo); | |
| 55 | + streamInfo = cmder.playStreamCmd(device, channelId); | |
| 56 | + } | |
| 57 | + | |
| 58 | + } | |
| 59 | + String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase(); | |
| 45 | 60 | // 等待推流, TODO 默认超时15s |
| 46 | 61 | boolean lockFlag = true; |
| 47 | 62 | long startTime = System.currentTimeMillis(); |
| 48 | - String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase(); | |
| 49 | 63 | |
| 50 | - // 判断推流是否存在 | |
| 51 | 64 | while (lockFlag) { |
| 52 | 65 | try { |
| 66 | + | |
| 53 | 67 | if (System.currentTimeMillis() - startTime > 15 * 1000) { |
| 68 | + storager.stopPlay(streamInfo); | |
| 69 | + return new ResponseEntity<String>("timeout",HttpStatus.OK); | |
| 70 | + }else { | |
| 54 | 71 | JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); |
| 55 | - if (rtpInfo == null){ | |
| 72 | + Boolean exist = rtpInfo.getBoolean("exist"); | |
| 73 | + if (rtpInfo == null || !rtpInfo.getBoolean("exist") || streamInfo.getFlv() != null){ | |
| 56 | 74 | continue; |
| 57 | 75 | }else { |
| 58 | 76 | lockFlag = false; |
| ... | ... | @@ -72,10 +90,9 @@ public class PlayController { |
| 72 | 90 | } |
| 73 | 91 | } |
| 74 | 92 | }; |
| 75 | - | |
| 76 | 93 | } |
| 77 | - | |
| 78 | 94 | Thread.sleep(200); |
| 95 | + streamInfo = storager.queryPlayByDevice(deviceId, channelId); | |
| 79 | 96 | } catch (InterruptedException e) { |
| 80 | 97 | e.printStackTrace(); |
| 81 | 98 | } | ... | ... |
src/main/resources/application.yml
web_src/src/components/channelList.vue
| ... | ... | @@ -56,7 +56,8 @@ |
| 56 | 56 | </el-table-column> |
| 57 | 57 | <el-table-column label="操作" width="240" align="center" fixed="right"> |
| 58 | 58 | <template slot-scope="scope"> |
| 59 | - <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">预览视频</el-button> | |
| 59 | + <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">播放</el-button> | |
| 60 | + <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="scope.row.play" @click="stopDevicePush(scope.row)">停止</el-button> | |
| 60 | 61 | <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.parental == 1" @click="changeSubchannel(scope.row)">查看子目录</el-button> |
| 61 | 62 | <!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> --> |
| 62 | 63 | </template> |
| ... | ... | @@ -198,7 +199,7 @@ |
| 198 | 199 | message: '请求成功', |
| 199 | 200 | type: 'success' |
| 200 | 201 | }); |
| 201 | - });; | |
| 202 | + }); | |
| 202 | 203 | }, |
| 203 | 204 | //通知设备上传媒体流 |
| 204 | 205 | sendDevicePush: function(itemData) { |
| ... | ... | @@ -212,12 +213,30 @@ |
| 212 | 213 | method: 'get', |
| 213 | 214 | url: '/api/play/' + deviceId + '/' + channelId |
| 214 | 215 | }).then(function(res) { |
| 216 | + console.log(res.data) | |
| 215 | 217 | let ssrc = res.data.ssrc; |
| 216 | 218 | that.isLoging = false |
| 217 | - that.$refs.devicePlayer.play(res.data,deviceId,channelId,itemData.hasAudio); | |
| 219 | + if (!!ssrc) { | |
| 220 | + that.$refs.devicePlayer.play(res.data,deviceId,channelId,itemData.hasAudio); | |
| 221 | + that.initData(); | |
| 222 | + }else { | |
| 223 | + that.$message.error(res.data); | |
| 224 | + } | |
| 218 | 225 | }).catch(function(e) { |
| 219 | 226 | }); |
| 220 | 227 | }, |
| 228 | + stopDevicePush: function(itemData) { | |
| 229 | + console.log(itemData) | |
| 230 | + var that = this; | |
| 231 | + this.$axios({ | |
| 232 | + method: 'post', | |
| 233 | + url: '/api/play/' + itemData.ssrc + '/stop' | |
| 234 | + }).then(function(res) { | |
| 235 | + console.log(JSON.stringify(res)); | |
| 236 | + that.initData(); | |
| 237 | + }); | |
| 238 | + }, | |
| 239 | + | |
| 221 | 240 | showDevice: function(){ |
| 222 | 241 | this.$router.push(this.beforeUrl).then(()=>{ |
| 223 | 242 | this.initParam(); | ... | ... |
web_src/src/components/gb28181/devicePlayer.vue
| 1 | 1 | <template> |
| 2 | 2 | <div id="devicePlayer"> |
| 3 | - <el-dialog title="视频播放" top="0" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="stop()"> | |
| 3 | + <el-dialog title="视频播放" top="0" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="close()"> | |
| 4 | 4 | <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :hasaudio="hasaudio" fluent autoplay live ></LivePlayer> |
| 5 | 5 | <div id="shared" style="text-align: right; margin-top: 1rem;"> |
| 6 | 6 | <el-tabs v-model="tabActiveName"> |
| ... | ... | @@ -145,24 +145,11 @@ |
| 145 | 145 | this.showVideoDialog = true; |
| 146 | 146 | console.log(this.ssrc); |
| 147 | 147 | }, |
| 148 | - stop: function() { | |
| 148 | + close: function() { | |
| 149 | 149 | console.log('关闭视频'); |
| 150 | 150 | this.$refs.videoPlayer.pause(); |
| 151 | 151 | this.videoUrl = ''; |
| 152 | 152 | this.showVideoDialog = false; |
| 153 | - this.$axios({ | |
| 154 | - method: 'post', | |
| 155 | - url: '/api/play/' + this.ssrc + '/stop' | |
| 156 | - }).then(function(res) { | |
| 157 | - console.log(JSON.stringify(res)); | |
| 158 | - }); | |
| 159 | - | |
| 160 | - this.$axios({ | |
| 161 | - method: 'post', | |
| 162 | - url: '/api/playback/' + this.ssrc + '/stop' | |
| 163 | - }).then(function(res) { | |
| 164 | - console.log(JSON.stringify(res)); | |
| 165 | - }); | |
| 166 | 153 | }, |
| 167 | 154 | copySharedInfo: function(data) { |
| 168 | 155 | console.log('复制内容:' + data); | ... | ... |