From 013448842833ebed52ef13846572408b7095bf7b Mon Sep 17 00:00:00 2001 From: 648540858 <648540858@qq.com> Date: Fri, 2 Sep 2022 17:00:41 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8E=A8=E6=B5=81?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transmit/event/request/impl/SubscribeRequestProcessor.java | 2 -- .../com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java index 4b7629ce9..8c04368a1 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java @@ -211,7 +211,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme }else if (subscribeInfo.getExpires() == 0) { subscribeHolder.removeCatalogSubscribe(platformId); } - try { ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformId); responseXmlAck(evt, resultXml.toString(), parentPlatform); @@ -219,5 +218,4 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme e.printStackTrace(); } } - } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java index abff3eb97..0e656cffe 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java @@ -77,7 +77,7 @@ public interface StreamPushMapper { "1=1 " + " AND (st.app LIKE '%${query}%' OR st.stream LIKE '%${query}%' OR gs.gbId LIKE '%${query}%' OR gs.name LIKE '%${query}%') " + " AND (gs.gbId is null OR st.pushIng=1)" + - " AND st.pushIng=0" + + " AND (gs.pushIng is null OR st.pushIng=0) " + " AND st.mediaServerId=#{mediaServerId} " + "order by st.createTime desc" + " "}) From d5e8aa62a11352f228ba449b204d53d4e17897a5 Mon Sep 17 00:00:00 2001 From: 648540858 <648540858@qq.com> Date: Mon, 5 Sep 2022 17:10:21 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9=E6=B5=B7?= =?UTF-8?q?=E5=BA=B7=E5=B9=B3=E5=8F=B0=E5=BD=95=E5=83=8F=E5=9B=9E=E6=94=BE?= =?UTF-8?q?=E7=9A=84=E5=85=BC=E5=AE=B9=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=BD=95?= =?UTF-8?q?=E5=83=8F=E4=BF=A1=E6=81=AF=E5=8F=91=E9=80=81=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?,=20=E7=BA=A7=E8=81=94=E5=B9=B3=E5=8F=B0=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=BC=80=E5=90=AFrtcp=E4=BF=9D=E6=B4=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iot/vmp/gb28181/bean/ParentPlatform.java | 1 - .../gb28181/transmit/cmd/ISIPCommander.java | 2 +- .../transmit/cmd/impl/SIPCommander.java | 9 +- .../request/impl/AckRequestProcessor.java | 5 + .../request/impl/ByeRequestProcessor.java | 2 +- .../request/impl/InviteRequestProcessor.java | 3 +- .../iot/vmp/media/zlm/AssistRESTfulUtils.java | 2 +- .../vmp/media/zlm/ZLMHttpHookListener.java | 59 +++++++-- .../vmp/media/zlm/dto/StreamProxyItem.java | 10 ++ .../service/impl/MediaServerServiceImpl.java | 1 + .../vmp/service/impl/MediaServiceImpl.java | 1 - .../iot/vmp/service/impl/PlayServiceImpl.java | 115 +++++++++++++----- .../iot/vmp/storager/IRedisCatchStorage.java | 2 + .../storager/impl/RedisCatchStorageImpl.java | 18 +++ .../src/components/dialog/platformEdit.vue | 41 ++++--- 15 files changed, 202 insertions(+), 69 deletions(-) diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java index 00a16f99f..c8ab2e849 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java @@ -113,7 +113,6 @@ public class ParentPlatform { /** * RTCP流保活 - * TODO 预留, 暂不实现 */ @Schema(description = "RTCP流保活") private boolean rtcp; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java index 068d2dfc9..8aadf2c8a 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java @@ -103,7 +103,7 @@ public interface ISIPCommander { * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss */ - void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event errorEvent); + void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent); /** * 请求历史媒体下载 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java index f006eccd5..4d1e568bc 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java @@ -456,7 +456,7 @@ public class SIPCommander implements ISIPCommander { @Override public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String startTime, String endTime, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent, - SipSubscribe.Event errorEvent) { + SipSubscribe.Event okEvent,SipSubscribe.Event errorEvent) { try { logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort()); @@ -535,10 +535,11 @@ public class SIPCommander implements ISIPCommander { }); Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader, ssrcInfo.getSsrc()); - transmitRequest(device, request, errorEvent, okEvent -> { - ResponseEvent responseEvent = (ResponseEvent) okEvent.event; + transmitRequest(device, request, errorEvent, event -> { + ResponseEvent responseEvent = (ResponseEvent) event.event; streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.playback); - streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog); + streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), event.dialog); + okEvent.response(event); }); if (inviteStreamCallback != null) { inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream())); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java index c46f181d2..a56a83c21 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java @@ -115,6 +115,11 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In param.put("pt", sendRtpItem.getPt()); param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); + if (!sendRtpItem.isTcp() && parentPlatform.isRtcp()) { + // 开启rtcp保活 + param.put("udp_rtcp_timeout", "1"); + } + if (mediaInfo == null) { RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance( sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(), diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java index 889737696..706d42297 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java @@ -98,8 +98,8 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In param.put("ssrc",sendRtpItem.getSsrc()); logger.info("收到bye:停止向上级推流:" + streamId); MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); - zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null); + zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId); if (totalReaderCount <= 0) { logger.info("收到bye: {} 无其它观看者,通知设备停止推流", streamId); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java index 5286aa511..a6956dab5 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java @@ -563,6 +563,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline"); } else if ("push".equals(gbStream.getStreamType())) { if (!platform.isStartOfflinePush()) { + // 平台设置中关闭了拉起离线的推流则直接回复 responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel unavailable"); return; } @@ -599,7 +600,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements app, stream, channelId, mediaTransmissionTCP); if (sendRtpItem == null) { - logger.warn("服务器端口资源不足"); + logger.warn("上级点时创建sendRTPItem失败,可能是服务器端口资源不足"); try { responseAck(evt, Response.BUSY_HERE); } catch (SipException e) { diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java index 2d117543f..345ea1e70 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java @@ -50,7 +50,7 @@ public class AssistRESTfulUtils { if (mediaServerItem == null) { return null; } - if (mediaServerItem.getRecordAssistPort() > 0) { + if (mediaServerItem.getRecordAssistPort() <= 0) { logger.warn("未启用Assist服务"); return null; } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java index 78b59d90c..b78997427 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -19,8 +19,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.PostMapping; @@ -544,6 +542,8 @@ public class ZLMHttpHookListener { for (SendRtpItem sendRtpItem : sendRtpItems) { ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); + redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), + sendRtpItem.getCallId(), sendRtpItem.getStreamId()); } } } @@ -573,13 +573,19 @@ public class ZLMHttpHookListener { return ret; }else { StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, streamId); - if (streamProxyItem != null && streamProxyItem.isEnable_remove_none_reader()) { - ret.put("close", true); - streamProxyService.del(app, streamId); - String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url(); - logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", app, streamId, url); - }else { - ret.put("close", false); + if (streamProxyItem != null ) { + if (streamProxyItem.isEnable_remove_none_reader()) { + // 无人观看自动移除 + ret.put("close", true); + streamProxyService.del(app, streamId); + String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url(); + logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", app, streamId, url); + }else if (streamProxyItem.isEnable_disable_none_reader()) { + // 无人观看停用 + ret.put("close", true); + }else { + ret.put("close", false); + } } return ret; } @@ -626,7 +632,7 @@ public class ZLMHttpHookListener { @ResponseBody @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8") public JSONObject onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){ - + if (logger.isDebugEnabled()) { logger.debug("[ ZLM HOOK ]on_server_started API调用,参数:" + jsonObject.toString()); } @@ -649,6 +655,39 @@ public class ZLMHttpHookListener { return ret; } + /** + * 发送rtp(startSendRtp)被动关闭时回调 + */ + @ResponseBody + @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8") + public JSONObject onSendRtpStopped(HttpServletRequest request, @RequestBody JSONObject jsonObject){ + + logger.info("[ ZLM HOOK ]on_send_rtp_stopped API调用,参数:" + jsonObject); + + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + + // 查找对应的上级推流,发送停止 + String app = jsonObject.getString("app"); + if (!"rtp".equals(app)) { + return ret; + } + String stream = jsonObject.getString("stream"); + List sendRtpItems = redisCatchStorage.querySendRTPServerByStream(stream); + if (sendRtpItems.size() > 0) { + for (SendRtpItem sendRtpItem : sendRtpItems) { + ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); + commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); + redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), + sendRtpItem.getCallId(), sendRtpItem.getStreamId()); + } + } + + + return ret; + } + private Map urlParamToMap(String params) { HashMap map = new HashMap<>(); if (ObjectUtils.isEmpty(params)) { diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java index 897e9e3a0..f0d08a145 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java @@ -37,6 +37,9 @@ public class StreamProxyItem extends GbStream { private boolean enable_mp4; @Schema(description = "是否 无人观看时删除") private boolean enable_remove_none_reader; + + @Schema(description = "是否 无人观看时不启用") + private boolean enable_disable_none_reader; @Schema(description = "上级平台国标ID") private String platformGbId; @Schema(description = "创建时间") @@ -177,4 +180,11 @@ public class StreamProxyItem extends GbStream { this.enable_remove_none_reader = enable_remove_none_reader; } + public boolean isEnable_disable_none_reader() { + return enable_disable_none_reader; + } + + public void setEnable_disable_none_reader(boolean enable_disable_none_reader) { + this.enable_disable_none_reader = enable_disable_none_reader; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java index 37aeca0d6..64a411aa0 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java @@ -531,6 +531,7 @@ public class MediaServerServiceImpl implements IMediaServerService { param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex)); param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex)); param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrex)); + param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrex)); if (mediaServerItem.getRecordAssistPort() > 0) { param.put("hook.on_record_mp4",String.format("http://127.0.0.1:%s/api/record/on_record_mp4", mediaServerItem.getRecordAssistPort())); }else { diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java index 6288a1645..dd3800f2a 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java @@ -73,7 +73,6 @@ public class MediaServiceImpl implements IMediaService { }else { streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null); } - } } return streamInfo; diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java index 5ccc5e83c..aa019229e 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java @@ -291,7 +291,7 @@ public class PlayServiceImpl implements IPlayService { } logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse ); if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { - logger.info("[SIP 消息] SSRC修正 {}->{}", ssrc, ssrcInResponse); + logger.info("[点播消息] SSRC修正 {}->{}", ssrc, ssrcInResponse); if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { // ssrc 不可用 @@ -441,37 +441,92 @@ public class PlayServiceImpl implements IPlayService { resultHolder.exist(DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId, uuid); }, userSetting.getPlayTimeout()); + SipSubscribe.Event errorEvent = event -> { + dynamicTask.stop(playBackTimeOutTaskKey); + requestMessage.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg))); + playBackResult.setCode(ErrorCode.ERROR100.getCode()); + playBackResult.setMsg(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)); + playBackResult.setData(requestMessage); + playBackResult.setEvent(event); + playBackCallback.call(playBackResult); + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + }; + + InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> { + logger.info("收到回放订阅消息: " + inviteStreamInfo.getResponse().toJSONString()); + dynamicTask.stop(playBackTimeOutTaskKey); + StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId); + if (streamInfo == null) { + logger.warn("设备回放API调用失败!"); + playBackResult.setCode(ErrorCode.ERROR100.getCode()); + playBackResult.setMsg("设备回放API调用失败!"); + playBackCallback.call(playBackResult); + return; + } + redisCatchStorage.startPlayback(streamInfo, inviteStreamInfo.getCallId()); + WVPResult success = WVPResult.success(streamInfo); + requestMessage.setData(success); + playBackResult.setCode(ErrorCode.SUCCESS.getCode()); + playBackResult.setMsg(ErrorCode.SUCCESS.getMsg()); + playBackResult.setData(requestMessage); + playBackResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem()); + playBackResult.setResponse(inviteStreamInfo.getResponse()); + playBackCallback.call(playBackResult); + }; + cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack, - (InviteStreamInfo inviteStreamInfo) -> { - logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString()); - dynamicTask.stop(playBackTimeOutTaskKey); - StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId); - if (streamInfo == null) { - logger.warn("设备回放API调用失败!"); - playBackResult.setCode(ErrorCode.ERROR100.getCode()); - playBackResult.setMsg("设备回放API调用失败!"); - playBackCallback.call(playBackResult); - return; + hookEvent, eventResult -> { + if (eventResult.type == SipSubscribe.EventResultType.response) { + ResponseEvent responseEvent = (ResponseEvent)eventResult.event; + String contentString = new String(responseEvent.getResponse().getRawContent()); + // 获取ssrc + int ssrcIndex = contentString.indexOf("y="); + // 检查是否有y字段 + if (ssrcIndex >= 0) { + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容 + String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); + // 查询到ssrc不一致且开启了ssrc校验则需要针对处理 + if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { + return; + } + logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse ); + if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { + logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); + + if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { + // ssrc 不可用 + // 释放ssrc + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用"; + eventResult.statusCode = 400; + errorEvent.response(eventResult); + return; + } + + // 单端口模式streamId也有变化,需要重新设置监听 + if (!mediaServerItem.isRtpEnable()) { + // 添加订阅 + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId()); + subscribe.removeSubscribe(hookSubscribe); + hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase()); + subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response)->{ + logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString()); + dynamicTask.stop(playBackTimeOutTaskKey); + // hook响应 + onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId, uuid); + hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream())); + }); + } + // 关闭rtp server + mediaServerService.closeRTPServer(device.getDeviceId(), channelId, ssrcInfo.getStream()); + // 重新开启ssrc server + mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort()); + } + } } - redisCatchStorage.startPlayback(streamInfo, inviteStreamInfo.getCallId()); - WVPResult success = WVPResult.success(streamInfo); - requestMessage.setData(success); - playBackResult.setCode(ErrorCode.SUCCESS.getCode()); - playBackResult.setMsg(ErrorCode.SUCCESS.getMsg()); - playBackResult.setData(requestMessage); - playBackResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem()); - playBackResult.setResponse(inviteStreamInfo.getResponse()); - playBackCallback.call(playBackResult); - }, event -> { - dynamicTask.stop(playBackTimeOutTaskKey); - requestMessage.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg))); - playBackResult.setCode(ErrorCode.ERROR100.getCode()); - playBackResult.setMsg(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)); - playBackResult.setData(requestMessage); - playBackResult.setEvent(event); - playBackCallback.call(playBackResult); - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); - }); + + }, errorEvent); return result; } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java index 1f467e4eb..f66b30112 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java @@ -236,4 +236,6 @@ public interface IRedisCatchStorage { void sendStreamPushRequestedMsgForStatus(); List querySendRTPServerByChnnelId(String channelId); + + List querySendRTPServerByStream(String stream); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java index ecefe7373..9d30fef35 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java @@ -387,6 +387,24 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { return result; } + @Override + public List querySendRTPServerByStream(String stream) { + if (stream == null) { + return null; + } + String platformGbId = "*"; + String callId = "*"; + String channelId = "*"; + String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + userSetting.getServerId() + "_" + platformGbId + + "_" + channelId + "_" + stream + "_" + callId; + List scan = RedisUtil.scan(key); + List result = new ArrayList<>(); + for (Object o : scan) { + result.add((SendRtpItem) RedisUtil.get((String) o)); + } + return result; + } + @Override public List querySendRTPServer(String platformGbId) { if (platformGbId == null) { diff --git a/web_src/src/components/dialog/platformEdit.vue b/web_src/src/components/dialog/platformEdit.vue index ef28b4c72..763823249 100644 --- a/web_src/src/components/dialog/platformEdit.vue +++ b/web_src/src/components/dialog/platformEdit.vue @@ -37,13 +37,13 @@ + + + - - - @@ -79,7 +79,7 @@ - + @@ -98,6 +98,7 @@ + {{ @@ -251,21 +252,7 @@ export default { }, onSubmit: function () { - if (this.onSubmit_text === "保存") { - this.$confirm("修改目录结构会导致关联目录与通道数据被清空", '提示', { - dangerouslyUseHTMLString: true, - confirmButtonText: '确定', - cancelButtonText: '取消', - center: true, - type: 'warning' - }).then(() => { - this.saveForm() - }).catch(() => { - - }); - }else { - this.saveForm() - } + this.saveForm() }, saveForm: function (){ this.$axios({ @@ -343,6 +330,22 @@ export default { if (this.platform.enable && this.platform.expires == "0") { this.platform.expires = "300"; } + }, + rtcpCheckBoxChange: function (result){ + if (result) { + this.$message({ + showClose: true, + message: "开启RTCP保活需要上级平台支持,可以避免无效推流", + type: "warning", + }); + } + }, + treeTypeChange: function (){ + this.$message({ + showClose: true, + message: "修改目录结构会导致关联目录与通道数据被清空,保存后生效", + type: "warning", + }); } }, }; From e7bdcc1f8ddf95ca4b0bb08fbcd962a8d8a2b83a Mon Sep 17 00:00:00 2001 From: 648540858 <648540858@qq.com> Date: Tue, 6 Sep 2022 16:31:28 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E9=80=9A=E9=81=93=E6=97=B6=E9=BB=98=E8=AE=A4=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iot/vmp/gb28181/bean/ParentPlatform.java | 2 +- web_src/src/components/ParentPlatformList.vue | 3 ++- web_src/src/components/dialog/chooseChannel.vue | 7 +++++-- .../dialog/chooseChannelForCatalog.vue | 7 +++++-- web_src/src/components/dialog/getCatalog.vue | 16 ++++++++++++---- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java index c8ab2e849..b056cc712 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java @@ -53,7 +53,7 @@ public class ParentPlatform { /** * 设备国标编号 */ - @Schema(description = "11111") + @Schema(description = "设备国标编号") private String deviceGBId; /** diff --git a/web_src/src/components/ParentPlatformList.vue b/web_src/src/components/ParentPlatformList.vue index 0cd517dd7..61e93fc89 100644 --- a/web_src/src/components/ParentPlatformList.vue +++ b/web_src/src/components/ParentPlatformList.vue @@ -143,7 +143,8 @@ export default { }); }, chooseChannel: function(platform) { - this.$refs.chooseChannelDialog.openDialog(platform.serverGBId, platform.name, platform.catalogId, platform.treeType, this.initData) + console.log("platform.name: " + platform.name) + this.$refs.chooseChannelDialog.openDialog(platform.serverGBId,platform.deviceGBId, platform.name, platform.catalogId, platform.treeType, this.initData) }, initData: function() { this.getPlatformList(); diff --git a/web_src/src/components/dialog/chooseChannel.vue b/web_src/src/components/dialog/chooseChannel.vue index ad911e8ce..e0e79c3a3 100644 --- a/web_src/src/components/dialog/chooseChannel.vue +++ b/web_src/src/components/dialog/chooseChannel.vue @@ -8,7 +8,7 @@ - + @@ -60,6 +60,7 @@ export default { tabActiveName: "gbChannel", catalogTabActiveName: "catalog", platformId: "", + platformDeviceId: "", catalogId: "", catalogName: "", currentCatalogId: "", @@ -73,8 +74,10 @@ export default { }; }, methods: { - openDialog(platformId, platformName, defaultCatalogId, treeType, closeCallback) { + openDialog(platformId, platformDeviceId, platformName, defaultCatalogId, treeType, closeCallback) { + console.log("defaultCatalogId: " + defaultCatalogId) this.platformId = platformId + this.platformDeviceId = platformDeviceId this.platformName = platformName this.defaultCatalogId = defaultCatalogId this.showDialog = true diff --git a/web_src/src/components/dialog/chooseChannelForCatalog.vue b/web_src/src/components/dialog/chooseChannelForCatalog.vue index 4303a2f52..c634b77da 100644 --- a/web_src/src/components/dialog/chooseChannelForCatalog.vue +++ b/web_src/src/components/dialog/chooseChannelForCatalog.vue @@ -38,7 +38,7 @@ import catalogEdit from './catalogEdit.vue' export default { name: 'chooseChannelForCatalog', - props: ['platformId', 'platformName', 'defaultCatalogId', 'catalogIdChange', 'treeType'], + props: ['platformId', 'platformDeviceId', 'platformName', 'defaultCatalogId', 'catalogIdChange', 'treeType'], created() { this.chooseId = this.defaultCatalogId; this.defaultCatalogIdSign = this.defaultCatalogId; @@ -171,6 +171,7 @@ export default { }); }, loadNode: function(node, resolve){ + console.log("this.platformDeviceId: " + this.platformDeviceId) if (node.level === 0) { resolve([ { @@ -179,7 +180,7 @@ export default { type: -1 },{ name: this.platformName, - id: this.platformId, + id: this.platformDeviceId, type: 0 } ]); @@ -298,6 +299,8 @@ export default { return false; }, nodeClickHandler: function (data, node, tree){ + console.log(data) + console.log(node) this.chooseId = data.id; this.chooseName = data.name; if (this.catalogIdChange)this.catalogIdChange(this.chooseId, this.chooseName); diff --git a/web_src/src/components/dialog/getCatalog.vue b/web_src/src/components/dialog/getCatalog.vue index 62bacdb86..fdc26de16 100644 --- a/web_src/src/components/dialog/getCatalog.vue +++ b/web_src/src/components/dialog/getCatalog.vue @@ -77,6 +77,7 @@ export default { }, methods: { openDialog(catalogIdResult) { + console.log(this.chooseId) this.showDialog = true this.catalogIdResult = catalogIdResult }, @@ -107,9 +108,6 @@ export default { }, loadNode: function(node, resolve){ - - - if (node.level === 0) { this.$axios({ method:"get", @@ -124,7 +122,7 @@ export default { resolve([ { name: this.platformName, - id: this.platformId, + id: res.data.data.deviceGBId, type: 0 } ]); @@ -142,9 +140,19 @@ export default { this.chooseId = data.id; }, close: function() { + this.chooseId = null; this.showDialog = false; }, submit: function() { + console.log(this.chooseId) + if (this.chooseId === null) { + this.$message({ + showClose: true, + message: '未选择任何节点,', + type: 'warning' + }); + return; + } if (this.catalogIdResult)this.catalogIdResult(this.chooseId) this.showDialog = false; }, From 3884e9a56aeab7ac2ad7c9483059adc0df6683d9 Mon Sep 17 00:00:00 2001 From: jiang <893224616@qq.com> Date: Wed, 7 Sep 2022 15:56:48 +0800 Subject: [PATCH 4/5] =?UTF-8?q?1.=E6=A0=B9=E6=8D=AEredis=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=8E=A8=E6=B5=81=E5=88=97=E8=A1=A8=E6=97=B6?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E6=9B=B4=E6=96=B0=E5=9C=A8=E7=BA=BF=E7=8A=B6?= =?UTF-8?q?=E6=80=81=202.=E6=8E=A8=E6=B5=81=E5=88=97=E8=A1=A8=E7=9A=84?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E5=A2=9E=E5=8A=A0=E5=9C=A8=E7=BA=BF=E7=8A=B6?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/RedisPushStreamListMsgListener.java | 1 - .../impl/StreamPushUploadFileHandler.java | 2 +- .../storager/dao/PlatformGbStreamMapper.java | 4 ++-- .../vmp/vmanager/bean/StreamPushExcelDto.java | 15 +++++++++++++++ web_src/static/file/推流通道导入.zip | Bin 13599 -> 15168 bytes 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/RedisPushStreamListMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/impl/RedisPushStreamListMsgListener.java index d70ddf13b..23745eb82 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/RedisPushStreamListMsgListener.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/RedisPushStreamListMsgListener.java @@ -53,7 +53,6 @@ public class RedisPushStreamListMsgListener implements MessageListener { boolean contains = allAppAndStream.contains(app + stream); //不存在就添加 if (!contains) { - streamPushItem.setStatus(false); streamPushItem.setStreamType("push"); streamPushItem.setCreateTime(DateUtil.getNow()); streamPushItem.setMediaServerId(mediaServerService.getDefaultMediaServer().getId()); diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushUploadFileHandler.java b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushUploadFileHandler.java index c9b9579e7..3e13f48ec 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushUploadFileHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushUploadFileHandler.java @@ -116,7 +116,7 @@ public class StreamPushUploadFileHandler extends AnalysisEventListener " + "INSERT into platform_gb_stream " + - "(gbStreamId, platformId, catalogId) " + + "(gbStreamId, platformId, catalogId,status) " + "values " + " " + - "(${item.gbStreamId}, '${item.platformId}', '${item.catalogId}')" + + "(${item.gbStreamId}, '${item.platformId}', '${item.catalogId}'), '${item.status}')" + " " + "") int batchAdd(List streamPushItems); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamPushExcelDto.java b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamPushExcelDto.java index 956f6479b..dcec60768 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamPushExcelDto.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamPushExcelDto.java @@ -22,6 +22,9 @@ public class StreamPushExcelDto { @ExcelProperty("目录ID") private String catalogId; + @ExcelProperty("在线状态") + private boolean status; + public String getName() { return name; } @@ -70,4 +73,16 @@ public class StreamPushExcelDto { public void setCatalogId(String catalogId) { this.catalogId = catalogId; } + + public boolean isStatus() { + return status; + } + + public boolean getStatus() { + return status; + } + + public void setStatus(boolean status) { + this.status = status; + } } diff --git a/web_src/static/file/推流通道导入.zip b/web_src/static/file/推流通道导入.zip index fb95c613dbe6ab761dfe26ea37dd160ad153867f..495702f4b0785ef893b324878f591beaeb27f731 100644 GIT binary patch literal 15168 zcmb8WV~}o5kgol7_ipXBZQHhO+t_W}wzb=~ZQHhOo9BHe=EOG>-<*hZvVLS$td*7d zYelWP@|KqZ`GpJufB*mtW8{!&-tCIwk;sNCE($0>}VU!+kGPrA@s}oue0Yg9Wumv~E_8Zq9Z@0H75xj*9(lD78z7b$_GyX@v*PT`0X=j1Rq@DebV~;4*yM#;@Z^H+DZP=-vLFbUc zHsUuLc5YlSHOm02y_0EpREQouK z3Lc(k)jZ-gr&Z~ARr%$UMu`-N!e9ztq3Gk4Su-OcX}z!(XlEC^J67e+jgHE9S$>=i z;@2%DtM5OZ^yP=he-m!mw`u*N@GJ@!{Cb@myp!cE}m0B-sq7NSgADy~6tw zo!&(0DL79SNEF}0jgG}AUltuwomvW!7Rs$wtZGA<3_6ifsh6A@bsJ>%>YGsm@xcgj z>WDUu#msaXb-eEB1DshNHp1P^#}rioWCu-jWrZm*J}D-q)V!wt z4p*kamw;&q@u%G-li}g+rP}gxW+Qz@dh07IF^C}Z(fEMvr@s;j&)O$8yF)X+e#3u#)zfF98GuBhjg3(fS+S5HA`AFuCV01tgIzE-;@Gg>x?Ow zXAf)p=zMR2od8m(L^{M;(awx{^bOU~GZFXQ2paw5{=&N@vurbypjgY4L0do1tHz!m ze3qulnkgQsMHrcTcK!}k%fsf+_&o)=Us9{ zKOXz$YgVd5pH0kwys+%1mM4(b2!HLmX@#u#BC#)b0WQlJCX;bK`rKCohhT_!} za`F=c3bHqxIje>s^X3Z15kDM2p8A z_XK)9TqUGpZw34;X<@cNOIV#?DDWy={a)365^t&;CQ-75%V&`BhUVkWe#M-KUOEY!ZrR#w$Vj=_XMPBo&G^QT`=&Xs&Xifg0M zn~+h9A%2d1lAJomC94cV?ixMLVJLb)ilTc0xtzX^})h@Z$AYy5_J zQ2f(tzWuN5YGbJP-35~#jq#yk9XNu)p>|(zl!13@%;C%n$sk@DioZ?-GEces)xBuh z1Cxw@8H39?UVcZ^TrLz;!v;XITGCz}h)XF06>W{~wU*U358gdIf(#)F84-Dzum{cD9U<`0(L!zch{Pnt~))PaL5M8z{`8Y3F@8!PdW6y^S|2> z(yiDTEiR(Q2%#^@f%?-O2TRXLLD-b{Fg6i$yyya8g4Vfa^ez;`^JSA>V*n`sM>cnk)_nMgz^KB#EUQ1AE36K+{f9PCzBgAd>&H>;T>{^n3 zlY+EMwKg>AbVf#}LU(DA!QPn4ks)ewnV1qoO-~mja#v$P?l)i`y6iUv`Q%-y%x7N+ zRla6}mtH0H!?Un3k5Ly_tT@jU`{tVUZkC3|j@BLu+iw8|u#6h5N~#q3=;5$@(ka_u zG@DFw4l1|lRC6i!(H@{33L&pk@}3R+h141n&RL$(aB_4X>zU>7>R)hfGO8cFzTZDe z36=`L89anY8Mpy{k8+Sf=^1J4f^FZecb|qb7L!iKzh9y+^ttkkb4CEaiLz%=wJcF`p%Uu;7X$b1^kNLAmyVhs$q24(w`c;GLvXmfXQVtE2hgC0J zty45C|r)qG8b$kR>is-!3is zO7~9|$2Cs->qYYzU=%;3Vcu{?nOlaPLym;tYd>fw(<@rlcpf1`6XG6=ioC$xcTU@~ zs-B)FeKEUD6^>d8-v_QVfMzU7i8YTbu)l1#9E3&4N?$HfS~u4&R)j$jp(oLwCZU;~ zTD%sLzcb(dH62+foI`Wq+e}F2JLlTNZJ?t)P3_c?%k!bzqeMUg{3Hk%9Coh0>60eu zKP>Plv$LYjM>`BV4uX@}#-TAWm8;~e~WG)vKO`jZXB%Sfqi7sNq^e z5L;&xFz3o1x}@OWqSYFUw)-{s4nEtSE&;i&cSIYWTfRBQpCU$R0wSVgv$(1UBR&@< z5j&85s0;FQ8uX+!8yPiI&;?uuAKi(IKhcD% z#d3*FxuN!NmG<5^f3KOCl~3G?e;VVv@r?Buj7zla1fH4zw>X)yGzrFzD@d|xfI!SZWxriFbk)!UPYr+|2N6cT zt}D(0!p|%kU5&(-)OX6R6i_Q8jzyj0GhYbpG6J( zdY)6S2T!i48R(m@vOBhO*QU$XGSh*X2@pz}gNiX`r}#5Wk#FWGQ9O+oL7V%SD{rI& zYArY#;Z(yk$TA;*_{mAUSh9=~l{!4#YWNTQmPV3t=;t+MHa5toL7OU1sDDp@kZoVQ z^9#g|$!QLVKYkHpdq*+^&QGC6eop3CHg=a}!FA_Ue#*)gg5fQGI^>_-7>%KFkj7GBFrpVl1h4ej-*%xb;%cAm$DUWKsaA*J?kW{cYd1)z z>0g=L-vuyw#hKNd@TAht$d;_!W#FWO?C9S3`0P2hMbJQe=NU47=4GDWTO1ufzZ$*~ zqTe9P9bfC%lTs&e_{?N!7*!&Y#8=0@=w+f#1Y=3m8zH=HfZ3XaI8rMI*0YO&Jotq{aY7(+2S87bBO}Vu zL;$~_vmek(I7^>wGmS8`)D4z&__C6q%sD}Vy(AmaQ zLkPXEOPd21!-8vCUm_}#M$pT46m0|$4@3JTls}1>d(Tasj|K-JY4Y9|UcZ^v*Fy~G z*PEEec`c$|EW-}#2CcwgGE4eWh&`2CzofLjuo{AH*L*-o=nww-!_Y-+B8|xs?8@-v zHhdsQ%n~|L_=02J4#(SPAGexWpJVp!0?tA4CpL?HT#X!#mjS1+(#ns7MRCUwx{98p zz#f*W2TCqAV}tEw7S|5dp;pFLnaO5QNb$rfm-qzA;U>t3ggbmP^F%0WllX-`!DJn|E#jIPbG4?9hLvF1l8enFr4UAz$h`96i6*wCsYYo9OQV+6V zM5=pdhw|uU99Ko&B`ILc4-6Nf%eur)8|qdKi)h!lk9Eq*@JHA013iNw{`dza7Y1If@yymWeMDAtS2~^(8RjXz#*Oc(ns3C z1fZY`lt!KBTRxhA>zhccoPty$G@wYm56>G*1gcDx1F4^3%y_)-H-b|yp+;(}VnvYG zRL~~{=orN|t47}+X%B6pd=k!h|EtmhyOMOSVA8WV*|6O9G9t5ujZ~|k{;XE7Pl?)2 zj!*)wj##K>E5vmn47qr>SR*@pMoY#8oi;aprXS|k<32-M-1209o+o)>i@lLP+B-*m_cR^aVbHwzmr(`r;s?&(jBj!0QlPop)L`BFF@koz$zP22{T zm6npTZo-N>dWr&i<7*TYd2EZ?l>!feaR1|LE@8G@@NSj~+p@1e9XQbkt znlF?TcT7L;3*GaXBZgKkm#qWQ@quUWfhd{shZfR!c$VGY9G@J2I$uNK)GRkLuBXA> zuoisEe%!#T@YwZ00$bMt0XBWj`4;D<)_=WAJ$^=I#k6z zD#(kTYFd3g(rl}CebGMn5D~Iem`;12tVwbwEzp2gZH&gZofJ4mix3Tsw;Mq77}Fe0 zGLwrg0uwXk(Uz_XIT?tORLLqDLrZ9R=)f{SsWALD7?}?^X&aaSG`?k5#gZtTl*^49 z5h1Bg;5wCb+W9C{{tJ1)L^V!irIZQbOP+>~4vr#0+9!NEQ1kH#qoKRx|DbZEL zq4#y_6*@P6t5q6U=7?$5N*p^g;`WAa7!{ys!EVvy3TbCmaku68G81nL%t93LKE|5K z%uH$E=uarmB%3m79A4nk-Qc=`FCf=}gzu}bu`T=~ejAFtgwPFaf&;bmK|YRL%M>j5 zm5pJ$n5)ln6%Te64)}?gX}%NAvqE4WN55&t*iBQyC@J9^zk8{9$=(QBeaF?NVZbjZ zd_i;E_Ul^3=Gavh+A4nz%M3TncV?5bulWEWDX|I{ld%`A`S_%5k2uo@_YexCYRr~} z_(qL5%9&&Iku=sfqRwBORI@~pTc4q6%5EC7*^RiGUe)6=t<_W8S% z)Req``O~}cb~Nc>OlOeSVR&*#9Gu14#QefzP+DX1fs<8&Zy5<)2m{e@5&FUE%@rG$ zDCa3~mc1i5o80ACiUCG%9Oe66ACXpdS~>|+))MvncIUy(yDLfERcstFR`?j?1pjv} z=z}z421ISxbVSrQPxqxu^*#;N(ABEGFn>VF?+d8bpE28_fRta_$4LNeXFR#dB*(5( znjul^*g;`kL_DixT$bk1J`vo(5&yswNLM?oSQgDDDs_9&J062SJ$wZA=aT6F=Lw-i z+IK&=9D82^V)2aG{?FUyrHTp#K8Jerg%*pgCON{>U|e*6-Q1(&`WtlLji+^N3H3qh zahrJliAN3X6;vRgLKZ9a>OBURyKnkqSsP#zazI!$| z(6{!HQ_dn*wC%!qjq(^Kev~?;)AL5UVbew!cbrE!^!%_LcLz8YmO`*oAcLD|v#J;A z$Dz;?+;6{7lEKyHBZsmv>Vykq^fp?ic(n^P_15yft&3eSG}j`$jk;slZ1aDZ6&*ps z!t2tB2K$#U;k24!s*}KZfxz7x+dC(b9`$KXwA7mK{&mnOxX;oY}l66^A8hZSeAN_%O4tkJ4K$;A1$v)%2_N)9+p z3B3n9POuNvA17`i`Idk2bNM*%_U|j)UDph-M%1O1R(dA*8+}dh&xV-=CsJ`Mu3(M? znc!l9N$&_hKz))!wv14psh90rS}hMt>iZ}jZtIuyz(x{$Vno>?H|u+(*@yQ8JGHXX zqAYxjs|eY*XCGy&`xtvvYoycKWg4*BscX9`=&vkMyS>T~CkiB8X+dp4@+^x)x>trq zw1J1qnoKfz>%rZiV>k$=Na=PsKYUi^S=^zyHQb?iryscV!Dr+JN@u|5`D7Uf?-Q zH~sy0TA?L6vd-x5Y~glC-BFSO-bD(n1_C4;!8SRHnWWolhULXY%_R9umxD-Bz7^9l zqZm=WS=eL?7L$ZXI)7Gs&2d_7ZR=vgd3(0D~* z<2FudLK`t(d%~;uI5}BPw1D`p)dEX}RV)9XoSj~w-BPP*xY)u_Mn?8q@mva{-AWMI zf@iX>pl78E9gxoHPs0W!Q-_2K_(ahkJJ@wfeH|QYwF=bN$RI;kEi*Um(UrIY;NU?+ zg7h)h@{&!>de(5*n()F6Dl+Zd=Pj0k7=^3Ss){)mOskpUU?Ljd6s=2+rX8x88>pu# z6iS08LGG`ljozH%{q8X2~BZp*qcO_eUGANg0vroR4zu_ zbG`FEVoefigJLaZfpD;TBRalYedNW1QD(4ah?i(tB@$n16d^gDiy1d0)o z#RHPzg~~eQ>~QK!=mG|$BeCxQWm+?TIrh&LlJx8sYZk5!Ze_i)pg3hB?}WloH#_O& z#UJR++Jul_3~*7qd`T>yz9I`#bKkLUm(TY-9kV@x zNr-^fD|y?R?+%YIl$Aw5uWh5W22jwqCFaNRlE*_Tp;M3O577UIoJRjaPRKhg-AT9r zKs*HifC-@a@5pJqyZ3ASt9$D|u@eOVXlYUr@qfThCxz{Y-I2%1wa3xb{|TQ=;j0)Cm*?p90K!NtLELfSx9?DX4C`LwIms44{oj)3C>#{mHVB)Px5cQ#PL0KNPGz%5|37EBSoHUnjK>M*z^ zF}VVvBDDZS1c;m38yA3jtM@5ZKxp!7b1H_$@vlc<@@l0?x03uc1Qt#DaDH9nqQjly zON5KI*OeQdy5@8kKgUJ|{v{{x@`M^YbIzX_zlGB=p-Pw%oFpr&5j7q*JHS<~fVhJq z0QW=#De{4rfg!wU8u%2|f54qTe#>s-5sal)`VcR7?aM*>QR)j=J)yFTSS_?z7DQg| zl|7i}TBC;PReh@@ii4?Rr{ri)h^iA6l82I!sLp6!FoAie?b1L1&3?Ruk)K6RA729f ztD9U34=*4}6*`+zE>ARBB$I)2(RnHeRJTPt3$!#5@tX6-bd0hbqk+>jq9-3(AtP93 zcaG82&_cZx<%@os&24;5A<@lA?*%55`v_-;jV~T$j2TG%%zYPp$SvNS@!Yf=J}2Rk z*vzc;C0c!Drll(9-B)}}h*p7xNUqXCELE(yzC$oxkD;T8ns);AZk3}9c?MTjsn9>_ zMX1tCH5gCyLk)N3bq@&k))jn+$;S-z<208l_B-_XG?$cC6}H-{lCS2+dGd$t^#=z< zVm!hQd(~is04}K@OAsnbIR|^y*d>YP#(S64+4$5@wAHt>}fxr~}j@2$t)pih)+;R3?YVEXYUZSMvwI~tZXHs4*iCFABk91*pOJ3J@01hgfm-@^WU=bEl5sw zz%C*Jpw|QY&O!ensMT+h0DgAHO|`HIc+7AFcGTLo%yv9+l;el zLLyVpiMkcFbz=M`Hn{Mzo;45O*)cNK$0s{gYDLY4b9#d#%1C6!URVyX$XI>u)D6QX zQ-u2njBJ2VFcaOXU{umXD*GrRUNF}h?0$R0FBcMQc$Km9Zf5&l8uTt@9HM;zQ?m#`4S@*emmY1DXp)aNbEf~7(?}p~)HgtEI7-SD%ktSKrcfL; zOxM(}2G0rJ7*!WxI0%Hnl!T6%49&ws!n1!Xol%8mF^r#Qji9LC4dt*p*TYZTSjGvz ztpJNX6`zyjUgw)iMzUjX6-s^T!GF*Rgpx3X8aC`}zjVFR9AOOMJcaX5ARk!QNxL)d zmF$uSbHi?-G(J~nKS`3V`%=6>t;)&{8CtJ5zpl;V^`UqaS#6%=8+ZO`WfQc@*b;K3V?xfnF9^s1XldB>qYy zD{B@qt-0>UIqXBVM7#{xl6tubG)Td4&D%DmuuGlm02bL$h*WgmZ}m>6wn84%m6fI~ ztAGOeoO?PC!h;EB8bQmdjqk+`GN=?W4Y+&p=(Kn8$IFJFZJr-wJZ%4aY`HJx)32zB zQE9=7(y;JF+s1&gYD)gt>%2_NYTbN`1FgMIbe<8syaOwt#LCpl4x%g!yP8hb!)K%@ zC_&g=o9C8xYe@|p-Q9&vM2v>wzn zqJd<7&g^5UF4<&B4IwA42{1Acb4)J2E_?DR+`$&I$?k{>dz?_VVh5DQM$anEc=hFI zY-~{*`gPk`9&r5d_$j}_*22mI`n&dRG^nv7fve;`a>|2-1$V>%fYbqURL$PZXoLn_6Szle(7^$M-}vo%8KbD z$iP!g2p%Qa-NU`S>fsR{m~ALuJ zR$IS)FFvr6n~|Cho=gBUHHG{adN~8GN)ZYztc_s%FtJ2V{xF!K6DW~ODM?yQhY@X* z499hOd>c0w8+Q4Ir$x(eEbtka#2j;p=JIh4=NGKRv zTF#e~tMLA|dka=|WG6TF13?jIRxfUvheZ3EC8<&-L+!Wi+d_!S37>^hZXM1{^TQFX zr65EQo6CMyTP1$ECD~az+)f`N?aIcdw8EVyAn|I`xuK`k|c2}qzCpRg<0Fw zvu0z^@C=T>cc0Qc4&He|>8lrBr%5CdPbGZq z!{r?(jHF5m_A30+TCwYQRL33r@Ax2ZENkvRORo#xfleWidEU|>a5Pi#ADoo$rMYY- z-vlYR11r|9z#7_>VL6*O6&*Bpl~dVIxz2}K%tA^@@r@t>Mrv^l^cI=W8K$8Vk-&U8 zg1Qd55^vmXb?&S73aKv_V`j`VW?M96x4`R#weU+|;N+9d8?DuPH+j26Mr)*HInaQc zKbnH8_j?fKxeZWn_*p)oaa`Elh8s1Yll&)5GSan!>!9!HZz59Nr zfnD6dGrx`S%_C%P+FM7RyVr9rTrv$wU3%+%A$&7Kw4|~gUqQzGo8}wx_Gv|Ssgx2% z8>x64hov1JhBiAb)3dt&-1OVe-N1$k>HY06MTrbeJ3^ELCnfDHTm#FJ+4Shj6cf6J zCmpzmXM5CTHFU2e&_d_w{cKF-iy|elKC_qe{~rIv3C%_@idd>KEYm|EuNz!(AP>I> zf#CUExWa+4PU+meLGljL;5-Q%BI4FhGoMR7Yngr1UcD zZgbYOw}&Wd1Bzg1uE`&flCB6pmR&D?l5W8(_(tu2x=&`!U>wfmI48Ev`T;baZbw3buA#Nw)UJe|@-+s*p-S}+jNJy_P5M7}O>3-rOJEX5Cq7dR zG0%?l0qaDU!fd-ae6?;8egcU}c!_|X4AGo$EH!`rcorpAt5D8*Z-f_X=3HYG1NfrqsI%@j{t_sYxO}&&SK-<;lR`!w;TJM zd7J1^(whWN4Pn6})}N%8*k%&D>6(4S!S1JGDh>=Wv#f`INmVjwTgRUmg4C>ZB_F z;Aax`3lOF7NuBg5U}n>-8l(Et`rT&??g2J8==BO@WoCFMnT{U5Bhkn!p6OCHE-9(S zNEp>KK}+ovjWmU7-tnH6bxo8Svz(P=dM|q=d?+(%#W{7-$gk$(VZ*m{LczeUO1^8! ze9Sddob2aa{zjbHs#%ygsv&_|N1RGZb4-ryWO1^~K(>lvqVKgY#i5YG4kR!r3yW$E zTFM(rJ@q2{+T9uHE+w(Hw(%j+%-{21JEfj)VCdfs?9jF~AOHGXJil_oXaFsk{X3kI zscSJ>e@lCqLZ5(+w0bAkOY90yPNWSr%k*A?y7z4vdt^@X9+chxgld1P;Sk~V+YUJ1 ztj!q337z1wD4LUvTVUWiLS_|Ax9b{_ps2q`(!v8))M_i z&#fNy-WIcmM`mpx{Tr21V9}ssSZD_gw!Qq#%kJ3jhuH+@SNlDMmf(gIRBu>*>PY5a zzh!g0`t!z6a-h`Aj)uuqDQ9*zQ2C2V#pufddE_2cEKxbmXCk1*!24K&;+t(EQMagKpQiWLMaxU6v6}$r(*Z!l&dG*Gyly@|I!NeGT(7ohllud&rfut+hSW8W z>YXcN2b)~q;@Ij3g++dw-GLQ%Jm$CLI}bc(VmE;T_&B7L6Ajx;V@W9%~Xp_Qx@hJjrE#5UNP(sMq)u{{O* zL%OlgZ*qhYQsK5!#m5f)Up)w9FbS+n#f9)P>QD^?`Nk9z<9+kt`qCNCim}*qHb{a% z$D(fSysZaEs5W?(<;dN~Cr!^M^_7dM!agY%YzYY>BxjjRhF=LZDJs+LOYCOSc&o7% zF=Qs#rCNXbH5xXE*l&-i|2eCDv`P+}6DGs1uhg;Jc9}75w$mu)pZ0OVLrUW4X9>g&J6<%<#;z^hjT)GDs5G_IKcivCgv3vI;V@>lA|cx5o6E4Br4tQy&Gp<3z>T7= zZK{*N@2(LT>2~gFiXlIWA2mn~5QB&WlX}btt4H-(G~9c_R^^qS{v9e%{?pyQ8 z#j1@db0fN7n;MI^g_k;7a`fXvY&S2c>t&c_AE64 z1`CLl-KRh<8KQ3>{Lju?Vyt~O_V^m1>hX%djEZrDH#%4Lu8f`6`5&q%#jUJoy1Q%i z(-x{SwnbLV3=dGQ!xewMrJNe^iZj)}2kwAiTEBV?Wcw)3KbQTR<)`ZUwE1vKjFyXRYph33EWY zJ*-ongGy<&IYnmaHB8Xsi5Rxnic*JyNa#@lJrfFh<0Z%-JT+X2lrkX`&Z4eJnNNq- zM6A)8e<{x>5Wwj!R66{2+b|PU6<`vZ-;%(^+JPi%oeI?{te6-MLPo&Iel^nMLB7 zq@+W~Y7o zIlGg4!ZTP?dlo5)Qi0ExKA6+=rXu7rktTyr?JQ=!Y^PJ&YuQwle763I>Lsag?@Do- z`PYsQ(qUPpW@%kuRPeWg3oDY)a~(7+wgwd>w0V{#rRHZCBn z4+ud2jS!==)rnK7?F+WAuvOOzs(a}w%q2J)TQ?X&FeS~uq9(>&>fb$#FDKU1>;HR2 z_DWjSWFA21AaKS+c-t<7aUCTT_pS+>gnjn*_ZG^EONM>r4Bh>8e^#8qhy;-+YUa$}sM? z^4bZnV-(S@-@`JUAcs_FG+<@i9Q0P)VxUb@6LaI8ViVlhezt?SvkPW%LcxMdb-jr^ z&f5Nkb2uhMj~dys{s~1(al=FnSM*+oQ;{LTkz1bP`n@_QidZT->d_~LXwT4Ni~HAg zpF`}?o6-dFYg_K~iumhg>azxGaK|U!12G$TYIF2`FClrs9v;TEp{SCkrU88l7Id<= zXT^O!H_?UanWw25DzH0sIOtT1SvyP6m8`ib#66Pm_ynCpH}D`!`r)+FlvphBo}&6AN#V^6dC3r{ znYwNqJoPGTQs$I_=l_xd2XKh(0(kv7n!VF+qpy3*CnyzyFgFXQXUB1N6dJV*`u~8u zolSss@G@B|Euh;`*)>h_)8F_%_p(16_cdT7!t9NAtgWufOtmlY-VXV=nF>wOPKiLG zzQtz1WZ^VCH**RaS`UA_3}>SQmE_LB`eP%Csj<)@Z`>ux_d}4NG;)T}z4VAqngQV*$rB=YtrRrgMRY?r_#SGV|()m)RcRIYEkE zWrWIFk|Oss>)tq^^_8!ikzaD?=`50umxf!5OZ@tBTudgPh2fAT({+p(m=UuN#B1|& zEw{F_(vm-HbcV=>oiW26&!T2Df2Lq&GJ`r?_fw*)WjwdWR3Y8vN=X@G`J=(g_TY7o z8c5;-)OabA;vVe>uEE^gx4euws;~qC9cgGHzLQq`Q(rx6BqdpE9kZx zm_Z#oo<=1IUWS9X9535!4tgG7Q=7hp@YSudkv6V|@k-so00OcEtM6DvcBwn5QqW(& z4d%Q%p`}4xx^Brye=W69^Songcx$Qjd|}6O&N;_3$yS}BNu1toez=F9m)>%zyS5mWVX`rQV(3}LtfcRb^st<}x`b6aUC-|hRC@n9}R5+Zq~a{TOE{W*mlhmRh= zfdY^f2^2%lUGsFb6$^`0f6!KE?lWd>3M@%nhykBZ-F~a^8uZb!l{XffU!Q=vbLkNX zstLB+XAK1=v)T%6H7YKS=_F1Uzb4HA;pVWql+-W9uK2>pO_*aj`hHNmU@cr2`j@0Q z6qE$kj)eZ4pQg^SlCn|X%M8oJ<(MxTM@Uq1JSmcYtP%6A1QD2@)Q+|5=}W|i0YhrB zSL@z(s^+Hm2rHvm!pb0ExIlj{^Y-B(QJtvl0c?*>wRiHn{WntfKI4qXW0?w#l5apt z_1hX1j#>rX<_Zsu?RHOg*>!^l`dDyF&pvnLLZ)Vcx*M7#G&UpcZtzn?ibaz63z4ki zemg!KbgE9QSXi1`*Wkz2d5yTVewUZ_S50h1EC_!{U66Wd8-J$u8Z|zx>W}C6SB>+; z4}a%kn(~?H$jixxZ1ywlc7fQKf~wCsmQ0V6wC_Wwy>3QFz5i>L92aRm^LE5cUGGs} zeueDj2ajl%{L8(>IWn{{=$*O7u&n;~Gqy~L@nKnBKEsTXh8FYWYMUhwm{Y*2ngdiX z=7IiD9C0k#8T&gg{JRgz@h1&Et33T_qpt_UCz9pVx(6ec{s)N<)IR$aiG-v08f{-; zglN)T$!~DrpnNzBMDzziTYB~{Rc{MSTOEqhL(!)iDup0exKaLy#6H3nA`=c8N7yh8 zQy6wvR<&zhxL3~cvR#i&d>yc$2ZJoQ-#)qqH87kYf!!EQ4SkCATvewQOmX8ntvy9& zg6>#>BrTz(?@ll|y-d4*P-bLr5PuWX^HTX&O^1gpQ0RAVVU zUi4_r>u#`WVQ_ThHh#}{(rVrFdwyu`?_TIK`|(cG^O6d#`m*RSk(K;tUgx+KFaYc7 zk6hzpA`4xki%S&ei^V303p>=Qgf6($QOG1qNH`K-Y(tkHHnbL z=E=3?pwe-v&MJ7cL`ardAr$9JC4Iq#H_A0;=}>R*FnC2I^*{%QQOC-35JM5j(9hq7 z!_Gaa` z+mSF05Vpzvo2iz*flvA-rIJbxlDxAYYX#x?>+g?E`|hUd9EZL}QeJiD%J2f(J{ql2 zEl>h}y}iMtCW_HdJD4zPFscM(EFigJtY`%80rkGzY^O!R=8*cob?gp(YFCH*nyrUa z;Q93OVA{Y>&g8G_X{>E6!R){4U3??il8Rn>uGS%tem>xlW9sHC1WuI&7jvJxK>a-6!Fdyua_Sa|hHNy8FF8W$(Ki`#(XDXkCvFD0_o|olMl($&zk5t(VJy72( z`d1{Su%CI`+lt$r8O5Invr<5B51+=D%1jNom9HS)*A8CGSNYLO`*V(d@0)e%)NQKwTTOmX@t|*i_TvlWeGTS!_VO1Bm+?RG#O^&2KRk`!D8Wg9-WvaBwX>dv z6+Hxg5Wr{1#3C{RysvBl&v!d+Piyq`$;J;rUJ3}959I$O&m{gwh54_+`+W)g>;DJJ z{rjJupMMPqfDZ7}p^8)>Tm|xr(2n{K|DWrBli&gVPm)fl|A+_t$36`I%>QSB=YP$U zKAfi&*1rn$zdeBeC*uur&{IP?%wgE<8*%eILg@FNP z6R-p6M+qR6xFzV{+QJP0bNy8liRVtz^?FvSc#EEc^m)=^tuZSwCRh^?CNs-=;S03ZmlY| zOP16N)iz2C`rt5YaaRWx6j1=^DLMq7X(`+7-N0$z-*qbN7P037zu5eJj~wMy?(dd% zRm+~8SK^=Shu3EEwM% z3w!a{E-E;xa3mN zV~md67v=yQ)f@+my1or`9`gs8J42r4*2nLk&votI8p=opCcDD-^>&d*Wb!!+7Ka}p7ZBL zzRa9z1rX-t-a_;)fVDUxLC_OzO+0&ePc|&3yj}04O8HlawNZeUi@lGD^PfBQDlpnY zLWlK8c>Bhk7pqXUkb(s6EH@meqB&>?T!MFfyP;2V{;sLUPs80=QV%Sx1 zYmyNeZy%>{T(~+ycY8xhSdHdEI6a=|x+Jr$@7{rBkwvZ$@s+wJoKIIuk)3BecQhYW zKYFci&=E2a_&WVqQmn@8wUPUR^n;H_UuoFMq}!6hD_RVHI9fUBs6bi3< zlQfGXZ-7@?Abl^|&Gl!~vuyA5G=Jmulf5O+)9>i;$Nh0jG=jJaXp=ak56Cq3Bmp>< zNMedODVc7HnK(?KoQ5i}Z_mz9*{;CcZ%(`Q>5$a3%T{(h;**=^g2gbEA@+U9PXtDr&6R&ulk5sT1dgMjmyVr)qJ(q58k?+997N{ z?9BN)Cc6wTzFmJchnYc^P9r>zu+#2zL>>SJ5yN@mvI5tDXx^AZ7+raCO#oR>$;CTP zjOg{PMhL&d&|j}2 z#0vwuf*eV2c(+@0@sQ440#+|o-zSFoYMJC{V!wyx%`eu>86f}IN0g6J1%|iIGGP(? zf!!gj0^9EXSiZ;0J!k4!k;*8ZtJH9uZt$*l63$p)gr30F+P`;I$TWE{ZFx&S(X?+f zMyfjpb?eT<(2xwOVH+3tML`NE(pI+hK1uI%0}q~wJ6~1zBd(An6(DDln0S%3JJbVp<#)8gx-212?WP#Wa0V!Fif!hpo5wQc&h zmbop+`g&4#Osq|{VNI5QD|ZqHk2J59m?2XWG>k z+x=a?^bD)ge;CjfJ*3?;wD&f`9bI#7#%a<@Rp6VL2R0D7V5TJ5U}Z~q!oWbR_s8Ddc^9#=1>V7wZo!<> z&)8-|*DAqRv^eDU6nAI3cyyu9eEMP$U`oS-IPqOf{UIyJ`}D`Gs2dNt#3JZVHvXC7 zVqXz2t{NA=qsO$;a+YFG$jk9b>Gwl%eNCeXs5ilD|2R-E4q}|d|6xi#GH*ZSi zqBgHa;J8A1xuzp8<@nO&oCQ0RUo##O+m@|_$)@|dT>}Ek+1YRlowF_b(^jOj*y7~T za(wi9_WaG<#fCW_r!|c58%KIX$=D2=OzRZleN^X6ludOeT+Ik@P#CFE?m+8=GzJFz z>m1D9JBer)JuLQ1LLj(p-!L7_vwII`T7oVsF`-9Lz{0fNlMcFymDGo%kG zhz`{+WKr9L)P8SIScD|n6s3JzBp?vbu9SV}^fxR_HVtB`H7-0%MI~Ra^7>e1-Gb-0 zef`?hkt+F3zowgwjgw`@(9-+a-1)2W_!QZ*OAtqJ)=@y<@x;Z=dGu5{9|m{#yBwU3 z_|!`&^X~D=#+Ws72v6;Avx9{41ird{*`3Zo__kqH?9i6d$7<^*gLAlo%kSh3+67SY z@F~gcNoZUXrOni9){n1+TbNwDo2Dzx2oF6d$`C_0uAJ7YFp)l$+fGO@b*|m=HZ(d3 z-ndkugb!f^>wUCmPXkcghk5OVPrRWu50{mrG_;#PQCqaQV%RFInyi6MYr``{D(zb^ zFQn#AQH5yQ2{|ZMGt9ISvv9*Ak1~y>Cb0vQb3FE>DQZYOi&a2 z^tTYInY4zMBBX^zxX3}apyHQRF}$Ru`P;R5 zc)?mcHN`7(fRrl<*bhZZ>ax71xsYm+XM3)WM1^!jt?a{Ptn5_^?!CFdJeo8|*`*3~ z_D_mDzAqFbwlV(LaMxb5Y~Q@w2nTcQxvsF#WAQFZl^rc~J#pHOO8qZ2o@$n2&EitFR+<~aJB`yD>cecf_61D77)7k= z^*;Ppct(T%RoIRm(&%-951KzZH=5w2+~gagZE}i~eegxCs7=dSnY~nP4>)@LYl)A^F(#!f{xq&$7PW0zwGmS=OJO>dHeV`A zF(k-|STJCY^F9Y5QI-VI52pL;_Vb(HvF(*GZDUAG%-Sk2QaE3{@MEUV(-p|FISdKt z$Qh&wvH8h8NyJ{w)jksyM3pZ(n#~r!5`iPAxAZ&lWhkOH%>0fC7jHuGQ`Ou24Y|9a zzFPl2w~yO1747?1+P6$yOar2Wc7Gz?9zP4Z42IJso|>Gn;F5$V|21PPE_v_8NKhhY$0>h@#sE#E)U z370r4aMhwO^0S%BaL_Ug%aNDJP*_Ck*v68Kc3kAsGw|2IQfiUMI2lr7bV@O8mpf&< z6vnyzIBz7rfzG55CeiZX^@x}t&0;0{#m3#!>R3p z&o>%3t<8#~JLc)nmU#GcQ^MJ71CyXC|0>sJ@ znZvj{{K{(LVc7!ZPY#NT0d9o)1n%I7R0&RAxreVp?rZ~asJ_^i6|xZCA?;~a{*2hs z4f5Ot<3qu4qMtyJ0LZRUW1-@QAZq-KSw&3d&1=I(aT>ejndU zdnS>5TG%N#EcU#`YLF#W=&q))4q~~sDitxn9%UGX*s3#dGOg`cG>Xz%nRdqwDUlH) zWoT;M;?U)=l2lK}hAnE3*SNJn*r%HhY$h z9P%&^+={l`G)at8+7wO7P}2KYWyBs+dkKwh#V@II>RL36Vducc`qyxOjW0e)z>9TN zg3rt=;CssNET&y)FWMZ5pG$3&DR!g1FkkzOV(3Yws5&47^2RSIDPG z{VD7A5h0NGto@#+=E&|O!#|nj&VtYqMwLG!ts-69^M;l$wfQ+k$ahjt=zmqrmJSPs z#efr+O|e^=HU)N0Q>7EXizOP6kVy#Db}?1kTQ)i2S}WH-f4w$LU$wbY7h_a~XW@YW zAM;Rl^6iJ*xEO{CRl2w2{V{+02Iu{)TvM*j*s>}9oSt0C=}q~oKYqP52atC~ytK~I zegeAYHM}ewQM0NPl+H`|m1k06up9@lX<};?Sb>$JeKw|N@hw+_7zjX0ExqrVY}r$S zef7!pbikV!G3SHJSN49N4o5%lpVP&5>GHOp!f2bok?~F;xiBasOV&;lhraz4p~82p zmacWpvvNT}@S=goO(z@Cac({17SyL4RwGOC?i_&1ZSq6_eZ+;= zl@K@?v#5gKmhRzREI@zNebE-CczXDfovm;H*&~w)i|d2w;YJG9{gSShCjbPt1@zce zn9?Pu@@M)mG1Uomh@pZNx0@dkKe`Dzx)A@TG1U^GP}-b{?$TW8iuvWYLfhWTP+W#t zxy={6u+j434x9ZF=JvdnnP0#fE;vm9w24}@0S<&mQ3s0nkcg5G@WPD?{~o5!5Ddwm z1|F#ayuZY_FQtn<*rxT8=RJt~>+D;w9ss2HuH<`kzR}b+sFVO_9|#adBStV`>+)?d z%+=QSE_AGvO^Ay;WkZ(0C%8_mVWo)@x3z>@8rr=;w*9S_(`5Z$+XBYBP|bmEmaZcD zO^&h340D*;ybkuT#Pm#;@)eSXoIxj?a^CYw6!#r2N5x3EGvSuC+c=F3Cte^BLjHhE zltOVH3*hMZWJUN3YE_RvScSC}8oF<6L_?PEp;S_tx15{}YlwTYZqzWeB-qeKXJ|bh zVMt);2pIL6oFn7X_e6CDUr9SkbH^cH(lR+Rs5bOCd?$cBS0-(}P)hkRGkWn(!|axU zUl~7GoEy_>%$nk2&(8SAX|9|HAAlo|b^nZx2$c(}0W9_*lg0#{7JPBN9FoD@^-`Hu zNf}X>4A(qERCfBL&s#)8>IW2&sU@?r6ieavL=PI(XxbczsBrt z^)=ZT%Sxq+!g^z3f?v79Vgx8pDfIl=XIaxH zZ6YraFSF6YAwnSvPhPuWAW7?r&NDrbvVu*QpR&|1hNUE%PK)kD9iCF#WL)O0|T-05B0R5hm%PM+E}wPnclDV=8m z79hq;Wzom;TW2O1`($P}O5Xm8_XrKTlN6q3RmT#wf&l@57bgX#6d1Kfsau*eI#R&_B|~_G`X_h1QQ>%)ELC2Z&}3z0 z_+<;WP;K#1RHM`~kAPYpwbpXQANB^-J0Y&fou(O4Rb}fA%#NH~LswZ?`+5N3{ic>vnm?VST{+tk^+sp!3?X8vUs8 zYGgvG?o=s;t$rPdzLJ?M4`|fOeE6-kjL|2;nsdVTEQ5cH0)I2T)dajJHKRfGH@Ep0 z70*KMJUsx~X2w}m5hxDSx+&IQIOPG;jgHzdWZWx@q~1MEPlZpZDP=Dl348X{ee9rZ zcNKZk^C1IA)+aoJIx`-J$1B>d-Dm>jhM-gOcg8?q zVQOPhisyYBM;Um9&j~1VX>x+qY${PJj_rx=vVCuv`rd4jFq`3Km7Fl1 ze2^=lcP5RHUxgWJY)^k-K~Lx!?ggQG7!n@h0G}y~zgF}nB$>O-t`i;v-i|#(g*WWh zbTeo6OQHGxPFkfM-+;u;tZrqIqI#T9F%&r)Y~!`<)LK=ZPu}QnHp&v>trD{U^ya~K zk^AY@n-!lxeVsiraK*u9h(@oYl|3$M8zDOTge3l-{c6^M2eG81?yJ+5m=Ki=ie9gp zx}u{40e>-axY8b@;2IVxIMw=T@MVQs4`Yn{T?hamtCzcpBqxcIdvci?tEa1@myqM#CqNedltx0es?$3 zvb?HT7~vxtPY@Ed<>`@g9n=gA&3@=#5J@k%O}>K;U+;cZXt@O!@O~O^0VMGSTlJEEV z51+m#ga4qp*?6%5>15QnJ1K+zW{|hv0}1cqT^$t;Jhf1L(A92UgvUAo9*3fT-+q8; zvW=XH);Px#&Lnb-Usv^@H#&W;QP@NDJmdY+$CN?*1~dQIed+^v`Pv_{Xm&crKQGZK z9m%Vam@6Nfq4)}_#Hy_%!JfQlonU01aro7hK~kCB{#Yo%PL=5$$0RxEy6|N9^^q;V zKDsUzWFX*4X{o)D$aRGvMv8SmZ$_Ed_7aW0wU(7i}PYo~k3 zxT(NW(gJ)(twN;ej3;GG94!e(91((OxzHVoHH7jyP^Hc=tCfmLjYw@p$;M%%0))#I zgEa#g6~5=qQuqYb_ji!Czk}FZYff!1+T)z*yWG|$-g zTV0F9_7<$I^gfxprs{tE5c>wc>@+X?#$`p_0Y_B`-nQlziNdDKD_xp zPeV$(5SpM+kn&KkUw=Po?0_;tD-n)-bxM0(Zaq^URsud0R>ple^K!X*T?Koi3gUmp z3BTBl%zc?4UPT?g8(z4Teo}c$egy7*R6kydp6IWBxB2GR`Ii6WM0^$ZApF=4UH%9G z_T&>wL_P<1K7T0el|LO6e3d`C$v;?U%}{$E{`8`sy&z53`UpvW^KQSf9_*Oi`8?J6 zKhbk`|M*@XQTQ6h`BZ%62)~=yqaW=6iuGN5@uxn|2~r;A>!*IOSe|nsX#HPrbROil zb{eO8VK)fBb3B)Uh`#DI@9=ym5FA7n`L%t4|9i^K;`yM`s}AC9f(iuWf&m0X@}H;N zCq*5HJ(0)Bb;r>)|269l)&N()(L@XusN2ONXp{auw~H0PEf`4OBZ023gYL>f)1M7< zeSHlt7?_yXPZ$@tsjAD7UEQn9s$5%n?ELeh-dEOI>KB8fzX@}@ zICK7!)JS4B*aeoCyaeM@jrLg$v+|O-=oxP-4c89$OSq%|Xneqk@F8az}?Kra*#N^|7A*g$`&st8K;(`C69YbzFw&nfs>;6fq%|afvAs zW~3U(lrm7RE=D)O8(WHmJw*aE>^weHX!HAx06rTLjes;ZS)jgsCz4+gMbI9Fy)H2= zOj*D6R|_+jAi#3y88VHtZCu%1V*ILfFW&BSBFM7T2YYo$F+o%$fXtEXW+$HE6AI#Z z$6}yOn~p%uOuH^QzPcJMGLn@-#~JSv$?wcQZ_goM+iDa@LgdIHTvDrG>beprIVqT- z({v&1b}I`bvuh?Bk;{L|W_>$bTW14M5vjlL_E!b7U^p-%Y>Wd`wL|XFE>Y-kby166 zN6`Q}!*g;oZ>cCzYaC{g484up(L<&gHhY4(1RcefG3ZZ~x!0(R&vlA1JgUEfy0(i6b0%{C#vyY#Es!EQTx368M6=&N^V(Ak~cW`X9q8x zkaTKZ*<8!n`RCo(H1tg$D9mn8KEp~Q-B$Tlc1i#2pi;Y@?rg4hlTZ^&|AgF zU7>9a)*717@{1zR2*fQ%$pJlpi9>qDaLMhL^F(+|)O_NZ!Zng#2pN$)xeW4nleoky z@RP{imcl2Y@y=jT4e4QpvOvRadc#~+6i8WP_gnjg?h3ioSB)yu8z;$}%g{FQGa@9E z&$KNH*10c#ws|_}ab-6PE3`EL0P?mUD%_?W>Cx?eG-E?weGX5pzet+347OqbhTcEP zDM~%z%aY5PtymlrwGfW@{BsU$KOaRH&PT|mt4Ela7th5l zEDJORlVpxjMyZTynJ0RtuIEiV@2#|_HUWAHh%RzC6L_pz1AaOc#c;(y0~s%S59Yrl ztX*zcj)<1o{Su)!YE?b@Z6eVWt%1m#QSM z>u>4houXMaeTM$gkFaexD9Qxp~`gbTk16jBVQ!E_xa(Yl;jAYWPlyTTyR z4q-A6i!k6H*xaV#(XFyg4&z=QE$KM(NcG&&VQ&#nADW8&9zM(1{SV_eBne>d`9?KtlEn8h z!}9cQ75Yb!Xme5Sa%4UUr&i!d0LS{2qPre7FT4sb)`Z}nYV1%;=F z0oSBoQXNm1Z~-WJ{2dvoZKQBeL%xiQay;~kR1MpD=W(YHP%!VDVNu;`A+`C7LA*}A zYl;i+H7u8m%1hBa4#Q&Yd7aNC^HDIdD2bM&y&&nCynN!oeBd?NbBUf#<~8k%T|DYz z=atStsdM0ELS+GHE#-yOJhV|Ld$v>ylZsS<6*ZTnQN+&b;%NifyM8Y0jeN`wyo_?78BYubiThmg8 z4JvF*GF}qxnm~|br-Z#Uo$l!$K3A6`AF;r#*u%(XZ_c4zW|T10wK*_ySk-bVK)qx= zd%L-N=8$IxTF^-*39tgP8Z-=+m|1^Hl;j{}w^@WnVhhA#w;^;@I0w73z}<4o6K`Rn z>oPzT{>maFgsNNR^U#CHaQ44TzmHF^hr-{}D^TDGJ(^Z_64_U?7@~~STYA2rB=ZSj zjVY6CP-9$xIQ!@Sh7I%W0ug3e#XFJy4AJH@seybrYDI7me{0+1|3 zk#1eMHZ{q?-&b1TBDP@cN?_U<)9261HY?{gxtr9$YU|izW7*JliJz&+JYNrp_3Z47 z_H|}o_C}o;!DO^18#H(_Gw9y=($urG}22~ ze|<>qjNsV5D+*RZmzh+%&(k>J);XeVx7^CTZXCG67gJT$8}^#bj#*wNWKl>N#o~R} zp2kUdc&n8^Ui5p^nHja4?lMJJXfp;EL%c*l{S@B#Nu{3QpA^6f@*{nE z(M>9XSra-4-e~zzmO__C*EvQ~5kBc0cog|970Ss)Kybnom4(gHAVd*>WJXO0I#{ZW zB6bnF2o9(3{sm={p}%}LD5;j#k{VF*eL)|+aj%J1Q@)m-1DnE0PSlLU0NLHJa_`ws z(DyIFOq~f#b!%5JrGP~zR|uiuXG@Las%bq`0x7XSLbQGyH>PegL85Wy{GHy5nLLho z8Ms`#0kNRA9dWR0gD5}T@6G1NzmRu>r<@Kjkm6mHW7Vzat!j#ql9zLidD$0L8Eenl zW}S+ud~w2IEgi(JXL5s6?Y}9nlFib?zvQ+N*Ou(tSfn7CEe1FY2}6PP;Tqe6ak!*D zgHs|t8%yHjF+OrU<6BE82P}$`S~(u-iP?5>J{AgD8+(0lrLp+~Pcrj4$K~|a$?O1e zzsJbPnf)=utIwbjX#E=__Mo%;$2Yd}>mXx^Nhe|>lzKigxOuZyV!g=PnRgjG*Ai^~ ziWoOF8be1%mu&`Owj z2lVfju8=W!!fG~@k?YrhQf5m!k{LS)H}gHWi1Zc9aILk9fvC5xXxU$$0t{jLxr5P! zjY{o!^gb^{7-*>=&0&V|5tzEU?{7*9>gw?VH+bVrNYRy`&f8I@R8F=+mscn z`+yOIij&ODQQYXzMWh$mhiEx`D8R^`1A5a|9y`BkIm*oF8N(p_l?fAA`c|+?m5&^wmk@RUuRJ{RiR7#D|88FlX$W5Q)Y#x>k%+-y#p6t1sH%x}&z zWgN781{A#Y<)fg*L&wJ6mv9Ad3P+3KEV70q*pC&lEKOSw3$?2VBRpF4!ex=~sci|5$5gFT0 zqZ%eBP6*ST%2La=oUP1aw z*fL3vfmZw1TK^|Ho+^;I`$~1%?ecajUC_%$3Ep#@uJZ|3qzd+yeEf-e2(|<(D03V% z_m697B=f`7vzWh1%0$km90wTKT1dfL^)9QCq@ngV>Fl>;5(3eBuNt?*TXv_ZJd zElGflcC*3=AhmO)TT^LaU5nsiyDpi_U`L3>!SvojK1ib-fiL5ev^*&%e3gtzp;#Ua80LbwyCRY;%entT z0p*A;Mi^*aLI{s8OU9iQsLRMkZ;L@Z&k5o@4UOU4GYoTyd}ymcVX(FKvX9b?g_s&A zKqtmCrRP3B0shX3QbNUXM3GXdb60v@YG9;*(U*|AE&wT;kB3R8d{w+sy-%hHn8uAJ zu1hCM9)DZzN7akfCG>7Bs9k21P~CN6J`!k)ojW=0NTaMdcNN$+CyfSKZpb&XrIPix zevSONH1YGjPRcKZM*MqHD?7Vo-}yg*d|hPfR@xMG?wR-QSvDczyhfA!LX0=e>!UtI zrIXU~5RnRx^VzX^N2Iy#nO3Xony9$(L0|9&-p@OoRYr?EjA*hg4wY)@Hc3l~24U)VUxXWklNM>`5osBStVVuZ(6| zI{3XX_Yt!KoN4qIEF5Cj(InHwe|Z(MWs(wT$qHeT$KO*jbv8n{^OlJ12K)FNRRn%p zH5&&=YdF6fQlklsj0{9X$Z(IIV4RtLCbn}a8qaG`DX2C6Hp&n~%@&wRWEbB2GN?FF z#m6F;i!_JlR$!Vu?VoM;_X;A3ZVQGFe(I)^cFtG*OSghHT=gV7axwbSKqXx?+Hu9M$x0?-XZi8Fj1oGKL?+mIn z?T?pKY!lfjyy~q`bMAmD&Wv5ipKi`06NFPOMl%kuev9g5^6P4(9jp6XdL(4t1Sx$` zF^?WlC{ z!f(*sA%|-*D4ht=xyG_pJD&S1=qriTf`dUT_tkSv`}L@~_2LCEw(M z=yW3yZ5l}g7C}*+n;;J=A4x~RWLvF@1<5e)iI_eJl9x$0fOl0ub*99*?=^r`bmy(hcQCMoig zkQZy)U#uPEEL=Eag{K2=xxR$|Xz`iil{uNT${d+KlKsCB-zO)vDsmI_I29JeiEt54v5~JxHE}x-wSyc9YeI&Iaszb!K ziRB>mMu}#7@k&rl{tgzHE`#e*V5Ri1LWTvK>yAI68H)EFHVj(4AMW`|C9ea?|*h*|DA_{{~vk(qZj*sQ5)QP1v3Ak Z{XeQ84G!_ImjL}|Ndp4`^)UY{`(M(*M_T{@ From ec0ec5eb54723cc9aeabb4f313da2a101ab98bd2 Mon Sep 17 00:00:00 2001 From: 648540858 <648540858@qq.com> Date: Wed, 7 Sep 2022 16:18:35 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E7=82=B9=E6=92=AD=E6=97=B6=E5=8F=AF=E8=83=BD=E5=87=BA=E7=8E=B0?= =?UTF-8?q?=E7=9A=84rtpServer=E5=BC=80=E5=90=AF=E4=BD=86=E6=98=AF=E8=BF=98?= =?UTF-8?q?=E6=9C=AA=E6=94=B6=E5=88=B0=E6=B5=81=E7=9A=84=E6=83=85=E5=86=B5?= =?UTF-8?q?=EF=BC=8C=E7=BC=96=E7=A0=81=E7=B1=BB=E5=9E=8B136=EF=BC=8C137,13?= =?UTF-8?q?8=E9=BB=98=E8=AE=A4=E5=BC=80=E5=90=AF=E9=9F=B3=E9=A2=91?= =?UTF-8?q?=E9=80=9A=E9=81=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transmit/cmd/impl/SIPCommander.java | 4 +-- .../request/impl/InviteRequestProcessor.java | 18 +++++++---- .../iot/vmp/gb28181/utils/XmlUtil.java | 7 ++++- .../vmp/media/zlm/ZLMRTPServerFactory.java | 4 +++ .../iot/vmp/service/IMediaServerService.java | 2 ++ .../service/impl/MediaServerServiceImpl.java | 13 +++++++- .../iot/vmp/service/impl/PlayServiceImpl.java | 30 +++++++++++++------ .../vmp/storager/dao/DeviceChannelMapper.java | 14 +++++---- .../impl/VideoManagerStorageImpl.java | 20 ++++++------- .../iot/vmp/vmanager/user/UserController.java | 1 + src/main/resources/logback-spring-local.xml | 5 ++++ 11 files changed, 85 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java index 4d1e568bc..4c40f54d2 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java @@ -356,7 +356,7 @@ public class SIPCommander implements ISIPCommander { // String streamMode = device.getStreamMode().toUpperCase(); logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort()); - HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtmp", mediaServerItem.getId()); + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId()); subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{ if (event != null) { event.response(mediaServerItemInUse, json); @@ -524,7 +524,7 @@ public class SIPCommander implements ISIPCommander { CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId() : udpSipProvider.getNewCallId(); - HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtmp", mediaServerItem.getId()); + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId()); // 添加订阅 subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{ if (hookEvent != null) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java index a6956dab5..82b3ba407 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java @@ -16,9 +16,7 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager; import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; -import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; -import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; -import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; +import com.genersoft.iot.vmp.media.zlm.dto.*; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IPlayService; import com.genersoft.iot.vmp.service.IStreamProxyService; @@ -90,6 +88,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements @Autowired private IMediaServerService mediaServerService; + @Autowired + private ZlmHttpHookSubscribe zlmHttpHookSubscribe; + @Autowired private SIPProcessorObserver sipProcessorObserver; @@ -400,7 +401,14 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements if (playTransaction != null) { Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream()); if (!streamReady) { - playTransaction = null; + boolean hasRtpServer = mediaServerService.checkRtpServer(mediaServerItem, "rtp", playTransaction.getStream()); + if (hasRtpServer) { + logger.info("[上级点播]已经开启rtpServer但是尚未收到流,开启监听流的到来"); + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", playTransaction.getStream(), true, "rtsp", mediaServerItem.getId()); + zlmHttpHookSubscribe.addSubscribe(hookSubscribe, hookEvent); + }else { + playTransaction = null; + } } } if (playTransaction == null) { @@ -564,7 +572,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } else if ("push".equals(gbStream.getStreamType())) { if (!platform.isStartOfflinePush()) { // 平台设置中关闭了拉起离线的推流则直接回复 - responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel unavailable"); + responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel stream not pushing"); return; } // 发送redis消息以使设备上线 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java index 733f78a2e..2d568a1cb 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java @@ -203,6 +203,12 @@ public class XmlUtil { return null; } deviceChannel.setChannelId(channelId); + int channelTypeCode = Integer.parseInt(channelId.substring(10, 13)); + if (channelTypeCode == 136 || channelTypeCode == 137 || channelTypeCode == 138) { + deviceChannel.setHasAudio(true); + }else { + deviceChannel.setHasAudio(false); + } if (event != null && !event.equals(CatalogEvent.ADD) && !event.equals(CatalogEvent.UPDATE)) { // 除了ADD和update情况下需要识别全部内容, return deviceChannel; @@ -396,7 +402,6 @@ public class XmlUtil { } else { deviceChannel.setPTZType(Integer.parseInt(XmlUtil.getText(itemDevice, "PTZType"))); } - deviceChannel.setHasAudio(true); // 默认含有音频,播放时再检查是否有音频及是否AAC return deviceChannel; } } \ No newline at end of file diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java index 6c70096e6..6caff71c5 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java @@ -96,6 +96,10 @@ public class ZLMRTPServerFactory { if(rtpInfo.getInteger("code") == 0){ if (rtpInfo.getBoolean("exist")) { result = rtpInfo.getInteger("local_port"); + if (result == 0) { + // 此时说明rtpServer已经创建但是流还没有推上来 + + } return result; } }else if(rtpInfo.getInteger("code") == -2){ diff --git a/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java b/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java index f1163cae0..55a4005fd 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java @@ -83,4 +83,6 @@ public interface IMediaServerService { MediaServerItem getDefaultMediaServer(); void updateMediaServerKeepalive(String mediaServerId, JSONObject data); + + boolean checkRtpServer(MediaServerItem mediaServerItem, String rtp, String stream); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java index 64a411aa0..702967d64 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java @@ -147,9 +147,11 @@ public class MediaServerServiceImpl implements IMediaServerService { if (streamId == null) { streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase(); } - int rtpServerPort = mediaServerItem.getRtpProxyPort(); + int rtpServerPort; if (mediaServerItem.isRtpEnable()) { rtpServerPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(ssrc):0, port); + } else { + rtpServerPort = mediaServerItem.getRtpProxyPort(); } RedisUtil.set(key, mediaServerItem); return new SSRCInfo(rtpServerPort, ssrc, streamId); @@ -681,4 +683,13 @@ public class MediaServerServiceImpl implements IMediaServerService { } } } + + @Override + public boolean checkRtpServer(MediaServerItem mediaServerItem, String app, String stream) { + JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, stream); + if(rtpInfo.getInteger("code") == 0){ + return rtpInfo.getBoolean("exist"); + } + return false; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java index aa019229e..5abb34254 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java @@ -164,17 +164,30 @@ public class PlayServiceImpl implements IPlayService { JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId); if(rtpInfo.getInteger("code") == 0){ if (rtpInfo.getBoolean("exist")) { + int localPort = rtpInfo.getInteger("local_port"); + if (localPort == 0) { + logger.warn("[点播],点播时发现rtpServerC存在,但是尚未开始推流"); + // 此时说明rtpServer已经创建但是流还没有推上来 + WVPResult wvpResult = new WVPResult(); + wvpResult.setCode(ErrorCode.ERROR100.getCode()); + wvpResult.setMsg("点播已经在进行中,请稍候重试"); + msg.setData(wvpResult); - WVPResult wvpResult = new WVPResult(); - wvpResult.setCode(ErrorCode.SUCCESS.getCode()); - wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); - wvpResult.setData(streamInfo); - msg.setData(wvpResult); + resultHolder.invokeAllResult(msg); + return playResult; + }else { + WVPResult wvpResult = new WVPResult(); + wvpResult.setCode(ErrorCode.SUCCESS.getCode()); + wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); + wvpResult.setData(streamInfo); + msg.setData(wvpResult); - resultHolder.invokeAllResult(msg); - if (hookEvent != null) { - hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo))); + resultHolder.invokeAllResult(msg); + if (hookEvent != null) { + hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo))); + } } + }else { redisCatchStorage.stopPlay(streamInfo); storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); @@ -187,7 +200,6 @@ public class PlayServiceImpl implements IPlayService { streamInfo = null; } - } if (streamInfo == null) { String streamId = null; diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java index bcebb943f..25745c4c1 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java @@ -143,15 +143,12 @@ public interface DeviceChannelMapper { @Update(value = {"UPDATE device_channel SET status=0 WHERE deviceId=#{deviceId}"}) void offlineByDeviceId(String deviceId); - @Update(value = {"UPDATE device_channel SET status=1 WHERE deviceId=#{deviceId} AND channelId=#{channelId}"}) - void online(String deviceId, String channelId); - @Insert("") int batchAdd(List addChannels); + @Update(value = {"UPDATE device_channel SET status=1 WHERE deviceId=#{deviceId} AND channelId=#{channelId}"}) + void online(String deviceId, String channelId); + @Update({"