Commit c014a90cc6a294dfc2aac740be87e75f44193a29
Merge branch 'wvp-28181-2.0' into main-dev
# 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 # src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
Showing
23 changed files
with
497 additions
and
237 deletions
doc/_sidebar.md
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
| ... | ... | @@ -101,6 +101,21 @@ public class VideoManagerConstants { |
| 101 | 101 | */ |
| 102 | 102 | public static final String VM_MSG_STREAM_PUSH_REQUESTED = "VM_MSG_STREAM_PUSH_REQUESTED"; |
| 103 | 103 | |
| 104 | + /** | |
| 105 | + * redis 消息通知上级平台开始观看流 | |
| 106 | + */ | |
| 107 | + public static final String VM_MSG_STREAM_START_PLAY_NOTIFY = "VM_MSG_STREAM_START_PLAY_NOTIFY"; | |
| 108 | + | |
| 109 | + /** | |
| 110 | + * redis 消息通知上级平台停止观看流 | |
| 111 | + */ | |
| 112 | + public static final String VM_MSG_STREAM_STOP_PLAY_NOTIFY = "VM_MSG_STREAM_STOP_PLAY_NOTIFY"; | |
| 113 | + | |
| 114 | + /** | |
| 115 | + * redis 消息接收关闭一个推流 | |
| 116 | + */ | |
| 117 | + public static final String VM_MSG_STREAM_PUSH_CLOSE_REQUESTED = "VM_MSG_STREAM_PUSH_CLOSE_REQUESTED"; | |
| 118 | + | |
| 104 | 119 | |
| 105 | 120 | /** |
| 106 | 121 | * redis 消息通知平台通知设备推流结果 | ... | ... |
src/main/java/com/genersoft/iot/vmp/conf/redis/RedisMsgListenConfig.java
| ... | ... | @@ -46,6 +46,9 @@ public class RedisMsgListenConfig { |
| 46 | 46 | @Autowired |
| 47 | 47 | private RedisCloseStreamMsgListener redisCloseStreamMsgListener; |
| 48 | 48 | |
| 49 | + @Autowired | |
| 50 | + private RedisPushStreamCloseResponseListener redisPushStreamCloseResponseListener; | |
| 51 | + | |
| 49 | 52 | |
| 50 | 53 | /** |
| 51 | 54 | * redis消息监听器容器 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器 |
| ... | ... | @@ -67,6 +70,7 @@ public class RedisMsgListenConfig { |
| 67 | 70 | container.addMessageListener(redisPushStreamListMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_PUSH_STREAM_LIST_CHANGE)); |
| 68 | 71 | container.addMessageListener(redisPushStreamResponseListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_RESPONSE)); |
| 69 | 72 | container.addMessageListener(redisCloseStreamMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_CLOSE)); |
| 73 | + container.addMessageListener(redisPushStreamCloseResponseListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_CLOSE_REQUESTED)); | |
| 70 | 74 | return container; |
| 71 | 75 | } |
| 72 | 76 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
| ... | ... | @@ -64,7 +64,7 @@ public class SipLayer implements CommandLineRunner { |
| 64 | 64 | try { |
| 65 | 65 | sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog())); |
| 66 | 66 | } catch (PeerUnavailableException e) { |
| 67 | - logger.error("[Sip Server] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp); | |
| 67 | + logger.error("[SIP SERVER] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp); | |
| 68 | 68 | return; |
| 69 | 69 | } |
| 70 | 70 | |
| ... | ... | @@ -76,12 +76,12 @@ public class SipLayer implements CommandLineRunner { |
| 76 | 76 | tcpSipProvider.addSipListener(sipProcessorObserver); |
| 77 | 77 | tcpSipProviderMap.put(monitorIp, tcpSipProvider); |
| 78 | 78 | |
| 79 | - logger.info("[Sip Server] tcp://{}:{} 启动成功", monitorIp, port); | |
| 79 | + logger.info("[SIP SERVER] tcp://{}:{} 启动成功", monitorIp, port); | |
| 80 | 80 | } catch (TransportNotSupportedException |
| 81 | 81 | | TooManyListenersException |
| 82 | 82 | | ObjectInUseException |
| 83 | 83 | | InvalidArgumentException e) { |
| 84 | - logger.error("[Sip Server] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" | |
| 84 | + logger.error("[SIP SERVER] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" | |
| 85 | 85 | , monitorIp, port); |
| 86 | 86 | } |
| 87 | 87 | |
| ... | ... | @@ -93,12 +93,12 @@ public class SipLayer implements CommandLineRunner { |
| 93 | 93 | |
| 94 | 94 | udpSipProviderMap.put(monitorIp, udpSipProvider); |
| 95 | 95 | |
| 96 | - logger.info("[Sip Server] udp://{}:{} 启动成功", monitorIp, port); | |
| 96 | + logger.info("[SIP SERVER] udp://{}:{} 启动成功", monitorIp, port); | |
| 97 | 97 | } catch (TransportNotSupportedException |
| 98 | 98 | | TooManyListenersException |
| 99 | 99 | | ObjectInUseException |
| 100 | 100 | | InvalidArgumentException e) { |
| 101 | - logger.error("[Sip Server] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" | |
| 101 | + logger.error("[SIP SERVER] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" | |
| 102 | 102 | , monitorIp, port); |
| 103 | 103 | } |
| 104 | 104 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
| ... | ... | @@ -284,7 +284,7 @@ public class SIPRequestHeaderPlarformProvider { |
| 284 | 284 | viaHeader.setRPort(); |
| 285 | 285 | viaHeaders.add(viaHeader); |
| 286 | 286 | // from |
| 287 | - SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getDeviceGBId(), | |
| 287 | + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sendRtpItem.getChannelId(), | |
| 288 | 288 | platform.getDeviceIp() + ":" + platform.getDevicePort()); |
| 289 | 289 | Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); |
| 290 | 290 | FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, sendRtpItem.getToTag()); |
| ... | ... | @@ -297,13 +297,10 @@ public class SIPRequestHeaderPlarformProvider { |
| 297 | 297 | MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); |
| 298 | 298 | // ceq |
| 299 | 299 | CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE); |
| 300 | - MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory(); | |
| 301 | - // 设置编码, 防止中文乱码 | |
| 302 | - messageFactory.setDefaultContentEncodingCharset("gb2312"); | |
| 303 | 300 | |
| 304 | 301 | CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId()); |
| 305 | 302 | |
| 306 | - request = (SIPRequest) messageFactory.createRequest(requestURI, Request.BYE, callIdHeader, cSeqHeader, fromHeader, | |
| 303 | + request = (SIPRequest) SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.BYE, callIdHeader, cSeqHeader, fromHeader, | |
| 307 | 304 | toHeader, viaHeaders, maxForwards); |
| 308 | 305 | |
| 309 | 306 | request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); |
| ... | ... | @@ -311,6 +308,7 @@ public class SIPRequestHeaderPlarformProvider { |
| 311 | 308 | String sipAddress = platform.getDeviceIp() + ":" + platform.getDevicePort(); |
| 312 | 309 | Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory() |
| 313 | 310 | .createSipURI(platform.getDeviceGBId(), sipAddress)); |
| 311 | + | |
| 314 | 312 | request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); |
| 315 | 313 | |
| 316 | 314 | return request; | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
| ... | ... | @@ -378,7 +378,6 @@ public class SIPCommander implements ISIPCommander { |
| 378 | 378 | mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); |
| 379 | 379 | errorEvent.response(e); |
| 380 | 380 | }), e -> { |
| 381 | - // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 | |
| 382 | 381 | ResponseEvent responseEvent = (ResponseEvent) e.event; |
| 383 | 382 | SIPResponse response = (SIPResponse) responseEvent.getResponse(); |
| 384 | 383 | streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
| ... | ... | @@ -237,6 +237,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { |
| 237 | 237 | }else { |
| 238 | 238 | if (channel.getChannelId().length() != 20) { |
| 239 | 239 | catalogXml.append("</Item>\r\n"); |
| 240 | + logger.warn("[编号长度异常] {} 长度错误,请使用20位长度的国标编号,当前长度:{}", channel.getChannelId(), channel.getChannelId().length()); | |
| 241 | + catalogXml.append("</Item>\r\n"); | |
| 240 | 242 | continue; |
| 241 | 243 | } |
| 242 | 244 | switch (Integer.parseInt(channel.getChannelId().substring(10, 13))){ | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
| ... | ... | @@ -3,6 +3,8 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; |
| 3 | 3 | import com.alibaba.fastjson2.JSONObject; |
| 4 | 4 | import com.genersoft.iot.vmp.conf.DynamicTask; |
| 5 | 5 | import com.genersoft.iot.vmp.conf.UserSetting; |
| 6 | +import com.genersoft.iot.vmp.conf.UserSetting; | |
| 7 | +import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType; | |
| 6 | 8 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 7 | 9 | import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; |
| 8 | 10 | import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; |
| ... | ... | @@ -14,6 +16,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; |
| 14 | 16 | import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRtpServerTimeout; |
| 15 | 17 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 16 | 18 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 19 | +import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; | |
| 17 | 20 | import com.genersoft.iot.vmp.service.IPlayService; |
| 18 | 21 | import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg; |
| 19 | 22 | import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener; |
| ... | ... | @@ -57,6 +60,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In |
| 57 | 60 | private IRedisCatchStorage redisCatchStorage; |
| 58 | 61 | |
| 59 | 62 | @Autowired |
| 63 | + private UserSetting userSetting; | |
| 64 | + | |
| 65 | + @Autowired | |
| 60 | 66 | private IVideoManagerStorage storager; |
| 61 | 67 | |
| 62 | 68 | @Autowired | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
| ... | ... | @@ -2,9 +2,11 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; |
| 2 | 2 | |
| 3 | 3 | import com.genersoft.iot.vmp.common.InviteInfo; |
| 4 | 4 | import com.genersoft.iot.vmp.common.InviteSessionType; |
| 5 | +import com.genersoft.iot.vmp.conf.UserSetting; | |
| 5 | 6 | import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; |
| 6 | 7 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 7 | 8 | import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; |
| 9 | +import com.genersoft.iot.vmp.gb28181.bean.*; | |
| 8 | 10 | import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; |
| 9 | 11 | import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; |
| 10 | 12 | import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; |
| ... | ... | @@ -18,6 +20,7 @@ import com.genersoft.iot.vmp.service.IDeviceService; |
| 18 | 20 | import com.genersoft.iot.vmp.service.IInviteStreamService; |
| 19 | 21 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 20 | 22 | import com.genersoft.iot.vmp.service.IPlayService; |
| 23 | +import com.genersoft.iot.vmp.service.*; | |
| 21 | 24 | import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; |
| 22 | 25 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 23 | 26 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| ... | ... | @@ -31,11 +34,7 @@ import org.springframework.stereotype.Component; |
| 31 | 34 | import javax.sip.InvalidArgumentException; |
| 32 | 35 | import javax.sip.RequestEvent; |
| 33 | 36 | import javax.sip.SipException; |
| 34 | -import javax.sip.address.SipURI; | |
| 35 | 37 | import javax.sip.header.CallIdHeader; |
| 36 | -import javax.sip.header.FromHeader; | |
| 37 | -import javax.sip.header.HeaderAddress; | |
| 38 | -import javax.sip.header.ToHeader; | |
| 39 | 38 | import javax.sip.message.Response; |
| 40 | 39 | import java.text.ParseException; |
| 41 | 40 | import java.util.HashMap; |
| ... | ... | @@ -64,12 +63,18 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In |
| 64 | 63 | private IInviteStreamService inviteStreamService; |
| 65 | 64 | |
| 66 | 65 | @Autowired |
| 66 | + private IPlatformService platformService; | |
| 67 | + | |
| 68 | + @Autowired | |
| 67 | 69 | private IDeviceService deviceService; |
| 68 | 70 | |
| 69 | 71 | @Autowired |
| 70 | 72 | private AudioBroadcastManager audioBroadcastManager; |
| 71 | 73 | |
| 72 | 74 | @Autowired |
| 75 | + private IDeviceChannelService channelService; | |
| 76 | + | |
| 77 | + @Autowired | |
| 73 | 78 | private IVideoManagerStorage storager; |
| 74 | 79 | |
| 75 | 80 | @Autowired |
| ... | ... | @@ -90,6 +95,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In |
| 90 | 95 | @Autowired |
| 91 | 96 | private IPlayService playService; |
| 92 | 97 | |
| 98 | + @Autowired | |
| 99 | + private UserSetting userSetting; | |
| 100 | + | |
| 93 | 101 | @Override |
| 94 | 102 | public void afterPropertiesSet() throws Exception { |
| 95 | 103 | // 添加消息处理的订阅 |
| ... | ... | @@ -102,201 +110,107 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In |
| 102 | 110 | */ |
| 103 | 111 | @Override |
| 104 | 112 | public void process(RequestEvent evt) { |
| 105 | - | |
| 106 | - // TODO 此处需要重构 | |
| 107 | - SIPRequest request =(SIPRequest) evt.getRequest(); | |
| 113 | + SIPRequest request = (SIPRequest) evt.getRequest(); | |
| 108 | 114 | try { |
| 109 | 115 | responseAck(request, Response.OK); |
| 110 | 116 | } catch (SipException | InvalidArgumentException | ParseException e) { |
| 111 | 117 | logger.error("[回复BYE信息失败],{}", e.getMessage()); |
| 112 | 118 | } |
| 113 | 119 | CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); |
| 114 | - String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser(); | |
| 115 | - String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser(); | |
| 116 | - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId()); | |
| 117 | - logger.info("[收到bye] {}/{}", platformGbId, channelId); | |
| 118 | - if (sendRtpItem != null){ | |
| 119 | - String streamId = sendRtpItem.getStream(); | |
| 120 | - Map<String, Object> param = new HashMap<>(); | |
| 121 | - param.put("vhost","__defaultVhost__"); | |
| 122 | - param.put("app",sendRtpItem.getApp()); | |
| 123 | - param.put("stream",streamId); | |
| 124 | - param.put("ssrc",sendRtpItem.getSsrc()); | |
| 125 | - logger.info("[收到bye] 停止向上级推流:{}", streamId); | |
| 126 | - MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 127 | - redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null); | |
| 128 | - ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); | |
| 129 | - zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); | |
| 130 | - int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId); | |
| 131 | - if (totalReaderCount <= 0) { | |
| 132 | - logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId); | |
| 133 | - if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) { | |
| 134 | - Device device = deviceService.getDevice(sendRtpItem.getDeviceId()); | |
| 135 | - if (device == null) { | |
| 136 | - logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId); | |
| 137 | - } | |
| 138 | - try { | |
| 139 | - logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), channelId); | |
| 140 | - cmder.streamByeCmd(device, channelId, streamId, null); | |
| 141 | - } catch (InvalidArgumentException | ParseException | SipException | | |
| 142 | - SsrcTransactionNotFoundException e) { | |
| 143 | - logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage()); | |
| 144 | - } | |
| 145 | - } | |
| 146 | - if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) { | |
| 147 | - MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, | |
| 148 | - sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(), | |
| 149 | - sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId()); | |
| 150 | - redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); | |
| 151 | - } | |
| 120 | + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId()); | |
| 121 | + | |
| 122 | + if (sendRtpItem != null){ | |
| 123 | + logger.info("[收到bye] 来自平台{}, 停止通道:{}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId()); | |
| 124 | + String streamId = sendRtpItem.getStream(); | |
| 125 | + Map<String, Object> param = new HashMap<>(); | |
| 126 | + param.put("vhost","__defaultVhost__"); | |
| 127 | + param.put("app",sendRtpItem.getApp()); | |
| 128 | + param.put("stream",streamId); | |
| 129 | + param.put("ssrc",sendRtpItem.getSsrc()); | |
| 130 | + logger.info("[收到bye] 停止向上级推流:{}", streamId); | |
| 131 | + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 132 | + redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), | |
| 133 | + callIdHeader.getCallId(), null); | |
| 134 | + zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); | |
| 135 | + if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) { | |
| 136 | + ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId()); | |
| 137 | + if (platform != null) { | |
| 138 | + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, | |
| 139 | + sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(), | |
| 140 | + sendRtpItem.getPlatformId(), platform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId()); | |
| 141 | + messageForPushChannel.setPlatFormIndex(platform.getId()); | |
| 142 | + redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel); | |
| 143 | + }else { | |
| 144 | + logger.info("[上级平台停止观看] 未找到平台{}的信息,发送redis消息失败", sendRtpItem.getPlatformId()); | |
| 152 | 145 | } |
| 153 | 146 | } |
| 154 | - // 可能是设备主动停止 | |
| 155 | - Device device = storager.queryVideoDeviceByChannelId(platformGbId); | |
| 156 | - if (device != null) { | |
| 157 | - storager.stopPlay(device.getDeviceId(), channelId); | |
| 158 | - SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null); | |
| 159 | - if (ssrcTransactionForPlay != null){ | |
| 160 | - if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){ | |
| 161 | - // 释放ssrc | |
| 162 | - MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlay.getMediaServerId()); | |
| 163 | - if (mediaServerItem != null) { | |
| 164 | - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlay.getSsrc()); | |
| 165 | - } | |
| 166 | - streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream()); | |
| 147 | + | |
| 148 | + int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId); | |
| 149 | + if (totalReaderCount <= 0) { | |
| 150 | + logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId); | |
| 151 | + if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) { | |
| 152 | + Device device = deviceService.getDevice(sendRtpItem.getDeviceId()); | |
| 153 | + if (device == null) { | |
| 154 | + logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId); | |
| 167 | 155 | } |
| 168 | - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); | |
| 169 | - inviteStreamService.removeInviteInfo(inviteInfo); | |
| 170 | - if (inviteInfo != null) { | |
| 171 | - if (inviteInfo.getStreamInfo() != null) { | |
| 172 | - mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream()); | |
| 173 | - } | |
| 156 | + try { | |
| 157 | + logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); | |
| 158 | + cmder.streamByeCmd(device, sendRtpItem.getChannelId(), streamId, null); | |
| 159 | + } catch (InvalidArgumentException | ParseException | SipException | | |
| 160 | + SsrcTransactionNotFoundException e) { | |
| 161 | + logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage()); | |
| 174 | 162 | } |
| 175 | 163 | } |
| 176 | - SsrcTransaction ssrcTransactionForPlayBack = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callIdHeader.getCallId(), null); | |
| 177 | - if (ssrcTransactionForPlayBack != null) { | |
| 178 | - // 释放ssrc | |
| 179 | - MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlayBack.getMediaServerId()); | |
| 180 | - if (mediaServerItem != null) { | |
| 181 | - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlayBack.getSsrc()); | |
| 182 | - } | |
| 183 | - streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlayBack.getStream()); | |
| 184 | - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAYBACK, device.getDeviceId(), channelId); | |
| 164 | + } | |
| 165 | + } | |
| 185 | 166 | |
| 186 | - if (inviteInfo != null) { | |
| 187 | - inviteStreamService.removeInviteInfo(inviteInfo); | |
| 188 | - if (inviteInfo.getStreamInfo() != null) { | |
| 189 | - mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream()); | |
| 190 | - } | |
| 191 | - } | |
| 192 | - } | |
| 167 | + | |
| 168 | + | |
| 169 | + // 可能是设备发送的停止 | |
| 170 | + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); | |
| 171 | + if (ssrcTransaction == null) { | |
| 172 | + logger.info("[收到bye] 但是无法获取推流信息和发流信息,忽略此请求"); | |
| 173 | + logger.info(request.toString()); | |
| 174 | + return; | |
| 193 | 175 | } |
| 176 | + logger.info("[收到bye] 来自设备:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); | |
| 194 | 177 | |
| 195 | - SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(null, null, request.getCallIdHeader().getCallId(), null); | |
| 196 | - if (ssrcTransaction != null) { | |
| 178 | + Device device = deviceService.getDevice(ssrcTransaction.getDeviceId()); | |
| 179 | + if (device == null) { | |
| 180 | + logger.info("[收到bye] 未找到设备:{} ", ssrcTransaction.getDeviceId()); | |
| 181 | + return; | |
| 182 | + } | |
| 183 | + DeviceChannel channel = channelService.getOne(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); | |
| 184 | + if (channel == null) { | |
| 185 | + logger.info("[收到bye] 未找到通道,设备:{}, 通道:{}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); | |
| 186 | + return; | |
| 187 | + } | |
| 188 | + storager.stopPlay(device.getDeviceId(), channel.getChannelId()); | |
| 189 | + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId()); | |
| 190 | + if (inviteInfo != null) { | |
| 191 | + inviteStreamService.removeInviteInfo(inviteInfo); | |
| 192 | + if (inviteInfo.getStreamInfo() != null) { | |
| 193 | + mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStreamInfo().getStream()); | |
| 194 | + } | |
| 195 | + } | |
| 197 | 196 | // 释放ssrc |
| 198 | 197 | MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId()); |
| 199 | 198 | if (mediaServerItem != null) { |
| 200 | 199 | mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc()); |
| 201 | 200 | } |
| 202 | - | |
| 203 | - switch (ssrcTransaction.getType()) { | |
| 204 | -// case play: | |
| 205 | -// break; | |
| 206 | -// case talk: | |
| 207 | -// break; | |
| 208 | -// case playback: | |
| 209 | -// break; | |
| 210 | -// case download: | |
| 211 | -// break; | |
| 212 | - case BROADCAST: | |
| 213 | - String channelId1 = ssrcTransaction.getChannelId(); | |
| 214 | - | |
| 215 | - Device deviceFromTransaction = storager.queryVideoDevice(ssrcTransaction.getDeviceId()); | |
| 216 | - if (deviceFromTransaction == null) { | |
| 217 | - ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(ssrcTransaction.getDeviceId()); | |
| 218 | - if (parentPlatform != null) { | |
| 219 | - // 来自上级平台的停止对讲 | |
| 220 | - logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", ssrcTransaction.getDeviceId(), channelId1); | |
| 221 | - // 释放ssrc | |
| 222 | - streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); | |
| 223 | - if (mediaServerItem != null) { | |
| 224 | - zlmrtpServerFactory.closeRtpServer(mediaServerItem, ssrcTransaction.getStream()); | |
| 225 | - } | |
| 226 | - // 查找来源的对讲设备,发送停止 | |
| 227 | - Device sourceDevice = storager.queryVideoDeviceByPlatformIdAndChannelId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); | |
| 228 | - if (sourceDevice != null) { | |
| 229 | - playService.stopAudioBroadcast(sourceDevice.getDeviceId(), channelId); | |
| 230 | - } | |
| 231 | - } | |
| 232 | - }else { | |
| 233 | - // 来自设备的停止对讲 | |
| 234 | - | |
| 235 | - // 如果是来自设备,则听停止推流。 来自上级则停止收流 | |
| 236 | - AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(ssrcTransaction.getDeviceId(), channelId1); | |
| 237 | - if (audioBroadcastCatch != null) { | |
| 238 | - // | |
| 239 | - SendRtpItem sendRtpItemForBroadcast = redisCatchStorage.querySendRTPServer(ssrcTransaction.getDeviceId(), channelId1, | |
| 240 | - audioBroadcastCatch.getStream(), audioBroadcastCatch.getSipTransactionInfo().getCallId()); | |
| 241 | - if (sendRtpItemForBroadcast != null) { | |
| 242 | - MediaServerItem mediaServerItemForBroadcast = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 243 | - if (mediaServerItemForBroadcast == null) { | |
| 244 | - return; | |
| 245 | - } | |
| 246 | - | |
| 247 | - Boolean ready = zlmrtpServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), audioBroadcastCatch.getStream()); | |
| 248 | - if (ready) { | |
| 249 | - Map<String, Object> param = new HashMap<>(); | |
| 250 | - param.put("vhost","__defaultVhost__"); | |
| 251 | - param.put("app",sendRtpItem.getApp()); | |
| 252 | - param.put("stream",audioBroadcastCatch.getStream()); | |
| 253 | - param.put("ssrc",sendRtpItem.getSsrc()); | |
| 254 | - logger.info("[收到bye] 停止推流:{}", audioBroadcastCatch.getStream()); | |
| 255 | - MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 256 | - redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), request.getCallIdHeader().getCallId(), null); | |
| 257 | - zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); | |
| 258 | - } | |
| 259 | - if (audioBroadcastCatch.isFromPlatform()) { | |
| 260 | - // 上级也正在点播。 向上级回复bye | |
| 261 | - List<SsrcTransaction> ssrcTransactions = streamSession.getSsrcTransactionForAll(null, channelId1, null, null); | |
| 262 | - if (ssrcTransactions.size() > 0) { | |
| 263 | - for (SsrcTransaction transaction : ssrcTransactions) { | |
| 264 | - if (transaction.getType().equals(InviteSessionType.BROADCAST)) { | |
| 265 | - ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(transaction.getDeviceId()); | |
| 266 | - if (parentPlatform != null) { | |
| 267 | - try { | |
| 268 | - commanderForPlatform.streamByeCmd(parentPlatform, channelId1, transaction.getStream(), transaction.getCallId(), eventResult -> { | |
| 269 | - streamSession.remove(transaction.getDeviceId(), transaction.getChannelId(), transaction.getStream()); | |
| 270 | - }); | |
| 271 | - audioBroadcastManager.del(transaction.getDeviceId(), channelId1); | |
| 272 | - } catch (InvalidArgumentException | SipException | ParseException | | |
| 273 | - SsrcTransactionNotFoundException e) { | |
| 274 | - logger.error("[命令发送失败] 向{}发送bye失败", transaction.getDeviceId()); | |
| 275 | - } | |
| 276 | - // 释放ssrc | |
| 277 | - MediaServerItem mediaServerItemFromTransaction = mediaServerService.getOne(transaction.getMediaServerId()); | |
| 278 | - if (mediaServerItemFromTransaction != null) { | |
| 279 | - mediaServerService.releaseSsrc(mediaServerItemFromTransaction.getId(), transaction.getSsrc()); | |
| 280 | - } | |
| 281 | - streamSession.remove(transaction.getDeviceId(), transaction.getChannelId(), transaction.getStream()); | |
| 282 | - } | |
| 283 | - } | |
| 284 | - } | |
| 285 | - } | |
| 286 | - | |
| 287 | - } | |
| 288 | - redisCatchStorage.deleteSendRTPServer(ssrcTransaction.getDeviceId(), channelId1, | |
| 289 | - audioBroadcastCatch.getStream(), audioBroadcastCatch.getSipTransactionInfo().getCallId()); | |
| 290 | - | |
| 291 | - } | |
| 292 | - } | |
| 293 | - } | |
| 294 | - audioBroadcastManager.del(ssrcTransaction.getDeviceId(), channelId1); | |
| 295 | - break; | |
| 296 | - default: | |
| 297 | - break; | |
| 201 | + streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getStream()); | |
| 202 | + if (ssrcTransaction.getType() == InviteSessionType.BROADCAST) { | |
| 203 | + // 查找来源的对讲设备,发送停止 | |
| 204 | + Device sourceDevice = storager.queryVideoDeviceByPlatformIdAndChannelId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); | |
| 205 | + if (sourceDevice != null) { | |
| 206 | + playService.stopAudioBroadcast(sourceDevice.getDeviceId(), channel.getChannelId()); | |
| 207 | + } | |
| 208 | + } | |
| 209 | + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(ssrcTransaction.getDeviceId(), channel.getChannelId()); | |
| 210 | + if (audioBroadcastCatch != null) { | |
| 211 | + // 来自上级平台的停止对讲 | |
| 212 | + logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", ssrcTransaction.getDeviceId(), channel.getChannelId()); | |
| 213 | + audioBroadcastManager.del(ssrcTransaction.getDeviceId(), channel.getChannelId()); | |
| 298 | 214 | } |
| 299 | - | |
| 300 | - } | |
| 301 | 215 | } |
| 302 | 216 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
| ... | ... | @@ -210,16 +210,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 210 | 210 | return; |
| 211 | 211 | } else { |
| 212 | 212 | streamPushItem = streamPushService.getPush(gbStream.getApp(), gbStream.getStream()); |
| 213 | - if (streamPushItem == null || streamPushItem.getServerId().equals(userSetting.getServerId())) { | |
| 214 | - logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId); | |
| 215 | - try { | |
| 216 | - responseAck(request, Response.GONE); | |
| 217 | - } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 218 | - logger.error("[命令发送失败] invite GONE: {}", e.getMessage()); | |
| 219 | - } | |
| 220 | - return; | |
| 221 | - }else { | |
| 222 | - // TODO 可能漏回复消息 | |
| 213 | + if (streamPushItem != null) { | |
| 214 | + mediaServerItem = mediaServerService.getOne(streamPushItem.getMediaServerId()); | |
| 215 | + } | |
| 216 | + if (mediaServerItem == null) { | |
| 217 | + mediaServerItem = mediaServerService.getDefaultMediaServer(); | |
| 223 | 218 | } |
| 224 | 219 | } |
| 225 | 220 | } else { |
| ... | ... | @@ -380,7 +375,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 380 | 375 | } |
| 381 | 376 | logger.info("[上级Invite] {}, 平台:{}, 通道:{}, 收流地址:{}:{},收流方式:{}, ssrc:{}", sessionName, username, channelId, addressStr, port, streamTypeStr, ssrc); |
| 382 | 377 | SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, |
| 383 | - device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp()); | |
| 378 | + device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback -> { | |
| 379 | + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null; | |
| 380 | + }); | |
| 384 | 381 | |
| 385 | 382 | if (tcpActive != null) { |
| 386 | 383 | sendRtpItem.setTcpActive(tcpActive); |
| ... | ... | @@ -584,14 +581,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 584 | 581 | * 安排推流 |
| 585 | 582 | */ |
| 586 | 583 | private void pushProxyStream(RequestEvent evt, SIPRequest request, GbStream gbStream, ParentPlatform platform, |
| 587 | - CallIdHeader callIdHeader, MediaServerItem mediaServerItem, | |
| 588 | - int port, Boolean tcpActive, boolean mediaTransmissionTCP, | |
| 589 | - String channelId, String addressStr, String ssrc, String requesterId) { | |
| 590 | - Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream()); | |
| 591 | - if (streamReady != null && streamReady) { | |
| 592 | - // 自平台内容 | |
| 593 | - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, | |
| 594 | - gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp()); | |
| 584 | + CallIdHeader callIdHeader, MediaServerItem mediaServerItem, | |
| 585 | + int port, Boolean tcpActive, boolean mediaTransmissionTCP, | |
| 586 | + String channelId, String addressStr, String ssrc, String requesterId) { | |
| 587 | + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream()); | |
| 588 | + if (streamReady != null && streamReady) { | |
| 589 | + // 自平台内容 | |
| 590 | + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, | |
| 591 | + gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback ->{ | |
| 592 | + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null; | |
| 593 | + }); | |
| 595 | 594 | |
| 596 | 595 | if (sendRtpItem == null) { |
| 597 | 596 | logger.warn("服务器端口资源不足"); |
| ... | ... | @@ -631,7 +630,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 631 | 630 | if (streamReady != null && streamReady) { |
| 632 | 631 | // 自平台内容 |
| 633 | 632 | SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, |
| 634 | - gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp()); | |
| 633 | + gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback ->{ | |
| 634 | + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null; | |
| 635 | + }); | |
| 635 | 636 | |
| 636 | 637 | if (sendRtpItem == null) { |
| 637 | 638 | logger.warn("服务器端口资源不足"); |
| ... | ... | @@ -747,7 +748,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements |
| 747 | 748 | dynamicTask.stop(callIdHeader.getCallId()); |
| 748 | 749 | if (serverId.equals(userSetting.getServerId())) { |
| 749 | 750 | SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, finalPort, ssrc, requesterId, |
| 750 | - app, stream, channelId, mediaTransmissionTCP, platform.isRtcp()); | |
| 751 | + app, stream, channelId, mediaTransmissionTCP, platform.isRtcp(), ssrcFromCallback -> { | |
| 752 | + return redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, null, callIdHeader.getCallId()) != null; | |
| 753 | + }); | |
| 751 | 754 | |
| 752 | 755 | if (sendRtpItem == null) { |
| 753 | 756 | logger.warn("上级点时创建sendRTPItem失败,可能是服务器端口资源不足"); | ... | ... |
src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java
| ... | ... | @@ -125,7 +125,7 @@ public class SipUtils { |
| 125 | 125 | strTmp = String.format("%02X", moveSpeed); |
| 126 | 126 | builder.append(strTmp, 0, 2); |
| 127 | 127 | builder.append(strTmp, 0, 2); |
| 128 | - | |
| 128 | + | |
| 129 | 129 | //优化zoom低倍速下的变倍速率 |
| 130 | 130 | if ((zoomSpeed > 0) && (zoomSpeed <16)) |
| 131 | 131 | { |
| ... | ... | @@ -283,4 +283,4 @@ public class SipUtils { |
| 283 | 283 | } |
| 284 | 284 | return localDateTime.format(DateUtil.formatterISO8601); |
| 285 | 285 | } |
| 286 | 286 | -} |
| 287 | +} | |
| 287 | 288 | \ No newline at end of file | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
| ... | ... | @@ -23,6 +23,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; |
| 23 | 23 | import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; |
| 24 | 24 | import com.genersoft.iot.vmp.media.zlm.dto.hook.*; |
| 25 | 25 | import com.genersoft.iot.vmp.service.*; |
| 26 | +import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; | |
| 26 | 27 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 27 | 28 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| 28 | 29 | import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; |
| ... | ... | @@ -543,6 +544,13 @@ public class ZLMHttpHookListener { |
| 543 | 544 | } |
| 544 | 545 | redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), |
| 545 | 546 | sendRtpItem.getCallId(), sendRtpItem.getStream()); |
| 547 | + if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) { | |
| 548 | + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, | |
| 549 | + sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(), | |
| 550 | + sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId()); | |
| 551 | + messageForPushChannel.setPlatFormIndex(parentPlatform.getId()); | |
| 552 | + redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel); | |
| 553 | + } | |
| 546 | 554 | } |
| 547 | 555 | } |
| 548 | 556 | } |
| ... | ... | @@ -595,7 +603,7 @@ public class ZLMHttpHookListener { |
| 595 | 603 | } |
| 596 | 604 | return ret; |
| 597 | 605 | } |
| 598 | - // 推流具有主动性,暂时不做处理 | |
| 606 | + // TODO 推流具有主动性,暂时不做处理 | |
| 599 | 607 | // StreamPushItem streamPushItem = streamPushService.getPush(app, streamId); |
| 600 | 608 | // if (streamPushItem != null) { |
| 601 | 609 | // // TODO 发送停止 | ... | ... |
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
| ... | ... | @@ -227,13 +227,14 @@ public class ZLMRTPServerFactory { |
| 227 | 227 | * @param tcp 是否为tcp |
| 228 | 228 | * @return SendRtpItem |
| 229 | 229 | */ |
| 230 | - public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp, boolean rtcp){ | |
| 230 | + public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, | |
| 231 | + String deviceId, String channelId, boolean tcp, boolean rtcp, KeepPortCallback callback){ | |
| 231 | 232 | |
| 232 | 233 | // 默认为随机端口 |
| 233 | 234 | int localPort = 0; |
| 234 | 235 | if (userSetting.getGbSendStreamStrict()) { |
| 235 | 236 | if (userSetting.getGbSendStreamStrict()) { |
| 236 | - localPort = keepPort(serverItem, ssrc, localPort); | |
| 237 | + localPort = keepPort(serverItem, ssrc, localPort, callback); | |
| 237 | 238 | if (localPort == 0) { |
| 238 | 239 | return null; |
| 239 | 240 | } |
| ... | ... | @@ -265,11 +266,12 @@ public class ZLMRTPServerFactory { |
| 265 | 266 | * @param tcp 是否为tcp |
| 266 | 267 | * @return SendRtpItem |
| 267 | 268 | */ |
| 268 | - public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp, boolean rtcp){ | |
| 269 | + public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, | |
| 270 | + String app, String stream, String channelId, boolean tcp, boolean rtcp, KeepPortCallback callback){ | |
| 269 | 271 | // 默认为随机端口 |
| 270 | 272 | int localPort = 0; |
| 271 | 273 | if (userSetting.getGbSendStreamStrict()) { |
| 272 | - localPort = keepPort(serverItem, ssrc, localPort); | |
| 274 | + localPort = keepPort(serverItem, ssrc, localPort, callback); | |
| 273 | 275 | if (localPort == 0) { |
| 274 | 276 | return null; |
| 275 | 277 | } |
| ... | ... | @@ -290,10 +292,14 @@ public class ZLMRTPServerFactory { |
| 290 | 292 | return sendRtpItem; |
| 291 | 293 | } |
| 292 | 294 | |
| 295 | + public interface KeepPortCallback{ | |
| 296 | + Boolean keep(String ssrc); | |
| 297 | + } | |
| 298 | + | |
| 293 | 299 | /** |
| 294 | 300 | * 保持端口,直到需要需要发流时再释放 |
| 295 | 301 | */ |
| 296 | - public int keepPort(MediaServerItem serverItem, String ssrc, Integer localPort) { | |
| 302 | + public int keepPort(MediaServerItem serverItem, String ssrc, int localPort, KeepPortCallback keepPortCallback) { | |
| 297 | 303 | Map<String, Object> param = new HashMap<>(3); |
| 298 | 304 | param.put("port", localPort); |
| 299 | 305 | param.put("enable_tcp", 1); |
| ... | ... | @@ -302,18 +308,20 @@ public class ZLMRTPServerFactory { |
| 302 | 308 | if (jsonObject.getInteger("code") == 0) { |
| 303 | 309 | localPort = jsonObject.getInteger("port"); |
| 304 | 310 | HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(ssrc, null, serverItem.getId()); |
| 311 | + // 订阅 zlm启动事件, 新的zlm也会从这里进入系统 | |
| 305 | 312 | Integer finalLocalPort = localPort; |
| 306 | 313 | hookSubscribe.addSubscribe(hookSubscribeForRtpServerTimeout, |
| 307 | 314 | (MediaServerItem mediaServerItem, HookParam hookParam)->{ |
| 308 | 315 | logger.info("[上级点播] {}->监听端口到期继续保持监听: {}", ssrc, finalLocalPort); |
| 309 | 316 | OnRtpServerTimeoutHookParam rtpServerTimeoutHookParam = (OnRtpServerTimeoutHookParam) hookParam; |
| 310 | - if (!ssrc.equals(rtpServerTimeoutHookParam.getSsrc())) { | |
| 311 | - return; | |
| 312 | - } | |
| 313 | - int port = keepPort(serverItem, ssrc, finalLocalPort); | |
| 314 | - if (port == 0) { | |
| 315 | - logger.info("[上级点播] {}->监听端口失败,移除监听", ssrc); | |
| 316 | - hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout); | |
| 317 | + if (ssrc.equals(rtpServerTimeoutHookParam.getSsrc())) { | |
| 318 | + if (keepPortCallback.keep(ssrc)) { | |
| 319 | + logger.info("[上级点播] {}->监听端口到期继续保持监听", ssrc); | |
| 320 | + keepPort(serverItem, ssrc, finalLocalPort, keepPortCallback); | |
| 321 | + }else { | |
| 322 | + logger.info("[上级点播] {}->发送取消,无需继续监听", ssrc); | |
| 323 | + releasePort(serverItem, ssrc); | |
| 324 | + } | |
| 317 | 325 | } |
| 318 | 326 | }); |
| 319 | 327 | logger.info("[上级点播] {}->监听端口: {}", ssrc, localPort); | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java
| 1 | 1 | package com.genersoft.iot.vmp.service.bean; |
| 2 | 2 | |
| 3 | -import java.util.stream.Stream; | |
| 4 | - | |
| 5 | 3 | /** |
| 6 | 4 | * 当上级平台 |
| 7 | 5 | * @author lin |
| ... | ... | @@ -29,11 +27,16 @@ public class MessageForPushChannel { |
| 29 | 27 | private String gbId; |
| 30 | 28 | |
| 31 | 29 | /** |
| 32 | - * 请求的平台ID | |
| 30 | + * 请求的平台国标编号 | |
| 33 | 31 | */ |
| 34 | 32 | private String platFormId; |
| 35 | 33 | |
| 36 | 34 | /** |
| 35 | + * 请求的平台自增ID | |
| 36 | + */ | |
| 37 | + private int platFormIndex; | |
| 38 | + | |
| 39 | + /** | |
| 37 | 40 | * 请求平台名称 |
| 38 | 41 | */ |
| 39 | 42 | private String platFormName; |
| ... | ... | @@ -128,4 +131,12 @@ public class MessageForPushChannel { |
| 128 | 131 | public void setMediaServerId(String mediaServerId) { |
| 129 | 132 | this.mediaServerId = mediaServerId; |
| 130 | 133 | } |
| 134 | + | |
| 135 | + public int getPlatFormIndex() { | |
| 136 | + return platFormIndex; | |
| 137 | + } | |
| 138 | + | |
| 139 | + public void setPlatFormIndex(int platFormIndex) { | |
| 140 | + this.platFormIndex = platFormIndex; | |
| 141 | + } | |
| 131 | 142 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
| 1 | 1 | package com.genersoft.iot.vmp.service.impl; |
| 2 | 2 | |
| 3 | -import com.genersoft.iot.vmp.common.InviteSessionType; | |
| 4 | 3 | import com.genersoft.iot.vmp.common.VideoManagerConstants; |
| 5 | 4 | import com.genersoft.iot.vmp.conf.DynamicTask; |
| 6 | 5 | import com.genersoft.iot.vmp.conf.UserSetting; |
| ... | ... | @@ -415,8 +414,8 @@ public class DeviceServiceImpl implements IDeviceService { |
| 415 | 414 | if (device == null) { |
| 416 | 415 | return null; |
| 417 | 416 | } |
| 418 | - if (ObjectUtils.isEmpty(parentId) || parentId.equals(deviceId)) { | |
| 419 | - parentId = null; | |
| 417 | + if (ObjectUtils.isEmpty(parentId) ) { | |
| 418 | + parentId = deviceId; | |
| 420 | 419 | } |
| 421 | 420 | List<DeviceChannel> rootNodes = deviceChannelMapper.getSubChannelsByDeviceId(deviceId, parentId, onlyCatalog); |
| 422 | 421 | return transportChannelsToTree(rootNodes, ""); | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
| ... | ... | @@ -362,7 +362,7 @@ public class PlayServiceImpl implements IPlayService { |
| 362 | 362 | null); |
| 363 | 363 | return; |
| 364 | 364 | } |
| 365 | - logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", | |
| 365 | + logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{}, 收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", | |
| 366 | 366 | device.getDeviceId(), channelId, device.isSwitchPrimarySubStream() ? "辅码流" : "主码流", ssrcInfo.getPort(), |
| 367 | 367 | device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); |
| 368 | 368 | //端口获取失败的ssrcInfo 没有必要发送点播指令 |
| ... | ... | @@ -445,7 +445,7 @@ public class PlayServiceImpl implements IPlayService { |
| 445 | 445 | InviteErrorCode.SUCCESS.getCode(), |
| 446 | 446 | InviteErrorCode.SUCCESS.getMsg(), |
| 447 | 447 | streamInfo); |
| 448 | - logger.info("[点播成功] deviceId: {}, channelId: {},码流类型:{}", device.getDeviceId(), | |
| 448 | + logger.info("[点播成功] deviceId: {}, channelId:{}, 码流类型:{}", device.getDeviceId(), | |
| 449 | 449 | device.isSwitchPrimarySubStream() ? "辅码流" : "主码流"); |
| 450 | 450 | String streamUrl; |
| 451 | 451 | if (mediaServerItemInuse.getRtspPort() != 0) { | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java
| ... | ... | @@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; |
| 13 | 13 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 14 | 14 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 15 | 15 | import com.genersoft.iot.vmp.service.bean.*; |
| 16 | +import com.genersoft.iot.vmp.utils.redis.RedisUtil; | |
| 16 | 17 | import com.genersoft.iot.vmp.vmanager.bean.WVPResult; |
| 17 | 18 | import org.slf4j.Logger; |
| 18 | 19 | import org.slf4j.LoggerFactory; |
| ... | ... | @@ -26,6 +27,7 @@ import org.springframework.stereotype.Component; |
| 26 | 27 | |
| 27 | 28 | import java.text.ParseException; |
| 28 | 29 | import java.util.HashMap; |
| 30 | +import java.util.List; | |
| 29 | 31 | import java.util.Map; |
| 30 | 32 | import java.util.UUID; |
| 31 | 33 | import java.util.concurrent.ConcurrentHashMap; |
| ... | ... | @@ -127,6 +129,7 @@ public class RedisGbPlayMsgListener implements MessageListener { |
| 127 | 129 | case WvpRedisMsgCmd.REQUEST_PUSH_STREAM: |
| 128 | 130 | RequestPushStreamMsg param = JSON.to(RequestPushStreamMsg.class, wvpRedisMsg.getContent()); |
| 129 | 131 | requestPushStreamMsgHand(param, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial()); |
| 132 | + | |
| 130 | 133 | break; |
| 131 | 134 | default: |
| 132 | 135 | break; |
| ... | ... | @@ -311,7 +314,9 @@ public class RedisGbPlayMsgListener implements MessageListener { |
| 311 | 314 | SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, content.getIp(), |
| 312 | 315 | content.getPort(), content.getSsrc(), content.getPlatformId(), |
| 313 | 316 | content.getApp(), content.getStream(), content.getChannelId(), |
| 314 | - content.getTcp(), content.getRtcp()); | |
| 317 | + content.getTcp(), content.getRtcp(), ssrcFromCallback -> { | |
| 318 | + return querySendRTPServer(content.getPlatformId(), content.getChannelId(), content.getStream(), null) != null; | |
| 319 | + }); | |
| 315 | 320 | |
| 316 | 321 | WVPResult<ResponseSendItemMsg> result = new WVPResult<>(); |
| 317 | 322 | result.setCode(0); |
| ... | ... | @@ -388,4 +393,31 @@ public class RedisGbPlayMsgListener implements MessageListener { |
| 388 | 393 | }); |
| 389 | 394 | redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); |
| 390 | 395 | } |
| 396 | + | |
| 397 | + private SendRtpItem querySendRTPServer(String platformGbId, String channelId, String streamId, String callId) { | |
| 398 | + if (platformGbId == null) { | |
| 399 | + platformGbId = "*"; | |
| 400 | + } | |
| 401 | + if (channelId == null) { | |
| 402 | + channelId = "*"; | |
| 403 | + } | |
| 404 | + if (streamId == null) { | |
| 405 | + streamId = "*"; | |
| 406 | + } | |
| 407 | + if (callId == null) { | |
| 408 | + callId = "*"; | |
| 409 | + } | |
| 410 | + String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX | |
| 411 | + + userSetting.getServerId() + "_*_" | |
| 412 | + + platformGbId + "_" | |
| 413 | + + channelId + "_" | |
| 414 | + + streamId + "_" | |
| 415 | + + callId; | |
| 416 | + List<Object> scan = RedisUtil.scan(redisTemplate, key); | |
| 417 | + if (scan.size() > 0) { | |
| 418 | + return (SendRtpItem)redisTemplate.opsForValue().get(scan.get(0)); | |
| 419 | + }else { | |
| 420 | + return null; | |
| 421 | + } | |
| 422 | + } | |
| 391 | 423 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamCloseResponseListener.java
0 → 100644
| 1 | +package com.genersoft.iot.vmp.service.redisMsg; | |
| 2 | + | |
| 3 | +import com.alibaba.fastjson2.JSON; | |
| 4 | +import com.genersoft.iot.vmp.conf.UserSetting; | |
| 5 | +import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType; | |
| 6 | +import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; | |
| 7 | +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; | |
| 8 | +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; | |
| 9 | +import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; | |
| 10 | +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | |
| 11 | +import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; | |
| 12 | +import com.genersoft.iot.vmp.service.IMediaServerService; | |
| 13 | +import com.genersoft.iot.vmp.service.IStreamPushService; | |
| 14 | +import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; | |
| 15 | +import com.genersoft.iot.vmp.service.bean.MessageForPushChannelResponse; | |
| 16 | +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | |
| 17 | +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | |
| 18 | +import org.slf4j.Logger; | |
| 19 | +import org.slf4j.LoggerFactory; | |
| 20 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 21 | +import org.springframework.data.redis.connection.Message; | |
| 22 | +import org.springframework.data.redis.connection.MessageListener; | |
| 23 | +import org.springframework.stereotype.Component; | |
| 24 | + | |
| 25 | +import javax.sip.InvalidArgumentException; | |
| 26 | +import javax.sip.SipException; | |
| 27 | +import java.text.ParseException; | |
| 28 | +import java.util.HashMap; | |
| 29 | +import java.util.List; | |
| 30 | +import java.util.Map; | |
| 31 | +import java.util.concurrent.ConcurrentHashMap; | |
| 32 | + | |
| 33 | +/** | |
| 34 | + * 接收redis发送的结束推流请求 | |
| 35 | + * @author lin | |
| 36 | + */ | |
| 37 | +@Component | |
| 38 | +public class RedisPushStreamCloseResponseListener implements MessageListener { | |
| 39 | + | |
| 40 | + private final static Logger logger = LoggerFactory.getLogger(RedisPushStreamCloseResponseListener.class); | |
| 41 | + | |
| 42 | + @Autowired | |
| 43 | + private IStreamPushService streamPushService; | |
| 44 | + | |
| 45 | + @Autowired | |
| 46 | + private IRedisCatchStorage redisCatchStorage; | |
| 47 | + | |
| 48 | + @Autowired | |
| 49 | + private IVideoManagerStorage storager; | |
| 50 | + | |
| 51 | + @Autowired | |
| 52 | + private ISIPCommanderForPlatform commanderFroPlatform; | |
| 53 | + | |
| 54 | + @Autowired | |
| 55 | + private UserSetting userSetting; | |
| 56 | + | |
| 57 | + @Autowired | |
| 58 | + private IMediaServerService mediaServerService; | |
| 59 | + | |
| 60 | + @Autowired | |
| 61 | + private ZLMRTPServerFactory zlmrtpServerFactory; | |
| 62 | + | |
| 63 | + | |
| 64 | + private Map<String, PushStreamResponseEvent> responseEvents = new ConcurrentHashMap<>(); | |
| 65 | + | |
| 66 | + public interface PushStreamResponseEvent{ | |
| 67 | + void run(MessageForPushChannelResponse response); | |
| 68 | + } | |
| 69 | + | |
| 70 | + @Override | |
| 71 | + public void onMessage(Message message, byte[] bytes) { | |
| 72 | + logger.info("[REDIS消息-推流结束]: {}", new String(message.getBody())); | |
| 73 | + MessageForPushChannel pushChannel = JSON.parseObject(message.getBody(), MessageForPushChannel.class); | |
| 74 | + StreamPushItem push = streamPushService.getPush(pushChannel.getApp(), pushChannel.getStream()); | |
| 75 | + if (push != null) { | |
| 76 | + if (redisCatchStorage.isChannelSendingRTP(push.getGbId())) { | |
| 77 | + List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId( | |
| 78 | + push.getGbId()); | |
| 79 | + if (sendRtpItems.size() > 0) { | |
| 80 | + for (SendRtpItem sendRtpItem : sendRtpItems) { | |
| 81 | + ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); | |
| 82 | + // 停止向上级推流 | |
| 83 | + String streamId = sendRtpItem.getStreamId(); | |
| 84 | + Map<String, Object> param = new HashMap<>(); | |
| 85 | + param.put("vhost","__defaultVhost__"); | |
| 86 | + param.put("app",sendRtpItem.getApp()); | |
| 87 | + param.put("stream",streamId); | |
| 88 | + param.put("ssrc",sendRtpItem.getSsrc()); | |
| 89 | + logger.info("[REDIS消息-推流结束] 停止向上级推流:{}", streamId); | |
| 90 | + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); | |
| 91 | + redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStreamId()); | |
| 92 | + zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); | |
| 93 | + | |
| 94 | + try { | |
| 95 | + commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem); | |
| 96 | + } catch (SipException | InvalidArgumentException | ParseException e) { | |
| 97 | + logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); | |
| 98 | + } | |
| 99 | + if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) { | |
| 100 | + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, | |
| 101 | + sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(), | |
| 102 | + sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId()); | |
| 103 | + messageForPushChannel.setPlatFormIndex(parentPlatform.getId()); | |
| 104 | + redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel); | |
| 105 | + } | |
| 106 | + } | |
| 107 | + } | |
| 108 | + } | |
| 109 | + } | |
| 110 | + | |
| 111 | + } | |
| 112 | + | |
| 113 | + public void addEvent(String app, String stream, PushStreamResponseEvent callback) { | |
| 114 | + responseEvents.put(app + stream, callback); | |
| 115 | + } | |
| 116 | + | |
| 117 | + public void removeEvent(String app, String stream) { | |
| 118 | + responseEvents.remove(app + stream); | |
| 119 | + } | |
| 120 | +} | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
| ... | ... | @@ -202,5 +202,10 @@ public interface IRedisCatchStorage { |
| 202 | 202 | void removeAllDevice(); |
| 203 | 203 | |
| 204 | 204 | void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online); |
| 205 | + | |
| 205 | 206 | void sendChannelAddOrDelete(String deviceId, String channelId, boolean add); |
| 207 | + | |
| 208 | + void sendPlatformStartPlayMsg(MessageForPushChannel messageForPushChannel); | |
| 209 | + | |
| 210 | + void sendPlatformStopPlayMsg(MessageForPushChannel messageForPushChannel); | |
| 206 | 211 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
| ... | ... | @@ -459,8 +459,8 @@ public interface DeviceChannelMapper { |
| 459 | 459 | "select * " + |
| 460 | 460 | "from wvp_device_channel " + |
| 461 | 461 | "where device_id=#{deviceId}" + |
| 462 | - " <if test='parentId != null '> and parent_id = #{parentId} </if>" + | |
| 463 | - " <if test='parentId == null '> and parent_id is null </if>" + | |
| 462 | + " <if test='parentId != null and parentId != deviceId'> and parent_id = #{parentId} </if>" + | |
| 463 | + " <if test='parentId == null or parentId == deviceId'> and parent_id is null or parent_id = #{deviceId}</if>" + | |
| 464 | 464 | " <if test='onlyCatalog == true '> and parental = 1 </if>" + |
| 465 | 465 | " </script>"}) |
| 466 | 466 | List<DeviceChannel> getSubChannelsByDeviceId(String deviceId, String parentId, boolean onlyCatalog); | ... | ... |
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
| ... | ... | @@ -622,4 +622,18 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { |
| 622 | 622 | // 使用 RedisTemplate<Object, Object> 发送字符串消息会导致发送的消息多带了双引号 |
| 623 | 623 | stringRedisTemplate.convertAndSend(key, msg.toString()); |
| 624 | 624 | } |
| 625 | + | |
| 626 | + @Override | |
| 627 | + public void sendPlatformStartPlayMsg(MessageForPushChannel msg) { | |
| 628 | + String key = VideoManagerConstants.VM_MSG_STREAM_START_PLAY_NOTIFY; | |
| 629 | + logger.info("[redis发送通知] 推流被上级平台观看 {}: {}/{}->{}", key, msg.getApp(), msg.getStream(), msg.getPlatFormId()); | |
| 630 | + redisTemplate.convertAndSend(key, JSON.toJSON(msg)); | |
| 631 | + } | |
| 632 | + | |
| 633 | + @Override | |
| 634 | + public void sendPlatformStopPlayMsg(MessageForPushChannel msg) { | |
| 635 | + String key = VideoManagerConstants.VM_MSG_STREAM_STOP_PLAY_NOTIFY; | |
| 636 | + logger.info("[redis发送通知] 上级平台停止观看 {}: {}/{}->{}", key, msg.getApp(), msg.getStream(), msg.getPlatFormId()); | |
| 637 | + redisTemplate.convertAndSend(key, JSON.toJSON(msg)); | |
| 638 | + } | |
| 625 | 639 | } | ... | ... |
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java
src/main/java/com/genersoft/iot/vmp/vmanager/rtp/RtpController.java
0 → 100644
| 1 | +package com.genersoft.iot.vmp.vmanager.rtp; | |
| 2 | + | |
| 3 | +import com.genersoft.iot.vmp.conf.SipConfig; | |
| 4 | +import com.genersoft.iot.vmp.conf.UserSetting; | |
| 5 | +import com.genersoft.iot.vmp.conf.VersionInfo; | |
| 6 | +import com.genersoft.iot.vmp.conf.exception.ControllerException; | |
| 7 | +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; | |
| 8 | +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; | |
| 9 | +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | |
| 10 | +import com.genersoft.iot.vmp.service.*; | |
| 11 | +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | |
| 12 | +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; | |
| 13 | +import io.swagger.v3.oas.annotations.Operation; | |
| 14 | +import io.swagger.v3.oas.annotations.Parameter; | |
| 15 | +import io.swagger.v3.oas.annotations.tags.Tag; | |
| 16 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 17 | +import org.springframework.beans.factory.annotation.Value; | |
| 18 | +import org.springframework.web.bind.annotation.GetMapping; | |
| 19 | +import org.springframework.web.bind.annotation.RequestMapping; | |
| 20 | +import org.springframework.web.bind.annotation.ResponseBody; | |
| 21 | +import org.springframework.web.bind.annotation.RestController; | |
| 22 | + | |
| 23 | +@SuppressWarnings("rawtypes") | |
| 24 | +@Tag(name = "第三方服务对接") | |
| 25 | + | |
| 26 | +@RestController | |
| 27 | +@RequestMapping("/api/rtp") | |
| 28 | +public class RtpController { | |
| 29 | + | |
| 30 | + @Autowired | |
| 31 | + private ZlmHttpHookSubscribe zlmHttpHookSubscribe; | |
| 32 | + | |
| 33 | + @Autowired | |
| 34 | + private IMediaServerService mediaServerService; | |
| 35 | + | |
| 36 | + @Autowired | |
| 37 | + private VersionInfo versionInfo; | |
| 38 | + | |
| 39 | + @Autowired | |
| 40 | + private SipConfig sipConfig; | |
| 41 | + | |
| 42 | + @Autowired | |
| 43 | + private UserSetting userSetting; | |
| 44 | + | |
| 45 | + @Autowired | |
| 46 | + private IDeviceService deviceService; | |
| 47 | + | |
| 48 | + @Autowired | |
| 49 | + private IDeviceChannelService channelService; | |
| 50 | + | |
| 51 | + @Autowired | |
| 52 | + private IStreamPushService pushService; | |
| 53 | + | |
| 54 | + | |
| 55 | + @Autowired | |
| 56 | + private IStreamProxyService proxyService; | |
| 57 | + | |
| 58 | + | |
| 59 | + @Value("${server.port}") | |
| 60 | + private int serverPort; | |
| 61 | + | |
| 62 | + | |
| 63 | + @Autowired | |
| 64 | + private IRedisCatchStorage redisCatchStorage; | |
| 65 | + | |
| 66 | + | |
| 67 | + @GetMapping(value = "/receive/open") | |
| 68 | + @ResponseBody | |
| 69 | + @Operation(summary = "开启收流和获取发流信息") | |
| 70 | + @Parameter(name = "isSend", description = "是否发送,false时只开启收流, true同时返回推流信息", required = true) | |
| 71 | + @Parameter(name = "callId", description = "整个过程的唯一标识,为了与后续接口关联", required = true) | |
| 72 | + @Parameter(name = "ssrc", description = "来源流的SSRC,不传则不校验来源ssrc", required = false) | |
| 73 | + @Parameter(name = "stream", description = "形成的流的ID", required = true) | |
| 74 | + @Parameter(name = "tcpMode", description = "收流模式, 0为UDP, 1为TCP被动", required = true) | |
| 75 | + @Parameter(name = "callBack", description = "回调地址,如果收流超时会通道回调通知,回调为get请求,参数为callId", required = true) | |
| 76 | + public SendRtpItem openRtpServer(Boolean isSend, String ssrc, String callId, String stream, Integer tcpMode, String callBack) { | |
| 77 | + MediaServerItem mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null); | |
| 78 | + if (mediaServerItem == null) { | |
| 79 | + throw new ControllerException(ErrorCode.ERROR100.getCode(),"没有可用的MediaServer"); | |
| 80 | + } | |
| 81 | + return null; | |
| 82 | + } | |
| 83 | + | |
| 84 | + @GetMapping(value = "/receive/close") | |
| 85 | + @ResponseBody | |
| 86 | + @Operation(summary = "关闭收流") | |
| 87 | + @Parameter(name = "stream", description = "流的ID", required = true) | |
| 88 | + public void closeRtpServer(String stream) { | |
| 89 | + | |
| 90 | + } | |
| 91 | + | |
| 92 | + @GetMapping(value = "/send/start") | |
| 93 | + @ResponseBody | |
| 94 | + @Operation(summary = "发送流") | |
| 95 | + @Parameter(name = "ssrc", description = "发送流的SSRC", required = true) | |
| 96 | + @Parameter(name = "ip", description = "目标IP", required = true) | |
| 97 | + @Parameter(name = "port", description = "目标端口", required = true) | |
| 98 | + @Parameter(name = "app", description = "待发送应用名", required = true) | |
| 99 | + @Parameter(name = "stream", description = "待发送流Id", required = true) | |
| 100 | + @Parameter(name = "callId", description = "整个过程的唯一标识,不传则使用随机端口发流", required = true) | |
| 101 | + @Parameter(name = "onlyAudio", description = "是否只有音频", required = true) | |
| 102 | + @Parameter(name = "streamType", description = "流类型,1为es流,2为ps流, 默认es流", required = false) | |
| 103 | + public void sendRTP(String ssrc, String ip, Integer port, String app, String stream, String callId, Boolean onlyAudio, Integer streamType) { | |
| 104 | + | |
| 105 | + } | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + @GetMapping(value = "/send/stop") | |
| 110 | + @ResponseBody | |
| 111 | + @Operation(summary = "关闭发送流") | |
| 112 | + @Parameter(name = "callId", description = "整个过程的唯一标识,不传则使用随机端口发流", required = true) | |
| 113 | + public void closeSendRTP(String callId) { | |
| 114 | + | |
| 115 | + } | |
| 116 | + | |
| 117 | +} | ... | ... |