Commit 2f76fa98bb839ede5b47a3c3e1bb6fe499952203
Merge branch 'wvp-28181-2.0'
# Conflicts: # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
Showing
33 changed files
with
332 additions
and
116 deletions
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
| ... | ... | @@ -53,7 +53,7 @@ public class ParentPlatform { |
| 53 | 53 | /** |
| 54 | 54 | * 设备国标编号 |
| 55 | 55 | */ |
| 56 | - @Schema(description = "11111") | |
| 56 | + @Schema(description = "设备国标编号") | |
| 57 | 57 | private String deviceGBId; |
| 58 | 58 | |
| 59 | 59 | /** |
| ... | ... | @@ -113,7 +113,6 @@ public class ParentPlatform { |
| 113 | 113 | |
| 114 | 114 | /** |
| 115 | 115 | * RTCP流保活 |
| 116 | - * TODO 预留, 暂不实现 | |
| 117 | 116 | */ |
| 118 | 117 | @Schema(description = "RTCP流保活") |
| 119 | 118 | private boolean rtcp; | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
| ... | ... | @@ -108,7 +108,7 @@ public interface ISIPCommander { |
| 108 | 108 | * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss |
| 109 | 109 | * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss |
| 110 | 110 | */ |
| 111 | - void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event errorEvent); | |
| 111 | + void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent); | |
| 112 | 112 | |
| 113 | 113 | /** |
| 114 | 114 | * 请求历史媒体下载 | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
| ... | ... | @@ -358,7 +358,7 @@ public class SIPCommander implements ISIPCommander { |
| 358 | 358 | // String streamMode = device.getStreamMode().toUpperCase(); |
| 359 | 359 | |
| 360 | 360 | logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort()); |
| 361 | - HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtmp", mediaServerItem.getId()); | |
| 361 | + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId()); | |
| 362 | 362 | subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{ |
| 363 | 363 | if (event != null) { |
| 364 | 364 | event.response(mediaServerItemInUse, json); |
| ... | ... | @@ -458,7 +458,7 @@ public class SIPCommander implements ISIPCommander { |
| 458 | 458 | @Override |
| 459 | 459 | public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, |
| 460 | 460 | String startTime, String endTime, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent, |
| 461 | - SipSubscribe.Event errorEvent) { | |
| 461 | + SipSubscribe.Event okEvent,SipSubscribe.Event errorEvent) { | |
| 462 | 462 | try { |
| 463 | 463 | |
| 464 | 464 | logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort()); |
| ... | ... | @@ -526,7 +526,7 @@ public class SIPCommander implements ISIPCommander { |
| 526 | 526 | |
| 527 | 527 | CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId() |
| 528 | 528 | : udpSipProvider.getNewCallId(); |
| 529 | - HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtmp", mediaServerItem.getId()); | |
| 529 | + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId()); | |
| 530 | 530 | // 添加订阅 |
| 531 | 531 | subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{ |
| 532 | 532 | if (hookEvent != null) { |
| ... | ... | @@ -537,10 +537,11 @@ public class SIPCommander implements ISIPCommander { |
| 537 | 537 | }); |
| 538 | 538 | Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader, ssrcInfo.getSsrc()); |
| 539 | 539 | |
| 540 | - transmitRequest(device, request, errorEvent, okEvent -> { | |
| 541 | - ResponseEvent responseEvent = (ResponseEvent) okEvent.event; | |
| 540 | + transmitRequest(device, request, errorEvent, event -> { | |
| 541 | + ResponseEvent responseEvent = (ResponseEvent) event.event; | |
| 542 | 542 | streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.playback); |
| 543 | - streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog); | |
| 543 | + streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), event.dialog); | |
| 544 | + okEvent.response(event); | |
| 544 | 545 | }); |
| 545 | 546 | if (inviteStreamCallback != null) { |
| 546 | 547 | inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream())); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
| ... | ... | @@ -121,6 +121,10 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 121 | 121 | param.put("pt", sendRtpItem.getPt()); |
| 122 | 122 | param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); |
| 123 | 123 | param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); |
| 124 | + if (!sendRtpItem.isTcp() && parentPlatform.isRtcp()) { | |
| 125 | + // 开启rtcp保活 | |
| 126 | + param.put("udp_rtcp_timeout", "1"); | |
| 127 | + } | |
| 124 | 128 | JSONObject jsonObject; |
| 125 | 129 | if (sendRtpItem.isTcpActive()) { |
| 126 | 130 | jsonObject = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
| ... | ... | @@ -104,6 +104,8 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In |
| 104 | 104 | MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); |
| 105 | 105 | zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); |
| 106 | 106 | redisCatchStorage.deleteSendRTPServer(platformGbId, sendRtpItem.getChannelId(), callIdHeader.getCallId(), null); |
| 107 | + redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null); | |
| 108 | + zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); | |
| 107 | 109 | int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId); |
| 108 | 110 | if (totalReaderCount <= 0) { |
| 109 | 111 | logger.info("收到bye: {} 无其它观看者,通知设备停止推流", streamId); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
| ... | ... | @@ -111,6 +111,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 111 | 111 | private ZLMRESTfulUtils zlmresTfulUtils; |
| 112 | 112 | |
| 113 | 113 | @Autowired |
| 114 | + private ZlmHttpHookSubscribe zlmHttpHookSubscribe; | |
| 115 | + | |
| 116 | + @Autowired | |
| 114 | 117 | private SIPProcessorObserver sipProcessorObserver; |
| 115 | 118 | |
| 116 | 119 | @Autowired |
| ... | ... | @@ -430,7 +433,14 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 430 | 433 | if (playTransaction != null) { |
| 431 | 434 | Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream()); |
| 432 | 435 | if (!streamReady) { |
| 433 | - playTransaction = null; | |
| 436 | + boolean hasRtpServer = mediaServerService.checkRtpServer(mediaServerItem, "rtp", playTransaction.getStream()); | |
| 437 | + if (hasRtpServer) { | |
| 438 | + logger.info("[上级点播]已经开启rtpServer但是尚未收到流,开启监听流的到来"); | |
| 439 | + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", playTransaction.getStream(), true, "rtsp", mediaServerItem.getId()); | |
| 440 | + zlmHttpHookSubscribe.addSubscribe(hookSubscribe, hookEvent); | |
| 441 | + }else { | |
| 442 | + playTransaction = null; | |
| 443 | + } | |
| 434 | 444 | } |
| 435 | 445 | } |
| 436 | 446 | if (playTransaction == null) { |
| ... | ... | @@ -593,7 +603,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 593 | 603 | responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline"); |
| 594 | 604 | } else if ("push".equals(gbStream.getStreamType())) { |
| 595 | 605 | if (!platform.isStartOfflinePush()) { |
| 596 | - responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel unavailable"); | |
| 606 | + // 平台设置中关闭了拉起离线的推流则直接回复 | |
| 607 | + responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel stream not pushing"); | |
| 597 | 608 | return; |
| 598 | 609 | } |
| 599 | 610 | // 发送redis消息以使设备上线 |
| ... | ... | @@ -629,7 +640,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 629 | 640 | app, stream, channelId, mediaTransmissionTCP); |
| 630 | 641 | |
| 631 | 642 | if (sendRtpItem == null) { |
| 632 | - logger.warn("服务器端口资源不足"); | |
| 643 | + logger.warn("上级点时创建sendRTPItem失败,可能是服务器端口资源不足"); | |
| 633 | 644 | try { |
| 634 | 645 | responseAck(evt, Response.BUSY_HERE); |
| 635 | 646 | } catch (SipException e) { | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java
| ... | ... | @@ -211,7 +211,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme |
| 211 | 211 | }else if (subscribeInfo.getExpires() == 0) { |
| 212 | 212 | subscribeHolder.removeCatalogSubscribe(platformId); |
| 213 | 213 | } |
| 214 | - | |
| 215 | 214 | try { |
| 216 | 215 | ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformId); |
| 217 | 216 | responseXmlAck(evt, resultXml.toString(), parentPlatform); |
| ... | ... | @@ -219,5 +218,4 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme |
| 219 | 218 | e.printStackTrace(); |
| 220 | 219 | } |
| 221 | 220 | } |
| 222 | - | |
| 223 | 221 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
| ... | ... | @@ -203,6 +203,12 @@ public class XmlUtil { |
| 203 | 203 | return null; |
| 204 | 204 | } |
| 205 | 205 | deviceChannel.setChannelId(channelId); |
| 206 | + int channelTypeCode = Integer.parseInt(channelId.substring(10, 13)); | |
| 207 | + if (channelTypeCode == 136 || channelTypeCode == 137 || channelTypeCode == 138) { | |
| 208 | + deviceChannel.setHasAudio(true); | |
| 209 | + }else { | |
| 210 | + deviceChannel.setHasAudio(false); | |
| 211 | + } | |
| 206 | 212 | if (event != null && !event.equals(CatalogEvent.ADD) && !event.equals(CatalogEvent.UPDATE)) { |
| 207 | 213 | // 除了ADD和update情况下需要识别全部内容, |
| 208 | 214 | return deviceChannel; |
| ... | ... | @@ -396,7 +402,6 @@ public class XmlUtil { |
| 396 | 402 | } else { |
| 397 | 403 | deviceChannel.setPTZType(Integer.parseInt(XmlUtil.getText(itemDevice, "PTZType"))); |
| 398 | 404 | } |
| 399 | - deviceChannel.setHasAudio(true); // 默认含有音频,播放时再检查是否有音频及是否AAC | |
| 400 | 405 | return deviceChannel; |
| 401 | 406 | } |
| 402 | 407 | } |
| 403 | 408 | \ No newline at end of file | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java
| ... | ... | @@ -50,7 +50,7 @@ public class AssistRESTfulUtils { |
| 50 | 50 | if (mediaServerItem == null) { |
| 51 | 51 | return null; |
| 52 | 52 | } |
| 53 | - if (mediaServerItem.getRecordAssistPort() > 0) { | |
| 53 | + if (mediaServerItem.getRecordAssistPort() <= 0) { | |
| 54 | 54 | logger.warn("未启用Assist服务"); |
| 55 | 55 | return null; |
| 56 | 56 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
| ... | ... | @@ -19,8 +19,6 @@ import org.slf4j.Logger; |
| 19 | 19 | import org.slf4j.LoggerFactory; |
| 20 | 20 | import org.springframework.beans.factory.annotation.Autowired; |
| 21 | 21 | import org.springframework.beans.factory.annotation.Qualifier; |
| 22 | -import org.springframework.http.HttpStatus; | |
| 23 | -import org.springframework.http.ResponseEntity; | |
| 24 | 22 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
| 25 | 23 | import org.springframework.util.ObjectUtils; |
| 26 | 24 | import org.springframework.web.bind.annotation.PostMapping; |
| ... | ... | @@ -544,6 +542,8 @@ public class ZLMHttpHookListener { |
| 544 | 542 | for (SendRtpItem sendRtpItem : sendRtpItems) { |
| 545 | 543 | ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); |
| 546 | 544 | commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); |
| 545 | + redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), | |
| 546 | + sendRtpItem.getCallId(), sendRtpItem.getStreamId()); | |
| 547 | 547 | } |
| 548 | 548 | } |
| 549 | 549 | } |
| ... | ... | @@ -573,13 +573,19 @@ public class ZLMHttpHookListener { |
| 573 | 573 | return ret; |
| 574 | 574 | }else { |
| 575 | 575 | StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, streamId); |
| 576 | - if (streamProxyItem != null && streamProxyItem.isEnable_remove_none_reader()) { | |
| 577 | - ret.put("close", true); | |
| 578 | - streamProxyService.del(app, streamId); | |
| 579 | - String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url(); | |
| 580 | - logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", app, streamId, url); | |
| 581 | - }else { | |
| 582 | - ret.put("close", false); | |
| 576 | + if (streamProxyItem != null ) { | |
| 577 | + if (streamProxyItem.isEnable_remove_none_reader()) { | |
| 578 | + // 无人观看自动移除 | |
| 579 | + ret.put("close", true); | |
| 580 | + streamProxyService.del(app, streamId); | |
| 581 | + String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url(); | |
| 582 | + logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", app, streamId, url); | |
| 583 | + }else if (streamProxyItem.isEnable_disable_none_reader()) { | |
| 584 | + // 无人观看停用 | |
| 585 | + ret.put("close", true); | |
| 586 | + }else { | |
| 587 | + ret.put("close", false); | |
| 588 | + } | |
| 583 | 589 | } |
| 584 | 590 | return ret; |
| 585 | 591 | } |
| ... | ... | @@ -626,7 +632,7 @@ public class ZLMHttpHookListener { |
| 626 | 632 | @ResponseBody |
| 627 | 633 | @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8") |
| 628 | 634 | public JSONObject onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){ |
| 629 | - | |
| 635 | + | |
| 630 | 636 | if (logger.isDebugEnabled()) { |
| 631 | 637 | logger.debug("[ ZLM HOOK ]on_server_started API调用,参数:" + jsonObject.toString()); |
| 632 | 638 | } |
| ... | ... | @@ -649,6 +655,39 @@ public class ZLMHttpHookListener { |
| 649 | 655 | return ret; |
| 650 | 656 | } |
| 651 | 657 | |
| 658 | + /** | |
| 659 | + * 发送rtp(startSendRtp)被动关闭时回调 | |
| 660 | + */ | |
| 661 | + @ResponseBody | |
| 662 | + @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8") | |
| 663 | + public JSONObject onSendRtpStopped(HttpServletRequest request, @RequestBody JSONObject jsonObject){ | |
| 664 | + | |
| 665 | + logger.info("[ ZLM HOOK ]on_send_rtp_stopped API调用,参数:" + jsonObject); | |
| 666 | + | |
| 667 | + JSONObject ret = new JSONObject(); | |
| 668 | + ret.put("code", 0); | |
| 669 | + ret.put("msg", "success"); | |
| 670 | + | |
| 671 | + // 查找对应的上级推流,发送停止 | |
| 672 | + String app = jsonObject.getString("app"); | |
| 673 | + if (!"rtp".equals(app)) { | |
| 674 | + return ret; | |
| 675 | + } | |
| 676 | + String stream = jsonObject.getString("stream"); | |
| 677 | + List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(stream); | |
| 678 | + if (sendRtpItems.size() > 0) { | |
| 679 | + for (SendRtpItem sendRtpItem : sendRtpItems) { | |
| 680 | + ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); | |
| 681 | + commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); | |
| 682 | + redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), | |
| 683 | + sendRtpItem.getCallId(), sendRtpItem.getStreamId()); | |
| 684 | + } | |
| 685 | + } | |
| 686 | + | |
| 687 | + | |
| 688 | + return ret; | |
| 689 | + } | |
| 690 | + | |
| 652 | 691 | private Map<String, String> urlParamToMap(String params) { |
| 653 | 692 | HashMap<String, String> map = new HashMap<>(); |
| 654 | 693 | if (ObjectUtils.isEmpty(params)) { | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
| ... | ... | @@ -96,6 +96,10 @@ public class ZLMRTPServerFactory { |
| 96 | 96 | if(rtpInfo.getInteger("code") == 0){ |
| 97 | 97 | if (rtpInfo.getBoolean("exist")) { |
| 98 | 98 | result = rtpInfo.getInteger("local_port"); |
| 99 | + if (result == 0) { | |
| 100 | + // 此时说明rtpServer已经创建但是流还没有推上来 | |
| 101 | + | |
| 102 | + } | |
| 99 | 103 | return result; |
| 100 | 104 | } |
| 101 | 105 | }else if(rtpInfo.getInteger("code") == -2){ | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java
| ... | ... | @@ -37,6 +37,9 @@ public class StreamProxyItem extends GbStream { |
| 37 | 37 | private boolean enable_mp4; |
| 38 | 38 | @Schema(description = "是否 无人观看时删除") |
| 39 | 39 | private boolean enable_remove_none_reader; |
| 40 | + | |
| 41 | + @Schema(description = "是否 无人观看时不启用") | |
| 42 | + private boolean enable_disable_none_reader; | |
| 40 | 43 | @Schema(description = "上级平台国标ID") |
| 41 | 44 | private String platformGbId; |
| 42 | 45 | @Schema(description = "创建时间") |
| ... | ... | @@ -177,4 +180,11 @@ public class StreamProxyItem extends GbStream { |
| 177 | 180 | this.enable_remove_none_reader = enable_remove_none_reader; |
| 178 | 181 | } |
| 179 | 182 | |
| 183 | + public boolean isEnable_disable_none_reader() { | |
| 184 | + return enable_disable_none_reader; | |
| 185 | + } | |
| 186 | + | |
| 187 | + public void setEnable_disable_none_reader(boolean enable_disable_none_reader) { | |
| 188 | + this.enable_disable_none_reader = enable_disable_none_reader; | |
| 189 | + } | |
| 180 | 190 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
| ... | ... | @@ -82,4 +82,6 @@ public interface IMediaServerService { |
| 82 | 82 | MediaServerItem getDefaultMediaServer(); |
| 83 | 83 | |
| 84 | 84 | void updateMediaServerKeepalive(String mediaServerId, JSONObject data); |
| 85 | + | |
| 86 | + boolean checkRtpServer(MediaServerItem mediaServerItem, String rtp, String stream); | |
| 85 | 87 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
| ... | ... | @@ -152,9 +152,11 @@ public class MediaServerServiceImpl implements IMediaServerService { |
| 152 | 152 | if (streamId == null) { |
| 153 | 153 | streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase(); |
| 154 | 154 | } |
| 155 | - int rtpServerPort = mediaServerItem.getRtpProxyPort(); | |
| 155 | + int rtpServerPort; | |
| 156 | 156 | if (mediaServerItem.isRtpEnable()) { |
| 157 | 157 | rtpServerPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(ssrc):0, port); |
| 158 | + } else { | |
| 159 | + rtpServerPort = mediaServerItem.getRtpProxyPort(); | |
| 158 | 160 | } |
| 159 | 161 | RedisUtil.set(key, mediaServerItem); |
| 160 | 162 | return new SSRCInfo(rtpServerPort, ssrc, streamId); |
| ... | ... | @@ -537,6 +539,7 @@ public class MediaServerServiceImpl implements IMediaServerService { |
| 537 | 539 | param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex)); |
| 538 | 540 | param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex)); |
| 539 | 541 | param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrex)); |
| 542 | + param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrex)); | |
| 540 | 543 | if (mediaServerItem.getRecordAssistPort() > 0) { |
| 541 | 544 | param.put("hook.on_record_mp4",String.format("http://127.0.0.1:%s/api/record/on_record_mp4", mediaServerItem.getRecordAssistPort())); |
| 542 | 545 | }else { |
| ... | ... | @@ -686,4 +689,13 @@ public class MediaServerServiceImpl implements IMediaServerService { |
| 686 | 689 | } |
| 687 | 690 | } |
| 688 | 691 | } |
| 692 | + | |
| 693 | + @Override | |
| 694 | + public boolean checkRtpServer(MediaServerItem mediaServerItem, String app, String stream) { | |
| 695 | + JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, stream); | |
| 696 | + if(rtpInfo.getInteger("code") == 0){ | |
| 697 | + return rtpInfo.getBoolean("exist"); | |
| 698 | + } | |
| 699 | + return false; | |
| 700 | + } | |
| 689 | 701 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
| ... | ... | @@ -193,17 +193,30 @@ public class PlayServiceImpl implements IPlayService { |
| 193 | 193 | JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId); |
| 194 | 194 | if(rtpInfo.getInteger("code") == 0){ |
| 195 | 195 | if (rtpInfo.getBoolean("exist")) { |
| 196 | - | |
| 197 | - WVPResult wvpResult = new WVPResult(); | |
| 198 | - wvpResult.setCode(ErrorCode.SUCCESS.getCode()); | |
| 199 | - wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); | |
| 200 | - wvpResult.setData(streamInfo); | |
| 201 | - msg.setData(wvpResult); | |
| 202 | - | |
| 203 | - resultHolder.invokeAllResult(msg); | |
| 204 | - if (hookEvent != null) { | |
| 205 | - hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo))); | |
| 196 | + int localPort = rtpInfo.getInteger("local_port"); | |
| 197 | + if (localPort == 0) { | |
| 198 | + logger.warn("[点播],点播时发现rtpServerC存在,但是尚未开始推流"); | |
| 199 | + // 此时说明rtpServer已经创建但是流还没有推上来 | |
| 200 | + WVPResult wvpResult = new WVPResult(); | |
| 201 | + wvpResult.setCode(ErrorCode.ERROR100.getCode()); | |
| 202 | + wvpResult.setMsg("点播已经在进行中,请稍候重试"); | |
| 203 | + msg.setData(wvpResult); | |
| 204 | + | |
| 205 | + resultHolder.invokeAllResult(msg); | |
| 206 | + return playResult; | |
| 207 | + }else { | |
| 208 | + WVPResult wvpResult = new WVPResult(); | |
| 209 | + wvpResult.setCode(ErrorCode.SUCCESS.getCode()); | |
| 210 | + wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); | |
| 211 | + wvpResult.setData(streamInfo); | |
| 212 | + msg.setData(wvpResult); | |
| 213 | + | |
| 214 | + resultHolder.invokeAllResult(msg); | |
| 215 | + if (hookEvent != null) { | |
| 216 | + hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo))); | |
| 217 | + } | |
| 206 | 218 | } |
| 219 | + | |
| 207 | 220 | }else { |
| 208 | 221 | redisCatchStorage.stopPlay(streamInfo); |
| 209 | 222 | storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); |
| ... | ... | @@ -318,7 +331,7 @@ public class PlayServiceImpl implements IPlayService { |
| 318 | 331 | } |
| 319 | 332 | logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse ); |
| 320 | 333 | if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { |
| 321 | - logger.info("[SIP 消息] SSRC修正 {}->{}", ssrc, ssrcInResponse); | |
| 334 | + logger.info("[点播消息] SSRC修正 {}->{}", ssrc, ssrcInResponse); | |
| 322 | 335 | |
| 323 | 336 | if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { |
| 324 | 337 | // ssrc 不可用 |
| ... | ... | @@ -468,37 +481,92 @@ public class PlayServiceImpl implements IPlayService { |
| 468 | 481 | resultHolder.exist(DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId, uuid); |
| 469 | 482 | }, userSetting.getPlayTimeout()); |
| 470 | 483 | |
| 484 | + SipSubscribe.Event errorEvent = event -> { | |
| 485 | + dynamicTask.stop(playBackTimeOutTaskKey); | |
| 486 | + requestMessage.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg))); | |
| 487 | + playBackResult.setCode(ErrorCode.ERROR100.getCode()); | |
| 488 | + playBackResult.setMsg(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)); | |
| 489 | + playBackResult.setData(requestMessage); | |
| 490 | + playBackResult.setEvent(event); | |
| 491 | + playBackCallback.call(playBackResult); | |
| 492 | + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); | |
| 493 | + }; | |
| 494 | + | |
| 495 | + InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> { | |
| 496 | + logger.info("收到回放订阅消息: " + inviteStreamInfo.getResponse().toJSONString()); | |
| 497 | + dynamicTask.stop(playBackTimeOutTaskKey); | |
| 498 | + StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId); | |
| 499 | + if (streamInfo == null) { | |
| 500 | + logger.warn("设备回放API调用失败!"); | |
| 501 | + playBackResult.setCode(ErrorCode.ERROR100.getCode()); | |
| 502 | + playBackResult.setMsg("设备回放API调用失败!"); | |
| 503 | + playBackCallback.call(playBackResult); | |
| 504 | + return; | |
| 505 | + } | |
| 506 | + redisCatchStorage.startPlayback(streamInfo, inviteStreamInfo.getCallId()); | |
| 507 | + WVPResult<StreamInfo> success = WVPResult.success(streamInfo); | |
| 508 | + requestMessage.setData(success); | |
| 509 | + playBackResult.setCode(ErrorCode.SUCCESS.getCode()); | |
| 510 | + playBackResult.setMsg(ErrorCode.SUCCESS.getMsg()); | |
| 511 | + playBackResult.setData(requestMessage); | |
| 512 | + playBackResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem()); | |
| 513 | + playBackResult.setResponse(inviteStreamInfo.getResponse()); | |
| 514 | + playBackCallback.call(playBackResult); | |
| 515 | + }; | |
| 516 | + | |
| 471 | 517 | cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack, |
| 472 | - (InviteStreamInfo inviteStreamInfo) -> { | |
| 473 | - logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString()); | |
| 474 | - dynamicTask.stop(playBackTimeOutTaskKey); | |
| 475 | - StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId); | |
| 476 | - if (streamInfo == null) { | |
| 477 | - logger.warn("设备回放API调用失败!"); | |
| 478 | - playBackResult.setCode(ErrorCode.ERROR100.getCode()); | |
| 479 | - playBackResult.setMsg("设备回放API调用失败!"); | |
| 480 | - playBackCallback.call(playBackResult); | |
| 481 | - return; | |
| 518 | + hookEvent, eventResult -> { | |
| 519 | + if (eventResult.type == SipSubscribe.EventResultType.response) { | |
| 520 | + ResponseEvent responseEvent = (ResponseEvent)eventResult.event; | |
| 521 | + String contentString = new String(responseEvent.getResponse().getRawContent()); | |
| 522 | + // 获取ssrc | |
| 523 | + int ssrcIndex = contentString.indexOf("y="); | |
| 524 | + // 检查是否有y字段 | |
| 525 | + if (ssrcIndex >= 0) { | |
| 526 | + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容 | |
| 527 | + String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); | |
| 528 | + // 查询到ssrc不一致且开启了ssrc校验则需要针对处理 | |
| 529 | + if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { | |
| 530 | + return; | |
| 531 | + } | |
| 532 | + logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse ); | |
| 533 | + if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { | |
| 534 | + logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); | |
| 535 | + | |
| 536 | + if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { | |
| 537 | + // ssrc 不可用 | |
| 538 | + // 释放ssrc | |
| 539 | + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); | |
| 540 | + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); | |
| 541 | + eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用"; | |
| 542 | + eventResult.statusCode = 400; | |
| 543 | + errorEvent.response(eventResult); | |
| 544 | + return; | |
| 545 | + } | |
| 546 | + | |
| 547 | + // 单端口模式streamId也有变化,需要重新设置监听 | |
| 548 | + if (!mediaServerItem.isRtpEnable()) { | |
| 549 | + // 添加订阅 | |
| 550 | + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId()); | |
| 551 | + subscribe.removeSubscribe(hookSubscribe); | |
| 552 | + hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase()); | |
| 553 | + subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response)->{ | |
| 554 | + logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString()); | |
| 555 | + dynamicTask.stop(playBackTimeOutTaskKey); | |
| 556 | + // hook响应 | |
| 557 | + onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId, uuid); | |
| 558 | + hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream())); | |
| 559 | + }); | |
| 560 | + } | |
| 561 | + // 关闭rtp server | |
| 562 | + mediaServerService.closeRTPServer(device.getDeviceId(), channelId, ssrcInfo.getStream()); | |
| 563 | + // 重新开启ssrc server | |
| 564 | + mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort()); | |
| 565 | + } | |
| 566 | + } | |
| 482 | 567 | } |
| 483 | - redisCatchStorage.startPlayback(streamInfo, inviteStreamInfo.getCallId()); | |
| 484 | - WVPResult<StreamInfo> success = WVPResult.success(streamInfo); | |
| 485 | - requestMessage.setData(success); | |
| 486 | - playBackResult.setCode(ErrorCode.SUCCESS.getCode()); | |
| 487 | - playBackResult.setMsg(ErrorCode.SUCCESS.getMsg()); | |
| 488 | - playBackResult.setData(requestMessage); | |
| 489 | - playBackResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem()); | |
| 490 | - playBackResult.setResponse(inviteStreamInfo.getResponse()); | |
| 491 | - playBackCallback.call(playBackResult); | |
| 492 | - }, event -> { | |
| 493 | - dynamicTask.stop(playBackTimeOutTaskKey); | |
| 494 | - requestMessage.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg))); | |
| 495 | - playBackResult.setCode(ErrorCode.ERROR100.getCode()); | |
| 496 | - playBackResult.setMsg(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)); | |
| 497 | - playBackResult.setData(requestMessage); | |
| 498 | - playBackResult.setEvent(event); | |
| 499 | - playBackCallback.call(playBackResult); | |
| 500 | - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); | |
| 501 | - }); | |
| 568 | + | |
| 569 | + }, errorEvent); | |
| 502 | 570 | return result; |
| 503 | 571 | } |
| 504 | 572 | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/RedisPushStreamListMsgListener.java
| ... | ... | @@ -53,7 +53,6 @@ public class RedisPushStreamListMsgListener implements MessageListener { |
| 53 | 53 | boolean contains = allAppAndStream.contains(app + stream); |
| 54 | 54 | //不存在就添加 |
| 55 | 55 | if (!contains) { |
| 56 | - streamPushItem.setStatus(false); | |
| 57 | 56 | streamPushItem.setStreamType("push"); |
| 58 | 57 | streamPushItem.setCreateTime(DateUtil.getNow()); |
| 59 | 58 | streamPushItem.setMediaServerId(mediaServerService.getDefaultMediaServer().getId()); | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushUploadFileHandler.java
| ... | ... | @@ -116,7 +116,7 @@ public class StreamPushUploadFileHandler extends AnalysisEventListener<StreamPus |
| 116 | 116 | streamPushItem.setApp(streamPushExcelDto.getApp()); |
| 117 | 117 | streamPushItem.setStream(streamPushExcelDto.getStream()); |
| 118 | 118 | streamPushItem.setGbId(streamPushExcelDto.getGbId()); |
| 119 | - streamPushItem.setStatus(false); | |
| 119 | + streamPushItem.setStatus(streamPushExcelDto.getStatus()); | |
| 120 | 120 | streamPushItem.setStreamType("push"); |
| 121 | 121 | streamPushItem.setCreateTime(DateUtil.getNow()); |
| 122 | 122 | streamPushItem.setMediaServerId(defaultMediaServerId); | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
| ... | ... | @@ -143,15 +143,12 @@ public interface DeviceChannelMapper { |
| 143 | 143 | @Update(value = {"UPDATE device_channel SET status=0 WHERE deviceId=#{deviceId}"}) |
| 144 | 144 | void offlineByDeviceId(String deviceId); |
| 145 | 145 | |
| 146 | - @Update(value = {"UPDATE device_channel SET status=1 WHERE deviceId=#{deviceId} AND channelId=#{channelId}"}) | |
| 147 | - void online(String deviceId, String channelId); | |
| 148 | - | |
| 149 | 146 | @Insert("<script> " + |
| 150 | 147 | "insert into device_channel " + |
| 151 | 148 | "(channelId, deviceId, name, manufacture, model, owner, civilCode, block, subCount, " + |
| 152 | 149 | " address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " + |
| 153 | 150 | " ipAddress, port, password, PTZType, status, streamId, longitude, latitude, longitudeGcj02, latitudeGcj02, " + |
| 154 | - " longitudeWgs84, latitudeWgs84, createTime, updateTime, businessGroupId, gpsTime) " + | |
| 151 | + " longitudeWgs84, latitudeWgs84, hasAudio, createTime, updateTime, businessGroupId, gpsTime) " + | |
| 155 | 152 | "values " + |
| 156 | 153 | "<foreach collection='addChannels' index='index' item='item' separator=','> " + |
| 157 | 154 | "('${item.channelId}', '${item.deviceId}', '${item.name}', '${item.manufacture}', '${item.model}', " + |
| ... | ... | @@ -160,7 +157,7 @@ public interface DeviceChannelMapper { |
| 160 | 157 | "'${item.certNum}', ${item.certifiable}, ${item.errCode}, '${item.secrecy}', " + |
| 161 | 158 | "'${item.ipAddress}', ${item.port}, '${item.password}', ${item.PTZType}, ${item.status}, " + |
| 162 | 159 | "'${item.streamId}', ${item.longitude}, ${item.latitude},${item.longitudeGcj02}, " + |
| 163 | - "${item.latitudeGcj02},${item.longitudeWgs84}, ${item.latitudeWgs84},'${item.createTime}', '${item.updateTime}', " + | |
| 160 | + "${item.latitudeGcj02},${item.longitudeWgs84}, ${item.latitudeWgs84}, ${item.hasAudio},'${item.createTime}', '${item.updateTime}', " + | |
| 164 | 161 | "'${item.businessGroupId}', '${item.gpsTime}') " + |
| 165 | 162 | "</foreach> " + |
| 166 | 163 | "ON DUPLICATE KEY UPDATE " + |
| ... | ... | @@ -193,11 +190,15 @@ public interface DeviceChannelMapper { |
| 193 | 190 | "latitudeGcj02=VALUES(latitudeGcj02), " + |
| 194 | 191 | "longitudeWgs84=VALUES(longitudeWgs84), " + |
| 195 | 192 | "latitudeWgs84=VALUES(latitudeWgs84), " + |
| 193 | + "hasAudio=VALUES(hasAudio), " + | |
| 196 | 194 | "businessGroupId=VALUES(businessGroupId), " + |
| 197 | 195 | "gpsTime=VALUES(gpsTime)" + |
| 198 | 196 | "</script>") |
| 199 | 197 | int batchAdd(List<DeviceChannel> addChannels); |
| 200 | 198 | |
| 199 | + @Update(value = {"UPDATE device_channel SET status=1 WHERE deviceId=#{deviceId} AND channelId=#{channelId}"}) | |
| 200 | + void online(String deviceId, String channelId); | |
| 201 | + | |
| 201 | 202 | @Update({"<script>" + |
| 202 | 203 | "<foreach collection='updateChannels' item='item' separator=';'>" + |
| 203 | 204 | " UPDATE" + |
| ... | ... | @@ -341,4 +342,7 @@ public interface DeviceChannelMapper { |
| 341 | 342 | " left join platform_catalog pc on pgc.catalogId = pc.id and pgc.platformId = pc.platformId" + |
| 342 | 343 | " where pgc.platformId=#{serverGBId}") |
| 343 | 344 | List<DeviceChannel> queryChannelWithCatalog(String serverGBId); |
| 345 | + | |
| 346 | + @Select("select * from device_channel where deviceId = #{deviceId}") | |
| 347 | + List<DeviceChannel> queryAllChannels(String deviceId); | |
| 344 | 348 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformGbStreamMapper.java
| ... | ... | @@ -23,10 +23,10 @@ public interface PlatformGbStreamMapper { |
| 23 | 23 | |
| 24 | 24 | @Insert("<script> " + |
| 25 | 25 | "INSERT into platform_gb_stream " + |
| 26 | - "(gbStreamId, platformId, catalogId) " + | |
| 26 | + "(gbStreamId, platformId, catalogId,status) " + | |
| 27 | 27 | "values " + |
| 28 | 28 | "<foreach collection='streamPushItems' index='index' item='item' separator=','> " + |
| 29 | - "(${item.gbStreamId}, '${item.platformId}', '${item.catalogId}')" + | |
| 29 | + "(${item.gbStreamId}, '${item.platformId}', '${item.catalogId}'), '${item.status}')" + | |
| 30 | 30 | "</foreach> " + |
| 31 | 31 | "</script>") |
| 32 | 32 | int batchAdd(List<StreamPushItem> streamPushItems); | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java
| ... | ... | @@ -77,7 +77,7 @@ public interface StreamPushMapper { |
| 77 | 77 | "1=1 " + |
| 78 | 78 | " <if test='query != null'> AND (st.app LIKE '%${query}%' OR st.stream LIKE '%${query}%' OR gs.gbId LIKE '%${query}%' OR gs.name LIKE '%${query}%')</if> " + |
| 79 | 79 | " <if test='pushing == true' > AND (gs.gbId is null OR st.pushIng=1)</if>" + |
| 80 | - " <if test='pushing == false' > AND st.pushIng=0</if>" + | |
| 80 | + " <if test='pushing == false' > AND (gs.pushIng is null OR st.pushIng=0) </if>" + | |
| 81 | 81 | " <if test='mediaServerId != null' > AND st.mediaServerId=#{mediaServerId} </if>" + |
| 82 | 82 | "order by st.createTime desc" + |
| 83 | 83 | " </script>"}) | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
| ... | ... | @@ -388,6 +388,24 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { |
| 388 | 388 | } |
| 389 | 389 | |
| 390 | 390 | @Override |
| 391 | + public List<SendRtpItem> querySendRTPServerByStream(String stream) { | |
| 392 | + if (stream == null) { | |
| 393 | + return null; | |
| 394 | + } | |
| 395 | + String platformGbId = "*"; | |
| 396 | + String callId = "*"; | |
| 397 | + String channelId = "*"; | |
| 398 | + String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + userSetting.getServerId() + "_" + platformGbId | |
| 399 | + + "_" + channelId + "_" + stream + "_" + callId; | |
| 400 | + List<Object> scan = RedisUtil.scan(key); | |
| 401 | + List<SendRtpItem> result = new ArrayList<>(); | |
| 402 | + for (Object o : scan) { | |
| 403 | + result.add((SendRtpItem) RedisUtil.get((String) o)); | |
| 404 | + } | |
| 405 | + return result; | |
| 406 | + } | |
| 407 | + | |
| 408 | + @Override | |
| 391 | 409 | public List<SendRtpItem> querySendRTPServer(String platformGbId) { |
| 392 | 410 | if (platformGbId == null) { |
| 393 | 411 | platformGbId = "*"; | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
| ... | ... | @@ -111,11 +111,11 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { |
| 111 | 111 | if (CollectionUtils.isEmpty(deviceChannelList)) { |
| 112 | 112 | return false; |
| 113 | 113 | } |
| 114 | - List<DeviceChannel> allChannelInPlay = deviceChannelMapper.getAllChannelInPlay(); | |
| 115 | - Map<String,DeviceChannel> allChannelMapInPlay = new ConcurrentHashMap<>(); | |
| 116 | - if (allChannelInPlay.size() > 0) { | |
| 117 | - for (DeviceChannel deviceChannel : allChannelInPlay) { | |
| 118 | - allChannelMapInPlay.put(deviceChannel.getChannelId(), deviceChannel); | |
| 114 | + List<DeviceChannel> allChannels = deviceChannelMapper.queryAllChannels(deviceId); | |
| 115 | + Map<String,DeviceChannel> allChannelMap = new ConcurrentHashMap<>(); | |
| 116 | + if (allChannels.size() > 0) { | |
| 117 | + for (DeviceChannel deviceChannel : allChannels) { | |
| 118 | + allChannelMap.put(deviceChannel.getChannelId(), deviceChannel); | |
| 119 | 119 | } |
| 120 | 120 | } |
| 121 | 121 | TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition); |
| ... | ... | @@ -123,15 +123,17 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { |
| 123 | 123 | List<DeviceChannel> channels = new ArrayList<>(); |
| 124 | 124 | StringBuilder stringBuilder = new StringBuilder(); |
| 125 | 125 | Map<String, Integer> subContMap = new HashMap<>(); |
| 126 | - if (deviceChannelList.size() > 1) { | |
| 126 | + if (deviceChannelList.size() > 0) { | |
| 127 | 127 | // 数据去重 |
| 128 | 128 | Set<String> gbIdSet = new HashSet<>(); |
| 129 | 129 | for (DeviceChannel deviceChannel : deviceChannelList) { |
| 130 | 130 | if (!gbIdSet.contains(deviceChannel.getChannelId())) { |
| 131 | 131 | gbIdSet.add(deviceChannel.getChannelId()); |
| 132 | - if (allChannelMapInPlay.containsKey(deviceChannel.getChannelId())) { | |
| 133 | - deviceChannel.setStreamId(allChannelMapInPlay.get(deviceChannel.getChannelId()).getStreamId()); | |
| 132 | + if (allChannelMap.containsKey(deviceChannel.getChannelId())) { | |
| 133 | + deviceChannel.setStreamId(allChannelMap.get(deviceChannel.getChannelId()).getStreamId()); | |
| 134 | + deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).isHasAudio()); | |
| 134 | 135 | } |
| 136 | + | |
| 135 | 137 | channels.add(deviceChannel); |
| 136 | 138 | if (!ObjectUtils.isEmpty(deviceChannel.getParentId())) { |
| 137 | 139 | if (subContMap.get(deviceChannel.getParentId()) == null) { |
| ... | ... | @@ -153,8 +155,6 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { |
| 153 | 155 | } |
| 154 | 156 | } |
| 155 | 157 | |
| 156 | - }else { | |
| 157 | - channels = deviceChannelList; | |
| 158 | 158 | } |
| 159 | 159 | if (stringBuilder.length() > 0) { |
| 160 | 160 | logger.info("[目录查询]收到的数据存在重复: {}" , stringBuilder); | ... | ... |
src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamPushExcelDto.java
| ... | ... | @@ -22,6 +22,9 @@ public class StreamPushExcelDto { |
| 22 | 22 | @ExcelProperty("目录ID") |
| 23 | 23 | private String catalogId; |
| 24 | 24 | |
| 25 | + @ExcelProperty("在线状态") | |
| 26 | + private boolean status; | |
| 27 | + | |
| 25 | 28 | public String getName() { |
| 26 | 29 | return name; |
| 27 | 30 | } |
| ... | ... | @@ -70,4 +73,16 @@ public class StreamPushExcelDto { |
| 70 | 73 | public void setCatalogId(String catalogId) { |
| 71 | 74 | this.catalogId = catalogId; |
| 72 | 75 | } |
| 76 | + | |
| 77 | + public boolean isStatus() { | |
| 78 | + return status; | |
| 79 | + } | |
| 80 | + | |
| 81 | + public boolean getStatus() { | |
| 82 | + return status; | |
| 83 | + } | |
| 84 | + | |
| 85 | + public void setStatus(boolean status) { | |
| 86 | + this.status = status; | |
| 87 | + } | |
| 73 | 88 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
| ... | ... | @@ -43,6 +43,7 @@ public class UserController { |
| 43 | 43 | private IRoleService roleService; |
| 44 | 44 | |
| 45 | 45 | @GetMapping("/login") |
| 46 | + @PostMapping("/login") | |
| 46 | 47 | @Operation(summary = "登录") |
| 47 | 48 | @Parameter(name = "username", description = "用户名", required = true) |
| 48 | 49 | @Parameter(name = "password", description = "密码(32位md5加密)", required = true) | ... | ... |
src/main/resources/logback-spring-local.xml
| ... | ... | @@ -98,6 +98,11 @@ |
| 98 | 98 | <appender-ref ref="STDOUT" /> |
| 99 | 99 | </root> |
| 100 | 100 | |
| 101 | + <logger name="wvp" level="debug" additivity="true"> | |
| 102 | + <appender-ref ref="RollingFileError"/> | |
| 103 | + <appender-ref ref="RollingFile"/> | |
| 104 | + </logger> | |
| 105 | + | |
| 101 | 106 | <logger name="GB28181_SIP" level="debug" additivity="true"> |
| 102 | 107 | <appender-ref ref="RollingFileError"/> |
| 103 | 108 | <appender-ref ref="sipRollingFile"/> | ... | ... |
web_src/src/components/ParentPlatformList.vue
| ... | ... | @@ -143,7 +143,8 @@ export default { |
| 143 | 143 | }); |
| 144 | 144 | }, |
| 145 | 145 | chooseChannel: function(platform) { |
| 146 | - this.$refs.chooseChannelDialog.openDialog(platform.serverGBId, platform.name, platform.catalogId, platform.treeType, this.initData) | |
| 146 | + console.log("platform.name: " + platform.name) | |
| 147 | + this.$refs.chooseChannelDialog.openDialog(platform.serverGBId,platform.deviceGBId, platform.name, platform.catalogId, platform.treeType, this.initData) | |
| 147 | 148 | }, |
| 148 | 149 | initData: function() { |
| 149 | 150 | this.getPlatformList(); | ... | ... |
web_src/src/components/dialog/chooseChannel.vue
| ... | ... | @@ -8,7 +8,7 @@ |
| 8 | 8 | <el-tab-pane label="目录结构" name="catalog"> |
| 9 | 9 | <el-container> |
| 10 | 10 | <el-main v-bind:style="{backgroundColor: '#FFF', maxHeight: winHeight + 'px'}"> |
| 11 | - <chooseChannelForCatalog ref="chooseChannelForCatalog" :platformId=platformId :platformName=platformName :defaultCatalogId=defaultCatalogId :catalogIdChange="catalogIdChange" :treeType=treeType ></chooseChannelForCatalog> | |
| 11 | + <chooseChannelForCatalog ref="chooseChannelForCatalog" :platformId=platformId :platformDeviceId=platformDeviceId :platformName=platformName :defaultCatalogId=defaultCatalogId :catalogIdChange="catalogIdChange" :treeType=treeType ></chooseChannelForCatalog> | |
| 12 | 12 | </el-main> |
| 13 | 13 | </el-container> |
| 14 | 14 | </el-tab-pane> |
| ... | ... | @@ -60,6 +60,7 @@ export default { |
| 60 | 60 | tabActiveName: "gbChannel", |
| 61 | 61 | catalogTabActiveName: "catalog", |
| 62 | 62 | platformId: "", |
| 63 | + platformDeviceId: "", | |
| 63 | 64 | catalogId: "", |
| 64 | 65 | catalogName: "", |
| 65 | 66 | currentCatalogId: "", |
| ... | ... | @@ -73,8 +74,10 @@ export default { |
| 73 | 74 | }; |
| 74 | 75 | }, |
| 75 | 76 | methods: { |
| 76 | - openDialog(platformId, platformName, defaultCatalogId, treeType, closeCallback) { | |
| 77 | + openDialog(platformId, platformDeviceId, platformName, defaultCatalogId, treeType, closeCallback) { | |
| 78 | + console.log("defaultCatalogId: " + defaultCatalogId) | |
| 77 | 79 | this.platformId = platformId |
| 80 | + this.platformDeviceId = platformDeviceId | |
| 78 | 81 | this.platformName = platformName |
| 79 | 82 | this.defaultCatalogId = defaultCatalogId |
| 80 | 83 | this.showDialog = true | ... | ... |
web_src/src/components/dialog/chooseChannelForCatalog.vue
| ... | ... | @@ -38,7 +38,7 @@ |
| 38 | 38 | import catalogEdit from './catalogEdit.vue' |
| 39 | 39 | export default { |
| 40 | 40 | name: 'chooseChannelForCatalog', |
| 41 | - props: ['platformId', 'platformName', 'defaultCatalogId', 'catalogIdChange', 'treeType'], | |
| 41 | + props: ['platformId', 'platformDeviceId', 'platformName', 'defaultCatalogId', 'catalogIdChange', 'treeType'], | |
| 42 | 42 | created() { |
| 43 | 43 | this.chooseId = this.defaultCatalogId; |
| 44 | 44 | this.defaultCatalogIdSign = this.defaultCatalogId; |
| ... | ... | @@ -171,6 +171,7 @@ export default { |
| 171 | 171 | }); |
| 172 | 172 | }, |
| 173 | 173 | loadNode: function(node, resolve){ |
| 174 | + console.log("this.platformDeviceId: " + this.platformDeviceId) | |
| 174 | 175 | if (node.level === 0) { |
| 175 | 176 | resolve([ |
| 176 | 177 | { |
| ... | ... | @@ -179,7 +180,7 @@ export default { |
| 179 | 180 | type: -1 |
| 180 | 181 | },{ |
| 181 | 182 | name: this.platformName, |
| 182 | - id: this.platformId, | |
| 183 | + id: this.platformDeviceId, | |
| 183 | 184 | type: 0 |
| 184 | 185 | } |
| 185 | 186 | ]); |
| ... | ... | @@ -298,6 +299,8 @@ export default { |
| 298 | 299 | return false; |
| 299 | 300 | }, |
| 300 | 301 | nodeClickHandler: function (data, node, tree){ |
| 302 | + console.log(data) | |
| 303 | + console.log(node) | |
| 301 | 304 | this.chooseId = data.id; |
| 302 | 305 | this.chooseName = data.name; |
| 303 | 306 | if (this.catalogIdChange)this.catalogIdChange(this.chooseId, this.chooseName); | ... | ... |
web_src/src/components/dialog/getCatalog.vue
| ... | ... | @@ -77,6 +77,7 @@ export default { |
| 77 | 77 | }, |
| 78 | 78 | methods: { |
| 79 | 79 | openDialog(catalogIdResult) { |
| 80 | + console.log(this.chooseId) | |
| 80 | 81 | this.showDialog = true |
| 81 | 82 | this.catalogIdResult = catalogIdResult |
| 82 | 83 | }, |
| ... | ... | @@ -107,9 +108,6 @@ export default { |
| 107 | 108 | |
| 108 | 109 | }, |
| 109 | 110 | loadNode: function(node, resolve){ |
| 110 | - | |
| 111 | - | |
| 112 | - | |
| 113 | 111 | if (node.level === 0) { |
| 114 | 112 | this.$axios({ |
| 115 | 113 | method:"get", |
| ... | ... | @@ -124,7 +122,7 @@ export default { |
| 124 | 122 | resolve([ |
| 125 | 123 | { |
| 126 | 124 | name: this.platformName, |
| 127 | - id: this.platformId, | |
| 125 | + id: res.data.data.deviceGBId, | |
| 128 | 126 | type: 0 |
| 129 | 127 | } |
| 130 | 128 | ]); |
| ... | ... | @@ -142,9 +140,19 @@ export default { |
| 142 | 140 | this.chooseId = data.id; |
| 143 | 141 | }, |
| 144 | 142 | close: function() { |
| 143 | + this.chooseId = null; | |
| 145 | 144 | this.showDialog = false; |
| 146 | 145 | }, |
| 147 | 146 | submit: function() { |
| 147 | + console.log(this.chooseId) | |
| 148 | + if (this.chooseId === null) { | |
| 149 | + this.$message({ | |
| 150 | + showClose: true, | |
| 151 | + message: '未选择任何节点,', | |
| 152 | + type: 'warning' | |
| 153 | + }); | |
| 154 | + return; | |
| 155 | + } | |
| 148 | 156 | if (this.catalogIdResult)this.catalogIdResult(this.chooseId) |
| 149 | 157 | this.showDialog = false; |
| 150 | 158 | }, | ... | ... |
web_src/src/components/dialog/platformEdit.vue
| ... | ... | @@ -37,13 +37,13 @@ |
| 37 | 37 | <el-form-item label="本地端口" prop="devicePort"> |
| 38 | 38 | <el-input v-model="platform.devicePort" :disabled="true" type="number"></el-input> |
| 39 | 39 | </el-form-item> |
| 40 | + <el-form-item label="SIP认证用户名" prop="username"> | |
| 41 | + <el-input v-model="platform.username"></el-input> | |
| 42 | + </el-form-item> | |
| 40 | 43 | </el-form> |
| 41 | 44 | </el-col> |
| 42 | 45 | <el-col :span="12"> |
| 43 | 46 | <el-form ref="platform2" :rules="rules" :model="platform" label-width="160px"> |
| 44 | - <el-form-item label="SIP认证用户名" prop="username"> | |
| 45 | - <el-input v-model="platform.username"></el-input> | |
| 46 | - </el-form-item> | |
| 47 | 47 | <el-form-item label="行政区划" prop="administrativeDivision"> |
| 48 | 48 | <el-input v-model="platform.administrativeDivision" clearable></el-input> |
| 49 | 49 | </el-form-item> |
| ... | ... | @@ -79,7 +79,7 @@ |
| 79 | 79 | </el-select> |
| 80 | 80 | </el-form-item> |
| 81 | 81 | <el-form-item label="目录结构" prop="treeType" > |
| 82 | - <el-select v-model="platform.treeType" style="width: 100%" > | |
| 82 | + <el-select v-model="platform.treeType" style="width: 100%" @change="treeTypeChange"> | |
| 83 | 83 | <el-option key="WGS84" label="行政区划" value="CivilCode"></el-option> |
| 84 | 84 | <el-option key="GCJ02" label="业务分组" value="BusinessGroup"></el-option> |
| 85 | 85 | </el-select> |
| ... | ... | @@ -98,6 +98,7 @@ |
| 98 | 98 | <el-checkbox label="启用" v-model="platform.enable" @change="checkExpires"></el-checkbox> |
| 99 | 99 | <el-checkbox label="云台控制" v-model="platform.ptz"></el-checkbox> |
| 100 | 100 | <el-checkbox label="拉起离线推流" v-model="platform.startOfflinePush"></el-checkbox> |
| 101 | + <el-checkbox label="RTCP保活" v-model="platform.rtcp" @change="rtcpCheckBoxChange"></el-checkbox> | |
| 101 | 102 | </el-form-item> |
| 102 | 103 | <el-form-item> |
| 103 | 104 | <el-button type="primary" @click="onSubmit">{{ |
| ... | ... | @@ -251,21 +252,7 @@ export default { |
| 251 | 252 | |
| 252 | 253 | }, |
| 253 | 254 | onSubmit: function () { |
| 254 | - if (this.onSubmit_text === "保存") { | |
| 255 | - this.$confirm("修改目录结构会导致关联目录与通道数据被清空", '提示', { | |
| 256 | - dangerouslyUseHTMLString: true, | |
| 257 | - confirmButtonText: '确定', | |
| 258 | - cancelButtonText: '取消', | |
| 259 | - center: true, | |
| 260 | - type: 'warning' | |
| 261 | - }).then(() => { | |
| 262 | - this.saveForm() | |
| 263 | - }).catch(() => { | |
| 264 | - | |
| 265 | - }); | |
| 266 | - }else { | |
| 267 | - this.saveForm() | |
| 268 | - } | |
| 255 | + this.saveForm() | |
| 269 | 256 | }, |
| 270 | 257 | saveForm: function (){ |
| 271 | 258 | this.$axios({ |
| ... | ... | @@ -343,6 +330,22 @@ export default { |
| 343 | 330 | if (this.platform.enable && this.platform.expires == "0") { |
| 344 | 331 | this.platform.expires = "300"; |
| 345 | 332 | } |
| 333 | + }, | |
| 334 | + rtcpCheckBoxChange: function (result){ | |
| 335 | + if (result) { | |
| 336 | + this.$message({ | |
| 337 | + showClose: true, | |
| 338 | + message: "开启RTCP保活需要上级平台支持,可以避免无效推流", | |
| 339 | + type: "warning", | |
| 340 | + }); | |
| 341 | + } | |
| 342 | + }, | |
| 343 | + treeTypeChange: function (){ | |
| 344 | + this.$message({ | |
| 345 | + showClose: true, | |
| 346 | + message: "修改目录结构会导致关联目录与通道数据被清空,保存后生效", | |
| 347 | + type: "warning", | |
| 348 | + }); | |
| 346 | 349 | } |
| 347 | 350 | }, |
| 348 | 351 | }; | ... | ... |
web_src/static/file/推流通道导入.zip
No preview for this file type