diff --git a/DOCKERFILE b/DOCKERFILE index d55e06a74..f2a2ffa01 100644 --- a/DOCKERFILE +++ b/DOCKERFILE @@ -1,3 +1,6 @@ +#很久没维护了,已经与定前版本不匹配 + + FROM ubuntu:20.04 AS build ARG DEBIAN_FRONTEND=noninteractive diff --git a/README.md b/README.md index 7ba443fe1..1c5f868ba 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,8 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 云端录像(需要部署单独服务配合使用) - [X] 多流媒体节点,自动选择负载最低的节点使用。 - [X] WEB端支持播放H264与H265,音频支持G.711A/G.711U/AAC,覆盖国标常用编码格式。 +- [X] 支持电子地图。 +- [X] 支持接入WGS84和GCJ02两种坐标系。 [//]: # (# docker快速体验) @@ -143,7 +145,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git # 合作 目前很多打着合作的幌子来私聊的,其实大家大可不必,目前作者没有精力,你有问题可以付费找我解答,也可以提PR -,如果对代码有建议可以提ISSUE;也可以加群一起聊聊。我们欢迎所有有兴趣但遇到项目中来的人。 +,如果对代码有建议可以提ISSUE;也可以加群一起聊聊。我们欢迎所有有兴趣参与到项目中来的人。 @@ -163,6 +165,7 @@ QQ私信一般不回, 精力有限.欢迎大家在群里讨论.觉得项目对 [hotcoffie](https://github.com/hotcoffie) [xiaomu](https://github.com/nikmu) [TristingChen](https://github.com/TristingChen) [chenparty](https://github.com/chenparty) [Hotleave](https://github.com/hotleave) [ydwxb](https://github.com/ydwxb) [ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666) +[mk1990](https://github.com/mk1990) ps: 刚增加了这个名单,肯定遗漏了一些大佬,欢迎大佬联系我添加。 diff --git a/pom.xml b/pom.xml index 1546e39cc..12091242e 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.genersoft wvp-pro - 2.2.1 + 2.3.1 web video platform 国标28181视频平台 @@ -159,9 +159,10 @@ com.alibaba fastjson - 1.2.73 + 1.2.83 + com.squareup.okhttp3 @@ -180,9 +181,9 @@ - com.burgstaller + io.github.rburgst okhttp-digest - 2.1 + 2.5 diff --git a/sql/mysql.sql b/sql/mysql.sql index 4086d11c8..5e8467f30 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -471,6 +471,7 @@ CREATE TABLE `stream_push` ( `createStamp` bigint(20) DEFAULT NULL, `aliveSecond` int(11) DEFAULT NULL, `mediaServerId` varchar(50) DEFAULT NULL, + `serverId` varchar(50) not NULL, PRIMARY KEY (`id`), UNIQUE KEY `stream_push_pk` (`app`,`stream`) ) ENGINE=InnoDB AUTO_INCREMENT=300838 DEFAULT CHARSET=utf8mb4; diff --git a/sql/update.sql b/sql/update.sql index 8bfe343af..b7e98f86a 100644 --- a/sql/update.sql +++ b/sql/update.sql @@ -1,5 +1,29 @@ +alter table parent_platform + add startOfflinePush int default 0 null; + +alter table parent_platform + add administrativeDivision varchar(50) not null; + +alter table parent_platform + add catalogGroup int default 1 null; + alter table device add audioChannelForReceive VARCHAR(50) null; alter table device - add audioChannelForSend VARCHAR(50) null; \ No newline at end of file + add audioChannelForSend VARCHAR(50) null; + +alter table stream_push + add serverId varchar(50) not null; +alter table device + add geoCoordSys varchar(50) not null; +update device set device.geoCoordSys='WGS84'; +alter table device_channel + add longitudeGcj02 double default null; +alter table device_channel + add latitudeGcj02 double default null; +alter table device_channel + add longitudeWgs84 double default null; +alter table device_channel + add latitudeWgs84 double default null; + diff --git a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java index eb98f6f28..57f764b57 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java +++ b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java @@ -97,4 +97,5 @@ public class VideoManagerConstants { //************************** 第三方 **************************************** public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_"; public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_"; + } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java b/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java index 3b021de41..ade2e6220 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java @@ -103,12 +103,12 @@ public class DynamicTask { public void stop(String key) { if (futureMap.get(key) != null && !futureMap.get(key).isCancelled()) { - futureMap.get(key).cancel(true); - Runnable runnable = runnableMap.get(key); - if (runnable instanceof ISubscribeTask) { - ISubscribeTask subscribeTask = (ISubscribeTask) runnable; - subscribeTask.stop(); - } +// Runnable runnable = runnableMap.get(key); +// if (runnable instanceof ISubscribeTask) { +// ISubscribeTask subscribeTask = (ISubscribeTask) runnable; +// subscribeTask.stop(); +// } + futureMap.get(key).cancel(false); } } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java index 85f468491..c24e0cad5 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java @@ -6,6 +6,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.regex.Pattern; + @Configuration("mediaConfig") public class MediaConfig{ @@ -161,7 +165,18 @@ public class MediaConfig{ if (StringUtils.isEmpty(sdpIp)){ return ip; }else { - return sdpIp; + if (isValidIPAddress(sdpIp)) { + return sdpIp; + }else { + // 按照域名解析 + String hostAddress = null; + try { + hostAddress = InetAddress.getByName(sdpIp).getHostAddress(); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + return hostAddress; + } } } @@ -211,4 +226,11 @@ public class MediaConfig{ return mediaServerItem; } + private boolean isValidIPAddress(String ipAddress) { + if ((ipAddress != null) && (!ipAddress.isEmpty())) { + return Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$", ipAddress); + } + return false; + } + } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java index 6b45eb5d7..ec1f9bac6 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java @@ -2,7 +2,9 @@ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.service.impl.RedisAlarmMsgListener; -import com.genersoft.iot.vmp.service.impl.RedisGPSMsgListener; +import com.genersoft.iot.vmp.service.impl.RedisGpsMsgListener; +import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener; +import com.genersoft.iot.vmp.service.impl.RedisStreamMsgListener; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -47,11 +49,17 @@ public class RedisConfig extends CachingConfigurerSupport { private int poolMaxWait; @Autowired - private RedisGPSMsgListener redisGPSMsgListener; + private RedisGpsMsgListener redisGPSMsgListener; @Autowired private RedisAlarmMsgListener redisAlarmMsgListener; + @Autowired + private RedisStreamMsgListener redisStreamMsgListener; + + @Autowired + private RedisGbPlayMsgListener redisGbPlayMsgListener; + @Bean public JedisPool jedisPool() { if (StringUtils.isBlank(password)) { @@ -98,6 +106,8 @@ public class RedisConfig extends CachingConfigurerSupport { container.setConnectionFactory(connectionFactory); container.addMessageListener(redisGPSMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_GPS)); container.addMessageListener(redisAlarmMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE)); + container.addMessageListener(redisStreamMsgListener, new PatternTopic(VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + "PUSH")); + container.addMessageListener(redisGbPlayMsgListener, new PatternTopic(RedisGbPlayMsgListener.WVP_PUSH_STREAM_KEY)); return container; } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java b/src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java index 9690c6d1e..2d7e8a1b0 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java @@ -11,6 +11,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +/** + * @author lin + */ @Component public class LoginSuccessHandler implements AuthenticationSuccessHandler { diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java index 47cfdaba4..a4bbdbac6 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java @@ -20,6 +20,7 @@ import java.util.List; /** * 配置Spring Security + * @author lin */ @Configuration @EnableWebSecurity @@ -132,15 +133,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .anyRequest().authenticated() // 异常处理(权限拒绝、登录失效等) .and().exceptionHandling() - .authenticationEntryPoint(anonymousAuthenticationEntryPoint)//匿名用户访问无权限资源时的异常处理 + //匿名用户访问无权限资源时的异常处理 + .authenticationEntryPoint(anonymousAuthenticationEntryPoint) // .accessDeniedHandler(accessDeniedHandler)//登录用户没有权限访问资源 - // 登入 - .and().formLogin().permitAll()//允许所有用户 - .successHandler(loginSuccessHandler)//登录成功处理逻辑 - .failureHandler(loginFailureHandler)//登录失败处理逻辑 + // 登入 允许所有用户 + .and().formLogin().permitAll() + //登录成功处理逻辑 + .successHandler(loginSuccessHandler) + //登录失败处理逻辑 + .failureHandler(loginFailureHandler) // 登出 - .and().logout().logoutUrl("/api/user/logout").permitAll()//允许所有用户 - .logoutSuccessHandler(logoutHandler)//登出成功处理逻辑 + .and().logout().logoutUrl("/api/user/logout").permitAll() + //登出成功处理逻辑 + .logoutSuccessHandler(logoutHandler) .deleteCookies("JSESSIONID") // 会话管理 // .and().sessionManagement().invalidSessionStrategy(invalidSessionHandler) // 超时处理 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java index a095f5140..c6406c5b1 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java @@ -1,6 +1,10 @@ package com.genersoft.iot.vmp.gb28181.bean; +/** + * 国标设备/平台 + * @author lin + */ public class Device { /** @@ -127,7 +131,12 @@ public class Device { /** * 是否开启ssrc校验,默认关闭,开启可以防止串流 */ - private boolean ssrcCheck; + private boolean ssrcCheck = true; + + /** + * 地理坐标系, 目前支持 WGS84,GCJ02 TODO CGCS2000 + */ + private String geoCoordSys; public String getDeviceId() { @@ -322,4 +331,12 @@ public class Device { this.ssrcCheck = ssrcCheck; } + public String getGeoCoordSys() { + return geoCoordSys; + } + + public void setGeoCoordSys(String geoCoordSys) { + this.geoCoordSys = geoCoordSys; + } + } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java index 95576f387..63452775c 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java @@ -154,6 +154,26 @@ public class DeviceChannel { */ private double latitude; + /** + * 经度 GCJ02 + */ + private double longitudeGcj02; + + /** + * 纬度 GCJ02 + */ + private double latitudeGcj02; + + /** + * 经度 WGS84 + */ + private double longitudeWgs84; + + /** + * 纬度 WGS84 + */ + private double latitudeWgs84; + /** * 子设备数 */ @@ -407,6 +427,38 @@ public class DeviceChannel { this.latitude = latitude; } + public double getLongitudeGcj02() { + return longitudeGcj02; + } + + public void setLongitudeGcj02(double longitudeGcj02) { + this.longitudeGcj02 = longitudeGcj02; + } + + public double getLatitudeGcj02() { + return latitudeGcj02; + } + + public void setLatitudeGcj02(double latitudeGcj02) { + this.latitudeGcj02 = latitudeGcj02; + } + + public double getLongitudeWgs84() { + return longitudeWgs84; + } + + public void setLongitudeWgs84(double longitudeWgs84) { + this.longitudeWgs84 = longitudeWgs84; + } + + public double getLatitudeWgs84() { + return latitudeWgs84; + } + + public void setLatitudeWgs84(double latitudeWgs84) { + this.latitudeWgs84 = latitudeWgs84; + } + public int getSubCount() { return subCount; } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/HandlerCatchData.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/HandlerCatchData.java new file mode 100644 index 000000000..97da8630c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/HandlerCatchData.java @@ -0,0 +1,44 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +import org.dom4j.Element; + +import javax.sip.RequestEvent; + +/** + * @author lin + */ +public class HandlerCatchData { + private RequestEvent evt; + private Device device; + private Element rootElement; + + public HandlerCatchData(RequestEvent evt, Device device, Element rootElement) { + this.evt = evt; + this.device = device; + this.rootElement = rootElement; + } + + public RequestEvent getEvt() { + return evt; + } + + public void setEvt(RequestEvent evt) { + this.evt = evt; + } + + public Device getDevice() { + return device; + } + + public void setDevice(Device device) { + this.device = device; + } + + public Element getRootElement() { + return rootElement; + } + + public void setRootElement(Element rootElement) { + this.rootElement = rootElement; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java index c7f618203..41e1af780 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java @@ -71,6 +71,11 @@ public class SendRtpItem { */ private String mediaServerId; + /** + * 使用的服务的ID + */ + private String serverId; + /** * invite的callId */ @@ -259,4 +264,12 @@ public class SendRtpItem { public void setOnlyAudio(boolean onlyAudio) { this.onlyAudio = onlyAudio; } + + public String getServerId() { + return serverId; + } + + public void setServerId(String serverId) { + this.serverId = serverId; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java index f191c005e..4a900c164 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeHandlerTask; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; @@ -38,7 +39,6 @@ public class SubscribeHolder { catalogMap.put(platformId, subscribeInfo); // 添加订阅到期 String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId; - dynamicTask.stop(taskOverdueKey); // 添加任务处理订阅过期 dynamicTask.startDelay(taskOverdueKey, () -> removeCatalogSubscribe(subscribeInfo.getId()), subscribeInfo.getExpires() * 1000); @@ -49,10 +49,17 @@ public class SubscribeHolder { } public void removeCatalogSubscribe(String platformId) { + catalogMap.remove(platformId); String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId; + Runnable runnable = dynamicTask.get(taskOverdueKey); + if (runnable instanceof ISubscribeTask) { + ISubscribeTask subscribeTask = (ISubscribeTask) runnable; + subscribeTask.stop(); + } // 添加任务处理订阅过期 dynamicTask.stop(taskOverdueKey); + } public void putMobilePositionSubscribe(String platformId, SubscribeInfo subscribeInfo) { @@ -63,7 +70,6 @@ public class SubscribeHolder { storager, platformId, subscribeInfo.getSn(), key, this, dynamicTask), subscribeInfo.getGpsInterval() * 1000); String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId; - dynamicTask.stop(taskOverdueKey); // 添加任务处理订阅过期 dynamicTask.startDelay(taskOverdueKey, () -> { removeMobilePositionSubscribe(subscribeInfo.getId()); @@ -81,6 +87,11 @@ public class SubscribeHolder { // 结束任务处理GPS定时推送 dynamicTask.stop(key); String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId; + Runnable runnable = dynamicTask.get(taskOverdueKey); + if (runnable instanceof ISubscribeTask) { + ISubscribeTask subscribeTask = (ISubscribeTask) runnable; + subscribeTask.stop(); + } // 添加任务处理订阅过期 dynamicTask.stop(taskOverdueKey); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java index 3d817c34b..c6cfc7a04 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java @@ -88,8 +88,8 @@ public class SipSubscribe { this.type = "timeout"; this.msg = "消息超时未回复"; this.statusCode = -1024; - this.callId = timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId(); this.dialog = timeoutEvent.getClientTransaction().getDialog(); + this.callId = this.dialog != null?timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId(): null; }else if (event instanceof TransactionTerminatedEvent) { TransactionTerminatedEvent transactionTerminatedEvent = (TransactionTerminatedEvent)event; this.type = "transactionTerminated"; @@ -109,8 +109,8 @@ public class SipSubscribe { this.type = "deviceNotFoundEvent"; this.msg = "设备未找到"; this.statusCode = -1024; - this.callId = deviceNotFoundEvent.getDialog().getCallId().getCallId(); this.dialog = deviceNotFoundEvent.getDialog(); + this.callId = this.dialog != null ?deviceNotFoundEvent.getDialog().getCallId().getCallId() : null; } } } @@ -130,6 +130,9 @@ public class SipSubscribe { } public void removeErrorSubscribe(String key) { + if(key == null){ + return; + } errorSubscribes.remove(key); errorTimeSubscribes.remove(key); } @@ -139,6 +142,9 @@ public class SipSubscribe { } public void removeOkSubscribe(String key) { + if(key == null){ + return; + } okSubscribes.remove(key); okTimeSubscribes.remove(key); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java index 7e5ecb495..e38733d5b 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java @@ -66,7 +66,7 @@ public class CatalogEventLister implements ApplicationListener { subscribe = subscribeHolder.getCatalogSubscribe(event.getPlatformId()); if (subscribe == null) { - logger.info("发送订阅消息时发现订阅信息已经不存在"); + logger.info("发送订阅消息时发现订阅信息已经不存在: {}", event.getPlatformId()); return; } }else { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java index 85bc39dc7..a22d24d68 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java @@ -99,8 +99,8 @@ public class VideoStreamSessionManager { return dialog; } - public SIPDialog getDialogByCallId(String deviceId, String channelId, String callID){ - SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callID, null); + public SIPDialog getDialogByCallId(String deviceId, String channelId, String callId){ + SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, null); if (ssrcTransaction == null) { return null; } @@ -108,11 +108,17 @@ public class VideoStreamSessionManager { if (dialogByteArray == null) { return null; } - SIPDialog dialog = (SIPDialog)SerializeUtils.deSerialize(dialogByteArray); - return dialog; + return (SIPDialog)SerializeUtils.deSerialize(dialogByteArray); } public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){ + + if (StringUtils.isEmpty(deviceId)) { + deviceId ="*"; + } + if (StringUtils.isEmpty(channelId)) { + channelId ="*"; + } if (StringUtils.isEmpty(callId)) { callId ="*"; } @@ -179,7 +185,7 @@ public class VideoStreamSessionManager { public List getAllSsrc() { - List ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId() + "_" )); + List ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId())); List result= new ArrayList<>(); for (int i = 0; i < ssrcTransactionKeys.size(); i++) { String key = (String)ssrcTransactionKeys.get(i); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java index c416766ec..66b57fec8 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java @@ -71,7 +71,9 @@ public class MobilePositionSubscribeHandlerTask implements ISubscribeTask { String gbId = gbStream.getGbId(); GPSMsgInfo gpsMsgInfo = redisCatchStorage.getGpsMsgInfo(gbId); if (gpsMsgInfo != null) { // 无最新位置不发送 - logger.info("无最新位置不发送"); + if (logger.isDebugEnabled()) { + logger.debug("无最新位置不发送"); + } // 经纬度都为0不发送 if (gpsMsgInfo.getLng() == 0 && gpsMsgInfo.getLat() == 0) { continue; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java index a06a73d03..a2fab8138 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java @@ -150,30 +150,24 @@ public class SIPProcessorObserver implements ISIPProcessorObserver { public void processTimeout(TimeoutEvent timeoutEvent) { logger.info("[消息发送超时]"); ClientTransaction clientTransaction = timeoutEvent.getClientTransaction(); - eventPublisher.requestTimeOut(timeoutEvent); + if (clientTransaction != null) { + logger.info("[发送错误订阅] clientTransaction != null"); Request request = clientTransaction.getRequest(); if (request != null) { + logger.info("[发送错误订阅] request != null"); CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); if (callIdHeader != null) { + logger.info("[发送错误订阅]"); SipSubscribe.Event subscribe = sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()); SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(timeoutEvent); subscribe.response(eventResult); + sipSubscribe.removeOkSubscribe(callIdHeader.getCallId()); sipSubscribe.removeErrorSubscribe(callIdHeader.getCallId()); } } } - -// Timeout timeout = timeoutEvent.getTimeout(); -// ServerTransaction serverTransaction = timeoutEvent.getServerTransaction(); -// if (serverTransaction != null) { -// Request request = serverTransaction.getRequest(); -// URI requestURI = request.getRequestURI(); -// Header header = request.getHeader(FromHeader.NAME); -// } -// if(timeoutProcessor != null) { -// timeoutProcessor.process(timeoutEvent); -// } + eventPublisher.requestTimeOut(timeoutEvent); } @Override 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 bd51cfa86..bf6146a2f 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 @@ -148,6 +148,14 @@ public interface ISIPCommander { * 回放倍速播放 */ void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed); + + /** + * 回放控制 + * @param device + * @param streamInfo + * @param content + */ + void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent); /** diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java index 7007e5a60..d000f5afb 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java @@ -103,6 +103,14 @@ public interface ISIPCommanderForPlatform { */ boolean recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo); + /** + * 录像播放推送完成时发送MediaStatus消息 + * @param platform + * @param sendRtpItem + * @return + */ + boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem); + /** * 向发起点播的上级回复bye * @param platform 平台信息 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 d8deb5cb7..832f7165d 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 @@ -32,7 +32,9 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.sip.*; +import javax.sip.address.Address; import javax.sip.address.SipURI; +import javax.sip.address.URI; import javax.sip.header.*; import javax.sip.message.Request; import java.lang.reflect.Field; @@ -708,22 +710,19 @@ public class SIPCommander implements ISIPCommander { } SIPDialog dialog; if (callId != null) { - dialog = streamSession.getDialogByCallId(deviceId, channelId, callId); + dialog = streamSession.getDialogByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), callId); }else { - if (stream == null) { + if (stream == null && ssrcTransaction == null && ssrcTransaction.getStream() == null) { return; } - dialog = streamSession.getDialogByStream(deviceId, channelId, stream); - } - if (ssrcTransaction != null) { - MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId()); - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc()); - mediaServerService.closeRTPServer(deviceId, channelId, ssrcTransaction.getStream()); - streamSession.remove(deviceId, channelId, ssrcTransaction.getStream()); + dialog = streamSession.getDialogByStream(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); } + mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc()); + mediaServerService.closeRTPServer(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); + streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); if (dialog == null) { - logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", deviceId, channelId); + logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); return; } SipStack sipStack = udpSipProvider.getSipStack(); @@ -1456,12 +1455,20 @@ public class SIPCommander implements ISIPCommander { Request request; if (dialog != null) { - logger.info("发送移动位置订阅消息时 dialog的状态为: {}", dialog.getState()); + SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); request = dialog.createRequest(Request.SUBSCRIBE); + ExpiresHeader expiresHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForCatalog()); + request.setExpires(expiresHeader); + + request.setRequestURI(requestURI); + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); request.setContent(subscribePostitionXml.toString(), contentTypeHeader); - ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition()); - request.addHeader(expireHeader); + + CSeqHeader cSeqHeader = (CSeqHeader)request.getHeader(CSeqHeader.NAME); + cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ(Request.SUBSCRIBE)); + request.removeHeader(CSeqHeader.NAME); + request.addHeader(cSeqHeader); }else { String tm = Long.toString(System.currentTimeMillis()); CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId() @@ -1552,12 +1559,21 @@ public class SIPCommander implements ISIPCommander { Request request; if (dialog != null) { - logger.info("发送目录订阅消息时 dialog的状态为: {}", dialog.getState()); + SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); request = dialog.createRequest(Request.SUBSCRIBE); + ExpiresHeader expiresHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForCatalog()); + request.setExpires(expiresHeader); + + request.setRequestURI(requestURI); + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); request.setContent(cmdXml.toString(), contentTypeHeader); - ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition()); - request.addHeader(expireHeader); + + CSeqHeader cSeqHeader = (CSeqHeader)request.getHeader(CSeqHeader.NAME); + cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ(Request.SUBSCRIBE)); + request.removeHeader(CSeqHeader.NAME); + request.addHeader(cSeqHeader); + }else { String tm = Long.toString(System.currentTimeMillis()); @@ -1779,6 +1795,43 @@ public class SIPCommander implements ISIPCommander { e.printStackTrace(); } } + + @Override + public void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) { + try { + Request request = headerProvider.createInfoRequest(device, streamInfo, content); + if (request == null) { + return; + } + logger.info(request.toString()); + ClientTransaction clientTransaction = null; + if ("TCP".equals(device.getTransport())) { + clientTransaction = tcpSipProvider.getNewClientTransaction(request); + } else if ("UDP".equals(device.getTransport())) { + clientTransaction = udpSipProvider.getNewClientTransaction(request); + } + CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME); + if(errorEvent != null) { + sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> { + errorEvent.response(eventResult); + sipSubscribe.removeErrorSubscribe(eventResult.callId); + sipSubscribe.removeOkSubscribe(eventResult.callId); + })); + } + + if(okEvent != null) { + sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> { + okEvent.response(eventResult); + sipSubscribe.removeOkSubscribe(eventResult.callId); + sipSubscribe.removeErrorSubscribe(eventResult.callId); + }); + } + clientTransaction.sendRequest(); + + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + } @Override public boolean sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java index a4fd5079e..2e70ea752 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java @@ -31,6 +31,7 @@ import javax.sip.address.SipURI; import javax.sip.header.*; import javax.sip.message.Request; import java.lang.reflect.Field; +import java.net.InetAddress; import java.text.ParseException; import java.util.ArrayList; import java.util.HashSet; @@ -276,8 +277,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { catalogXml.append("" + channel.getOwner() + "\r\n"); catalogXml.append("" + channel.getCivilCode() + "\r\n"); catalogXml.append("
" + channel.getAddress() + "
\r\n"); - catalogXml.append("" + channel.getLongitude() + "\r\n"); - catalogXml.append("" + channel.getLatitude() + "\r\n"); + catalogXml.append("" + channel.getLongitudeWgs84() + "\r\n"); + catalogXml.append("" + channel.getLatitudeWgs84() + "\r\n"); catalogXml.append("" + channel.getIpAddress() + "\r\n"); catalogXml.append("" + channel.getPort() + "\r\n"); catalogXml.append("\r\n"); @@ -546,14 +547,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { } notifyRequest.addHeader(event); SipURI sipURI = (SipURI) notifyRequest.getRequestURI(); - if (subscribeInfo.getTransaction() != null) { - SIPRequest request = (SIPRequest) subscribeInfo.getTransaction().getRequest(); - sipURI.setHost(request.getRemoteAddress().getHostAddress()); - sipURI.setPort(request.getRemotePort()); - }else { - sipURI.setHost(parentPlatform.getServerIP()); - sipURI.setPort(parentPlatform.getServerPort()); - } + sipURI.setHost(parentPlatform.getServerIP()); + sipURI.setPort(parentPlatform.getServerPort()); ClientTransaction transaction = null; if ("TCP".equals(parentPlatform.getTransport())) { @@ -750,6 +745,82 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { return true; } + @Override + public boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem) { + if (sendRtpItem == null) { + return false; + } + if (platform == null) { + return false; + } + + byte[] dialogByteArray = sendRtpItem.getDialog(); + if (dialogByteArray == null) { + return false; + } + try{ + SIPDialog dialog = (SIPDialog) SerializeUtils.deSerialize(dialogByteArray); + SipStack sipStack; + if ("TCP".equals(platform.getTransport())) { + sipStack = tcpSipProvider.getSipStack(); + } else { + sipStack = udpSipProvider.getSipStack(); + } + SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog); + if (dialog != sipDialog) { + dialog = sipDialog; + } + if ("TCP".equals(platform.getTransport())) { + dialog.setSipProvider(tcpSipProvider); + } else { + dialog.setSipProvider(udpSipProvider); + } + + Field sipStackField = SIPDialog.class.getDeclaredField("sipStack"); + sipStackField.setAccessible(true); + sipStackField.set(dialog, sipStack); + Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners"); + eventListenersField.setAccessible(true); + eventListenersField.set(dialog, new HashSet<>()); + + SIPRequest messageRequest = (SIPRequest)dialog.createRequest(Request.MESSAGE); + String characterSet = platform.getCharacterSet(); + StringBuffer mediaStatusXml = new StringBuffer(200); + mediaStatusXml.append("\r\n"); + mediaStatusXml.append("\r\n"); + mediaStatusXml.append("MediaStatus\r\n"); + mediaStatusXml.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); + mediaStatusXml.append("" + sendRtpItem.getChannelId() + "\r\n"); + mediaStatusXml.append("121\r\n"); + mediaStatusXml.append("\r\n"); + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); + messageRequest.setContent(mediaStatusXml.toString(), contentTypeHeader); + SipURI sipURI = (SipURI) messageRequest.getRequestURI(); + sipURI.setHost(platform.getServerIP()); + sipURI.setPort(platform.getServerPort()); + ClientTransaction clientTransaction; + if ("TCP".equals(platform.getTransport())) { + clientTransaction = tcpSipProvider.getNewClientTransaction(messageRequest); + }else { + clientTransaction = udpSipProvider.getNewClientTransaction(messageRequest); + } + dialog.sendRequest(clientTransaction); + } catch (SipException e) { + e.printStackTrace(); + return false; + } catch (ParseException e) { + e.printStackTrace(); + return false; + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + return true; + + + } + @Override public void streamByeCmd(ParentPlatform platform, String callId) { if (platform == null) { @@ -766,45 +837,51 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { byte[] dialogByteArray = sendRtpItem.getDialog(); if (dialogByteArray != null) { SIPDialog dialog = (SIPDialog) SerializeUtils.deSerialize(dialogByteArray); - SipStack sipStack = udpSipProvider.getSipStack(); + SipStack sipStack; + if ("TCP".equals(platform.getTransport())) { + sipStack = tcpSipProvider.getSipStack(); + } else { + sipStack = udpSipProvider.getSipStack(); + } SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog); if (dialog != sipDialog) { dialog = sipDialog; - } else { - try { - dialog.setSipProvider(udpSipProvider); - Field sipStackField = SIPDialog.class.getDeclaredField("sipStack"); - sipStackField.setAccessible(true); - sipStackField.set(dialog, sipStack); - Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners"); - eventListenersField.setAccessible(true); - eventListenersField.set(dialog, new HashSet<>()); - - byte[] transactionByteArray = sendRtpItem.getTransaction(); - ClientTransaction clientTransaction = (ClientTransaction) SerializeUtils.deSerialize(transactionByteArray); - Request byeRequest = dialog.createRequest(Request.BYE); - - SipURI byeURI = (SipURI) byeRequest.getRequestURI(); - SIPRequest request = (SIPRequest) clientTransaction.getRequest(); - byeURI.setHost(request.getRemoteAddress().getHostAddress()); - byeURI.setPort(request.getRemotePort()); - if ("TCP".equals(platform.getTransport())) { - clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest); - } else if ("UDP".equals(platform.getTransport())) { - clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest); - } - dialog.sendRequest(clientTransaction); - } catch (SipException e) { - e.printStackTrace(); - } catch (ParseException e) { - e.printStackTrace(); - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } + try { + if ("TCP".equals(platform.getTransport())) { + dialog.setSipProvider(tcpSipProvider); + } else { + dialog.setSipProvider(udpSipProvider); + } + Field sipStackField = SIPDialog.class.getDeclaredField("sipStack"); + sipStackField.setAccessible(true); + sipStackField.set(dialog, sipStack); + Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners"); + eventListenersField.setAccessible(true); + eventListenersField.set(dialog, new HashSet<>()); + + Request byeRequest = dialog.createRequest(Request.BYE); + + SipURI byeURI = (SipURI) byeRequest.getRequestURI(); + byeURI.setHost(platform.getServerIP()); + byeURI.setPort(platform.getServerPort()); + ClientTransaction clientTransaction; + if ("TCP".equals(platform.getTransport())) { + clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest); + } else { + clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest); + } + dialog.sendRequest(clientTransaction); + } catch (SipException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorAbstract.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorAbstract.java deleted file mode 100644 index dd098f7b9..000000000 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorAbstract.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.genersoft.iot.vmp.gb28181.transmit.event.request; - -import gov.nist.javax.sip.SipProviderImpl; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - -/** - * @description:处理接收IPCamera发来的SIP协议请求消息 - * @author: songww - * @date: 2020年5月3日 下午4:42:22 - */ -public abstract class SIPRequestProcessorAbstract { - - - @Autowired - @Qualifier(value="tcpSipProvider") - private SipProviderImpl tcpSipProvider; - - @Autowired - @Qualifier(value="udpSipProvider") - private SipProviderImpl udpSipProvider; - -} 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 85ee6471c..33bee758f 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 @@ -15,10 +15,13 @@ import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.IMediaServerService; +import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg; +import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.stack.SIPDialog; +import com.genersoft.iot.vmp.utils.SerializeUtils; import org.ehcache.shadow.org.terracotta.offheapstore.storage.IntegerStorageEngine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +49,7 @@ import java.util.*; public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { private Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class); - private String method = "ACK"; + private final String method = "ACK"; @Autowired private SIPProcessorObserver sipProcessorObserver; @@ -84,6 +87,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In @Autowired private AudioBroadcastManager audioBroadcastManager; + @Autowired + private RedisGbPlayMsgListener redisGbPlayMsgListener; + /** * 处理 ACK请求 @@ -159,60 +165,41 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In // 向上级平台 commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId()); } + if (mediaInfo == null) { + RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance( + sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(), + sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(), + sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio()); + redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, jsonObject->{ + startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader); + }); + }else { + JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); + startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader); } -// if (streamInfo == null) { // 流还没上来,对方就回复ack -// logger.info("监听流以等待流上线1 rtp/{}", sendRtpItem.getStreamId()); -// // 监听流上线 -// // 添加订阅 -// JSONObject subscribeKey = new JSONObject(); -// subscribeKey.put("app", "rtp"); -// subscribeKey.put("stream", sendRtpItem.getStreamId()); -// subscribeKey.put("regist", true); -// subscribeKey.put("schema", "rtmp"); -// subscribeKey.put("mediaServerId", sendRtpItem.getMediaServerId()); -// subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, -// (MediaServerItem mediaServerItemInUse, JSONObject json)->{ -// Map param = new HashMap<>(); -// param.put("vhost","__defaultVhost__"); -// param.put("app",json.getString("app")); -// param.put("stream",json.getString("stream")); -// param.put("ssrc", sendRtpItem.getSsrc()); -// param.put("dst_url",sendRtpItem.getIp()); -// param.put("dst_port", sendRtpItem.getPort()); -// param.put("is_udp", is_Udp); -// param.put("src_port", sendRtpItem.getLocalPort()); -// zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); -// }); -// }else { -// Map param = new HashMap<>(); -// param.put("vhost","__defaultVhost__"); -// param.put("app",streamInfo.getApp()); -// param.put("stream",streamInfo.getStream()); -// param.put("ssrc", sendRtpItem.getSsrc()); -// param.put("dst_url",sendRtpItem.getIp()); -// param.put("dst_port", sendRtpItem.getPort()); -// param.put("is_udp", is_Udp); -// param.put("src_port", sendRtpItem.getLocalPort()); -// -// JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); -// if (jsonObject.getInteger("code") != 0) { -// logger.info("监听流以等待流上线2 {}/{}", streamInfo.getApp(), streamInfo.getStream()); -// // 监听流上线 -// // 添加订阅 -// JSONObject subscribeKey = new JSONObject(); -// subscribeKey.put("app", "rtp"); -// subscribeKey.put("stream", streamInfo.getStream()); -// subscribeKey.put("regist", true); -// subscribeKey.put("schema", "rtmp"); -// subscribeKey.put("mediaServerId", sendRtpItem.getMediaServerId()); -// subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, -// (MediaServerItem mediaServerItemInUse, JSONObject json)->{ -// zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); -// }); -// } -// } + } + } + private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform, + JSONObject jsonObject, Map param, CallIdHeader callIdHeader) { + if (jsonObject == null) { + logger.error("RTP推流失败: 请检查ZLM服务"); + } else if (jsonObject.getInteger("code") == 0) { + logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port")); + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); + sendRtpItem.setDialog(dialogByteArray); + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); + sendRtpItem.setTransaction(transactionByteArray); + redisCatchStorage.updateSendRTPSever(sendRtpItem); + } else { + logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"),JSONObject.toJSON(param)); + if (sendRtpItem.isOnlyAudio()) { + // TODO 可能是语音对讲 + }else { + // 向上级平台 + commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId()); + } } } } 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 7944787a0..628bc15a0 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 @@ -114,13 +114,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In playService.stopAudioBroadcast(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); } if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) { - MessageForPushChannel messageForPushChannel = new MessageForPushChannel(); - messageForPushChannel.setType(0); - messageForPushChannel.setGbId(sendRtpItem.getChannelId()); - messageForPushChannel.setApp(sendRtpItem.getApp()); - messageForPushChannel.setStream(sendRtpItem.getStreamId()); - messageForPushChannel.setMediaServerId(sendRtpItem.getMediaServerId()); - messageForPushChannel.setPlatFormId(sendRtpItem.getPlatformId()); + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, + sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(), + sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId()); redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java index 0a818ee6e..b04352a62 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java @@ -15,7 +15,7 @@ import javax.sip.RequestEvent; @Component public class CancelRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { - private String method = "CANCEL"; + private final String method = "CANCEL"; @Autowired private SIPProcessorObserver sipProcessorObserver; 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 eabfd1aff..095f5e293 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 @@ -26,11 +26,14 @@ import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItemLite; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IPlayService; +import com.genersoft.iot.vmp.service.IStreamPushService; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; import com.genersoft.iot.vmp.service.bean.SSRCInfo; +import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.utils.DateUtil; @@ -66,33 +69,42 @@ import java.util.Vector; @Component public class InviteRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { - private final static Logger logger = LoggerFactory.getLogger(InviteRequestProcessor.class); + private final static Logger logger = LoggerFactory.getLogger(InviteRequestProcessor.class); - private String method = "INVITE"; + private final String method = "INVITE"; - @Autowired - private SIPCommanderFroPlatform cmderFroPlatform; + @Autowired + private SIPCommanderFroPlatform cmderFroPlatform; - @Autowired - private IVideoManagerStorage storager; + @Autowired + private IVideoManagerStorage storager; - @Autowired - private IRedisCatchStorage redisCatchStorage; + @Autowired + private IStreamPushService streamPushService; - @Autowired - private DynamicTask dynamicTask; + @Autowired + private IRedisCatchStorage redisCatchStorage; - @Autowired - private SIPCommander cmder; + @Autowired + private DynamicTask dynamicTask; - @Autowired - private IPlayService playService; + @Autowired + private SIPCommander cmder; + @Autowired + private IPlayService playService; + + @Autowired + private ISIPCommander commander; + @Autowired private AudioBroadcastManager audioBroadcastManager; - @Autowired - private ZLMRTPServerFactory zlmrtpServerFactory; + @Autowired + private ZLMRTPServerFactory zlmrtpServerFactory; + + @Autowired + private IMediaServerService mediaServerService; @Autowired private ZLMRESTfulUtils zlmresTfulUtils; @@ -100,17 +112,17 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements @Autowired private IMediaServerService mediaServerService; - @Autowired - private SIPProcessorObserver sipProcessorObserver; + @Autowired + private SIPProcessorObserver sipProcessorObserver; - @Autowired - private VideoStreamSessionManager sessionManager; + @Autowired + private VideoStreamSessionManager sessionManager; - @Autowired - private UserSetting userSetting; + @Autowired + private UserSetting userSetting; - @Autowired - private ZLMMediaListManager mediaListManager; + @Autowired + private ZLMMediaListManager mediaListManager; @Autowired private DeferredResultHolder resultHolder; @@ -123,542 +135,664 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements - @Override - public void afterPropertiesSet() throws Exception { - // 添加消息处理的订阅 - sipProcessorObserver.addRequestProcessor(method, this); - } - - /** - * 处理invite请求 - * - * @param evt - * 请求消息 - */ - @Override - public void process(RequestEvent evt) { - // Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令 - try { - Request request = evt.getRequest(); - SipURI sipURI = (SipURI) request.getRequestURI(); - //从subject读取channelId,不再从request-line读取。 有些平台request-line是平台国标编码,不是设备国标编码。 - //String channelId = sipURI.getUser(); - String channelId = SipUtils.getChannelIdFromHeader(request); - String requesterId = SipUtils.getUserIdFromFromHeader(request); - CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME); - if (requesterId == null || channelId == null) { - logger.info("无法从FromHeader的Address中获取到平台id,返回400"); - responseAck(evt, Response.BAD_REQUEST); // 参数不全, 发400,请求错误 - return; - } - - // 查询请求是否来自上级平台\设备 - ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId); - if (platform == null) { - inviteFromDeviceHandle(evt, requesterId, channelId); - }else { - // 查询平台下是否有该通道 - DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId); - GbStream gbStream = storager.queryStreamInParentPlatform(requesterId, channelId); - PlatformCatalog catalog = storager.getCatalog(channelId); - MediaServerItem mediaServerItem = null; - // 不是通道可能是直播流 - if (channel != null && gbStream == null ) { - if (channel.getStatus() == 0) { - logger.info("通道离线,返回400"); - responseAck(evt, Response.BAD_REQUEST, "channel [" + channel.getChannelId() + "] offline"); - return; - } - responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中 - }else if(channel == null && gbStream != null){ - String mediaServerId = gbStream.getMediaServerId(); - mediaServerItem = mediaServerService.getOne(mediaServerId); - if (mediaServerItem == null) { - logger.info("[ app={}, stream={} ]找不到zlm {},返回410",gbStream.getApp(), gbStream.getStream(), mediaServerId); - responseAck(evt, Response.GONE); - return; - } - responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中 - }else if (catalog != null) { - responseAck(evt, Response.BAD_REQUEST, "catalog channel can not play"); // 目录不支持点播 - return; - } else { - logger.info("通道不存在,返回404"); - responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在 - return; - } - // 解析sdp消息, 使用jainsip 自带的sdp解析方式 - String contentString = new String(request.getRawContent()); - - // jainSip不支持y=字段, 移除以解析。 - int ssrcIndex = contentString.indexOf("y="); - // 检查是否有y字段 - String ssrcDefault = "0000000000"; - String ssrc; - SessionDescription sdp; - if (ssrcIndex >= 0) { - //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 - ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); - String substring = contentString.substring(0, contentString.indexOf("y=")); - sdp = SdpFactory.getInstance().createSessionDescription(substring); - }else { - ssrc = ssrcDefault; - sdp = SdpFactory.getInstance().createSessionDescription(contentString); - } - String sessionName = sdp.getSessionName().getValue(); - - Long startTime = null; - Long stopTime = null; - Instant start = null; - Instant end = null; - if (sdp.getTimeDescriptions(false) != null && sdp.getTimeDescriptions(false).size() > 0) { - TimeDescriptionImpl timeDescription = (TimeDescriptionImpl)(sdp.getTimeDescriptions(false).get(0)); - TimeField startTimeFiled = (TimeField)timeDescription.getTime(); - startTime = startTimeFiled.getStartTime(); - stopTime = startTimeFiled.getStopTime(); - - start = Instant.ofEpochMilli(startTime*1000); - end = Instant.ofEpochMilli(stopTime*1000); - } - // 获取支持的格式 - Vector mediaDescriptions = sdp.getMediaDescriptions(true); - // 查看是否支持PS 负载96 - //String ip = null; - int port = -1; - boolean mediaTransmissionTCP = false; - Boolean tcpActive = null; - for (Object description : mediaDescriptions) { - MediaDescription mediaDescription = (MediaDescription) description; - Media media = mediaDescription.getMedia(); - - Vector mediaFormats = media.getMediaFormats(false); - if (mediaFormats.contains("96")) { - port = media.getMediaPort(); - //String mediaType = media.getMediaType(); - String protocol = media.getProtocol(); - - // 区分TCP发流还是udp, 当前默认udp - if ("TCP/RTP/AVP".equals(protocol)) { - String setup = mediaDescription.getAttribute("setup"); - if (setup != null) { - mediaTransmissionTCP = true; - if ("active".equals(setup)) { - tcpActive = true; - // 不支持tcp主动 - responseAck(evt, Response.NOT_IMPLEMENTED, "tcp active not support"); // 目录不支持点播 - return; - } else if ("passive".equals(setup)) { - tcpActive = false; - } - } - } - break; - } - } - if (port == -1) { - logger.info("不支持的媒体格式,返回415"); - // 回复不支持的格式 - responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 - return; - } - String username = sdp.getOrigin().getUsername(); - String addressStr = sdp.getOrigin().getAddress(); - - logger.info("[上级点播]用户:{}, 地址:{}:{}, ssrc:{}", username, addressStr, port, ssrc); - Device device = null; - // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标 - if (channel != null) { - device = storager.queryVideoDeviceByPlatformIdAndChannelId(requesterId, channelId); - if (device == null) { - logger.warn("点播平台{}的通道{}时未找到设备信息", requesterId, channel); - responseAck(evt, Response.SERVER_INTERNAL_ERROR); - return; - } - mediaServerItem = playService.getNewMediaServerItem(device); - if (mediaServerItem == null) { - logger.warn("未找到可用的zlm"); - responseAck(evt, Response.BUSY_HERE); - return; - } - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, - device.getDeviceId(), channelId, - mediaTransmissionTCP); - if (tcpActive != null) { - sendRtpItem.setTcpActive(tcpActive); - } - if (sendRtpItem == null) { - logger.warn("服务器端口资源不足"); - responseAck(evt, Response.BUSY_HERE); - return; - } - sendRtpItem.setCallId(callIdHeader.getCallId()); - sendRtpItem.setPlayType("Play".equals(sessionName)?InviteStreamType.PLAY:InviteStreamType.PLAYBACK); - byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); - sendRtpItem.setDialog(dialogByteArray); - byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); - sendRtpItem.setTransaction(transactionByteArray); - Long finalStartTime = startTime; - Long finalStopTime = stopTime; - ZLMHttpHookSubscribe.Event hookEvent = (mediaServerItemInUSe, responseJSON)->{ - String app = responseJSON.getString("app"); - String stream = responseJSON.getString("stream"); - logger.info("[上级点播]下级已经开始推流。 回复200OK(SDP), {}/{}", app, stream); - // * 0 等待设备推流上来 - // * 1 下级已经推流,等待上级平台回复ack - // * 2 推流中 - sendRtpItem.setStatus(1); - redisCatchStorage.updateSendRTPSever(sendRtpItem); - - StringBuffer content = new StringBuffer(200); - content.append("v=0\r\n"); - content.append("o="+ channelId +" 0 0 IN IP4 "+mediaServerItemInUSe.getSdpIp()+"\r\n"); - content.append("s=" + sessionName+"\r\n"); - content.append("c=IN IP4 "+mediaServerItemInUSe.getSdpIp()+"\r\n"); - if ("Playback".equals(sessionName)) { - content.append("t=" + finalStartTime + " " + finalStopTime + "\r\n"); - }else { - content.append("t=0 0\r\n"); - } - content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n"); - content.append("a=sendonly\r\n"); - content.append("a=rtpmap:96 PS/90000\r\n"); - content.append("y="+ ssrc + "\r\n"); - content.append("f=\r\n"); - - try { - // 超时未收到Ack应该回复bye,当前等待时间为10秒 - dynamicTask.startDelay(callIdHeader.getCallId(), ()->{ - logger.info("Ack 等待超时"); - mediaServerService.releaseSsrc(mediaServerItemInUSe.getId(), ssrc); - // 回复bye - cmderFroPlatform.streamByeCmd(platform, callIdHeader.getCallId()); - }, 60*1000); - responseSdpAck(evt, content.toString(), platform); - - } catch (SipException e) { - e.printStackTrace(); - } catch (InvalidArgumentException e) { - e.printStackTrace(); - } catch (ParseException e) { - e.printStackTrace(); - } - }; - SipSubscribe.Event errorEvent = ((event) -> { - // 未知错误。直接转发设备点播的错误 - Response response = null; - try { - response = getMessageFactory().createResponse(event.statusCode, evt.getRequest()); - ServerTransaction serverTransaction = getServerTransaction(evt); - serverTransaction.sendResponse(response); - if (serverTransaction.getDialog() != null) { - serverTransaction.getDialog().delete(); - } - } catch (ParseException | SipException | InvalidArgumentException e) { - e.printStackTrace(); - } - }); - sendRtpItem.setApp("rtp"); - if ("Playback".equals(sessionName)) { - sendRtpItem.setPlayType(InviteStreamType.PLAYBACK); - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true, true); - sendRtpItem.setStreamId(ssrcInfo.getStream()); - // 写入redis, 超时时回复 - redisCatchStorage.updateSendRTPSever(sendRtpItem); - playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start), - DateUtil.formatter.format(end), null, result -> { - if (result.getCode() != 0){ - logger.warn("录像回放失败"); - if (result.getEvent() != null) { - errorEvent.response(result.getEvent()); - } - redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); - try { - responseAck(evt, Response.REQUEST_TIMEOUT); - } catch (SipException e) { - e.printStackTrace(); - } catch (InvalidArgumentException e) { - e.printStackTrace(); - } catch (ParseException e) { - e.printStackTrace(); - } - }else { - if (result.getMediaServerItem() != null) { - hookEvent.response(result.getMediaServerItem(), result.getResponse()); - } - } - }); - }else { - sendRtpItem.setPlayType(InviteStreamType.PLAY); - SsrcTransaction playTransaction = sessionManager.getSsrcTransaction(device.getDeviceId(), channelId, "play", null); - if (playTransaction != null) { - Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream()); - if (!streamReady) { - playTransaction = null; - } - } - if (playTransaction == null) { - String streamId = null; - if (mediaServerItem.isRtpEnable()) { - streamId = String.format("%s_%s", device.getDeviceId(), channelId); - } - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, true, false); - sendRtpItem.setStreamId(ssrcInfo.getStream()); - // 写入redis, 超时时回复 - redisCatchStorage.updateSendRTPSever(sendRtpItem); - playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg)->{ - redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); - }, null); - }else { - sendRtpItem.setStreamId(playTransaction.getStream()); - // 写入redis, 超时时回复 - redisCatchStorage.updateSendRTPSever(sendRtpItem); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("app", sendRtpItem.getApp()); - jsonObject.put("stream", sendRtpItem.getStreamId()); - hookEvent.response(mediaServerItem, jsonObject); - } - } - }else if (gbStream != null) { - - Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream()); - if (!streamReady ) { - if ("proxy".equals(gbStream.getStreamType())) { - // TODO 控制启用以使设备上线 - logger.info("[ app={}, stream={} ]通道离线,启用流后开始推流",gbStream.getApp(), gbStream.getStream()); - 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; - } - // 发送redis消息以使设备上线 - logger.info("[ app={}, stream={} ]通道离线,发送redis信息控制设备开始推流",gbStream.getApp(), gbStream.getStream()); - MessageForPushChannel messageForPushChannel = new MessageForPushChannel(); - messageForPushChannel.setType(1); - messageForPushChannel.setGbId(gbStream.getGbId()); - messageForPushChannel.setApp(gbStream.getApp()); - messageForPushChannel.setStream(gbStream.getStream()); - // TODO 获取低负载的节点 - messageForPushChannel.setMediaServerId(gbStream.getMediaServerId()); - messageForPushChannel.setPlatFormId(platform.getServerGBId()); - messageForPushChannel.setPlatFormName(platform.getName()); - redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); - // 设置超时 - dynamicTask.startDelay(callIdHeader.getCallId(), ()->{ - logger.info("[ app={}, stream={} ] 等待设备开始推流超时", gbStream.getApp(), gbStream.getStream()); - try { - mediaListManager.removedChannelOnlineEventLister(gbStream.getGbId()); - responseAck(evt, Response.REQUEST_TIMEOUT); // 超时 - } catch (SipException e) { - e.printStackTrace(); - } catch (InvalidArgumentException e) { - e.printStackTrace(); - } catch (ParseException e) { - e.printStackTrace(); - } - }, userSetting.getPlatformPlayTimeout()); - // 添加监听 - MediaServerItem finalMediaServerItem = mediaServerItem; - int finalPort = port; - boolean finalMediaTransmissionTCP = mediaTransmissionTCP; - Boolean finalTcpActive = tcpActive; - mediaListManager.addChannelOnlineEventLister(gbStream.getGbId(), (app, stream)->{ - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(finalMediaServerItem, addressStr, finalPort, ssrc, requesterId, - app, stream, channelId, finalMediaTransmissionTCP); - - if (sendRtpItem == null) { - logger.warn("服务器端口资源不足"); - try { - responseAck(evt, Response.BUSY_HERE); - } catch (SipException e) { - e.printStackTrace(); - } catch (InvalidArgumentException e) { - e.printStackTrace(); - } catch (ParseException e) { - e.printStackTrace(); - } - return; - } - if (finalTcpActive != null) { - sendRtpItem.setTcpActive(finalTcpActive); - } - sendRtpItem.setPlayType(InviteStreamType.PUSH); - // 写入redis, 超时时回复 - sendRtpItem.setStatus(1); - sendRtpItem.setCallId(callIdHeader.getCallId()); - byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); - sendRtpItem.setDialog(dialogByteArray); - byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); - sendRtpItem.setTransaction(transactionByteArray); - redisCatchStorage.updateSendRTPSever(sendRtpItem); - sendStreamAck(finalMediaServerItem, sendRtpItem, platform, evt); - - }); - } - }else { - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, - gbStream.getApp(), gbStream.getStream(), channelId, - mediaTransmissionTCP); - - if (sendRtpItem == null) { - logger.warn("服务器端口资源不足"); - responseAck(evt, Response.BUSY_HERE); - return; - } - if (tcpActive != null) { - sendRtpItem.setTcpActive(tcpActive); - } - sendRtpItem.setPlayType(InviteStreamType.PUSH); - // 写入redis, 超时时回复 - sendRtpItem.setStatus(1); - sendRtpItem.setCallId(callIdHeader.getCallId()); - byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); - sendRtpItem.setDialog(dialogByteArray); - byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); - sendRtpItem.setTransaction(transactionByteArray); - redisCatchStorage.updateSendRTPSever(sendRtpItem); - sendStreamAck(mediaServerItem, sendRtpItem, platform, evt); - } + @Autowired + private RedisGbPlayMsgListener redisGbPlayMsgListener; - } + @Override + public void afterPropertiesSet() throws Exception { + // 添加消息处理的订阅 + sipProcessorObserver.addRequestProcessor(method, this); + } - } + /** + * 处理invite请求 + * + * @param evt 请求消息 + */ + @Override + public void process(RequestEvent evt) { + // Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令 + try { + Request request = evt.getRequest(); + SipURI sipUri = (SipURI) request.getRequestURI(); + //从subject读取channelId,不再从request-line读取。 有些平台request-line是平台国标编码,不是设备国标编码。 + //String channelId = sipURI.getUser(); + String channelId = SipUtils.getChannelIdFromHeader(request); + String requesterId = SipUtils.getUserIdFromFromHeader(request); + CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); + if (requesterId == null || channelId == null) { + logger.info("无法从FromHeader的Address中获取到平台id,返回400"); + // 参数不全, 发400,请求错误 + responseAck(evt, Response.BAD_REQUEST); + return; + } - } catch (SipException | InvalidArgumentException | ParseException e) { - e.printStackTrace(); - logger.warn("sdp解析错误"); - e.printStackTrace(); - } catch (SdpParseException e) { - e.printStackTrace(); - } catch (SdpException e) { - e.printStackTrace(); - } - } + // 查询请求是否来自上级平台\设备 + ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId); + if (platform == null) { + inviteFromDeviceHandle(evt, requesterId); + } else { + // 查询平台下是否有该通道 + DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId); + GbStream gbStream = storager.queryStreamInParentPlatform(requesterId, channelId); + PlatformCatalog catalog = storager.getCatalog(channelId); - public void sendStreamAck(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt){ + MediaServerItem mediaServerItem = null; + StreamPushItem streamPushItem = null; + // 不是通道可能是直播流 + if (channel != null && gbStream == null) { + if (channel.getStatus() == 0) { + logger.info("通道离线,返回400"); + responseAck(evt, Response.BAD_REQUEST, "channel [" + channel.getChannelId() + "] offline"); + return; + } + responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中 + } else if (channel == null && gbStream != null) { - StringBuffer content = new StringBuffer(200); - content.append("v=0\r\n"); - content.append("o="+ sendRtpItem.getChannelId() +" 0 0 IN IP4 "+ mediaServerItem.getSdpIp()+"\r\n"); - content.append("s=Play\r\n"); - content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n"); - content.append("t=0 0\r\n"); - content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n"); - content.append("a=sendonly\r\n"); - content.append("a=rtpmap:96 PS/90000\r\n"); - if (sendRtpItem.isTcp()) { - content.append("a=connection:new\r\n"); - if (!sendRtpItem.isTcpActive()) { - content.append("a=setup:active\r\n"); - }else { - content.append("a=setup:passive\r\n"); - } - } - content.append("y="+ sendRtpItem.getSsrc() + "\r\n"); - content.append("f=\r\n"); + String mediaServerId = gbStream.getMediaServerId(); + mediaServerItem = mediaServerService.getOne(mediaServerId); + if (mediaServerItem == null) { + if ("proxy".equals(gbStream.getStreamType())) { + logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId); + responseAck(evt, Response.GONE); + return; + } else { + streamPushItem = streamPushService.getPush(gbStream.getApp(), gbStream.getStream()); + if (streamPushItem == null || streamPushItem.getServerId().equals(userSetting.getServerId())) { + logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId); + responseAck(evt, Response.GONE); + return; + } + } + } else { + if ("push".equals(gbStream.getStreamType())) { + streamPushItem = streamPushService.getPush(gbStream.getApp(), gbStream.getStream()); + if (streamPushItem == null) { + logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId); + responseAck(evt, Response.GONE); + return; + } + } + } + responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中 + } else if (catalog != null) { + responseAck(evt, Response.BAD_REQUEST, "catalog channel can not play"); // 目录不支持点播 + return; + } else { + logger.info("通道不存在,返回404"); + responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在 + return; + } + // 解析sdp消息, 使用jainsip 自带的sdp解析方式 + String contentString = new String(request.getRawContent()); - try { - responseSdpAck(evt, content.toString(), platform); - } catch (SipException e) { - e.printStackTrace(); - } catch (InvalidArgumentException e) { - e.printStackTrace(); - } catch (ParseException e) { - e.printStackTrace(); - } - } + // jainSip不支持y=字段, 移除以解析。 + int ssrcIndex = contentString.indexOf("y="); + // 检查是否有y字段 + String ssrcDefault = "0000000000"; + String ssrc; + SessionDescription sdp; + if (ssrcIndex >= 0) { + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); + String substring = contentString.substring(0, contentString.indexOf("y=")); + sdp = SdpFactory.getInstance().createSessionDescription(substring); + } else { + ssrc = ssrcDefault; + sdp = SdpFactory.getInstance().createSessionDescription(contentString); + } + String sessionName = sdp.getSessionName().getValue(); - public void inviteFromDeviceHandle(RequestEvent evt, String requesterId, String channelId1) throws InvalidArgumentException, ParseException, SipException, SdpException { + Long startTime = null; + Long stopTime = null; + Instant start = null; + Instant end = null; + if (sdp.getTimeDescriptions(false) != null && sdp.getTimeDescriptions(false).size() > 0) { + TimeDescriptionImpl timeDescription = (TimeDescriptionImpl) (sdp.getTimeDescriptions(false).get(0)); + TimeField startTimeFiled = (TimeField) timeDescription.getTime(); + startTime = startTimeFiled.getStartTime(); + stopTime = startTimeFiled.getStopTime(); - // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备) - Device device = redisCatchStorage.getDevice(requesterId); - AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(requesterId, channelId1); - if (audioBroadcastCatch == null) { - logger.warn("来自设备的Invite请求非语音广播,已忽略"); - responseAck(evt, Response.FORBIDDEN); - return; - } - Request request = evt.getRequest(); - if (device != null) { - logger.info("收到设备" + requesterId + "的语音广播Invite请求"); - responseAck(evt, Response.TRYING); + start = Instant.ofEpochSecond(startTime); + end = Instant.ofEpochSecond(stopTime); + } + // 获取支持的格式 + Vector mediaDescriptions = sdp.getMediaDescriptions(true); + // 查看是否支持PS 负载96 + //String ip = null; + int port = -1; + boolean mediaTransmissionTCP = false; + Boolean tcpActive = null; + for (Object description : mediaDescriptions) { + MediaDescription mediaDescription = (MediaDescription) description; + Media media = mediaDescription.getMedia(); - String contentString = new String(request.getRawContent()); - // jainSip不支持y=字段, 移除移除以解析。 - String substring = contentString; - String ssrc = "0000000404"; - int ssrcIndex = contentString.indexOf("y="); - if (ssrcIndex > 0) { - substring = contentString.substring(0, ssrcIndex); - ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12).trim(); - } - ssrcIndex = substring.indexOf("f="); - if (ssrcIndex > 0) { - substring = contentString.substring(0, ssrcIndex); - } - SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); + Vector mediaFormats = media.getMediaFormats(false); + if (mediaFormats.contains("96")) { + port = media.getMediaPort(); + //String mediaType = media.getMediaType(); + String protocol = media.getProtocol(); - // 获取支持的格式 - Vector mediaDescriptions = sdp.getMediaDescriptions(true); + // 区分TCP发流还是udp, 当前默认udp + if ("TCP/RTP/AVP".equals(protocol)) { + String setup = mediaDescription.getAttribute("setup"); + if (setup != null) { + mediaTransmissionTCP = true; + if ("active".equals(setup)) { + tcpActive = true; + // 不支持tcp主动 + responseAck(evt, Response.NOT_IMPLEMENTED, "tcp active not support"); // 目录不支持点播 + return; + } else if ("passive".equals(setup)) { + tcpActive = false; + } + } + } + break; + } + } + if (port == -1) { + logger.info("不支持的媒体格式,返回415"); + // 回复不支持的格式 + responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 + return; + } + String username = sdp.getOrigin().getUsername(); + String addressStr = sdp.getOrigin().getAddress(); - // 查看是否支持PS 负载96 - int port = -1; - boolean mediaTransmissionTCP = false; - Boolean tcpActive = null; - for (int i = 0; i < mediaDescriptions.size(); i++) { - MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i); - Media media = mediaDescription.getMedia(); + logger.info("[上级点播]用户:{}, 通道:{}, 地址:{}:{}, ssrc:{}", username, channelId, addressStr, port, ssrc); + Device device = null; + // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标 + if (channel != null) { + device = storager.queryVideoDeviceByPlatformIdAndChannelId(requesterId, channelId); + if (device == null) { + logger.warn("点播平台{}的通道{}时未找到设备信息", requesterId, channel); + responseAck(evt, Response.SERVER_INTERNAL_ERROR); + return; + } + mediaServerItem = playService.getNewMediaServerItem(device); + if (mediaServerItem == null) { + logger.warn("未找到可用的zlm"); + responseAck(evt, Response.BUSY_HERE); + return; + } + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, + device.getDeviceId(), channelId, + mediaTransmissionTCP); + if (tcpActive != null) { + sendRtpItem.setTcpActive(tcpActive); + } + if (sendRtpItem == null) { + logger.warn("服务器端口资源不足"); + responseAck(evt, Response.BUSY_HERE); + return; + } + sendRtpItem.setCallId(callIdHeader.getCallId()); + sendRtpItem.setPlayType("Play".equals(sessionName) ? InviteStreamType.PLAY : InviteStreamType.PLAYBACK); - Vector mediaFormats = media.getMediaFormats(false); - if (mediaFormats.contains("8")) { - port = media.getMediaPort(); - String protocol = media.getProtocol(); - // 区分TCP发流还是udp, 当前默认udp - if ("TCP/RTP/AVP".equals(protocol)) { - String setup = mediaDescription.getAttribute("setup"); - if (setup != null) { - mediaTransmissionTCP = true; - if ("active".equals(setup)) { - tcpActive = true; - } else if ("passive".equals(setup)) { - tcpActive = false; - } - } - } - break; - } - } - if (port == -1) { - logger.info("不支持的媒体格式,返回415"); - // 回复不支持的格式 - responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 - return; - } - String addressStr = sdp.getOrigin().getAddress(); - logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", requesterId, addressStr, port, ssrc); + Long finalStartTime = startTime; + Long finalStopTime = stopTime; + ZLMHttpHookSubscribe.Event hookEvent = (mediaServerItemInUSe, responseJSON) -> { + String app = responseJSON.getString("app"); + String stream = responseJSON.getString("stream"); + logger.info("[上级点播]下级已经开始推流。 回复200OK(SDP), {}/{}", app, stream); + // * 0 等待设备推流上来 + // * 1 下级已经推流,等待上级平台回复ack + // * 2 推流中 + sendRtpItem.setStatus(1); + redisCatchStorage.updateSendRTPSever(sendRtpItem); - MediaServerItem mediaServerItem = playService.getNewMediaServerItem(device); - if (mediaServerItem == null) { - logger.warn("未找到可用的zlm"); - responseAck(evt, Response.BUSY_HERE); - return; - } - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, - device.getDeviceId(), audioBroadcastCatch.getChannelId(), - mediaTransmissionTCP); - if (sendRtpItem == null) { - logger.warn("服务器端口资源不足"); - responseAck(evt, Response.BUSY_HERE); - return; - } - sendRtpItem.setTcp(mediaTransmissionTCP); - if (tcpActive != null) { - sendRtpItem.setTcpActive(tcpActive); - } - String app = "broadcast"; - String stream = device.getDeviceId() + "_" + audioBroadcastCatch.getChannelId(); + StringBuffer content = new StringBuffer(200); + content.append("v=0\r\n"); + content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n"); + content.append("s=" + sessionName + "\r\n"); + content.append("c=IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n"); + if ("Playback".equals(sessionName)) { + content.append("t=" + finalStartTime + " " + finalStopTime + "\r\n"); + } else { + content.append("t=0 0\r\n"); + } + content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n"); + content.append("a=sendonly\r\n"); + content.append("a=rtpmap:96 PS/90000\r\n"); + content.append("y=" + ssrc + "\r\n"); + content.append("f=\r\n"); + try { + // 超时未收到Ack应该回复bye,当前等待时间为10秒 + dynamicTask.startDelay(callIdHeader.getCallId(), () -> { + logger.info("Ack 等待超时"); + mediaServerService.releaseSsrc(mediaServerItemInUSe.getId(), ssrc); + // 回复bye + cmderFroPlatform.streamByeCmd(platform, callIdHeader.getCallId()); + }, 60 * 1000); + responseSdpAck(evt, content.toString(), platform); + + } catch (SipException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + }; + SipSubscribe.Event errorEvent = ((event) -> { + // 未知错误。直接转发设备点播的错误 + Response response = null; + try { + response = getMessageFactory().createResponse(event.statusCode, evt.getRequest()); + ServerTransaction serverTransaction = getServerTransaction(evt); + serverTransaction.sendResponse(response); + if (serverTransaction.getDialog() != null) { + serverTransaction.getDialog().delete(); + } + } catch (ParseException | SipException | InvalidArgumentException e) { + e.printStackTrace(); + } + }); + sendRtpItem.setApp("rtp"); + if ("Playback".equals(sessionName)) { + sendRtpItem.setPlayType(InviteStreamType.PLAYBACK); + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true, true); + sendRtpItem.setStreamId(ssrcInfo.getStream()); + // 写入redis, 超时时回复 + redisCatchStorage.updateSendRTPSever(sendRtpItem); + playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start), + DateUtil.formatter.format(end), null, result -> { + if (result.getCode() != 0) { + logger.warn("录像回放失败"); + if (result.getEvent() != null) { + errorEvent.response(result.getEvent()); + } + redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); + try { + responseAck(evt, Response.REQUEST_TIMEOUT); + } catch (SipException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + } else { + if (result.getMediaServerItem() != null) { + hookEvent.response(result.getMediaServerItem(), result.getResponse()); + } + } + }); + } else { + sendRtpItem.setPlayType(InviteStreamType.PLAY); + SsrcTransaction playTransaction = sessionManager.getSsrcTransaction(device.getDeviceId(), channelId, "play", null); + if (playTransaction != null) { + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream()); + if (!streamReady) { + playTransaction = null; + } + } + if (playTransaction == null) { + String streamId = null; + if (mediaServerItem.isRtpEnable()) { + streamId = String.format("%s_%s", device.getDeviceId(), channelId); + } + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false); + sendRtpItem.setStreamId(ssrcInfo.getStream()); + // 写入redis, 超时时回复 + redisCatchStorage.updateSendRTPSever(sendRtpItem); + playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg) -> { + logger.info("[上级点播]超时, 用户:{}, 通道:{}", username, channelId); + redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); + }, null); + } else { + sendRtpItem.setStreamId(playTransaction.getStream()); + // 写入redis, 超时时回复 + redisCatchStorage.updateSendRTPSever(sendRtpItem); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("app", sendRtpItem.getApp()); + jsonObject.put("stream", sendRtpItem.getStreamId()); + hookEvent.response(mediaServerItem, jsonObject); + } + } + } else if (gbStream != null) { + if (streamPushItem.isStatus()) { + // 在线状态 + pushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); + } else { + // 不在线 拉起 + notifyStreamOnline(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); + } + + } + + } + + } catch (SipException | InvalidArgumentException | ParseException e) { + e.printStackTrace(); + logger.warn("sdp解析错误"); + e.printStackTrace(); + } catch (SdpParseException e) { + e.printStackTrace(); + } catch (SdpException e) { + e.printStackTrace(); + } + } + + /** + * 安排推流 + */ + + private void pushStream(RequestEvent evt, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform, + CallIdHeader callIdHeader, MediaServerItem mediaServerItem, + int port, Boolean tcpActive, boolean mediaTransmissionTCP, + String channelId, String addressStr, String ssrc, String requesterId) throws InvalidArgumentException, ParseException, SipException { + // 推流 + if (streamPushItem.getServerId().equals(userSetting.getServerId())) { + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream()); + if (streamReady) { + // 自平台内容 + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, + gbStream.getApp(), gbStream.getStream(), channelId, + mediaTransmissionTCP); + + if (sendRtpItem == null) { + logger.warn("服务器端口资源不足"); + responseAck(evt, Response.BUSY_HERE); + return; + } + if (tcpActive != null) { + sendRtpItem.setTcpActive(tcpActive); + } + sendRtpItem.setPlayType(InviteStreamType.PUSH); + // 写入redis, 超时时回复 + sendRtpItem.setStatus(1); + sendRtpItem.setCallId(callIdHeader.getCallId()); + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); + sendRtpItem.setDialog(dialogByteArray); + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); + sendRtpItem.setTransaction(transactionByteArray); + redisCatchStorage.updateSendRTPSever(sendRtpItem); + sendStreamAck(mediaServerItem, sendRtpItem, platform, evt); + } else { + // 不在线 拉起 + notifyStreamOnline(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); + } + + } else { + // 其他平台内容 + otherWvpPushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); + } + + } + + /** + * 通知流上线 + */ + private void notifyStreamOnline(RequestEvent evt, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform, + CallIdHeader callIdHeader, MediaServerItem mediaServerItem, + int port, Boolean tcpActive, boolean mediaTransmissionTCP, + String channelId, String addressStr, String ssrc, String requesterId) throws InvalidArgumentException, ParseException, SipException { + if ("proxy".equals(gbStream.getStreamType())) { + // TODO 控制启用以使设备上线 + logger.info("[ app={}, stream={} ]通道离线,启用流后开始推流", gbStream.getApp(), gbStream.getStream()); + 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; + } + // 发送redis消息以使设备上线 + logger.info("[ app={}, stream={} ]通道离线,发送redis信息控制设备开始推流", gbStream.getApp(), gbStream.getStream()); + + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(1, + gbStream.getApp(), gbStream.getStream(), gbStream.getGbId(), gbStream.getPlatformId(), + platform.getName(), null, gbStream.getMediaServerId()); + redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); + // 设置超时 + dynamicTask.startDelay(callIdHeader.getCallId(), () -> { + logger.info("[ app={}, stream={} ] 等待设备开始推流超时", gbStream.getApp(), gbStream.getStream()); + try { + mediaListManager.removedChannelOnlineEventLister(gbStream.getGbId()); + responseAck(evt, Response.REQUEST_TIMEOUT); // 超时 + } catch (SipException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + }, userSetting.getPlatformPlayTimeout()); + // 添加监听 + int finalPort = port; + Boolean finalTcpActive = tcpActive; + + // 添加在本机上线的通知 + mediaListManager.addChannelOnlineEventLister(gbStream.getGbId(), (app, stream, serverId) -> { + dynamicTask.stop(callIdHeader.getCallId()); + if (serverId.equals(userSetting.getServerId())) { + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, finalPort, ssrc, requesterId, + app, stream, channelId, mediaTransmissionTCP); + + if (sendRtpItem == null) { + logger.warn("服务器端口资源不足"); + try { + responseAck(evt, Response.BUSY_HERE); + } catch (SipException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + return; + } + if (finalTcpActive != null) { + sendRtpItem.setTcpActive(finalTcpActive); + } + sendRtpItem.setPlayType(InviteStreamType.PUSH); + // 写入redis, 超时时回复 + sendRtpItem.setStatus(1); + sendRtpItem.setCallId(callIdHeader.getCallId()); + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); + sendRtpItem.setDialog(dialogByteArray); + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); + sendRtpItem.setTransaction(transactionByteArray); + redisCatchStorage.updateSendRTPSever(sendRtpItem); + sendStreamAck(mediaServerItem, sendRtpItem, platform, evt); + } else { + // 其他平台内容 + otherWvpPushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); + } + }); + } + } + + /** + * 来自其他wvp的推流 + */ + private void otherWvpPushStream(RequestEvent evt, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform, + CallIdHeader callIdHeader, MediaServerItem mediaServerItem, + int port, Boolean tcpActive, boolean mediaTransmissionTCP, + String channelId, String addressStr, String ssrc, String requesterId) { + logger.info("[级联点播]直播流来自其他平台,发送redis消息"); + // 发送redis消息 + redisGbPlayMsgListener.sendMsg(streamPushItem.getServerId(), streamPushItem.getMediaServerId(), + streamPushItem.getApp(), streamPushItem.getStream(), addressStr, port, ssrc, requesterId, + channelId, mediaTransmissionTCP, null, responseSendItemMsg -> { + SendRtpItem sendRtpItem = responseSendItemMsg.getSendRtpItem(); + if (sendRtpItem == null || responseSendItemMsg.getMediaServerItem() == null) { + logger.warn("服务器端口资源不足"); + try { + responseAck(evt, Response.BUSY_HERE); + } catch (SipException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + return; + } + // 收到sendItem + if (tcpActive != null) { + sendRtpItem.setTcpActive(tcpActive); + } + sendRtpItem.setPlayType(InviteStreamType.PUSH); + // 写入redis, 超时时回复 + sendRtpItem.setStatus(1); + sendRtpItem.setCallId(callIdHeader.getCallId()); + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog()); + sendRtpItem.setDialog(dialogByteArray); + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction()); + sendRtpItem.setTransaction(transactionByteArray); + redisCatchStorage.updateSendRTPSever(sendRtpItem); + sendStreamAck(responseSendItemMsg.getMediaServerItem(), sendRtpItem, platform, evt); + }, (wvpResult) -> { + try { + // 错误 + if (wvpResult.getCode() == RedisGbPlayMsgListener.ERROR_CODE_OFFLINE) { + // 离线 + // 查询是否在本机上线了 + StreamPushItem currentStreamPushItem = streamPushService.getPush(streamPushItem.getApp(), streamPushItem.getStream()); + if (currentStreamPushItem.isStatus()) { + // 在线状态 + pushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); + + } else { + // 不在线 拉起 + notifyStreamOnline(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); + } + } + } catch (InvalidArgumentException e) { + throw new RuntimeException(e); + } catch (ParseException e) { + throw new RuntimeException(e); + } catch (SipException e) { + throw new RuntimeException(e); + } + + + try { + responseAck(evt, Response.BUSY_HERE); + } catch (SipException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + return; + }); + } + + public void sendStreamAck(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt) { + + StringBuffer content = new StringBuffer(200); + content.append("v=0\r\n"); + content.append("o=" + sendRtpItem.getChannelId() + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); + content.append("s=Play\r\n"); + content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); + content.append("t=0 0\r\n"); + content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n"); + content.append("a=sendonly\r\n"); + content.append("a=rtpmap:96 PS/90000\r\n"); + if (sendRtpItem.isTcp()) { + content.append("a=connection:new\r\n"); + if (!sendRtpItem.isTcpActive()) { + content.append("a=setup:active\r\n"); + } else { + content.append("a=setup:passive\r\n"); + } + } + content.append("y=" + sendRtpItem.getSsrc() + "\r\n"); + content.append("f=\r\n"); + + try { + responseSdpAck(evt, content.toString(), platform); + } catch (SipException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + } + + public void inviteFromDeviceHandle(RequestEvent evt, String requesterId) throws InvalidArgumentException, ParseException, SipException, SdpException { + + // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备) + Device device = redisCatchStorage.getDevice(requesterId); + Request request = evt.getRequest(); + if (device != null) { + logger.info("收到设备" + requesterId + "的语音广播Invite请求"); + responseAck(evt, Response.TRYING); + + String contentString = new String(request.getRawContent()); + // jainSip不支持y=字段, 移除移除以解析。 + String substring = contentString; + String ssrc = "0000000404"; + int ssrcIndex = contentString.indexOf("y="); + if (ssrcIndex > 0) { + substring = contentString.substring(0, ssrcIndex); + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); + } + ssrcIndex = substring.indexOf("f="); + if (ssrcIndex > 0) { + substring = contentString.substring(0, ssrcIndex); + } + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); + + // 获取支持的格式 + Vector mediaDescriptions = sdp.getMediaDescriptions(true); + // 查看是否支持PS 负载96 + int port = -1; + //boolean recvonly = false; + boolean mediaTransmissionTCP = false; + Boolean tcpActive = null; + for (int i = 0; i < mediaDescriptions.size(); i++) { + MediaDescription mediaDescription = (MediaDescription) mediaDescriptions.get(i); + Media media = mediaDescription.getMedia(); + + Vector mediaFormats = media.getMediaFormats(false); + if (mediaFormats.contains("8")) { + port = media.getMediaPort(); + String protocol = media.getProtocol(); + // 区分TCP发流还是udp, 当前默认udp + if ("TCP/RTP/AVP".equals(protocol)) { + String setup = mediaDescription.getAttribute("setup"); + if (setup != null) { + mediaTransmissionTCP = true; + if ("active".equals(setup)) { + tcpActive = true; + } else if ("passive".equals(setup)) { + tcpActive = false; + } + } + } + break; + } + } + if (port == -1) { + logger.info("不支持的媒体格式,返回415"); + // 回复不支持的格式 + responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 + return; + } + String username = sdp.getOrigin().getUsername(); + String addressStr = sdp.getOrigin().getAddress(); + logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc); + + } else { + logger.warn("来自无效设备/平台的请求"); + responseAck(evt, Response.BAD_REQUEST); + } + } CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); sendRtpItem.setPlayType(InviteStreamType.PLAY); sendRtpItem.setCallId(callIdHeader.getCallId()); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java index e923a54a2..7e666581d 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java @@ -1,7 +1,6 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; import com.alibaba.fastjson.JSONObject; -import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.*; @@ -18,6 +17,7 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; +import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import org.dom4j.DocumentException; import org.dom4j.Element; @@ -25,6 +25,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -35,6 +37,7 @@ import javax.sip.header.FromHeader; import javax.sip.message.Response; import java.text.ParseException; import java.util.Iterator; +import java.util.concurrent.ConcurrentLinkedQueue; /** * SIP命令类型: NOTIFY请求 @@ -63,11 +66,19 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements @Autowired private EventPublisher publisher; - private String method = "NOTIFY"; + private final String method = "NOTIFY"; @Autowired private SIPProcessorObserver sipProcessorObserver; + private boolean taskQueueHandlerRun = false; + + private final ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); + + @Qualifier("taskExecutor") + @Autowired + private ThreadPoolTaskExecutor taskExecutor; + @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 @@ -77,23 +88,40 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements @Override public void process(RequestEvent evt) { try { - Element rootElement = getRootElement(evt); - String cmd = XmlUtil.getText(rootElement, "CmdType"); - if (CmdType.CATALOG.equals(cmd)) { - logger.info("接收到Catalog通知"); - processNotifyCatalogList(evt); - } else if (CmdType.ALARM.equals(cmd)) { - logger.info("接收到Alarm通知"); - processNotifyAlarm(evt); - } else if (CmdType.MOBILE_POSITION.equals(cmd)) { - logger.info("接收到MobilePosition通知"); - processNotifyMobilePosition(evt); - } else { - logger.info("接收到消息:" + cmd); - responseAck(evt, Response.OK); + taskQueue.offer(new HandlerCatchData(evt, null, null)); + responseAck(evt, Response.OK); + if (!taskQueueHandlerRun) { + taskQueueHandlerRun = true; + taskExecutor.execute(()-> { + while (!taskQueue.isEmpty()) { + try { + HandlerCatchData take = taskQueue.poll(); + Element rootElement = getRootElement(take.getEvt()); + String cmd = XmlUtil.getText(rootElement, "CmdType"); + + if (CmdType.CATALOG.equals(cmd)) { + logger.info("接收到Catalog通知"); + processNotifyCatalogList(take.getEvt()); + } else if (CmdType.ALARM.equals(cmd)) { + logger.info("接收到Alarm通知"); + processNotifyAlarm(take.getEvt()); + } else if (CmdType.MOBILE_POSITION.equals(cmd)) { + logger.info("接收到MobilePosition通知"); + processNotifyMobilePosition(take.getEvt()); + } else { + logger.info("接收到消息:" + cmd); + } + } catch (DocumentException e) { + throw new RuntimeException(e); + } + } + taskQueueHandlerRun = false; + }); } - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { + + + } catch (SipException | InvalidArgumentException | ParseException e) { e.printStackTrace(); } } @@ -166,8 +194,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements jsonObject.put("direction", mobilePosition.getDirection()); jsonObject.put("speed", mobilePosition.getSpeed()); redisCatchStorage.sendMobilePositionMsg(jsonObject); - responseAck(evt, Response.OK); - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { + } catch (DocumentException e) { e.printStackTrace(); } } @@ -188,6 +215,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements Device device = redisCatchStorage.getDevice(deviceId); if (device == null) { + logger.warn("[ NotifyAlarm ] 未找到设备:{}", deviceId); return; } rootElement = getRootElement(evt, device.getCharset()); @@ -195,7 +223,12 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements deviceAlarm.setDeviceId(deviceId); deviceAlarm.setAlarmPriority(XmlUtil.getText(rootElement, "AlarmPriority")); deviceAlarm.setAlarmMethod(XmlUtil.getText(rootElement, "AlarmMethod")); - deviceAlarm.setAlarmTime(XmlUtil.getText(rootElement, "AlarmTime")); + String alarmTime = XmlUtil.getText(rootElement, "AlarmTime"); + if (alarmTime == null) { + logger.warn("[ NotifyAlarm ] AlarmTime cannot be null"); + return; + } + deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime)); if (XmlUtil.getText(rootElement, "AlarmDescription") == null) { deviceAlarm.setAlarmDescription(""); } else { @@ -212,7 +245,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements deviceAlarm.setLatitude(0.00); } logger.info("[收到Notify-Alarm]:{}/{}", device.getDeviceId(), deviceAlarm.getChannelId()); - if (deviceAlarm.getAlarmMethod().equals("4")) { + if ("4".equals(deviceAlarm.getAlarmMethod())) { MobilePosition mobilePosition = new MobilePosition(); mobilePosition.setDeviceId(deviceAlarm.getDeviceId()); mobilePosition.setTime(deviceAlarm.getAlarmTime()); @@ -233,11 +266,10 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements // TODO: 需要实现存储报警信息、报警分类 // 回复200 OK - responseAck(evt, Response.OK); if (redisCatchStorage.deviceIsOnline(deviceId)) { publisher.deviceAlarmEventPublish(deviceAlarm); } - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { + } catch (DocumentException e) { e.printStackTrace(); } } @@ -273,64 +305,60 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements continue; } Element eventElement = itemDevice.element("Event"); - DeviceChannel channel = XmlUtil.channelContentHander(itemDevice); + String event; + if (eventElement == null) { + logger.warn("[收到 目录订阅]:{}, 但是Event为空, 设为默认值 ADD", (device != null ? device.getDeviceId():"" )); + event = CatalogEvent.ADD; + }else { + event = eventElement.getText().toUpperCase(); + } + DeviceChannel channel = XmlUtil.channelContentHander(itemDevice, device); channel.setDeviceId(device.getDeviceId()); logger.info("[收到 目录订阅]:{}/{}", device.getDeviceId(), channel.getChannelId()); - switch (eventElement.getText().toUpperCase()) { + switch (event) { case CatalogEvent.ON: // 上线 logger.info("收到来自设备【{}】的通道【{}】上线通知", device.getDeviceId(), channel.getChannelId()); storager.deviceChannelOnline(deviceId, channel.getChannelId()); - // 回复200 OK - responseAck(evt, Response.OK); break; case CatalogEvent.OFF : // 离线 logger.info("收到来自设备【{}】的通道【{}】离线通知", device.getDeviceId(), channel.getChannelId()); storager.deviceChannelOffline(deviceId, channel.getChannelId()); - // 回复200 OK - responseAck(evt, Response.OK); break; case CatalogEvent.VLOST: // 视频丢失 logger.info("收到来自设备【{}】的通道【{}】视频丢失通知", device.getDeviceId(), channel.getChannelId()); storager.deviceChannelOffline(deviceId, channel.getChannelId()); - // 回复200 OK - responseAck(evt, Response.OK); break; case CatalogEvent.DEFECT: // 故障 - // 回复200 OK - responseAck(evt, Response.OK); break; case CatalogEvent.ADD: // 增加 logger.info("收到来自设备【{}】的增加通道【{}】通知", device.getDeviceId(), channel.getChannelId()); storager.updateChannel(deviceId, channel); - responseAck(evt, Response.OK); break; case CatalogEvent.DEL: // 删除 logger.info("收到来自设备【{}】的删除通道【{}】通知", device.getDeviceId(), channel.getChannelId()); storager.delChannel(deviceId, channel.getChannelId()); - responseAck(evt, Response.OK); break; case CatalogEvent.UPDATE: // 更新 logger.info("收到来自设备【{}】的更新通道【{}】通知", device.getDeviceId(), channel.getChannelId()); storager.updateChannel(deviceId, channel); - responseAck(evt, Response.OK); break; default: - responseAck(evt, Response.BAD_REQUEST, "event not found"); + logger.warn("[ NotifyCatalog ] event not found : {}", event ); } // 转发变化信息 - eventPublisher.catalogEventPublish(null, channel, eventElement.getText().toUpperCase()); + eventPublisher.catalogEventPublish(null, channel, event); } } - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { + } catch (DocumentException e) { e.printStackTrace(); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java index 47a4e008b..c2226a180 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java @@ -1,17 +1,13 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; -import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.SipConfig; -import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate; -import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; +import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper; import com.genersoft.iot.vmp.service.IDeviceService; -import com.genersoft.iot.vmp.storager.IRedisCatchStorage; -import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.utils.DateUtil; import gov.nist.javax.sip.RequestEventExt; import gov.nist.javax.sip.address.AddressImpl; @@ -45,20 +41,11 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen private final Logger logger = LoggerFactory.getLogger(RegisterRequestProcessor.class); - public String method = "REGISTER"; + public final String method = "REGISTER"; @Autowired private SipConfig sipConfig; - @Autowired - private IRedisCatchStorage redisCatchStorage; - - @Autowired - private IVideoManagerStorage storager; - - @Autowired - private EventPublisher publisher; - @Autowired private SIPProcessorObserver sipProcessorObserver; @@ -86,7 +73,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME); Response response = null; boolean passwordCorrect = false; - // 注册标志 0:未携带授权头或者密码错误 1:注册成功 2:注销成功 + // 注册标志 boolean registerFlag = false; FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME); AddressImpl address = (AddressImpl) fromHeader.getAddress(); @@ -105,7 +92,6 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen // 校验密码是否正确 passwordCorrect = StringUtils.isEmpty(sipConfig.getPassword()) || new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, sipConfig.getPassword()); - // 未携带授权头或者密码错误 回复401 if (!passwordCorrect) { // 注册失败 @@ -154,6 +140,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen device = new Device(); device.setStreamMode("UDP"); device.setCharset("GB2312"); + device.setGeoCoordSys("WGS84"); device.setDeviceId(deviceId); } device.setIp(received); 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 548dbde82..d9d947970 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 @@ -82,7 +82,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme @Override public void process(RequestEvent evt) { Request request = evt.getRequest(); - try { Element rootElement = getRootElement(evt); String cmd = XmlUtil.getText(rootElement, "CmdType"); @@ -176,6 +175,8 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme } private void processNotifyCatalogList(RequestEvent evt, Element rootElement) throws SipException { + + System.out.println(evt.getRequest().toString()); String platformId = SipUtils.getUserIdFromFromHeader(evt.getRequest()); String deviceId = XmlUtil.getText(rootElement, "DeviceID"); ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java new file mode 100644 index 000000000..a1e98f9ea --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java @@ -0,0 +1,143 @@ +package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info; + +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; +import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; +import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; +import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; +import com.genersoft.iot.vmp.gb28181.utils.SipUtils; +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; +import gov.nist.javax.sip.message.SIPRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.header.*; +import javax.sip.message.Response; +import java.text.ParseException; + +@Component +public class InfoRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { + + private final static Logger logger = LoggerFactory.getLogger(InfoRequestProcessor.class); + + private final String method = "INFO"; + + @Autowired + private SIPProcessorObserver sipProcessorObserver; + + @Autowired + private IVideoManagerStorage storage; + + @Autowired + private SipSubscribe sipSubscribe; + + @Autowired + private IRedisCatchStorage redisCatchStorage; + + @Autowired + private IVideoManagerStorage storager; + + @Autowired + private SIPCommander cmder; + + @Autowired + private VideoStreamSessionManager sessionManager; + + @Override + public void afterPropertiesSet() throws Exception { + // 添加消息处理的订阅 + sipProcessorObserver.addRequestProcessor(method, this); + } + + @Override + public void process(RequestEvent evt) { + logger.debug("接收到消息:" + evt.getRequest()); + String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest()); + CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); + // 先从会话内查找 + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); + if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题 + deviceId = ssrcTransaction.getDeviceId(); + } + // 查询设备是否存在 + Device device = redisCatchStorage.getDevice(deviceId); + // 查询上级平台是否存在 + ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId); + try { + if (device != null && parentPlatform != null) { + logger.warn("[重复]平台与设备编号重复:{}", deviceId); + SIPRequest request = (SIPRequest) evt.getRequest(); + String hostAddress = request.getRemoteAddress().getHostAddress(); + int remotePort = request.getRemotePort(); + if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) { + parentPlatform = null; + }else { + device = null; + } + } + if (device == null && parentPlatform == null) { + // 不存在则回复404 + responseAck(evt, Response.NOT_FOUND, "device "+ deviceId +" not found"); + logger.warn("[设备未找到 ]: {}", deviceId); + if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){ + SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new DeviceNotFoundEvent(evt.getDialog())); + sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()).response(eventResult); + }; + }else { + ContentTypeHeader header = (ContentTypeHeader)evt.getRequest().getHeader(ContentTypeHeader.NAME); + String contentType = header.getContentType(); + String contentSubType = header.getContentSubType(); + if ("Application".equals(contentType) && "MANSRTSP".equals(contentSubType)) { + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId()); + String streamId = sendRtpItem.getStreamId(); + StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null); + if (null == streamInfo) { + responseAck(evt, Response.NOT_FOUND, "stream " + streamId + " not found"); + return; + } + Device device1 = storager.queryVideoDevice(streamInfo.getDeviceID()); + cmder.playbackControlCmd(device1,streamInfo,new String(evt.getRequest().getRawContent()),eventResult -> { + // 失败的回复 + try { + responseAck(evt, eventResult.statusCode, eventResult.msg); + } catch (SipException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + }, eventResult -> { + // 成功的回复 + try { + responseAck(evt, eventResult.statusCode); + } catch (SipException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + }); + } + } + } catch (SipException e) { + logger.warn("SIP 回复错误", e); + } catch (InvalidArgumentException e) { + logger.warn("参数无效", e); + } catch (ParseException e) { + logger.warn("SIP回复时解析异常", e); + } + } + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java index 136b91206..cd858896c 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java @@ -72,7 +72,7 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest()); CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); // 先从会话内查找 - SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, null, callIdHeader.getCallId()); + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题 deviceId = ssrcTransaction.getDeviceId(); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java index b7e222df0..6a23dfa13 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java @@ -9,9 +9,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessag import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler; import com.genersoft.iot.vmp.gb28181.utils.Coordtransform; import com.genersoft.iot.vmp.gb28181.utils.NumericUtil; +import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; import com.genersoft.iot.vmp.service.IDeviceAlarmService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; +import com.genersoft.iot.vmp.utils.DateUtil; import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,7 +86,11 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme deviceAlarm.setChannelId(channelId); deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority")); deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod")); - deviceAlarm.setAlarmTime(getText(rootElement, "AlarmTime")); + String alarmTime = XmlUtil.getText(rootElement, "AlarmTime"); + if (alarmTime == null) { + return; + } + deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime)); String alarmDescription = getText(rootElement, "AlarmDescription"); if (alarmDescription == null) { deviceAlarm.setAlarmDescription(""); @@ -175,7 +181,11 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme deviceAlarm.setChannelId(channelId); deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority")); deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod")); - deviceAlarm.setAlarmTime(getText(rootElement, "AlarmTime")); + String alarmTime = XmlUtil.getText(rootElement, "AlarmTime"); + if (alarmTime == null) { + return; + } + deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime)); String alarmDescription = getText(rootElement, "AlarmDescription"); if (alarmDescription == null) { deviceAlarm.setAlarmDescription(""); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java index cf07250d9..c4294576a 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java @@ -64,16 +64,14 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp device.setHostAddress(received.concat(":").concat(String.valueOf(rPort))); } device.setKeepaliveTime(DateUtil.getNow()); + // 回复200 OK + responseAck(evt, Response.OK); if (device.getOnline() == 1) { - // 回复200 OK - responseAck(evt, Response.OK); deviceService.updateDevice(device); }else { // 对于已经离线的设备判断他的注册是否已经过期 if (!deviceService.expire(device)){ deviceService.online(device); - // 回复200 OK - responseAck(evt, Response.OK); } } } catch (SipException e) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java index 4cf976855..8a5ef793c 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java @@ -3,11 +3,16 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; +import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,9 +41,18 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i @Autowired private SIPCommander cmder; + @Autowired + private SIPCommanderFroPlatform sipCommanderFroPlatform; + @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private IVideoManagerStorage storage; + + @Autowired + private VideoStreamSessionManager sessionManager; + @Override public void afterPropertiesSet() throws Exception { notifyMessageHandler.addHandler(cmdType, this); @@ -59,17 +73,32 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i } CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); String NotifyType =getText(rootElement, "NotifyType"); - if (NotifyType.equals("121")){ + if ("121".equals(NotifyType)){ logger.info("[录像流]推送完毕,收到关流通知"); - String channelId =getText(rootElement, "DeviceID"); // 查询是设备 - StreamInfo streamInfo = redisCatchStorage.queryDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); - // 设置进度100% - streamInfo.setProgress(1); - redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId()); - cmder.streamByeCmd(device.getDeviceId(), channelId, null, callIdHeader.getCallId()); - // TODO 如果级联播放,需要给上级发送此通知 + StreamInfo streamInfo = redisCatchStorage.queryDownload(null, null, null, callIdHeader.getCallId()); + if (streamInfo != null) { + // 设置进度100% + streamInfo.setProgress(1); + redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId()); + } + // 先从会话内查找 + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); + if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题 + cmder.streamByeCmd(device.getDeviceId(), ssrcTransaction.getChannelId(), null, callIdHeader.getCallId()); + + // 如果级联播放,需要给上级发送此通知 TODO 多个上级同时观看一个下级 可能存在停错的问题,需要将点播CallId进行上下级绑定 + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, ssrcTransaction.getChannelId(), null, null); + if (sendRtpItem != null) { + ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); + if (parentPlatform == null) { + logger.warn("[级联消息发送]:发送MediaStatus发现上级平台{}不存在", sendRtpItem.getPlatformId()); + return; + } + sipCommanderFroPlatform.sendMediaStatusNotify(parentPlatform, sendRtpItem); + } + } } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java index 891b21da4..c4e88c57b 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java @@ -20,6 +20,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -31,6 +33,7 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; @Component public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { @@ -38,9 +41,13 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class); private final String cmdType = "Catalog"; + private boolean taskQueueHandlerRun = false; + @Autowired private ResponseMessageHandler responseMessageHandler; + private ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); + @Autowired private IVideoManagerStorage storager; @@ -63,6 +70,10 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp @Autowired private IRedisCatchStorage redisCatchStorage; + @Qualifier("taskExecutor") + @Autowired + private ThreadPoolTaskExecutor taskExecutor; + @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); @@ -70,68 +81,88 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp @Override public void handForDevice(RequestEvent evt, Device device, Element element) { - String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + device.getDeviceId(); - Element rootElement = null; + taskQueue.offer(new HandlerCatchData(evt, device, element)); + // 回复200 OK try { - rootElement = getRootElement(evt, device.getCharset()); - Element deviceListElement = rootElement.element("DeviceList"); - Element sumNumElement = rootElement.element("SumNum"); - Element snElement = rootElement.element("SN"); - if (snElement == null || sumNumElement == null || deviceListElement == null) { - responseAck(evt, Response.BAD_REQUEST, "xml error"); - return; - } - int sumNum = Integer.parseInt(sumNumElement.getText()); - - if (sumNum == 0) { - // 数据已经完整接收 - storager.cleanChannelsForDevice(device.getDeviceId()); - catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null); - }else { - Iterator deviceListIterator = deviceListElement.elementIterator(); - if (deviceListIterator != null) { - List channelList = new ArrayList<>(); - // 遍历DeviceList - while (deviceListIterator.hasNext()) { - Element itemDevice = deviceListIterator.next(); - Element channelDeviceElement = itemDevice.element("DeviceID"); - if (channelDeviceElement == null) { - continue; + responseAck(evt, Response.OK); + } catch (SipException e) { + throw new RuntimeException(e); + } catch (InvalidArgumentException e) { + throw new RuntimeException(e); + } catch (ParseException e) { + throw new RuntimeException(e); + } + if (!taskQueueHandlerRun) { + taskQueueHandlerRun = true; + taskExecutor.execute(()-> { + while (!taskQueue.isEmpty()) { + HandlerCatchData take = taskQueue.poll(); + String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + take.getDevice().getDeviceId(); + Element rootElement = null; + try { + rootElement = getRootElement(take.getEvt(), take.getDevice().getCharset()); + Element deviceListElement = rootElement.element("DeviceList"); + Element sumNumElement = rootElement.element("SumNum"); + Element snElement = rootElement.element("SN"); + if (snElement == null || sumNumElement == null || deviceListElement == null) { + responseAck(take.getEvt(), Response.BAD_REQUEST, "xml error"); + return; } - //by brewswang -// if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {//如果包含位置信息,就更新一下位置 -// processNotifyMobilePosition(evt, itemDevice); -// } - DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice); - deviceChannel.setDeviceId(device.getDeviceId()); + int sumNum = Integer.parseInt(sumNumElement.getText()); - channelList.add(deviceChannel); - } - int sn = Integer.parseInt(snElement.getText()); - catalogDataCatch.put(device.getDeviceId(), sn, sumNum, device, channelList); - logger.info("收到来自设备【{}】的通道: {}个,{}/{}", device.getDeviceId(), channelList.size(), catalogDataCatch.get(device.getDeviceId()) == null ? 0 :catalogDataCatch.get(device.getDeviceId()).size(), sumNum); - if (catalogDataCatch.get(device.getDeviceId()).size() == sumNum) { - // 数据已经完整接收 - boolean resetChannelsResult = storager.resetChannels(device.getDeviceId(), catalogDataCatch.get(device.getDeviceId())); - if (!resetChannelsResult) { - String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(device.getDeviceId()).size() + "条"; - catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), errorMsg); + if (sumNum == 0) { + // 数据已经完整接收 + storager.cleanChannelsForDevice(take.getDevice().getDeviceId()); + catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null); }else { - catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null); + Iterator deviceListIterator = deviceListElement.elementIterator(); + if (deviceListIterator != null) { + List channelList = new ArrayList<>(); + // 遍历DeviceList + while (deviceListIterator.hasNext()) { + Element itemDevice = deviceListIterator.next(); + Element channelDeviceElement = itemDevice.element("DeviceID"); + if (channelDeviceElement == null) { + continue; + } + //by brewswang + // if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {//如果包含位置信息,就更新一下位置 + // processNotifyMobilePosition(evt, itemDevice); + // } + DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice, device); + deviceChannel.setDeviceId(take.getDevice().getDeviceId()); + + channelList.add(deviceChannel); + } + int sn = Integer.parseInt(snElement.getText()); + catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(), channelList); + logger.info("收到来自设备【{}】的通道: {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.get(take.getDevice().getDeviceId()) == null ? 0 :catalogDataCatch.get(take.getDevice().getDeviceId()).size(), sumNum); + if (catalogDataCatch.get(take.getDevice().getDeviceId()).size() == sumNum) { + // 数据已经完整接收 + boolean resetChannelsResult = storager.resetChannels(take.getDevice().getDeviceId(), catalogDataCatch.get(take.getDevice().getDeviceId())); + if (!resetChannelsResult) { + String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(take.getDevice().getDeviceId()).size() + "条"; + catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), errorMsg); + }else { + catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null); + } + } + } + } + } catch (DocumentException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } catch (SipException e) { + e.printStackTrace(); } } - // 回复200 OK - responseAck(evt, Response.OK); - } - } catch (DocumentException e) { - e.printStackTrace(); - } catch (InvalidArgumentException e) { - e.printStackTrace(); - } catch (ParseException e) { - e.printStackTrace(); - } catch (SipException e) { - e.printStackTrace(); + taskQueueHandlerRun = false; + }); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java index 1fed401a8..17738a5fe 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java @@ -82,7 +82,7 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen deviceService.offline(device.getDeviceId()); } RequestMessage msg = new RequestMessage(); - msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId() + channelId); + msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId()); msg.setData(json); deferredResultHolder.invokeAllResult(msg); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java index 4509e424f..57e80454f 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java @@ -1,9 +1,6 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; -import com.genersoft.iot.vmp.gb28181.bean.Device; -import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; -import com.genersoft.iot.vmp.gb28181.bean.RecordInfo; -import com.genersoft.iot.vmp.gb28181.bean.RecordItem; +import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.session.RecordDataCatch; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; @@ -19,6 +16,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -28,6 +27,9 @@ import javax.sip.SipException; import javax.sip.message.Response; import java.text.ParseException; import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.LinkedBlockingQueue; import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; @@ -38,10 +40,11 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private Logger logger = LoggerFactory.getLogger(RecordInfoResponseMessageHandler.class); - public static volatile List threadNameList = new ArrayList(); private final String cmdType = "RecordInfo"; - private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_"; + private ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); + + private boolean taskQueueHandlerRun = false; @Autowired private ResponseMessageHandler responseMessageHandler; @@ -51,11 +54,13 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent @Autowired private DeferredResultHolder deferredResultHolder; - - @Autowired private EventPublisher eventPublisher; + @Qualifier("taskExecutor") + @Autowired + private ThreadPoolTaskExecutor taskExecutor; + @Override public void afterPropertiesSet() throws Exception { responseMessageHandler.addHandler(cmdType, this); @@ -67,67 +72,89 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent // 回复200 OK try { responseAck(evt, Response.OK); + taskQueue.offer(new HandlerCatchData(evt, device, rootElement)); + if (!taskQueueHandlerRun) { + taskQueueHandlerRun = true; + taskExecutor.execute(()->{ + try { + while (!taskQueue.isEmpty()) { + HandlerCatchData take = taskQueue.poll(); + Element rootElementForCharset = getRootElement(take.getEvt(), take.getDevice().getCharset()); + String sn = getText(rootElementForCharset, "SN"); + String channelId = getText(rootElementForCharset, "DeviceID"); + RecordInfo recordInfo = new RecordInfo(); + recordInfo.setChannelId(channelId); + recordInfo.setDeviceId(take.getDevice().getDeviceId()); + recordInfo.setSn(sn); + recordInfo.setName(getText(rootElementForCharset, "Name")); + String sumNumStr = getText(rootElementForCharset, "SumNum"); + int sumNum = 0; + if (!StringUtils.isEmpty(sumNumStr)) { + sumNum = Integer.parseInt(sumNumStr); + } + recordInfo.setSumNum(sumNum); + Element recordListElement = rootElementForCharset.element("RecordList"); + if (recordListElement == null || sumNum == 0) { + logger.info("无录像数据"); + eventPublisher.recordEndEventPush(recordInfo); + recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, new ArrayList<>()); + releaseRequest(take.getDevice().getDeviceId(), sn); + } else { + Iterator recordListIterator = recordListElement.elementIterator(); + if (recordListIterator != null) { + List recordList = new ArrayList<>(); + // 遍历DeviceList + while (recordListIterator.hasNext()) { + Element itemRecord = recordListIterator.next(); + Element recordElement = itemRecord.element("DeviceID"); + if (recordElement == null) { + logger.info("记录为空,下一个..."); + continue; + } + RecordItem record = new RecordItem(); + record.setDeviceId(getText(itemRecord, "DeviceID")); + record.setName(getText(itemRecord, "Name")); + record.setFilePath(getText(itemRecord, "FilePath")); + record.setFileSize(getText(itemRecord, "FileSize")); + record.setAddress(getText(itemRecord, "Address")); - rootElement = getRootElement(evt, device.getCharset()); - String sn = getText(rootElement, "SN"); + String startTimeStr = getText(itemRecord, "StartTime"); + record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr)); - String sumNumStr = getText(rootElement, "SumNum"); - int sumNum = 0; - if (!StringUtils.isEmpty(sumNumStr)) { - sumNum = Integer.parseInt(sumNumStr); - } - Element recordListElement = rootElement.element("RecordList"); - if (recordListElement == null || sumNum == 0) { - logger.info("无录像数据"); - recordDataCatch.put(device.getDeviceId(), sn, sumNum, new ArrayList<>()); - releaseRequest(device.getDeviceId(), sn); - } else { - Iterator recordListIterator = recordListElement.elementIterator(); - if (recordListIterator != null) { - List recordList = new ArrayList<>(); - // 遍历DeviceList - while (recordListIterator.hasNext()) { - Element itemRecord = recordListIterator.next(); - Element recordElement = itemRecord.element("DeviceID"); - if (recordElement == null) { - logger.info("记录为空,下一个..."); - continue; + String endTimeStr = getText(itemRecord, "EndTime"); + record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr)); + + record.setSecrecy(itemRecord.element("Secrecy") == null ? 0 + : Integer.parseInt(getText(itemRecord, "Secrecy"))); + record.setType(getText(itemRecord, "Type")); + record.setRecorderId(getText(itemRecord, "RecorderID")); + recordList.add(record); + } + recordInfo.setRecordList(recordList); + // 发送消息,如果是上级查询此录像,则会通过这里通知给上级 + eventPublisher.recordEndEventPush(recordInfo); + int count = recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, recordList); + logger.info("[国标录像], {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum); + } + + if (recordDataCatch.isComplete(take.getDevice().getDeviceId(), sn)){ + releaseRequest(take.getDevice().getDeviceId(), sn); + } + } } - RecordItem record = new RecordItem(); - record.setDeviceId(getText(itemRecord, "DeviceID")); - record.setName(getText(itemRecord, "Name")); - record.setFilePath(getText(itemRecord, "FilePath")); - record.setFileSize(getText(itemRecord, "FileSize")); - record.setAddress(getText(itemRecord, "Address")); - - String startTimeStr = getText(itemRecord, "StartTime"); - record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr)); - - String endTimeStr = getText(itemRecord, "EndTime"); - record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr)); - - record.setSecrecy(itemRecord.element("Secrecy") == null ? 0 - : Integer.parseInt(getText(itemRecord, "Secrecy"))); - record.setType(getText(itemRecord, "Type")); - record.setRecorderId(getText(itemRecord, "RecorderID")); - recordList.add(record); + taskQueueHandlerRun = false; + }catch (DocumentException e) { + throw new RuntimeException(e); } - int count = recordDataCatch.put(device.getDeviceId(), sn, sumNum, recordList); - logger.info("[国标录像], {}->{}: {}/{}", device.getDeviceId(), sn, count, sumNum); - } - - if (recordDataCatch.isComplete(device.getDeviceId(), sn)){ - releaseRequest(device.getDeviceId(), sn); - } + }); } + } catch (SipException e) { e.printStackTrace(); } catch (InvalidArgumentException e) { e.printStackTrace(); } catch (ParseException e) { e.printStackTrace(); - } catch (DocumentException e) { - e.printStackTrace(); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java index 64933b80b..ff63fad04 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java @@ -17,7 +17,7 @@ import javax.sip.ResponseEvent; @Component public class ByeResponseProcessor extends SIPResponseProcessorAbstract { - private String method = "BYE"; + private final String method = "BYE"; @Autowired private SipLayer sipLayer; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/CancelResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/CancelResponseProcessor.java index 80d7e2b2c..775beeb6d 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/CancelResponseProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/CancelResponseProcessor.java @@ -17,7 +17,7 @@ import javax.sip.ResponseEvent; @Component public class CancelResponseProcessor extends SIPResponseProcessorAbstract { - private String method = "CANCEL"; + private final String method = "CANCEL"; @Autowired private SipLayer sipLayer; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java index c81aabb69..89958e928 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java @@ -31,7 +31,7 @@ import java.text.ParseException; public class InviteResponseProcessor extends SIPResponseProcessorAbstract { private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class); - private String method = "INVITE"; + private final String method = "INVITE"; @Autowired private SipLayer sipLayer; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java index 02f5e1d0a..f3a9f65f2 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java @@ -27,7 +27,7 @@ import javax.sip.message.Response; public class RegisterResponseProcessor extends SIPResponseProcessorAbstract { private Logger logger = LoggerFactory.getLogger(RegisterResponseProcessor.class); - private String method = "REGISTER"; + private final String method = "REGISTER"; @Autowired private ISIPCommanderForPlatform sipCommanderForPlatform; 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 2caab0f83..5ada1e4da 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 @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.utils; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import org.dom4j.Attribute; import org.dom4j.Document; @@ -180,7 +181,7 @@ public class XmlUtil { return xml.getRootElement(); } - public static DeviceChannel channelContentHander(Element itemDevice){ + public static DeviceChannel channelContentHander(Element itemDevice, Device device){ Element channdelNameElement = itemDevice.element("Name"); String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim().toString() : ""; Element statusElement = itemDevice.element("Status"); @@ -254,6 +255,8 @@ public class XmlUtil { }else if (deviceChannel.getChannelId().length() == 20) { if (Integer.parseInt(deviceChannel.getChannelId().substring(10, 13)) == 216) { // 虚拟组织 deviceChannel.setParentId(businessGroupID); + }else if (Integer.parseInt(device.getDeviceId().substring(10, 13) )== 118) {//NVR 如果上级设备编号是NVR则直接将NVR的编号设置给通道的上级编号 + deviceChannel.setParentId(device.getDeviceId()); }else if (deviceChannel.getCivilCode() != null) { // 设备, 无parentId的20位是使用CivilCode表示上级的设备, // 注:215 业务分组是需要有parentId的 @@ -308,6 +311,31 @@ public class XmlUtil { } else { deviceChannel.setLatitude(0.00); } + if (deviceChannel.getLongitude()*deviceChannel.getLatitude() > 0) { + if ("WGS84".equals(device.getGeoCoordSys())) { + deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude()); + deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude()); + Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude()); + deviceChannel.setLongitudeGcj02(position[0]); + deviceChannel.setLatitudeGcj02(position[1]); + }else if ("GCJ02".equals(device.getGeoCoordSys())) { + deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude()); + deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude()); + Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude()); + deviceChannel.setLongitudeWgs84(position[0]); + deviceChannel.setLatitudeWgs84(position[1]); + }else { + deviceChannel.setLongitudeGcj02(0.00); + deviceChannel.setLatitudeGcj02(0.00); + deviceChannel.setLongitudeWgs84(0.00); + deviceChannel.setLatitudeWgs84(0.00); + } + }else { + deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude()); + deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude()); + deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude()); + deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude()); + } if (XmlUtil.getText(itemDevice, "PTZType") == null || "".equals(XmlUtil.getText(itemDevice, "PTZType"))) { //兼容INFO中的信息 Element info = itemDevice.element("Info"); 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 7f62968a8..4ea9cf14e 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 @@ -11,7 +11,6 @@ import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.GbStream; import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; -import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.media.zlm.dto.*; import com.genersoft.iot.vmp.service.*; @@ -92,10 +91,9 @@ public class ZLMHttpHookListener { public ResponseEntity onServerKeepalive(@RequestBody JSONObject json){ if (logger.isDebugEnabled()) { - logger.debug("[ ZLM HOOK ]on_server_keepalive API调用,参数:" + json.toString()); + logger.debug("[ ZLM HOOK ] on_server_keepalive API调用,参数:" + json.toString()); } String mediaServerId = json.getString("mediaServerId"); - List subscribes = this.subscribe.getSubscribes(ZLMHttpHookSubscribe.HookType.on_server_keepalive); if (subscribes != null && subscribes.size() > 0) { for (ZLMHttpHookSubscribe.Event subscribe : subscribes) { @@ -165,7 +163,6 @@ public class ZLMHttpHookListener { if (mediaInfo != null) { subscribe.response(mediaInfo, json); } - } JSONObject ret = new JSONObject(); ret.put("code", 0); @@ -248,6 +245,23 @@ public class ZLMHttpHookListener { ret.put("msg", "success"); return new ResponseEntity(ret.toString(),HttpStatus.OK); } + /** + * 录制hls完成后通知事件;此事件对回复不敏感。 + * + */ + @ResponseBody + @PostMapping(value = "/on_record_ts", produces = "application/json;charset=UTF-8") + public ResponseEntity onRecordTs(@RequestBody JSONObject json){ + + if (logger.isDebugEnabled()) { + logger.debug("[ ZLM HOOK ]on_record_ts API调用,参数:" + json.toString()); + } + String mediaServerId = json.getString("mediaServerId"); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); + return new ResponseEntity(ret.toString(),HttpStatus.OK); + } /** * rtsp专用的鉴权事件,先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件。 @@ -383,21 +397,22 @@ public class ZLMHttpHookListener { if (item.getOriginType() == OriginType.RTSP_PUSH.ordinal() || item.getOriginType() == OriginType.RTMP_PUSH.ordinal() || item.getOriginType() == OriginType.RTC_PUSH.ordinal() ) { - streamPushItem = zlmMediaListManager.addPush(item); + item.setSeverId(userSetting.getServerId()); + zlmMediaListManager.addPush(item); } - List gbStreams = new ArrayList<>(); - if (streamPushItem == null || streamPushItem.getGbId() == null) { - GbStream gbStream = storager.getGbStream(app, streamId); - gbStreams.add(gbStream); - }else { - if (streamPushItem.getGbId() != null) { - gbStreams.add(streamPushItem); - } - } - if (gbStreams.size() > 0) { +// List gbStreams = new ArrayList<>(); +// if (streamPushItem == null || streamPushItem.getGbId() == null) { +// GbStream gbStream = storager.getGbStream(app, streamId); +// gbStreams.add(gbStream); +// }else { +// if (streamPushItem.getGbId() != null) { +// gbStreams.add(streamPushItem); +// } +// } +// if (gbStreams.size() > 0) { // eventPublisher.catalogEventPublishForStream(null, gbStreams, CatalogEvent.ON); - } +// } }else { // 兼容流注销时类型从redis记录获取 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java index 9beac16ff..959c06ec6 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java @@ -24,6 +24,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * @author lin + */ @Component public class ZLMMediaListManager { @@ -147,7 +150,6 @@ public class ZLMMediaListManager { } } } - // StreamProxyItem streamProxyItem = gbStreamMapper.selectOne(transform.getApp(), transform.getStream()); List gbStreamList = gbStreamMapper.selectByGBId(transform.getGbId()); if (gbStreamList != null && gbStreamList.size() == 1) { transform.setGbStreamId(gbStreamList.get(0).getGbStreamId()); @@ -162,13 +164,12 @@ public class ZLMMediaListManager { } if (transform != null) { if (channelOnlineEvents.get(transform.getGbId()) != null) { - channelOnlineEvents.get(transform.getGbId()).run(transform.getApp(), transform.getStream()); + channelOnlineEvents.get(transform.getGbId()).run(transform.getApp(), transform.getStream(), transform.getServerId()); channelOnlineEvents.remove(transform.getGbId()); } } } - storager.updateMedia(transform); return transform; } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java index 75f55cf2c..54f148a95 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Component; import java.io.*; import java.net.ConnectException; +import java.net.SocketTimeoutException; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -28,6 +29,9 @@ public class ZLMRESTfulUtils { private OkHttpClient getClient(){ OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); + //todo 暂时写死超时时间 均为5s + httpClientBuilder.connectTimeout(5,TimeUnit.SECONDS); //设置连接超时时间 + httpClientBuilder.readTimeout(5,TimeUnit.SECONDS); //设置读取超时时间 if (logger.isDebugEnabled()) { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> { logger.debug("http请求参数:" + message); @@ -47,7 +51,10 @@ public class ZLMRESTfulUtils { return null; } String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); - JSONObject responseJSON = null; + JSONObject responseJSON = new JSONObject(); + //-2自定义流媒体 调用错误码 + responseJSON.put("code",-2); + responseJSON.put("msg","流媒体调用失败"); FormBody.Builder builder = new FormBody.Builder(); builder.add("secret",mediaServerItem.getSecret()); @@ -78,11 +85,20 @@ public class ZLMRESTfulUtils { response.close(); Objects.requireNonNull(response.body()).close(); } - } catch (ConnectException e) { - logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage())); - logger.info("请检查media配置并确认ZLM已启动..."); }catch (IOException e) { logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + + if(e instanceof SocketTimeoutException){ + //读取超时超时异常 + logger.error(String.format("读取ZLM数据失败: %s, %s", url, e.getMessage())); + } + if(e instanceof ConnectException){ + //判断连接异常,我这里是报Failed to connect to 10.7.5.144 + logger.error(String.format("连接ZLM失败: %s, %s", url, e.getMessage())); + } + + }catch (Exception e){ + logger.error(String.format("访问ZLM失败: %s, %s", url, e.getMessage())); } }else { client.newCall(request).enqueue(new Callback(){ @@ -105,8 +121,16 @@ public class ZLMRESTfulUtils { @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { - logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage())); - logger.info("请检查media配置并确认ZLM已启动..."); + logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage())); + + if(e instanceof SocketTimeoutException){ + //读取超时超时异常 + logger.error(String.format("读取ZLM数据失败: %s, %s", call.request().toString(), e.getMessage())); + } + if(e instanceof ConnectException){ + //判断连接异常,我这里是报Failed to connect to 10.7.5.144 + logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage())); + } } }); } @@ -151,7 +175,7 @@ public class ZLMRESTfulUtils { } } - File snapFile = new File(targetPath + "/" + fileName); + File snapFile = new File(targetPath + File.separator + fileName); FileOutputStream outStream = new FileOutputStream(snapFile); outStream.write(Objects.requireNonNull(response.body()).bytes()); 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 34918ae14..4007a40dd 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 @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import org.slf4j.Logger; @@ -20,6 +21,9 @@ public class ZLMRTPServerFactory { @Autowired private ZLMRESTfulUtils zlmresTfulUtils; + @Autowired + private UserSetting userSetting; + private int[] portRangeArray = new int[2]; public int getFreePort(MediaServerItem mediaServerItem, int startPort, int endPort, List usedFreelist) { @@ -87,10 +91,15 @@ public class ZLMRTPServerFactory { int result = -1; // 查询此rtp server 是否已经存在 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId); - if (rtpInfo != null && rtpInfo.getInteger("code") == 0 && rtpInfo.getBoolean("exist")) { - result = rtpInfo.getInteger("local_port"); + if(rtpInfo.getInteger("code") == 0){ + if (rtpInfo.getBoolean("exist")) { + result = rtpInfo.getInteger("local_port"); + return result; + } + }else if(rtpInfo.getInteger("code") == -2){ return result; } + Map param = new HashMap<>(); // 推流端口设置0则使用随机端口 param.put("enable_tcp", 1); @@ -197,6 +206,7 @@ public class ZLMRTPServerFactory { sendRtpItem.setTcp(tcp); sendRtpItem.setApp("rtp"); sendRtpItem.setLocalPort(localPort); + sendRtpItem.setServerId(userSetting.getServerId()); sendRtpItem.setMediaServerId(serverItem.getId()); return sendRtpItem; } @@ -238,6 +248,7 @@ public class ZLMRTPServerFactory { sendRtpItem.setChannelId(channelId); sendRtpItem.setTcp(tcp); sendRtpItem.setLocalPort(localPort); + sendRtpItem.setServerId(userSetting.getServerId()); sendRtpItem.setMediaServerId(serverItem.getId()); return sendRtpItem; } @@ -279,10 +290,10 @@ public class ZLMRTPServerFactory { */ public int totalReaderCount(MediaServerItem mediaServerItem, String app, String streamId) { JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId); - Integer code = mediaInfo.getInteger("code"); if (mediaInfo == null) { return 0; } + Integer code = mediaInfo.getInteger("code"); if ( code < 0) { logger.warn("查询流({}/{})是否有其它观看者时得到: {}", app, streamId, mediaInfo.getString("msg")); return -1; diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java index ba7daecdb..21e6ca032 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java @@ -1,6 +1,9 @@ package com.genersoft.iot.vmp.media.zlm.dto; +/** + * @author lin + */ public interface ChannelOnlineEvent { - void run(String app, String stream); + void run(String app, String stream, String serverId); } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java index ad158ec77..8abac5b07 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java @@ -61,10 +61,15 @@ public class MediaItem { private String originUrl; /** - * 服务器id + * 流媒体服务器id */ private String mediaServerId; + /** + * 服务器id + */ + private String severId; + /** * GMT unix系统时间戳,单位秒 */ @@ -414,4 +419,12 @@ public class MediaItem { public void setStreamInfo(StreamInfo streamInfo) { this.streamInfo = streamInfo; } + + public String getSeverId() { + return severId; + } + + public void setSeverId(String severId) { + this.severId = severId; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java index 81c9c768b..ceb48b3f9 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java @@ -81,6 +81,11 @@ public class StreamPushItem extends GbStream implements Comparable gpsMsgInfo = redisCatchStorage.getAllGpsMsgInfo(); diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java b/src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java index 3ab1b80d9..6dcc515f6 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java @@ -1,7 +1,10 @@ package com.genersoft.iot.vmp.service.bean; +import java.util.stream.Stream; + /** * 当上级平台 + * @author lin */ public class MessageForPushChannel { /** @@ -45,6 +48,20 @@ public class MessageForPushChannel { */ private String mediaServerId; + public static MessageForPushChannel getInstance(int type, String app, String stream, String gbId, + String platFormId, String platFormName, String serverId, + String mediaServerId){ + MessageForPushChannel messageForPushChannel = new MessageForPushChannel(); + messageForPushChannel.setType(type); + messageForPushChannel.setGbId(gbId); + messageForPushChannel.setApp(app); + messageForPushChannel.setStream(stream); + messageForPushChannel.setMediaServerId(mediaServerId); + messageForPushChannel.setPlatFormId(platFormId); + messageForPushChannel.setPlatFormName(platFormName); + return messageForPushChannel; + } + public int getType() { return type; diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/RequestPushStreamMsg.java b/src/main/java/com/genersoft/iot/vmp/service/bean/RequestPushStreamMsg.java new file mode 100644 index 000000000..5827d0132 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/RequestPushStreamMsg.java @@ -0,0 +1,170 @@ +package com.genersoft.iot.vmp.service.bean; + +/** + * redis消息:请求下级推送流信息 + * @author lin + */ +public class RequestPushStreamMsg { + + + /** + * 下级服务ID + */ + private String mediaServerId; + + /** + * 流ID + */ + private String app; + + /** + * 应用名 + */ + private String stream; + + /** + * 目标IP + */ + private String ip; + + /** + * 目标端口 + */ + private int port; + + /** + * ssrc + */ + private String ssrc; + + /** + * 是否使用TCP方式 + */ + private boolean tcp; + + /** + * 本地使用的端口 + */ + private int srcPort; + + /** + * 发送时,rtp的pt(uint8_t),不传时默认为96 + */ + private int pt; + + /** + * 发送时,rtp的负载类型。为true时,负载为ps;为false时,为es; + */ + private boolean ps; + + /** + * 是否只有音频 + */ + private boolean onlyAudio; + + + public static RequestPushStreamMsg getInstance(String mediaServerId, String app, String stream, String ip, int port, String ssrc, + boolean tcp, int srcPort, int pt, boolean ps, boolean onlyAudio) { + RequestPushStreamMsg requestPushStreamMsg = new RequestPushStreamMsg(); + requestPushStreamMsg.setMediaServerId(mediaServerId); + requestPushStreamMsg.setApp(app); + requestPushStreamMsg.setStream(stream); + requestPushStreamMsg.setIp(ip); + requestPushStreamMsg.setPort(port); + requestPushStreamMsg.setSsrc(ssrc); + requestPushStreamMsg.setTcp(tcp); + requestPushStreamMsg.setSrcPort(srcPort); + requestPushStreamMsg.setPt(pt); + requestPushStreamMsg.setPs(ps); + requestPushStreamMsg.setOnlyAudio(onlyAudio); + return requestPushStreamMsg; + } + + public String getMediaServerId() { + return mediaServerId; + } + + public void setMediaServerId(String mediaServerId) { + this.mediaServerId = mediaServerId; + } + + public String getApp() { + return app; + } + + public void setApp(String app) { + this.app = app; + } + + public String getStream() { + return stream; + } + + public void setStream(String stream) { + this.stream = stream; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getSsrc() { + return ssrc; + } + + public void setSsrc(String ssrc) { + this.ssrc = ssrc; + } + + public boolean isTcp() { + return tcp; + } + + public void setTcp(boolean tcp) { + this.tcp = tcp; + } + + public int getSrcPort() { + return srcPort; + } + + public void setSrcPort(int srcPort) { + this.srcPort = srcPort; + } + + public int getPt() { + return pt; + } + + public void setPt(int pt) { + this.pt = pt; + } + + public boolean isPs() { + return ps; + } + + public void setPs(boolean ps) { + this.ps = ps; + } + + public boolean isOnlyAudio() { + return onlyAudio; + } + + public void setOnlyAudio(boolean onlyAudio) { + this.onlyAudio = onlyAudio; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java b/src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java new file mode 100644 index 000000000..66689fa6a --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java @@ -0,0 +1,173 @@ +package com.genersoft.iot.vmp.service.bean; + +/** + * redis消息:请求下级回复推送信息 + * @author lin + */ +public class RequestSendItemMsg { + + /** + * 下级服务ID + */ + private String serverId; + + /** + * 下级服务ID + */ + private String mediaServerId; + + /** + * 流ID + */ + private String app; + + /** + * 应用名 + */ + private String stream; + + /** + * 目标IP + */ + private String ip; + + /** + * 目标端口 + */ + private int port; + + /** + * ssrc + */ + private String ssrc; + + /** + * 平台国标编号 + */ + private String platformId; + + /** + * 平台名称 + */ + private String platformName; + + /** + * 通道ID + */ + private String channelId; + + + /** + * 是否使用TCP + */ + private Boolean isTcp; + + + + + public static RequestSendItemMsg getInstance(String serverId, String mediaServerId, String app, String stream, String ip, int port, + String ssrc, String platformId, String channelId, Boolean isTcp, String platformName) { + RequestSendItemMsg requestSendItemMsg = new RequestSendItemMsg(); + requestSendItemMsg.setServerId(serverId); + requestSendItemMsg.setMediaServerId(mediaServerId); + requestSendItemMsg.setApp(app); + requestSendItemMsg.setStream(stream); + requestSendItemMsg.setIp(ip); + requestSendItemMsg.setPort(port); + requestSendItemMsg.setSsrc(ssrc); + requestSendItemMsg.setPlatformId(platformId); + requestSendItemMsg.setPlatformName(platformName); + requestSendItemMsg.setChannelId(channelId); + requestSendItemMsg.setTcp(isTcp); + + return requestSendItemMsg; + } + + public String getServerId() { + return serverId; + } + + public void setServerId(String serverId) { + this.serverId = serverId; + } + + public String getMediaServerId() { + return mediaServerId; + } + + public void setMediaServerId(String mediaServerId) { + this.mediaServerId = mediaServerId; + } + + public String getApp() { + return app; + } + + public void setApp(String app) { + this.app = app; + } + + public String getStream() { + return stream; + } + + public void setStream(String stream) { + this.stream = stream; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getSsrc() { + return ssrc; + } + + public void setSsrc(String ssrc) { + this.ssrc = ssrc; + } + + public String getPlatformId() { + return platformId; + } + + public void setPlatformId(String platformId) { + this.platformId = platformId; + } + + public String getPlatformName() { + return platformName; + } + + public void setPlatformName(String platformName) { + this.platformName = platformName; + } + + public String getChannelId() { + return channelId; + } + + public void setChannelId(String channelId) { + this.channelId = channelId; + } + + public Boolean getTcp() { + return isTcp; + } + + public void setTcp(Boolean tcp) { + isTcp = tcp; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/ResponseSendItemMsg.java b/src/main/java/com/genersoft/iot/vmp/service/bean/ResponseSendItemMsg.java new file mode 100644 index 000000000..501621bb4 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/ResponseSendItemMsg.java @@ -0,0 +1,31 @@ +package com.genersoft.iot.vmp.service.bean; + +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; + +/** + * redis消息:下级回复推送信息 + * @author lin + */ +public class ResponseSendItemMsg { + + private SendRtpItem sendRtpItem; + + private MediaServerItem mediaServerItem; + + public SendRtpItem getSendRtpItem() { + return sendRtpItem; + } + + public void setSendRtpItem(SendRtpItem sendRtpItem) { + this.sendRtpItem = sendRtpItem; + } + + public MediaServerItem getMediaServerItem() { + return mediaServerItem; + } + + public void setMediaServerItem(MediaServerItem mediaServerItem) { + this.mediaServerItem = mediaServerItem; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java b/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java new file mode 100644 index 000000000..12d79cb23 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java @@ -0,0 +1,116 @@ +package com.genersoft.iot.vmp.service.bean; + +/** + * @author lin + */ +public class WvpRedisMsg { + + public static WvpRedisMsg getInstance(String fromId, String toId, String type, String cmd, String serial, String content){ + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); + wvpRedisMsg.setFromId(fromId); + wvpRedisMsg.setToId(toId); + wvpRedisMsg.setType(type); + wvpRedisMsg.setCmd(cmd); + wvpRedisMsg.setSerial(serial); + wvpRedisMsg.setContent(content); + return wvpRedisMsg; + } + + private String fromId; + + private String toId; + /** + * req 请求, res 回复 + */ + private String type; + private String cmd; + + /** + * 消息的ID + */ + private String serial; + private Object content; + + private final static String requestTag = "req"; + private final static String responseTag = "res"; + + public static WvpRedisMsg getRequestInstance(String fromId, String toId, String cmd, String serial, Object content) { + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); + wvpRedisMsg.setType(requestTag); + wvpRedisMsg.setFromId(fromId); + wvpRedisMsg.setToId(toId); + wvpRedisMsg.setCmd(cmd); + wvpRedisMsg.setSerial(serial); + wvpRedisMsg.setContent(content); + return wvpRedisMsg; + } + + public static WvpRedisMsg getResponseInstance() { + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); + wvpRedisMsg.setType(responseTag); + return wvpRedisMsg; + } + + public static WvpRedisMsg getResponseInstance(String fromId, String toId, String cmd, String serial, Object content) { + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); + wvpRedisMsg.setType(responseTag); + wvpRedisMsg.setFromId(fromId); + wvpRedisMsg.setToId(toId); + wvpRedisMsg.setCmd(cmd); + wvpRedisMsg.setSerial(serial); + wvpRedisMsg.setContent(content); + return wvpRedisMsg; + } + + public static boolean isRequest(WvpRedisMsg wvpRedisMsg) { + return requestTag.equals(wvpRedisMsg.getType()); + } + + public String getSerial() { + return serial; + } + + public void setSerial(String serial) { + this.serial = serial; + } + + public String getFromId() { + return fromId; + } + + public void setFromId(String fromId) { + this.fromId = fromId; + } + + public String getToId() { + return toId; + } + + public void setToId(String toId) { + this.toId = toId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getCmd() { + return cmd; + } + + public void setCmd(String cmd) { + this.cmd = cmd; + } + + public Object getContent() { + return content; + } + + public void setContent(Object content) { + this.content = content; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsgCmd.java b/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsgCmd.java new file mode 100644 index 000000000..cb118865c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsgCmd.java @@ -0,0 +1,12 @@ +package com.genersoft.iot.vmp.service.bean; + +/** + * @author lin + */ + +public class WvpRedisMsgCmd { + + public static final String GET_SEND_ITEM = "GetSendItem"; + public static final String REQUEST_PUSH_STREAM = "RequestPushStream"; + +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java index 0144e83bc..ec30cd482 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java @@ -2,16 +2,21 @@ package com.genersoft.iot.vmp.service.impl; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.gb28181.bean.Device; +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; +import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; +import com.genersoft.iot.vmp.gb28181.utils.Coordtransform; import com.genersoft.iot.vmp.service.IDeviceService; import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; import com.genersoft.iot.vmp.gb28181.bean.SyncStatus; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; +import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.storager.dao.DeviceMapper; import com.genersoft.iot.vmp.utils.DateUtil; import org.slf4j.Logger; @@ -49,6 +54,12 @@ public class DeviceServiceImpl implements IDeviceService { @Autowired private DeviceMapper deviceMapper; + @Autowired + private DeviceChannelMapper deviceChannelMapper; + + @Autowired + private IVideoManagerStorage storage; + @Autowired private ISIPCommander commander; @@ -68,7 +79,6 @@ public class DeviceServiceImpl implements IDeviceService { if (deviceInRedis != null && deviceInDb == null) { // redis 存在脏数据 redisCatchStorage.clearCatchByDeviceId(device.getDeviceId()); - } device.setUpdateTime(now); device.setOnline(1); @@ -77,13 +87,15 @@ public class DeviceServiceImpl implements IDeviceService { if (device.getCreateTime() == null) { device.setCreateTime(now); logger.info("[设备上线,首次注册]: {},查询设备信息以及通道信息", device.getDeviceId()); + deviceMapper.add(device); + redisCatchStorage.updateDevice(device); commander.deviceInfoQuery(device); sync(device); - deviceMapper.add(device); }else { deviceMapper.update(device); + redisCatchStorage.updateDevice(device); } - redisCatchStorage.updateDevice(device); + // 上线添加订阅 if (device.getSubscribeCycleForCatalog() > 0) { // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅 @@ -94,7 +106,6 @@ public class DeviceServiceImpl implements IDeviceService { } // 刷新过期任务 String registerExpireTaskKey = registerExpireTaskKeyPrefix + device.getDeviceId(); - dynamicTask.stop(registerExpireTaskKey); dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId()), device.getExpires() * 1000); } @@ -143,8 +154,16 @@ public class DeviceServiceImpl implements IDeviceService { if (device == null || device.getSubscribeCycleForCatalog() < 0) { return false; } - logger.info("移除目录订阅: {}", device.getDeviceId()); - dynamicTask.stop(device.getDeviceId() + "catalog"); + logger.info("[移除目录订阅]: {}", device.getDeviceId()); + String taskKey = device.getDeviceId() + "catalog"; + if (device.getOnline() == 1) { + Runnable runnable = dynamicTask.get(taskKey); + if (runnable instanceof ISubscribeTask) { + ISubscribeTask subscribeTask = (ISubscribeTask) runnable; + subscribeTask.stop(); + } + } + dynamicTask.stop(taskKey); return true; } @@ -168,8 +187,16 @@ public class DeviceServiceImpl implements IDeviceService { if (device == null || device.getSubscribeCycleForCatalog() < 0) { return false; } - logger.info("移除移动位置订阅: {}", device.getDeviceId()); - dynamicTask.stop(device.getDeviceId() + "mobile_position"); + logger.info("[移除移动位置订阅]: {}", device.getDeviceId()); + String taskKey = device.getDeviceId() + "mobile_position"; + if (device.getOnline() == 1) { + Runnable runnable = dynamicTask.get(taskKey); + if (runnable instanceof ISubscribeTask) { + ISubscribeTask subscribeTask = (ISubscribeTask) runnable; + subscribeTask.stop(); + } + } + dynamicTask.stop(taskKey); return true; } @@ -275,6 +302,10 @@ public class DeviceServiceImpl implements IDeviceService { removeMobilePositionSubscribe(deviceInStore); } } + // 坐标系变化,需要重新计算GCJ02坐标和WGS84坐标 + if (!deviceInStore.getGeoCoordSys().equals(device.getGeoCoordSys())) { + updateDeviceChannelGeoCoordSys(device); + } String now = DateUtil.getNow(); device.setUpdateTime(now); @@ -282,6 +313,32 @@ public class DeviceServiceImpl implements IDeviceService { device.setUpdateTime(DateUtil.getNow()); if (deviceMapper.update(device) > 0) { redisCatchStorage.updateDevice(device); + } } + + /** + * 更新通道坐标系 + */ + private void updateDeviceChannelGeoCoordSys(Device device) { + List deviceChannels = deviceChannelMapper.getAllChannelWithCoordinate(device.getDeviceId()); + if (deviceChannels.size() > 0) { + for (DeviceChannel deviceChannel : deviceChannels) { + if ("WGS84".equals(device.getGeoCoordSys())) { + deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude()); + deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude()); + Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude()); + deviceChannel.setLongitudeGcj02(position[0]); + deviceChannel.setLatitudeGcj02(position[1]); + }else if ("GCJ02".equals(device.getGeoCoordSys())) { + deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude()); + deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude()); + Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude()); + deviceChannel.setLongitudeWgs84(position[0]); + deviceChannel.setLatitudeWgs84(position[1]); + } + } + } + storage.updateChannels(device.getDeviceId(), deviceChannels); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java index a7a552814..c813b1155 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java @@ -106,7 +106,8 @@ public class GbStreamServiceImpl implements IGbStreamService { deviceChannel.setStatus(1); deviceChannel.setParentId(catalogId ==null?gbStream.getCatalogId():catalogId); deviceChannel.setRegisterWay(1); - if (catalogId.length() <= 10) { // 父节点是行政区划,则设置CivilCode使用此行政区划 + if (catalogId.length() > 0 && catalogId.length() <= 10) { + // 父节点是行政区划,则设置CivilCode使用此行政区划 deviceChannel.setCivilCode(catalogId); }else { deviceChannel.setCivilCode(platform.getAdministrativeDivision()); 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 5468faefc..0c84b7391 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 @@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.session.SsrcConfig; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; @@ -35,7 +36,9 @@ import org.springframework.util.StringUtils; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; /** * 媒体服务器节点管理 @@ -189,6 +192,7 @@ public class MediaServerServiceImpl implements IMediaServerService { public void clearRTPServer(MediaServerItem mediaServerItem) { mediaServerItem.setSsrcConfig(new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getDomain())); redisUtil.zAdd(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId(), mediaServerItem.getId(), 0); + } @@ -229,11 +233,10 @@ public class MediaServerServiceImpl implements IMediaServerService { } result.sort((serverItem1, serverItem2)->{ int sortResult = 0; - try { - sortResult = DateUtil.format.parse(serverItem1.getCreateTime()).compareTo(DateUtil.format.parse(serverItem2.getCreateTime())); - } catch (ParseException e) { - e.printStackTrace(); - } + LocalDateTime localDateTime1 = LocalDateTime.parse(serverItem1.getCreateTime(), DateUtil.formatter); + LocalDateTime localDateTime2 = LocalDateTime.parse(serverItem2.getCreateTime(), DateUtil.formatter); + + sortResult = localDateTime1.compareTo(localDateTime2); return sortResult; }); return result; @@ -495,14 +498,14 @@ public class MediaServerServiceImpl implements IMediaServerService { param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline param.put("ffmpeg.cmd","%s -fflags nobuffer -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s"); param.put("hook.enable","1"); - param.put("hook.on_flow_report",""); + param.put("hook.on_flow_report",String.format("%s/on_flow_report", hookPrex)); param.put("hook.on_play",String.format("%s/on_play", hookPrex)); - param.put("hook.on_http_access",""); + param.put("hook.on_http_access",String.format("%s/on_http_access", hookPrex)); param.put("hook.on_publish", String.format("%s/on_publish", hookPrex)); param.put("hook.on_record_mp4",recordHookPrex != null? String.format("%s/on_record_mp4", recordHookPrex): ""); - param.put("hook.on_record_ts",""); - param.put("hook.on_rtsp_auth",""); - param.put("hook.on_rtsp_realm",""); + param.put("hook.on_record_ts",String.format("%s/on_record_ts", hookPrex)); + param.put("hook.on_rtsp_auth",String.format("%s/on_rtsp_auth", hookPrex)); + param.put("hook.on_rtsp_realm",String.format("%s/on_rtsp_realm", hookPrex)); param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex)); param.put("hook.on_shell_login",String.format("%s/on_shell_login", hookPrex)); param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex)); 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 f0ffb84b0..6422cfc73 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 @@ -21,6 +21,8 @@ import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.IMediaServerService; +import com.genersoft.iot.vmp.service.IMediaService; +import com.genersoft.iot.vmp.service.IPlayService; import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback; import com.genersoft.iot.vmp.service.bean.PlayBackCallback; import com.genersoft.iot.vmp.service.bean.PlayBackResult; @@ -32,8 +34,6 @@ import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent; import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult; -import com.genersoft.iot.vmp.service.IMediaService; -import com.genersoft.iot.vmp.service.IPlayService; import gov.nist.javax.sip.stack.SIPDialog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,6 +49,7 @@ import javax.sip.SipException; import java.io.FileNotFoundException; import java.math.BigDecimal; import java.text.ParseException; +import java.math.RoundingMode; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -74,9 +75,6 @@ public class PlayServiceImpl implements IPlayService { @Autowired private IRedisCatchStorage redisCatchStorage; - @Autowired - private RedisUtil redis; - @Autowired private DeferredResultHolder resultHolder; @@ -140,36 +138,19 @@ public class PlayServiceImpl implements IPlayService { result.onCompletion(()->{ // 点播结束时调用截图接口 // TODO 应该在上流时调用更好,结束也可能是错误结束 - try { - String classPath = ResourceUtils.getURL("classpath:").getPath(); - // 兼容打包为jar的class路径 - if(classPath.contains("jar")) { - classPath = classPath.substring(0, classPath.lastIndexOf(".")); - classPath = classPath.substring(0, classPath.lastIndexOf("/") + 1); + String path = "static/static/snap/"; + String fileName = deviceId + "_" + channelId + ".jpg"; + ResponseEntity responseEntity = (ResponseEntity)result.getResult(); + if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) { + WVPResult wvpResult = (WVPResult)responseEntity.getBody(); + if (Objects.requireNonNull(wvpResult).getCode() == 0) { + StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData(); + MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId()); + String streamUrl = streamInfoForSuccess.getFmp4(); + // 请求截图 + logger.info("[请求截图]: " + fileName); + zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName); } - if (classPath.startsWith("file:")) { - classPath = classPath.substring(classPath.indexOf(":") + 1); - } - String path = classPath + "static/static/snap/"; - // 兼容Windows系统路径(去除前面的“/”) - if(System.getProperty("os.name").contains("indows")) { - path = path.substring(1); - } - String fileName = deviceId + "_" + channelId + ".jpg"; - ResponseEntity responseEntity = (ResponseEntity)result.getResult(); - if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) { - WVPResult wvpResult = (WVPResult)responseEntity.getBody(); - if (Objects.requireNonNull(wvpResult).getCode() == 0) { - StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData(); - MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId()); - String streamUrl = streamInfoForSuccess.getFmp4(); - // 请求截图 - logger.info("[请求截图]: " + fileName); - zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName); - } - } - } catch (FileNotFoundException e) { - e.printStackTrace(); } }); if (streamInfo != null) { @@ -186,24 +167,33 @@ public class PlayServiceImpl implements IPlayService { MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId); - if (rtpInfo != null && rtpInfo.getBoolean("exist")) { + if(rtpInfo.getInteger("code") == 0){ + if (rtpInfo.getBoolean("exist")) { - WVPResult wvpResult = new WVPResult(); - wvpResult.setCode(0); - wvpResult.setMsg("success"); - wvpResult.setData(streamInfo); - msg.setData(wvpResult); + WVPResult wvpResult = new WVPResult(); + wvpResult.setCode(0); + wvpResult.setMsg("success"); + 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()); + streamInfo = null; } }else { + //zlm连接失败 redisCatchStorage.stopPlay(streamInfo); storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); streamInfo = null; + } + } if (streamInfo == null) { String streamId = null; @@ -256,33 +246,41 @@ public class PlayServiceImpl implements IPlayService { if (ssrcInfo == null) { ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false); } - + logger.info("[点播开始] deviceId: {}, channelId: {}, SSRC: {}", device.getDeviceId(), channelId, ssrcInfo.getSsrc() ); // 超时处理 String timeOutTaskKey = UUID.randomUUID().toString(); SSRCInfo finalSsrcInfo = ssrcInfo; dynamicTask.startDelay( timeOutTaskKey,()->{ - logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", device.getDeviceId(), channelId)); SIPDialog dialog = streamSession.getDialogByStream(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); if (dialog != null) { + logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {}", device.getDeviceId(), channelId); timeoutCallback.run(1, "收流超时"); // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 cmder.streamByeCmd(device.getDeviceId(), channelId, finalSsrcInfo.getStream(), null); }else { + logger.info("[点播超时] 消息未响应 deviceId: {}, channelId: {}", device.getDeviceId(), channelId); timeoutCallback.run(0, "点播超时"); mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc()); mediaServerService.closeRTPServer(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream()); } - }, userSetting.getPlayTimeout()*1000); + }, userSetting.getPlayTimeout()); final String ssrc = ssrcInfo.getSsrc(); final String stream = ssrcInfo.getStream(); + //端口获取失败的ssrcInfo 没有必要发送点播指令 + if(ssrcInfo.getPort() <= 0){ + logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo); + return; + } cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> { logger.info("收到订阅消息: " + response.toJSONString()); dynamicTask.stop(timeOutTaskKey); // hook响应 onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId, uuid); hookEvent.response(mediaServerItemInuse, response); + logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId); + }, (event) -> { ResponseEvent responseEvent = (ResponseEvent)event.event; String contentString = new String(responseEvent.getResponse().getRawContent()); @@ -296,8 +294,10 @@ public class PlayServiceImpl implements IPlayService { if (ssrc.equals(ssrcInResponse)) { return; } - logger.info("[SIP 消息] 收到invite 200, 发现下级自定义了ssrc 开启修正"); + logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse ); if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { + logger.info("[SIP 消息] SSRC修正 {}->{}", ssrc, ssrcInResponse); + if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { // ssrc 不可用 // 释放ssrc @@ -450,7 +450,7 @@ public class PlayServiceImpl implements IPlayService { cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null); // 回复之前所有的点播请求 playBackCallback.call(playBackResult); - }, userSetting.getPlayTimeout()*1000); + }, userSetting.getPlayTimeout()); cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack, (InviteStreamInfo inviteStreamInfo) -> { @@ -539,7 +539,7 @@ public class PlayServiceImpl implements IPlayService { cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null); // 回复之前所有的点播请求 hookCallBack.call(downloadResult); - }, userSetting.getPlayTimeout()*1000); + }, userSetting.getPlayTimeout()); cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack, inviteStreamInfo -> { logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString()); @@ -605,7 +605,7 @@ public class PlayServiceImpl implements IPlayService { BigDecimal currentCount = new BigDecimal(duration/1000); BigDecimal totalCount = new BigDecimal(end-start); - BigDecimal divide = currentCount.divide(totalCount,2, BigDecimal.ROUND_HALF_UP); + BigDecimal divide = currentCount.divide(totalCount,2, RoundingMode.HALF_UP); double process = divide.doubleValue(); streamInfo.setProgress(process); } @@ -728,4 +728,9 @@ public class PlayServiceImpl implements IPlayService { } + + @Override + public void zlmServerOnline(String mediaServerId) { + // 似乎没啥需要做的 + } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/RedisGbPlayMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/impl/RedisGbPlayMsgListener.java new file mode 100644 index 000000000..638ea41d5 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/RedisGbPlayMsgListener.java @@ -0,0 +1,377 @@ +package com.genersoft.iot.vmp.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; +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.service.IMediaServerService; +import com.genersoft.iot.vmp.service.bean.*; +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.utils.redis.RedisUtil; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.stereotype.Component; + +import javax.sip.InvalidArgumentException; +import javax.sip.SipException; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * 监听下级发送推送信息,并发送国标推流消息上级 + * @author lin + */ +@Component +public class RedisGbPlayMsgListener implements MessageListener { + + private final static Logger logger = LoggerFactory.getLogger(RedisGbPlayMsgListener.class); + + public static final String WVP_PUSH_STREAM_KEY = "WVP_PUSH_STREAM"; + + /** + * 流媒体不存在的错误玛 + */ + public static final int ERROR_CODE_MEDIA_SERVER_NOT_FOUND = -1; + + /** + * 离线的错误玛 + */ + public static final int ERROR_CODE_OFFLINE = -2; + + /** + * 超时的错误玛 + */ + public static final int ERROR_CODE_TIMEOUT = -3; + + private Map callbacks = new ConcurrentHashMap<>(); + private Map callbacksForStartSendRtpStream = new ConcurrentHashMap<>(); + private Map callbacksForError = new ConcurrentHashMap<>(); + + @Autowired + private UserSetting userSetting; + + @Autowired + private RedisUtil redis; + + @Autowired + private ZLMMediaListManager zlmMediaListManager; + + @Autowired + private ZLMRTPServerFactory zlmrtpServerFactory; + + @Autowired + private IMediaServerService mediaServerService; + + @Autowired + private IRedisCatchStorage redisCatchStorage; + + @Autowired + private DynamicTask dynamicTask; + + @Autowired + private ZLMMediaListManager mediaListManager; + + @Autowired + private ZLMHttpHookSubscribe subscribe; + + + public interface PlayMsgCallback{ + void handler(ResponseSendItemMsg responseSendItemMsg); + } + + public interface PlayMsgCallbackForStartSendRtpStream{ + void handler(JSONObject jsonObject); + } + + public interface PlayMsgErrorCallback{ + void handler(WVPResult wvpResult); + } + + @Override + public void onMessage(Message message, byte[] bytes) { + JSONObject msgJSON = JSON.parseObject(message.getBody(), JSONObject.class); + WvpRedisMsg wvpRedisMsg = JSON.toJavaObject(msgJSON, WvpRedisMsg.class); + if (!userSetting.getServerId().equals(wvpRedisMsg.getToId())) { + return; + } + if (WvpRedisMsg.isRequest(wvpRedisMsg)) { + logger.info("[收到REDIS通知] 请求: {}", new String(message.getBody())); + + switch (wvpRedisMsg.getCmd()){ + case WvpRedisMsgCmd.GET_SEND_ITEM: + RequestSendItemMsg content = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), RequestSendItemMsg.class); + requestSendItemMsgHand(content, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial()); + break; + case WvpRedisMsgCmd.REQUEST_PUSH_STREAM: + RequestPushStreamMsg param = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), RequestPushStreamMsg.class);; + requestPushStreamMsgHand(param, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial()); + break; + default: + break; + } + + }else { + logger.info("[收到REDIS通知] 回复: {}", new String(message.getBody())); + switch (wvpRedisMsg.getCmd()){ + case WvpRedisMsgCmd.GET_SEND_ITEM: + + WVPResult content = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), WVPResult.class); + + String key = wvpRedisMsg.getSerial(); + switch (content.getCode()) { + case 0: + ResponseSendItemMsg responseSendItemMsg =JSON.toJavaObject((JSONObject)content.getData(), ResponseSendItemMsg.class); + PlayMsgCallback playMsgCallback = callbacks.get(key); + if (playMsgCallback != null) { + callbacksForError.remove(key); + playMsgCallback.handler(responseSendItemMsg); + } + break; + case ERROR_CODE_MEDIA_SERVER_NOT_FOUND: + case ERROR_CODE_OFFLINE: + case ERROR_CODE_TIMEOUT: + PlayMsgErrorCallback errorCallback = callbacksForError.get(key); + if (errorCallback != null) { + callbacks.remove(key); + errorCallback.handler(content); + } + break; + default: + break; + } + break; + case WvpRedisMsgCmd.REQUEST_PUSH_STREAM: + WVPResult wvpResult = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), WVPResult.class); + String serial = wvpRedisMsg.getSerial(); + switch (wvpResult.getCode()) { + case 0: + JSONObject jsonObject = (JSONObject)wvpResult.getData(); + PlayMsgCallbackForStartSendRtpStream playMsgCallback = callbacksForStartSendRtpStream.get(serial); + if (playMsgCallback != null) { + callbacksForError.remove(serial); + playMsgCallback.handler(jsonObject); + } + break; + case ERROR_CODE_MEDIA_SERVER_NOT_FOUND: + case ERROR_CODE_OFFLINE: + case ERROR_CODE_TIMEOUT: + PlayMsgErrorCallback errorCallback = callbacksForError.get(serial); + if (errorCallback != null) { + callbacks.remove(serial); + errorCallback.handler(wvpResult); + } + break; + default: + break; + } + break; + default: + break; + } + } + + + + + } + + /** + * 处理收到的请求推流的请求 + */ + private void requestPushStreamMsgHand(RequestPushStreamMsg requestPushStreamMsg, String fromId, String serial) { + MediaServerItem mediaInfo = mediaServerService.getOne(requestPushStreamMsg.getMediaServerId()); + if (mediaInfo == null) { + // TODO 回复错误 + return; + } + String is_Udp = requestPushStreamMsg.isTcp() ? "0" : "1"; + Map param = new HashMap<>(); + param.put("vhost","__defaultVhost__"); + param.put("app",requestPushStreamMsg.getApp()); + param.put("stream",requestPushStreamMsg.getStream()); + param.put("ssrc", requestPushStreamMsg.getSsrc()); + param.put("dst_url",requestPushStreamMsg.getIp()); + param.put("dst_port", requestPushStreamMsg.getPort()); + param.put("is_udp", is_Udp); + param.put("src_port", requestPushStreamMsg.getSrcPort()); + param.put("pt", requestPushStreamMsg.getPt()); + param.put("use_ps", requestPushStreamMsg.isPs() ? "1" : "0"); + param.put("only_audio", requestPushStreamMsg.isOnlyAudio() ? "1" : "0"); + JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param); + // 回复消息 + responsePushStream(jsonObject, fromId, serial); + } + + private void responsePushStream(JSONObject content, String toId, String serial) { + + WVPResult result = new WVPResult<>(); + result.setCode(0); + result.setData(content); + + WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), toId, + WvpRedisMsgCmd.REQUEST_PUSH_STREAM, serial, result); + JSONObject jsonObject = (JSONObject)JSON.toJSON(response); + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); + } + + /** + * 处理收到的请求sendItem的请求 + */ + private void requestSendItemMsgHand(RequestSendItemMsg content, String toId, String serial) { + MediaServerItem mediaServerItem = mediaServerService.getOne(content.getMediaServerId()); + if (mediaServerItem == null) { + logger.info("[回复推流信息] 流媒体{}不存在 ", content.getMediaServerId()); + + WVPResult result = new WVPResult<>(); + result.setCode(ERROR_CODE_MEDIA_SERVER_NOT_FOUND); + result.setMsg("流媒体不存在"); + + WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), toId, + WvpRedisMsgCmd.GET_SEND_ITEM, serial, result); + + JSONObject jsonObject = (JSONObject)JSON.toJSON(response); + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); + return; + } + // 确定流是否在线 + boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, content.getApp(), content.getStream()); + if (streamReady) { + logger.info("[回复推流信息] {}/{}", content.getApp(), content.getStream()); + responseSendItem(mediaServerItem, content, toId, serial); + }else { + // 流已经离线 + // 发送redis消息以使设备上线 + logger.info("[ app={}, stream={} ]通道离线,发送redis信息控制设备开始推流",content.getApp(), content.getStream()); + + String taskKey = UUID.randomUUID().toString(); + // 设置超时 + dynamicTask.startDelay(taskKey, ()->{ + logger.info("[ app={}, stream={} ] 等待设备开始推流超时", content.getApp(), content.getStream()); + WVPResult result = new WVPResult<>(); + result.setCode(ERROR_CODE_TIMEOUT); + WvpRedisMsg response = WvpRedisMsg.getResponseInstance( + userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, result + ); + JSONObject jsonObject = (JSONObject)JSON.toJSON(response); + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); + }, userSetting.getPlatformPlayTimeout()); + + // 添加订阅 + JSONObject subscribeKey = new JSONObject(); + subscribeKey.put("app", content.getApp()); + subscribeKey.put("stream", content.getStream()); + subscribeKey.put("regist", true); + subscribeKey.put("schema", "rtmp"); + subscribeKey.put("mediaServerId", mediaServerItem.getId()); + subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, + (MediaServerItem mediaServerItemInUse, JSONObject json)->{ + dynamicTask.stop(taskKey); + responseSendItem(mediaServerItem, content, toId, serial); + }); + + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(1, content.getApp(), content.getStream(), + content.getChannelId(), content.getPlatformId(), content.getPlatformName(), content.getServerId(), + content.getMediaServerId()); + redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); + + } + } + + /** + * 将获取到的sendItem发送出去 + */ + private void responseSendItem(MediaServerItem mediaServerItem, RequestSendItemMsg content, String toId, String serial) { + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, content.getIp(), + content.getPort(), content.getSsrc(), content.getPlatformId(), + content.getApp(), content.getStream(), content.getChannelId(), + content.getTcp()); + + WVPResult result = new WVPResult<>(); + result.setCode(0); + ResponseSendItemMsg responseSendItemMsg = new ResponseSendItemMsg(); + responseSendItemMsg.setSendRtpItem(sendRtpItem); + responseSendItemMsg.setMediaServerItem(mediaServerItem); + result.setData(responseSendItemMsg); + + WvpRedisMsg response = WvpRedisMsg.getResponseInstance( + userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, result + ); + JSONObject jsonObject = (JSONObject)JSON.toJSON(response); + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); + } + + /** + * 发送消息要求下级生成推流信息 + * @param serverId 下级服务ID + * @param app 应用名 + * @param stream 流ID + * @param ip 目标IP + * @param port 目标端口 + * @param ssrc ssrc + * @param platformId 平台国标编号 + * @param channelId 通道ID + * @param isTcp 是否使用TCP + * @param callback 得到信息的回调 + */ + public void sendMsg(String serverId, String mediaServerId, String app, String stream, String ip, int port, String ssrc, + String platformId, String channelId, boolean isTcp, String platformName, PlayMsgCallback callback, PlayMsgErrorCallback errorCallback) { + RequestSendItemMsg requestSendItemMsg = RequestSendItemMsg.getInstance( + serverId, mediaServerId, app, stream, ip, port, ssrc, platformId, channelId, isTcp, platformName); + requestSendItemMsg.setServerId(serverId); + String key = UUID.randomUUID().toString(); + WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId, WvpRedisMsgCmd.GET_SEND_ITEM, + key, requestSendItemMsg); + + JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg); + logger.info("[请求推流SendItem] {}: {}", serverId, jsonObject); + callbacks.put(key, callback); + callbacksForError.put(key, errorCallback); + dynamicTask.startDelay(key, ()->{ + callbacks.remove(key); + callbacksForError.remove(key); + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(ERROR_CODE_TIMEOUT); + wvpResult.setMsg("timeout"); + errorCallback.handler(wvpResult); + }, userSetting.getPlatformPlayTimeout()); + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); + } + + /** + * 发送请求推流的消息 + * @param param 推流参数 + * @param callback 回调 + */ + public void sendMsgForStartSendRtpStream(String serverId, RequestPushStreamMsg param, PlayMsgCallbackForStartSendRtpStream callback) { + String key = UUID.randomUUID().toString(); + WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId, + WvpRedisMsgCmd.REQUEST_PUSH_STREAM, key, param); + + JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg); + logger.info("[REDIS 请求其他平台推流] {}: {}", serverId, jsonObject); + dynamicTask.startDelay(key, ()->{ + callbacksForStartSendRtpStream.remove(key); + callbacksForError.remove(key); + }, userSetting.getPlatformPlayTimeout()); + callbacksForStartSendRtpStream.put(key, callback); + callbacksForError.put(key, (wvpResult)->{ + logger.info("[REDIS 请求其他平台推流] 失败: {}", wvpResult.getMsg()); + callbacksForStartSendRtpStream.remove(key); + callbacksForError.remove(key); + }); + redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/RedisGPSMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/impl/RedisGpsMsgListener.java similarity index 66% rename from src/main/java/com/genersoft/iot/vmp/service/impl/RedisGPSMsgListener.java rename to src/main/java/com/genersoft/iot/vmp/service/impl/RedisGpsMsgListener.java index c688d13f7..238aafdba 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/RedisGPSMsgListener.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/RedisGpsMsgListener.java @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.service.impl; import com.alibaba.fastjson.JSON; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -10,17 +11,23 @@ import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.stereotype.Component; +/** + * 接收来自redis的GPS更新通知 + * @author lin + */ @Component -public class RedisGPSMsgListener implements MessageListener { +public class RedisGpsMsgListener implements MessageListener { - private final static Logger logger = LoggerFactory.getLogger(RedisGPSMsgListener.class); + private final static Logger logger = LoggerFactory.getLogger(RedisGpsMsgListener.class); @Autowired private IRedisCatchStorage redisCatchStorage; @Override - public void onMessage(Message message, byte[] bytes) { - logger.info("收到来自REDIS的GPS通知: {}", new String(message.getBody())); + public void onMessage(@NotNull Message message, byte[] bytes) { + if (logger.isDebugEnabled()) { + logger.debug("收到来自REDIS的GPS通知: {}", new String(message.getBody())); + } GPSMsgInfo gpsMsgInfo = JSON.parseObject(message.getBody(), GPSMsgInfo.class); redisCatchStorage.updateGpsMsgInfo(gpsMsgInfo); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/RedisStreamMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/impl/RedisStreamMsgListener.java new file mode 100644 index 000000000..07fffdcc1 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/RedisStreamMsgListener.java @@ -0,0 +1,83 @@ +package com.genersoft.iot.vmp.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.gb28181.bean.AlarmChannelMessage; +import com.genersoft.iot.vmp.gb28181.bean.Device; +import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; +import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; +import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager; +import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; +import com.genersoft.iot.vmp.utils.DateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.stereotype.Component; + + +/** + * @author lin + */ +@Component +public class RedisStreamMsgListener implements MessageListener { + + private final static Logger logger = LoggerFactory.getLogger(RedisStreamMsgListener.class); + + @Autowired + private ISIPCommander commander; + + @Autowired + private ISIPCommanderForPlatform commanderForPlatform; + + @Autowired + private IVideoManagerStorage storage; + + @Autowired + private UserSetting userSetting; + + @Autowired + private ZLMMediaListManager zlmMediaListManager; + + @Override + public void onMessage(Message message, byte[] bytes) { + + JSONObject steamMsgJson = JSON.parseObject(message.getBody(), JSONObject.class); + if (steamMsgJson == null) { + logger.warn("[REDIS的ALARM通知]消息解析失败"); + return; + } + String serverId = steamMsgJson.getString("serverId"); + + if (userSetting.getServerId().equals(serverId)) { + // 自己发送的消息忽略即可 + return; + } + logger.info("[REDIS通知] 流变化: {}", new String(message.getBody())); + String app = steamMsgJson.getString("app"); + String stream = steamMsgJson.getString("stream"); + boolean register = steamMsgJson.getBoolean("register"); + String mediaServerId = steamMsgJson.getString("mediaServerId"); + MediaItem mediaItem = new MediaItem(); + mediaItem.setSeverId(serverId); + mediaItem.setApp(app); + mediaItem.setStream(stream); + mediaItem.setRegist(register); + mediaItem.setMediaServerId(mediaServerId); + mediaItem.setCreateStamp(System.currentTimeMillis()/1000); + mediaItem.setAliveSecond(0L); + mediaItem.setTotalReaderCount("0"); + mediaItem.setOriginType(0); + mediaItem.setOriginTypeStr("0"); + mediaItem.setOriginTypeStr("unknown"); + + zlmMediaListManager.addPush(mediaItem); + + + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java index d710dad76..1e00faa12 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java @@ -107,6 +107,7 @@ public class StreamPushServiceImpl implements IStreamPushService { streamPushItem.setStatus(true); streamPushItem.setStreamType("push"); streamPushItem.setVhost(item.getVhost()); + streamPushItem.setServerId(item.getSeverId()); return streamPushItem; } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java index d94669b8c..6b8628026 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java @@ -356,6 +356,15 @@ public interface IVideoManagerStorage { int removeMedia(String app, String stream); + /** + * 获取但个推流 + * @param app + * @param stream + * @return + */ + StreamPushItem getMedia(String app, String stream); + + /** * 清空推流列表 */ 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 c3b94f687..5c46fb924 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 @@ -17,10 +17,10 @@ public interface DeviceChannelMapper { @Insert("INSERT INTO device_channel (channelId, deviceId, name, manufacture, model, owner, civilCode, block, " + "address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " + - "ipAddress, port, password, PTZType, status, streamId, longitude, latitude, createTime, updateTime) " + + "ipAddress, port, password, PTZType, status, streamId, longitude, latitude, longitudeGcj02, latitudeGcj02, longitudeWgs84, latitudeWgs84, createTime, updateTime) " + "VALUES ('${channelId}', '${deviceId}', '${name}', '${manufacture}', '${model}', '${owner}', '${civilCode}', '${block}'," + "'${address}', ${parental}, '${parentId}', ${safetyWay}, ${registerWay}, '${certNum}', ${certifiable}, ${errCode}, '${secrecy}', " + - "'${ipAddress}', ${port}, '${password}', ${PTZType}, ${status}, '${streamId}', ${longitude}, ${latitude},'${createTime}', '${updateTime}')") + "'${ipAddress}', ${port}, '${password}', ${PTZType}, ${status}, '${streamId}', ${longitude}, ${latitude}, ${longitudeGcj02}, ${latitudeGcj02}, ${longitudeWgs84}, ${latitudeWgs84},'${createTime}', '${updateTime}')") int add(DeviceChannel channel); @Update(value = {" "}) int update(DeviceChannel channel); @@ -67,7 +71,7 @@ public interface DeviceChannelMapper { " AND dc.status=0" + " AND dc.subCount > 0 " + " AND dc.subCount = 0 " + - "GROUP BY dc.channelId " + + "ORDER BY dc.channelId " + " "}) List queryChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online); @@ -138,7 +142,8 @@ public interface DeviceChannelMapper { "insert into device_channel " + "(channelId, deviceId, name, manufacture, model, owner, civilCode, block, subCount, " + " address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " + - " ipAddress, port, password, PTZType, status, streamId, longitude, latitude, createTime, updateTime) " + + " ipAddress, port, password, PTZType, status, streamId, longitude, latitude, longitudeGcj02, latitudeGcj02, " + + " longitudeWgs84, latitudeWgs84, createTime, updateTime) " + "values " + " " + "('${item.channelId}', '${item.deviceId}', '${item.name}', '${item.manufacture}', '${item.model}', " + @@ -146,7 +151,8 @@ public interface DeviceChannelMapper { "'${item.address}', ${item.parental}, '${item.parentId}', ${item.safetyWay}, ${item.registerWay}, " + "'${item.certNum}', ${item.certifiable}, ${item.errCode}, '${item.secrecy}', " + "'${item.ipAddress}', ${item.port}, '${item.password}', ${item.PTZType}, ${item.status}, " + - "'${item.streamId}', ${item.longitude}, ${item.latitude},'${item.createTime}', '${item.updateTime}')" + + "'${item.streamId}', ${item.longitude}, ${item.latitude},${item.longitudeGcj02}, " + + "${item.latitudeGcj02},${item.longitudeWgs84}, ${item.latitudeWgs84},'${item.createTime}', '${item.updateTime}')" + " " + "ON DUPLICATE KEY UPDATE " + "updateTime=VALUES(updateTime), " + @@ -173,7 +179,11 @@ public interface DeviceChannelMapper { "status=VALUES(status), " + "streamId=VALUES(streamId), " + "longitude=VALUES(longitude), " + - "latitude=VALUES(latitude)" + + "latitude=VALUES(latitude), " + + "longitudeGcj02=VALUES(longitudeGcj02), " + + "latitudeGcj02=VALUES(latitudeGcj02), " + + "longitudeWgs84=VALUES(longitudeWgs84), " + + "latitudeWgs84=VALUES(latitudeWgs84) " + "") int batchAdd(List addChannels); @@ -207,7 +217,11 @@ public interface DeviceChannelMapper { ", hasAudio=${item.hasAudio}" + ", longitude=${item.longitude}" + ", latitude=${item.latitude}" + - "WHERE deviceId=#{item.deviceId} AND channelId=#{item.channelId}"+ + ", longitudeGcj02=${item.longitudeGcj02}" + + ", latitudeGcj02=${item.latitudeGcj02}" + + ", longitudeWgs84=${item.longitudeWgs84}" + + ", latitudeWgs84=${item.latitudeWgs84}" + + "WHERE deviceId='${item.deviceId}' AND channelId='${item.channelId}'"+ "" + ""}) int batchUpdate(List updateChannels); @@ -261,4 +275,6 @@ public interface DeviceChannelMapper { @Select("SELECT * FROM device_channel WHERE length(trim(streamId)) > 0") List getAllChannelInPlay(); + @Select("select * from device_channel where longitude*latitude > 0 and deviceId = #{deviceId}") + List getAllChannelWithCoordinate(String deviceId); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java index 37d951e20..3e15b7392 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java @@ -38,6 +38,7 @@ public interface DeviceMapper { "mobilePositionSubmissionInterval," + "subscribeCycleForAlarm," + "ssrcCheck," + + "geoCoordSys," + "online" + ") VALUES (" + "#{deviceId}," + @@ -61,6 +62,7 @@ public interface DeviceMapper { "#{mobilePositionSubmissionInterval}," + "#{subscribeCycleForAlarm}," + "#{ssrcCheck}," + + "#{geoCoordSys}," + "#{online}" + ")") int add(Device device); @@ -87,6 +89,7 @@ public interface DeviceMapper { ", mobilePositionSubmissionInterval=${mobilePositionSubmissionInterval}" + ", subscribeCycleForAlarm=${subscribeCycleForAlarm}" + ", ssrcCheck=${ssrcCheck}" + + ", geoCoordSys=#{geoCoordSys}" + "WHERE deviceId='${deviceId}'"+ " "}) int update(Device device); 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 909d3a8b5..ebd347828 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 @@ -14,9 +14,9 @@ import java.util.List; public interface StreamPushMapper { @Insert("INSERT INTO stream_push (app, stream, totalReaderCount, originType, originTypeStr, " + - "createStamp, aliveSecond, mediaServerId) VALUES" + + "createStamp, aliveSecond, mediaServerId, serverId) VALUES" + "('${app}', '${stream}', '${totalReaderCount}', '${originType}', '${originTypeStr}', " + - "'${createStamp}', '${aliveSecond}', '${mediaServerId}' )") + "'${createStamp}', '${aliveSecond}', '${mediaServerId}' , '${serverId}' )") int add(StreamPushItem streamPushItem); @Update("UPDATE stream_push " + 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 39daeda9d..5377e23f5 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 @@ -587,11 +587,11 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { String scanKey = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetting.getServerId() + "_*"; List result = new ArrayList<>(); List keys = redis.scan(scanKey); - for (int i = 0; i < keys.size(); i++) { - String key = (String) keys.get(i); + for (Object o : keys) { + String key = (String) o; GPSMsgInfo gpsMsgInfo = (GPSMsgInfo) redis.get(key); if (!gpsMsgInfo.isStored()) { // 只取没有存过得 - result.add((GPSMsgInfo)redis.get(key)); + result.add((GPSMsgInfo) redis.get(key)); } } @@ -667,7 +667,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Override public void sendStreamPushRequestedMsg(MessageForPushChannel msg) { String key = VideoManagerConstants.VM_MSG_STREAM_PUSH_REQUESTED; - logger.info("[redis 推流被请求通知] {}: {}-{}", key, msg.getApp(), msg.getStream()); + logger.info("[redis 推流被请求通知] {}: {}/{}", key, msg.getApp(), msg.getStream()); redis.convertAndSend(key, (JSONObject)JSON.toJSON(msg)); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java index 1f3591142..ac870f7d3 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java @@ -25,12 +25,13 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -/** +/** * 视频设备数据存储-jdbc实现 * swwheihei * 2020年5月6日 下午2:31:42 @@ -195,7 +196,7 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { @Override public boolean resetChannels(String deviceId, List deviceChannelList) { - if (deviceChannelList == null) { + if (CollectionUtils.isEmpty(deviceChannelList)) { return false; } List allChannelInPlay = deviceChannelMapper.getAllChannelInPlay(); @@ -246,6 +247,10 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { if (stringBuilder.length() > 0) { logger.info("[目录查询]收到的数据存在重复: {}" , stringBuilder); } + if(CollectionUtils.isEmpty(channels)){ + logger.info("通道重设,数据为空={}" , deviceChannelList); + return false; + } try { int cleanChannelsResult = deviceChannelMapper.cleanChannelsNotInList(deviceId, channels); int limitCount = 300; @@ -315,6 +320,9 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { List all; if (catalogUnderDevice != null && catalogUnderDevice) { all = deviceChannelMapper.queryChannels(deviceId, deviceId, query, hasSubChannel, online); + // 海康设备的parentId是SIP id + List deviceChannels = deviceChannelMapper.queryChannels(deviceId, sipConfig.getId(), query, hasSubChannel, online); + all.addAll(deviceChannels); }else { all = deviceChannelMapper.queryChannels(deviceId, null, query, hasSubChannel, online); } @@ -876,6 +884,11 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { return streamPushMapper.del(app, stream); } + @Override + public StreamPushItem getMedia(String app, String stream) { + return streamPushMapper.selectOne(app, stream); + } + @Override public void clearMediaList() { streamPushMapper.clear(); diff --git a/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java b/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java index 9d37dcd31..494bcbbb0 100644 --- a/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java +++ b/src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java @@ -1,7 +1,6 @@ package com.genersoft.iot.vmp.utils; -import java.text.SimpleDateFormat; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; @@ -18,35 +17,63 @@ import java.util.Locale; */ public class DateUtil { - private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss"; - public static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss"; + /** + * 兼容不规范的iso8601时间格式 + */ + private static final String ISO8601_COMPATIBLE_PATTERN = "yyyy-M-d'T'H:m:s"; - public static final SimpleDateFormat formatISO8601 = new SimpleDateFormat(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault()); - public static final SimpleDateFormat format = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss, Locale.getDefault()); + /** + * 用以输出标准的iso8601时间格式 + */ + private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"; - public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault()).withZone(ZoneId.systemDefault()); - public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(yyyy_MM_dd_HH_mm_ss, Locale.getDefault()).withZone(ZoneId.systemDefault()); + /** + * wvp内部统一时间格式 + */ + public static final String PATTERN = "yyyy-MM-dd HH:mm:ss"; + + public static final String zoneStr = "Asia/Shanghai"; + + public static final DateTimeFormatter formatterCompatibleISO8601 = DateTimeFormatter.ofPattern(ISO8601_COMPATIBLE_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); + public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(ISO8601_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); + public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr)); public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) { + return formatterISO8601.format(formatter.parse(formatTime)); } public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) { - return formatter.format(formatterISO8601.parse(formatTime)); + return formatter.format(formatterCompatibleISO8601.parse(formatTime)); } - + + /** + * yyyy_MM_dd_HH_mm_ss 转时间戳 + * @param formatTime + * @return + */ public static long yyyy_MM_dd_HH_mm_ssToTimestamp(String formatTime) { TemporalAccessor temporalAccessor = formatter.parse(formatTime); Instant instant = Instant.from(temporalAccessor); return instant.getEpochSecond(); } + /** + * 获取当前时间 + * @return + */ public static String getNow() { LocalDateTime nowDateTime = LocalDateTime.now(); return formatter.format(nowDateTime); } + /** + * 格式校验 + * @param timeStr 时间字符串 + * @param dateTimeFormatter 待校验的格式 + * @return + */ public static boolean verification(String timeStr, DateTimeFormatter dateTimeFormatter) { try { LocalDate.parse(timeStr, dateTimeFormatter); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java index 56864dbcd..509c988c0 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java @@ -24,6 +24,7 @@ import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import java.text.ParseException; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; @@ -68,8 +69,8 @@ public class AlarmController { @ApiImplicitParam(name="alarmMethod", value = "查询内容" ,dataTypeClass = String.class), @ApiImplicitParam(name="alarmMethod", value = "查询内容" ,dataTypeClass = String.class), @ApiImplicitParam(name="alarmType", value = "查询内容" ,dataTypeClass = String.class), - @ApiImplicitParam(name="startTime", value = "查询内容" ,dataTypeClass = String.class), - @ApiImplicitParam(name="endTime", value = "查询内容" ,dataTypeClass = String.class), + @ApiImplicitParam(name="startTime", value = "开始时间" ,dataTypeClass = String.class), + @ApiImplicitParam(name="endTime", value = "结束时间" ,dataTypeClass = String.class), }) public ResponseEntity> getAll( @RequestParam int page, @@ -98,14 +99,7 @@ public class AlarmController { } - try { - if (startTime != null) { - DateUtil.format.parse(startTime); - } - if (endTime != null) { - DateUtil.format.parse(endTime); - } - } catch (ParseException e) { + if (!DateUtil.verification(startTime, DateUtil.formatter) || !DateUtil.verification(endTime, DateUtil.formatter)){ return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); } @@ -144,11 +138,7 @@ public class AlarmController { if (StringUtils.isEmpty(time)) { time = null; } - try { - if (time != null) { - DateUtil.format.parse(time); - } - } catch (ParseException e) { + if (!DateUtil.verification(time, DateUtil.formatter) ){ return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); } List deviceIdList = null; @@ -189,7 +179,7 @@ public class AlarmController { deviceAlarm.setAlarmDescription("test"); deviceAlarm.setAlarmMethod("1"); deviceAlarm.setAlarmPriority("1"); - deviceAlarm.setAlarmTime(DateUtil.formatISO8601.format(System.currentTimeMillis())); + deviceAlarm.setAlarmTime(DateUtil.formatterISO8601.format(LocalDateTime.now())); deviceAlarm.setAlarmType("1"); deviceAlarm.setLongitude(115.33333); deviceAlarm.setLatitude(39.33333); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java index 3e538486e..5e1e40bea 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java @@ -21,16 +21,22 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; +import org.apache.commons.compress.utils.IOUtils; +import org.apache.http.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; +import javax.servlet.http.HttpServletResponse; import javax.sip.DialogState; +import java.io.*; +import java.nio.file.Files; import java.util.*; @Api(tags = "国标设备查询", value = "国标设备查询") @@ -200,6 +206,11 @@ public class DeviceQuery { Set allKeys = dynamicTask.getAllKeys(); for (String key : allKeys) { if (key.startsWith(deviceId)) { + Runnable runnable = dynamicTask.get(key); + if (runnable instanceof ISubscribeTask) { + ISubscribeTask subscribeTask = (ISubscribeTask) runnable; + subscribeTask.stop(); + } dynamicTask.stop(key); } } @@ -306,12 +317,7 @@ public class DeviceQuery { public ResponseEntity> updateDevice(Device device){ if (device != null && device.getDeviceId() != null) { - - - // TODO 报警订阅相关的信息 - deviceService.updateDevice(device); -// cmder.deviceInfoQuery(device); } WVPResult result = new WVPResult<>(); result.setCode(0); @@ -336,6 +342,11 @@ public class DeviceQuery { Device device = storager.queryVideoDevice(deviceId); String uuid = UUID.randomUUID().toString(); String key = DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId; + DeferredResult> result = new DeferredResult>(2*1000L); + if(device == null) { + result.setResult(new ResponseEntity(String.format("设备%s不存在", deviceId),HttpStatus.OK)); + return result; + } cmder.deviceStatusQuery(device, event -> { RequestMessage msg = new RequestMessage(); msg.setId(uuid); @@ -343,7 +354,6 @@ public class DeviceQuery { msg.setData(String.format("获取设备状态失败,错误码: %s, %s", event.statusCode, event.msg)); resultHolder.invokeResult(msg); }); - DeferredResult> result = new DeferredResult>(2*1000L); result.onTimeout(()->{ logger.warn(String.format("获取设备状态超时")); // 释放rtpserver @@ -456,4 +466,17 @@ public class DeviceQuery { wvpResult.setData(dialogStateMap); return wvpResult; } + + @GetMapping("/snap/{deviceId}/{channelId}") + @ApiOperation(value = "请求截图", notes = "请求截图") + public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId) { + + try { + final InputStream in = Files.newInputStream(new File("snap" + File.separator + deviceId + "_" + channelId + ".jpg").toPath()); + resp.setContentType(MediaType.IMAGE_PNG_VALUE); + IOUtils.copy(in, resp.getOutputStream()); + } catch (IOException e) { + resp.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java index 8cb923a77..f0f2eb2b8 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java @@ -72,7 +72,7 @@ public class GBRecordController { if (!DateUtil.verification(startTime, DateUtil.formatter)){ WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(-1); - wvpResult.setMsg("startTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss); + wvpResult.setMsg("startTime error, format is " + DateUtil.PATTERN); ResponseEntity> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK); result.setResult(resultResponseEntity); @@ -81,7 +81,7 @@ public class GBRecordController { if (!DateUtil.verification(endTime, DateUtil.formatter)){ WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(-1); - wvpResult.setMsg("endTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss); + wvpResult.setMsg("endTime error, format is " + DateUtil.PATTERN); ResponseEntity> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK); result.setResult(resultResponseEntity); return result; diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java index 65f5f7c70..a9b23eff8 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java @@ -76,14 +76,7 @@ public class LogController { logger.warn("自动记录日志功能已关闭,查询结果可能不完整。"); } - try { - if (startTime != null) { - DateUtil.format.parse(startTime); - } - if (endTime != null) { - DateUtil.format.parse(endTime); - } - } catch (ParseException e) { + if (!DateUtil.verification(startTime, DateUtil.formatter) || !DateUtil.verification(endTime, DateUtil.formatter)){ return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST); } diff --git a/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java index d4928ec7f..3be4be3e7 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java @@ -146,8 +146,8 @@ public class ApiDeviceController { // 2-基于口令的双向认证, // 3-基于数字证书的双向认证 deviceJOSNChannel.put("Status", deviceChannel.getStatus()); - deviceJOSNChannel.put("Longitude", deviceChannel.getLongitude()); - deviceJOSNChannel.put("Latitude", deviceChannel.getLatitude()); + deviceJOSNChannel.put("Longitude", deviceChannel.getLongitudeWgs84()); + deviceJOSNChannel.put("Latitude", deviceChannel.getLatitudeWgs84()); deviceJOSNChannel.put("PTZType ", deviceChannel.getPTZType()); // 云台类型, 0 - 未知, 1 - 球机, 2 - 半球, // 3 - 固定枪机, 4 - 遥控枪机 deviceJOSNChannel.put("CustomPTZType", ""); diff --git a/src/main/resources/all-application.yml b/src/main/resources/all-application.yml index 1233a890a..9dedcb143 100644 --- a/src/main/resources/all-application.yml +++ b/src/main/resources/all-application.yml @@ -32,7 +32,7 @@ spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false + url: jdbc:mysql://127.0.0.1:3306/wvp2?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true username: root password: root123 druid: diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 353143146..35ddc86ce 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -20,7 +20,7 @@ spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false + url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true username: root password: 123456 druid: diff --git a/src/main/resources/application-docker.yml b/src/main/resources/application-docker.yml index 1653a5868..53a863560 100644 --- a/src/main/resources/application-docker.yml +++ b/src/main/resources/application-docker.yml @@ -20,7 +20,7 @@ spring: datasource: # 使用mysql 打开23-28行注释, 删除29-36行 name: wvp - url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&allowMultiQueries=true&useSSL=false + url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&allowMultiQueries=true&useSSL=false&allowMultiQueries=true username: root password: root type: com.alibaba.druid.pool.DruidDataSource diff --git a/src/test/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImplTest.java b/src/test/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImplTest.java index 6d6ff37ff..c627511a7 100644 --- a/src/test/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImplTest.java +++ b/src/test/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImplTest.java @@ -8,6 +8,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.TemporalAccessor; import java.util.Date; @@ -64,8 +68,8 @@ class DeviceAlarmServiceImplTest { * * 7其他报警;可以为直接组合如12为电话报警或 设备报警- */ deviceAlarm.setAlarmMethod((int)(Math.random()*7 + 1) + ""); - Date date = randomDate("2021-01-01 00:00:00", "2021-06-01 00:00:00"); - deviceAlarm.setAlarmTime(DateUtil.format.format(date)); + Instant date = randomDate("2021-01-01 00:00:00", "2021-06-01 00:00:00"); + deviceAlarm.setAlarmTime(DateUtil.formatter.format(date)); /** * 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级 警情- */ @@ -85,17 +89,20 @@ class DeviceAlarmServiceImplTest { - private Date randomDate(String beginDate, String endDate) { + private Instant randomDate(String beginDate, String endDate) { try { - Date start = DateUtil.format.parse(beginDate);//构造开始日期 - Date end = DateUtil.format.parse(endDate);//构造结束日期 + //构造开始日期 + LocalDateTime start = LocalDateTime.parse(beginDate, DateUtil.formatter); + + //构造结束日期 + LocalDateTime end = LocalDateTime.parse(endDate, DateUtil.formatter); //getTime()表示返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。 - if (start.getTime() >= end.getTime()) { + if (start.isAfter(end)) { return null; } - long date = random(start.getTime(), end.getTime()); - return new Date(date); + long date = random(start.toInstant(ZoneOffset.of("+8")).toEpochMilli(), end.toInstant(ZoneOffset.of("+8")).toEpochMilli()); + return Instant.ofEpochMilli(date); } catch (Exception e) { e.printStackTrace(); } diff --git a/web_src/package.json b/web_src/package.json index 1e7f0432d..b825f8403 100644 --- a/web_src/package.json +++ b/web_src/package.json @@ -52,7 +52,7 @@ "postcss-url": "^7.2.1", "rimraf": "^2.6.0", "semver": "^5.3.0", - "shelljs": "^0.7.6", + "shelljs": "^0.8.5", "uglifyjs-webpack-plugin": "^1.1.1", "url-loader": "^0.5.8", "vue-loader": "^13.3.0", diff --git a/web_src/src/App.vue b/web_src/src/App.vue index 3590f7377..4ae7ea84d 100644 --- a/web_src/src/App.vue +++ b/web_src/src/App.vue @@ -76,7 +76,7 @@ body, line-height: 60px; } .el-main { - background-color: #e9eef3; + background-color: #f0f2f5; color: #333; text-align: center; padding-top: 0px !important; @@ -101,4 +101,8 @@ body, box-shadow: inset 0 0 6px rgba(0, 0, 0, .1); -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .1); } +.table-header { + color: #727272; + font-weight: 600; +} diff --git a/web_src/src/components/CloudRecord.vue b/web_src/src/components/CloudRecord.vue index 1d0819b08..897e142bd 100644 --- a/web_src/src/components/CloudRecord.vue +++ b/web_src/src/components/CloudRecord.vue @@ -18,19 +18,17 @@
- - + + - + - + - + diff --git a/web_src/src/components/DeviceList.vue b/web_src/src/components/DeviceList.vue index 62cba31fb..1911d1dbc 100644 --- a/web_src/src/components/DeviceList.vue +++ b/web_src/src/components/DeviceList.vue @@ -7,34 +7,33 @@ @click="getDeviceList()">
- - - + + - + - + - + - + - + - + - + - - - - - + + + + + - + @@ -347,4 +348,5 @@ export default { padding: 0.3rem; width: 14.4rem; } + diff --git a/web_src/src/components/MediaServerManger.vue b/web_src/src/components/MediaServerManger.vue index 2e3eeeffc..1d3c057ca 100644 --- a/web_src/src/components/MediaServerManger.vue +++ b/web_src/src/components/MediaServerManger.vue @@ -15,7 +15,7 @@ {{item.id}} 编辑 查看 - 移除 + 移除
{{item.ip}} {{item.createTime}} diff --git a/web_src/src/components/ParentPlatformList.vue b/web_src/src/components/ParentPlatformList.vue index 20a3e8237..3ae0b6571 100644 --- a/web_src/src/components/ParentPlatformList.vue +++ b/web_src/src/components/ParentPlatformList.vue @@ -4,14 +4,15 @@
上级平台列表
添加 +
- - - - + + + + - + - + - - - - + + + + - + @@ -168,6 +169,9 @@ export default { console.log(error); }); + }, + refresh: function (){ + this.initData(); } } diff --git a/web_src/src/components/PushVideoList.vue b/web_src/src/components/PushVideoList.vue index 7b5a406b8..678d13f5c 100644 --- a/web_src/src/components/PushVideoList.vue +++ b/web_src/src/components/PushVideoList.vue @@ -34,52 +34,54 @@ 批量移除 + - - + - + - + - + - + - + - + - + - + @@ -284,6 +286,9 @@ export default { handleSelectionChange: function (val) { this.multipleSelection = val; }, + refresh: function () { + this.initData(); + }, } }; diff --git a/web_src/src/components/StreamProxyList.vue b/web_src/src/components/StreamProxyList.vue index d5533927b..9f7ed61e1 100644 --- a/web_src/src/components/StreamProxyList.vue +++ b/web_src/src/components/StreamProxyList.vue @@ -5,14 +5,15 @@
添加代理 搜索ONVIF +
- - - - - + + + + + - - + + - - + + - + - - + + - + - +