Commit 26739237e2d93460eb869067a6004bfa63a1bdb8

Authored by 648540858
2 parents 038d360d 0da45229

Merge remote-tracking branch 'origin/wvp-28181-2.0' into commercial

# Conflicts:
#	sql/update.sql
#	src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
#	web_src/src/components/dialog/deviceEdit.vue
#	web_src/src/components/dialog/devicePlayer.vue
Showing 66 changed files with 2399 additions and 998 deletions

Too many changes to show.

To preserve performance only 66 of 106 files are displayed.

DOCKERFILE
  1 +#很久没维护了,已经与定前版本不匹配
  2 +
  3 +
1 4 FROM ubuntu:20.04 AS build
2 5  
3 6 ARG DEBIAN_FRONTEND=noninteractive
... ...
README.md
... ... @@ -107,6 +107,8 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
107 107 - [X] 鈭垢敶閬蝵脣蝙嚗
108 108 - [X] 憭嚗韐蝸雿雿輻
109 109 - [X] WEB蝡舀H264銝265嚗憸.711A/G.711U/AAC,閬虜蝻撘
  110 +- [X] 摮
  111 +- [X] WGS84CJ02銝斤頂
110 112  
111 113 [//]: # (# docker敹恍)
112 114  
... ... @@ -143,7 +145,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
143 145  
144 146 #
145 147 蝘摰之摰嗅之銝瓷移憸隞乩晶圾蝑隞交R
146 148 -嚗笆隞遣霈桀隞交SSUE嚗隞亙黎銝韏瑁賑甈Z頞憿寧銝剜犖
  149 +嚗笆隞遣霈桀隞交SSUE嚗隞亙黎銝韏瑁賑甈Z頞憿寧銝剜犖
147 150  
148 151  
149 152  
... ... @@ -163,6 +165,7 @@ QQ蝘縑銝銝, 蝎曉.甈Z之摰嗅蝢日悄霈.閫★撖嫣
163 165 [hotcoffie](https://github.com/hotcoffie) [xiaomu](https://github.com/nikmu) [TristingChen](https://github.com/TristingChen)
164 166 [chenparty](https://github.com/chenparty) [Hotleave](https://github.com/hotleave) [ydwxb](https://github.com/ydwxb)
165 167 [ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666)
  168 +[mk1990](https://github.com/mk1990)
166 169  
167 170 ps: 葵摰鈭之雿穿洽餈之雿祈頂溶
168 171  
... ...
... ... @@ -11,7 +11,7 @@
11 11  
12 12 <groupId>com.genersoft</groupId>
13 13 <artifactId>wvp-pro</artifactId>
14   - <version>2.2.1</version>
  14 + <version>2.3.1</version>
15 15 <name>web video platform</name>
16 16 <description>国标28181视频平台</description>
17 17  
... ... @@ -159,9 +159,10 @@
159 159 <dependency>
160 160 <groupId>com.alibaba</groupId>
161 161 <artifactId>fastjson</artifactId>
162   - <version>1.2.73</version>
  162 + <version>1.2.83</version>
163 163 </dependency>
164 164  
  165 +
165 166 <!-- okhttp -->
166 167 <dependency>
167 168 <groupId>com.squareup.okhttp3</groupId>
... ... @@ -180,9 +181,9 @@
180 181  
181 182 <!-- okhttp-digest -->
182 183 <dependency>
183   - <groupId>com.burgstaller</groupId>
  184 + <groupId>io.github.rburgst</groupId>
184 185 <artifactId>okhttp-digest</artifactId>
185   - <version>2.1</version>
  186 + <version>2.5</version>
186 187 </dependency>
187 188  
188 189 <!-- https://mvnrepository.com/artifact/net.sf.kxml/kxml2 -->
... ...
sql/mysql.sql
... ... @@ -471,6 +471,7 @@ CREATE TABLE `stream_push` (
471 471 `createStamp` bigint(20) DEFAULT NULL,
472 472 `aliveSecond` int(11) DEFAULT NULL,
473 473 `mediaServerId` varchar(50) DEFAULT NULL,
  474 + `serverId` varchar(50) not NULL,
474 475 PRIMARY KEY (`id`),
475 476 UNIQUE KEY `stream_push_pk` (`app`,`stream`)
476 477 ) ENGINE=InnoDB AUTO_INCREMENT=300838 DEFAULT CHARSET=utf8mb4;
... ...
sql/update.sql
  1 +alter table parent_platform
  2 + add startOfflinePush int default 0 null;
  3 +
  4 +alter table parent_platform
  5 + add administrativeDivision varchar(50) not null;
  6 +
  7 +alter table parent_platform
  8 + add catalogGroup int default 1 null;
  9 +
1 10 alter table device
2 11 add audioChannelForReceive VARCHAR(50) null;
3 12  
4 13 alter table device
5   - add audioChannelForSend VARCHAR(50) null;
6 14 \ No newline at end of file
  15 + add audioChannelForSend VARCHAR(50) null;
  16 +
  17 +alter table stream_push
  18 + add serverId varchar(50) not null;
  19 +alter table device
  20 + add geoCoordSys varchar(50) not null;
  21 +update device set device.geoCoordSys='WGS84';
  22 +alter table device_channel
  23 + add longitudeGcj02 double default null;
  24 +alter table device_channel
  25 + add latitudeGcj02 double default null;
  26 +alter table device_channel
  27 + add longitudeWgs84 double default null;
  28 +alter table device_channel
  29 + add latitudeWgs84 double default null;
  30 +
... ...
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
... ... @@ -97,4 +97,5 @@ public class VideoManagerConstants {
97 97 //************************** 第三方 ****************************************
98 98 public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_";
99 99 public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_";
  100 +
100 101 }
... ...
src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java
... ... @@ -103,12 +103,12 @@ public class DynamicTask {
103 103  
104 104 public void stop(String key) {
105 105 if (futureMap.get(key) != null && !futureMap.get(key).isCancelled()) {
106   - futureMap.get(key).cancel(true);
107   - Runnable runnable = runnableMap.get(key);
108   - if (runnable instanceof ISubscribeTask) {
109   - ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
110   - subscribeTask.stop();
111   - }
  106 +// Runnable runnable = runnableMap.get(key);
  107 +// if (runnable instanceof ISubscribeTask) {
  108 +// ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
  109 +// subscribeTask.stop();
  110 +// }
  111 + futureMap.get(key).cancel(false);
112 112 }
113 113 }
114 114  
... ...
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
... ... @@ -6,6 +6,10 @@ import org.springframework.beans.factory.annotation.Value;
6 6 import org.springframework.context.annotation.Configuration;
7 7 import org.springframework.util.StringUtils;
8 8  
  9 +import java.net.InetAddress;
  10 +import java.net.UnknownHostException;
  11 +import java.util.regex.Pattern;
  12 +
9 13  
10 14 @Configuration("mediaConfig")
11 15 public class MediaConfig{
... ... @@ -161,7 +165,18 @@ public class MediaConfig{
161 165 if (StringUtils.isEmpty(sdpIp)){
162 166 return ip;
163 167 }else {
164   - return sdpIp;
  168 + if (isValidIPAddress(sdpIp)) {
  169 + return sdpIp;
  170 + }else {
  171 + // 按照域名解析
  172 + String hostAddress = null;
  173 + try {
  174 + hostAddress = InetAddress.getByName(sdpIp).getHostAddress();
  175 + } catch (UnknownHostException e) {
  176 + throw new RuntimeException(e);
  177 + }
  178 + return hostAddress;
  179 + }
165 180 }
166 181 }
167 182  
... ... @@ -211,4 +226,11 @@ public class MediaConfig{
211 226 return mediaServerItem;
212 227 }
213 228  
  229 + private boolean isValidIPAddress(String ipAddress) {
  230 + if ((ipAddress != null) && (!ipAddress.isEmpty())) {
  231 + 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);
  232 + }
  233 + return false;
  234 + }
  235 +
214 236 }
... ...
src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java
... ... @@ -2,7 +2,9 @@ package com.genersoft.iot.vmp.conf;
2 2  
3 3 import com.genersoft.iot.vmp.common.VideoManagerConstants;
4 4 import com.genersoft.iot.vmp.service.impl.RedisAlarmMsgListener;
5   -import com.genersoft.iot.vmp.service.impl.RedisGPSMsgListener;
  5 +import com.genersoft.iot.vmp.service.impl.RedisGpsMsgListener;
  6 +import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener;
  7 +import com.genersoft.iot.vmp.service.impl.RedisStreamMsgListener;
6 8 import org.apache.commons.lang3.StringUtils;
7 9 import org.springframework.beans.factory.annotation.Autowired;
8 10 import org.springframework.beans.factory.annotation.Value;
... ... @@ -47,11 +49,17 @@ public class RedisConfig extends CachingConfigurerSupport {
47 49 private int poolMaxWait;
48 50  
49 51 @Autowired
50   - private RedisGPSMsgListener redisGPSMsgListener;
  52 + private RedisGpsMsgListener redisGPSMsgListener;
51 53  
52 54 @Autowired
53 55 private RedisAlarmMsgListener redisAlarmMsgListener;
54 56  
  57 + @Autowired
  58 + private RedisStreamMsgListener redisStreamMsgListener;
  59 +
  60 + @Autowired
  61 + private RedisGbPlayMsgListener redisGbPlayMsgListener;
  62 +
55 63 @Bean
56 64 public JedisPool jedisPool() {
57 65 if (StringUtils.isBlank(password)) {
... ... @@ -98,6 +106,8 @@ public class RedisConfig extends CachingConfigurerSupport {
98 106 container.setConnectionFactory(connectionFactory);
99 107 container.addMessageListener(redisGPSMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_GPS));
100 108 container.addMessageListener(redisAlarmMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE));
  109 + container.addMessageListener(redisStreamMsgListener, new PatternTopic(VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + "PUSH"));
  110 + container.addMessageListener(redisGbPlayMsgListener, new PatternTopic(RedisGbPlayMsgListener.WVP_PUSH_STREAM_KEY));
101 111 return container;
102 112 }
103 113  
... ...
src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java
... ... @@ -11,6 +11,9 @@ import javax.servlet.http.HttpServletRequest;
11 11 import javax.servlet.http.HttpServletResponse;
12 12 import java.io.IOException;
13 13  
  14 +/**
  15 + * @author lin
  16 + */
14 17 @Component
15 18 public class LoginSuccessHandler implements AuthenticationSuccessHandler {
16 19  
... ...
src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
... ... @@ -20,6 +20,7 @@ import java.util.List;
20 20  
21 21 /**
22 22 * 配置Spring Security
  23 + * @author lin
23 24 */
24 25 @Configuration
25 26 @EnableWebSecurity
... ... @@ -132,15 +133,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
132 133 .anyRequest().authenticated()
133 134 // 异常处理(权限拒绝、登录失效等)
134 135 .and().exceptionHandling()
135   - .authenticationEntryPoint(anonymousAuthenticationEntryPoint)//匿名用户访问无权限资源时的异常处理
  136 + //匿名用户访问无权限资源时的异常处理
  137 + .authenticationEntryPoint(anonymousAuthenticationEntryPoint)
136 138 // .accessDeniedHandler(accessDeniedHandler)//登录用户没有权限访问资源
137   - // 登入
138   - .and().formLogin().permitAll()//允许所有用户
139   - .successHandler(loginSuccessHandler)//登录成功处理逻辑
140   - .failureHandler(loginFailureHandler)//登录失败处理逻辑
  139 + // 登入 允许所有用户
  140 + .and().formLogin().permitAll()
  141 + //登录成功处理逻辑
  142 + .successHandler(loginSuccessHandler)
  143 + //登录失败处理逻辑
  144 + .failureHandler(loginFailureHandler)
141 145 // 登出
142   - .and().logout().logoutUrl("/api/user/logout").permitAll()//允许所有用户
143   - .logoutSuccessHandler(logoutHandler)//登出成功处理逻辑
  146 + .and().logout().logoutUrl("/api/user/logout").permitAll()
  147 + //登出成功处理逻辑
  148 + .logoutSuccessHandler(logoutHandler)
144 149 .deleteCookies("JSESSIONID")
145 150 // 会话管理
146 151 // .and().sessionManagement().invalidSessionStrategy(invalidSessionHandler) // 超时处理
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
1 1 package com.genersoft.iot.vmp.gb28181.bean;
2 2  
3 3  
  4 +/**
  5 + * 国标设备/平台
  6 + * @author lin
  7 + */
4 8 public class Device {
5 9  
6 10 /**
... ... @@ -127,7 +131,12 @@ public class Device {
127 131 /**
128 132 * 是否开启ssrc校验,默认关闭,开启可以防止串流
129 133 */
130   - private boolean ssrcCheck;
  134 + private boolean ssrcCheck = true;
  135 +
  136 + /**
  137 + * 地理坐标系, 目前支持 WGS84,GCJ02 TODO CGCS2000
  138 + */
  139 + private String geoCoordSys;
131 140  
132 141  
133 142 public String getDeviceId() {
... ... @@ -322,4 +331,12 @@ public class Device {
322 331 this.ssrcCheck = ssrcCheck;
323 332 }
324 333  
  334 + public String getGeoCoordSys() {
  335 + return geoCoordSys;
  336 + }
  337 +
  338 + public void setGeoCoordSys(String geoCoordSys) {
  339 + this.geoCoordSys = geoCoordSys;
  340 + }
  341 +
325 342 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
... ... @@ -155,6 +155,26 @@ public class DeviceChannel {
155 155 private double latitude;
156 156  
157 157 /**
  158 + * 经度 GCJ02
  159 + */
  160 + private double longitudeGcj02;
  161 +
  162 + /**
  163 + * 纬度 GCJ02
  164 + */
  165 + private double latitudeGcj02;
  166 +
  167 + /**
  168 + * 经度 WGS84
  169 + */
  170 + private double longitudeWgs84;
  171 +
  172 + /**
  173 + * 纬度 WGS84
  174 + */
  175 + private double latitudeWgs84;
  176 +
  177 + /**
158 178 * 子设备数
159 179 */
160 180 private int subCount;
... ... @@ -407,6 +427,38 @@ public class DeviceChannel {
407 427 this.latitude = latitude;
408 428 }
409 429  
  430 + public double getLongitudeGcj02() {
  431 + return longitudeGcj02;
  432 + }
  433 +
  434 + public void setLongitudeGcj02(double longitudeGcj02) {
  435 + this.longitudeGcj02 = longitudeGcj02;
  436 + }
  437 +
  438 + public double getLatitudeGcj02() {
  439 + return latitudeGcj02;
  440 + }
  441 +
  442 + public void setLatitudeGcj02(double latitudeGcj02) {
  443 + this.latitudeGcj02 = latitudeGcj02;
  444 + }
  445 +
  446 + public double getLongitudeWgs84() {
  447 + return longitudeWgs84;
  448 + }
  449 +
  450 + public void setLongitudeWgs84(double longitudeWgs84) {
  451 + this.longitudeWgs84 = longitudeWgs84;
  452 + }
  453 +
  454 + public double getLatitudeWgs84() {
  455 + return latitudeWgs84;
  456 + }
  457 +
  458 + public void setLatitudeWgs84(double latitudeWgs84) {
  459 + this.latitudeWgs84 = latitudeWgs84;
  460 + }
  461 +
410 462 public int getSubCount() {
411 463 return subCount;
412 464 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/HandlerCatchData.java 0 → 100644
  1 +package com.genersoft.iot.vmp.gb28181.bean;
  2 +
  3 +import org.dom4j.Element;
  4 +
  5 +import javax.sip.RequestEvent;
  6 +
  7 +/**
  8 + * @author lin
  9 + */
  10 +public class HandlerCatchData {
  11 + private RequestEvent evt;
  12 + private Device device;
  13 + private Element rootElement;
  14 +
  15 + public HandlerCatchData(RequestEvent evt, Device device, Element rootElement) {
  16 + this.evt = evt;
  17 + this.device = device;
  18 + this.rootElement = rootElement;
  19 + }
  20 +
  21 + public RequestEvent getEvt() {
  22 + return evt;
  23 + }
  24 +
  25 + public void setEvt(RequestEvent evt) {
  26 + this.evt = evt;
  27 + }
  28 +
  29 + public Device getDevice() {
  30 + return device;
  31 + }
  32 +
  33 + public void setDevice(Device device) {
  34 + this.device = device;
  35 + }
  36 +
  37 + public Element getRootElement() {
  38 + return rootElement;
  39 + }
  40 +
  41 + public void setRootElement(Element rootElement) {
  42 + this.rootElement = rootElement;
  43 + }
  44 +}
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java
... ... @@ -72,6 +72,11 @@ public class SendRtpItem {
72 72 private String mediaServerId;
73 73  
74 74 /**
  75 + * 使用的服务的ID
  76 + */
  77 + private String serverId;
  78 +
  79 + /**
75 80 * invite的callId
76 81 */
77 82 private String CallId;
... ... @@ -259,4 +264,12 @@ public class SendRtpItem {
259 264 public void setOnlyAudio(boolean onlyAudio) {
260 265 this.onlyAudio = onlyAudio;
261 266 }
  267 +
  268 + public String getServerId() {
  269 + return serverId;
  270 + }
  271 +
  272 + public void setServerId(String serverId) {
  273 + this.serverId = serverId;
  274 + }
262 275 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java
... ... @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean;
2 2  
3 3 import com.genersoft.iot.vmp.common.VideoManagerConstants;
4 4 import com.genersoft.iot.vmp.conf.DynamicTask;
  5 +import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
5 6 import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeHandlerTask;
6 7 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
7 8 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
... ... @@ -38,7 +39,6 @@ public class SubscribeHolder {
38 39 catalogMap.put(platformId, subscribeInfo);
39 40 // 添加订阅到期
40 41 String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId;
41   - dynamicTask.stop(taskOverdueKey);
42 42 // 添加任务处理订阅过期
43 43 dynamicTask.startDelay(taskOverdueKey, () -> removeCatalogSubscribe(subscribeInfo.getId()),
44 44 subscribeInfo.getExpires() * 1000);
... ... @@ -49,10 +49,17 @@ public class SubscribeHolder {
49 49 }
50 50  
51 51 public void removeCatalogSubscribe(String platformId) {
  52 +
52 53 catalogMap.remove(platformId);
53 54 String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId;
  55 + Runnable runnable = dynamicTask.get(taskOverdueKey);
  56 + if (runnable instanceof ISubscribeTask) {
  57 + ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
  58 + subscribeTask.stop();
  59 + }
54 60 // 添加任务处理订阅过期
55 61 dynamicTask.stop(taskOverdueKey);
  62 +
56 63 }
57 64  
58 65 public void putMobilePositionSubscribe(String platformId, SubscribeInfo subscribeInfo) {
... ... @@ -63,7 +70,6 @@ public class SubscribeHolder {
63 70 storager, platformId, subscribeInfo.getSn(), key, this, dynamicTask),
64 71 subscribeInfo.getGpsInterval() * 1000);
65 72 String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId;
66   - dynamicTask.stop(taskOverdueKey);
67 73 // 添加任务处理订阅过期
68 74 dynamicTask.startDelay(taskOverdueKey, () -> {
69 75 removeMobilePositionSubscribe(subscribeInfo.getId());
... ... @@ -81,6 +87,11 @@ public class SubscribeHolder {
81 87 // 结束任务处理GPS定时推送
82 88 dynamicTask.stop(key);
83 89 String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId;
  90 + Runnable runnable = dynamicTask.get(taskOverdueKey);
  91 + if (runnable instanceof ISubscribeTask) {
  92 + ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
  93 + subscribeTask.stop();
  94 + }
84 95 // 添加任务处理订阅过期
85 96 dynamicTask.stop(taskOverdueKey);
86 97 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
... ... @@ -88,8 +88,8 @@ public class SipSubscribe {
88 88 this.type = "timeout";
89 89 this.msg = "消息超时未回复";
90 90 this.statusCode = -1024;
91   - this.callId = timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId();
92 91 this.dialog = timeoutEvent.getClientTransaction().getDialog();
  92 + this.callId = this.dialog != null?timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId(): null;
93 93 }else if (event instanceof TransactionTerminatedEvent) {
94 94 TransactionTerminatedEvent transactionTerminatedEvent = (TransactionTerminatedEvent)event;
95 95 this.type = "transactionTerminated";
... ... @@ -109,8 +109,8 @@ public class SipSubscribe {
109 109 this.type = "deviceNotFoundEvent";
110 110 this.msg = "设备未找到";
111 111 this.statusCode = -1024;
112   - this.callId = deviceNotFoundEvent.getDialog().getCallId().getCallId();
113 112 this.dialog = deviceNotFoundEvent.getDialog();
  113 + this.callId = this.dialog != null ?deviceNotFoundEvent.getDialog().getCallId().getCallId() : null;
114 114 }
115 115 }
116 116 }
... ... @@ -130,6 +130,9 @@ public class SipSubscribe {
130 130 }
131 131  
132 132 public void removeErrorSubscribe(String key) {
  133 + if(key == null){
  134 + return;
  135 + }
133 136 errorSubscribes.remove(key);
134 137 errorTimeSubscribes.remove(key);
135 138 }
... ... @@ -139,6 +142,9 @@ public class SipSubscribe {
139 142 }
140 143  
141 144 public void removeOkSubscribe(String key) {
  145 + if(key == null){
  146 + return;
  147 + }
142 148 okSubscribes.remove(key);
143 149 okTimeSubscribes.remove(key);
144 150 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java
... ... @@ -66,7 +66,7 @@ public class CatalogEventLister implements ApplicationListener&lt;CatalogEvent&gt; {
66 66 subscribe = subscribeHolder.getCatalogSubscribe(event.getPlatformId());
67 67  
68 68 if (subscribe == null) {
69   - logger.info("发送订阅消息时发现订阅信息已经不存在");
  69 + logger.info("发送订阅消息时发现订阅信息已经不存在: {}", event.getPlatformId());
70 70 return;
71 71 }
72 72 }else {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
... ... @@ -99,8 +99,8 @@ public class VideoStreamSessionManager {
99 99 return dialog;
100 100 }
101 101  
102   - public SIPDialog getDialogByCallId(String deviceId, String channelId, String callID){
103   - SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callID, null);
  102 + public SIPDialog getDialogByCallId(String deviceId, String channelId, String callId){
  103 + SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, null);
104 104 if (ssrcTransaction == null) {
105 105 return null;
106 106 }
... ... @@ -108,11 +108,17 @@ public class VideoStreamSessionManager {
108 108 if (dialogByteArray == null) {
109 109 return null;
110 110 }
111   - SIPDialog dialog = (SIPDialog)SerializeUtils.deSerialize(dialogByteArray);
112   - return dialog;
  111 + return (SIPDialog)SerializeUtils.deSerialize(dialogByteArray);
113 112 }
114 113  
115 114 public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){
  115 +
  116 + if (StringUtils.isEmpty(deviceId)) {
  117 + deviceId ="*";
  118 + }
  119 + if (StringUtils.isEmpty(channelId)) {
  120 + channelId ="*";
  121 + }
116 122 if (StringUtils.isEmpty(callId)) {
117 123 callId ="*";
118 124 }
... ... @@ -179,7 +185,7 @@ public class VideoStreamSessionManager {
179 185  
180 186  
181 187 public List<SsrcTransaction> getAllSsrc() {
182   - List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId() + "_" ));
  188 + List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId()));
183 189 List<SsrcTransaction> result= new ArrayList<>();
184 190 for (int i = 0; i < ssrcTransactionKeys.size(); i++) {
185 191 String key = (String)ssrcTransactionKeys.get(i);
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java
... ... @@ -71,7 +71,9 @@ public class MobilePositionSubscribeHandlerTask implements ISubscribeTask {
71 71 String gbId = gbStream.getGbId();
72 72 GPSMsgInfo gpsMsgInfo = redisCatchStorage.getGpsMsgInfo(gbId);
73 73 if (gpsMsgInfo != null) { // 无最新位置不发送
74   - logger.info("无最新位置不发送");
  74 + if (logger.isDebugEnabled()) {
  75 + logger.debug("无最新位置不发送");
  76 + }
75 77 // 经纬度都为0不发送
76 78 if (gpsMsgInfo.getLng() == 0 && gpsMsgInfo.getLat() == 0) {
77 79 continue;
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java
... ... @@ -150,30 +150,24 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
150 150 public void processTimeout(TimeoutEvent timeoutEvent) {
151 151 logger.info("[消息发送超时]");
152 152 ClientTransaction clientTransaction = timeoutEvent.getClientTransaction();
153   - eventPublisher.requestTimeOut(timeoutEvent);
  153 +
154 154 if (clientTransaction != null) {
  155 + logger.info("[发送错误订阅] clientTransaction != null");
155 156 Request request = clientTransaction.getRequest();
156 157 if (request != null) {
  158 + logger.info("[发送错误订阅] request != null");
157 159 CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
158 160 if (callIdHeader != null) {
  161 + logger.info("[发送错误订阅]");
159 162 SipSubscribe.Event subscribe = sipSubscribe.getErrorSubscribe(callIdHeader.getCallId());
160 163 SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(timeoutEvent);
161 164 subscribe.response(eventResult);
  165 + sipSubscribe.removeOkSubscribe(callIdHeader.getCallId());
162 166 sipSubscribe.removeErrorSubscribe(callIdHeader.getCallId());
163 167 }
164 168 }
165 169 }
166   -
167   -// Timeout timeout = timeoutEvent.getTimeout();
168   -// ServerTransaction serverTransaction = timeoutEvent.getServerTransaction();
169   -// if (serverTransaction != null) {
170   -// Request request = serverTransaction.getRequest();
171   -// URI requestURI = request.getRequestURI();
172   -// Header header = request.getHeader(FromHeader.NAME);
173   -// }
174   -// if(timeoutProcessor != null) {
175   -// timeoutProcessor.process(timeoutEvent);
176   -// }
  170 + eventPublisher.requestTimeOut(timeoutEvent);
177 171 }
178 172  
179 173 @Override
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
... ... @@ -148,6 +148,14 @@ public interface ISIPCommander {
148 148 * 回放倍速播放
149 149 */
150 150 void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed);
  151 +
  152 + /**
  153 + * 回放控制
  154 + * @param device
  155 + * @param streamInfo
  156 + * @param content
  157 + */
  158 + void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent);
151 159  
152 160  
153 161 /**
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
... ... @@ -104,6 +104,14 @@ public interface ISIPCommanderForPlatform {
104 104 boolean recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo);
105 105  
106 106 /**
  107 + * 录像播放推送完成时发送MediaStatus消息
  108 + * @param platform
  109 + * @param sendRtpItem
  110 + * @return
  111 + */
  112 + boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem);
  113 +
  114 + /**
107 115 * 向发起点播的上级回复bye
108 116 * @param platform 平台信息
109 117 * @param callId callId
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
... ... @@ -32,7 +32,9 @@ import org.springframework.stereotype.Component;
32 32 import org.springframework.util.StringUtils;
33 33  
34 34 import javax.sip.*;
  35 +import javax.sip.address.Address;
35 36 import javax.sip.address.SipURI;
  37 +import javax.sip.address.URI;
36 38 import javax.sip.header.*;
37 39 import javax.sip.message.Request;
38 40 import java.lang.reflect.Field;
... ... @@ -708,22 +710,19 @@ public class SIPCommander implements ISIPCommander {
708 710 }
709 711 SIPDialog dialog;
710 712 if (callId != null) {
711   - dialog = streamSession.getDialogByCallId(deviceId, channelId, callId);
  713 + dialog = streamSession.getDialogByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), callId);
712 714 }else {
713   - if (stream == null) {
  715 + if (stream == null && ssrcTransaction == null && ssrcTransaction.getStream() == null) {
714 716 return;
715 717 }
716   - dialog = streamSession.getDialogByStream(deviceId, channelId, stream);
717   - }
718   - if (ssrcTransaction != null) {
719   - MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId());
720   - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
721   - mediaServerService.closeRTPServer(deviceId, channelId, ssrcTransaction.getStream());
722   - streamSession.remove(deviceId, channelId, ssrcTransaction.getStream());
  718 + dialog = streamSession.getDialogByStream(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
723 719 }
  720 + mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
  721 + mediaServerService.closeRTPServer(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
  722 + streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
724 723  
725 724 if (dialog == null) {
726   - logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", deviceId, channelId);
  725 + logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
727 726 return;
728 727 }
729 728 SipStack sipStack = udpSipProvider.getSipStack();
... ... @@ -1456,12 +1455,20 @@ public class SIPCommander implements ISIPCommander {
1456 1455  
1457 1456 Request request;
1458 1457 if (dialog != null) {
1459   - logger.info("发送移动位置订阅消息时 dialog的状态为: {}", dialog.getState());
  1458 + SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
1460 1459 request = dialog.createRequest(Request.SUBSCRIBE);
  1460 + ExpiresHeader expiresHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForCatalog());
  1461 + request.setExpires(expiresHeader);
  1462 +
  1463 + request.setRequestURI(requestURI);
  1464 +
1461 1465 ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
1462 1466 request.setContent(subscribePostitionXml.toString(), contentTypeHeader);
1463   - ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition());
1464   - request.addHeader(expireHeader);
  1467 +
  1468 + CSeqHeader cSeqHeader = (CSeqHeader)request.getHeader(CSeqHeader.NAME);
  1469 + cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ(Request.SUBSCRIBE));
  1470 + request.removeHeader(CSeqHeader.NAME);
  1471 + request.addHeader(cSeqHeader);
1465 1472 }else {
1466 1473 String tm = Long.toString(System.currentTimeMillis());
1467 1474 CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
... ... @@ -1552,12 +1559,21 @@ public class SIPCommander implements ISIPCommander {
1552 1559  
1553 1560 Request request;
1554 1561 if (dialog != null) {
1555   - logger.info("发送目录订阅消息时 dialog的状态为: {}", dialog.getState());
  1562 + SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
1556 1563 request = dialog.createRequest(Request.SUBSCRIBE);
  1564 + ExpiresHeader expiresHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForCatalog());
  1565 + request.setExpires(expiresHeader);
  1566 +
  1567 + request.setRequestURI(requestURI);
  1568 +
1557 1569 ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
1558 1570 request.setContent(cmdXml.toString(), contentTypeHeader);
1559   - ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition());
1560   - request.addHeader(expireHeader);
  1571 +
  1572 + CSeqHeader cSeqHeader = (CSeqHeader)request.getHeader(CSeqHeader.NAME);
  1573 + cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ(Request.SUBSCRIBE));
  1574 + request.removeHeader(CSeqHeader.NAME);
  1575 + request.addHeader(cSeqHeader);
  1576 +
1561 1577 }else {
1562 1578 String tm = Long.toString(System.currentTimeMillis());
1563 1579  
... ... @@ -1779,6 +1795,43 @@ public class SIPCommander implements ISIPCommander {
1779 1795 e.printStackTrace();
1780 1796 }
1781 1797 }
  1798 +
  1799 + @Override
  1800 + public void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) {
  1801 + try {
  1802 + Request request = headerProvider.createInfoRequest(device, streamInfo, content);
  1803 + if (request == null) {
  1804 + return;
  1805 + }
  1806 + logger.info(request.toString());
  1807 + ClientTransaction clientTransaction = null;
  1808 + if ("TCP".equals(device.getTransport())) {
  1809 + clientTransaction = tcpSipProvider.getNewClientTransaction(request);
  1810 + } else if ("UDP".equals(device.getTransport())) {
  1811 + clientTransaction = udpSipProvider.getNewClientTransaction(request);
  1812 + }
  1813 + CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
  1814 + if(errorEvent != null) {
  1815 + sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
  1816 + errorEvent.response(eventResult);
  1817 + sipSubscribe.removeErrorSubscribe(eventResult.callId);
  1818 + sipSubscribe.removeOkSubscribe(eventResult.callId);
  1819 + }));
  1820 + }
  1821 +
  1822 + if(okEvent != null) {
  1823 + sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
  1824 + okEvent.response(eventResult);
  1825 + sipSubscribe.removeOkSubscribe(eventResult.callId);
  1826 + sipSubscribe.removeErrorSubscribe(eventResult.callId);
  1827 + });
  1828 + }
  1829 + clientTransaction.sendRequest();
  1830 +
  1831 + } catch (SipException | ParseException | InvalidArgumentException e) {
  1832 + e.printStackTrace();
  1833 + }
  1834 + }
1782 1835  
1783 1836 @Override
1784 1837 public boolean sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
... ... @@ -31,6 +31,7 @@ import javax.sip.address.SipURI;
31 31 import javax.sip.header.*;
32 32 import javax.sip.message.Request;
33 33 import java.lang.reflect.Field;
  34 +import java.net.InetAddress;
34 35 import java.text.ParseException;
35 36 import java.util.ArrayList;
36 37 import java.util.HashSet;
... ... @@ -276,8 +277,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
276 277 catalogXml.append("<Owner>" + channel.getOwner() + "</Owner>\r\n");
277 278 catalogXml.append("<CivilCode>" + channel.getCivilCode() + "</CivilCode>\r\n");
278 279 catalogXml.append("<Address>" + channel.getAddress() + "</Address>\r\n");
279   - catalogXml.append("<Longitude>" + channel.getLongitude() + "</Longitude>\r\n");
280   - catalogXml.append("<Latitude>" + channel.getLatitude() + "</Latitude>\r\n");
  280 + catalogXml.append("<Longitude>" + channel.getLongitudeWgs84() + "</Longitude>\r\n");
  281 + catalogXml.append("<Latitude>" + channel.getLatitudeWgs84() + "</Latitude>\r\n");
281 282 catalogXml.append("<IPAddress>" + channel.getIpAddress() + "</IPAddress>\r\n");
282 283 catalogXml.append("<Port>" + channel.getPort() + "</Port>\r\n");
283 284 catalogXml.append("<Info>\r\n");
... ... @@ -546,14 +547,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
546 547 }
547 548 notifyRequest.addHeader(event);
548 549 SipURI sipURI = (SipURI) notifyRequest.getRequestURI();
549   - if (subscribeInfo.getTransaction() != null) {
550   - SIPRequest request = (SIPRequest) subscribeInfo.getTransaction().getRequest();
551   - sipURI.setHost(request.getRemoteAddress().getHostAddress());
552   - sipURI.setPort(request.getRemotePort());
553   - }else {
554   - sipURI.setHost(parentPlatform.getServerIP());
555   - sipURI.setPort(parentPlatform.getServerPort());
556   - }
  550 + sipURI.setHost(parentPlatform.getServerIP());
  551 + sipURI.setPort(parentPlatform.getServerPort());
557 552  
558 553 ClientTransaction transaction = null;
559 554 if ("TCP".equals(parentPlatform.getTransport())) {
... ... @@ -751,6 +746,82 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
751 746 }
752 747  
753 748 @Override
  749 + public boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem) {
  750 + if (sendRtpItem == null) {
  751 + return false;
  752 + }
  753 + if (platform == null) {
  754 + return false;
  755 + }
  756 +
  757 + byte[] dialogByteArray = sendRtpItem.getDialog();
  758 + if (dialogByteArray == null) {
  759 + return false;
  760 + }
  761 + try{
  762 + SIPDialog dialog = (SIPDialog) SerializeUtils.deSerialize(dialogByteArray);
  763 + SipStack sipStack;
  764 + if ("TCP".equals(platform.getTransport())) {
  765 + sipStack = tcpSipProvider.getSipStack();
  766 + } else {
  767 + sipStack = udpSipProvider.getSipStack();
  768 + }
  769 + SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog);
  770 + if (dialog != sipDialog) {
  771 + dialog = sipDialog;
  772 + }
  773 + if ("TCP".equals(platform.getTransport())) {
  774 + dialog.setSipProvider(tcpSipProvider);
  775 + } else {
  776 + dialog.setSipProvider(udpSipProvider);
  777 + }
  778 +
  779 + Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
  780 + sipStackField.setAccessible(true);
  781 + sipStackField.set(dialog, sipStack);
  782 + Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
  783 + eventListenersField.setAccessible(true);
  784 + eventListenersField.set(dialog, new HashSet<>());
  785 +
  786 + SIPRequest messageRequest = (SIPRequest)dialog.createRequest(Request.MESSAGE);
  787 + String characterSet = platform.getCharacterSet();
  788 + StringBuffer mediaStatusXml = new StringBuffer(200);
  789 + mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
  790 + mediaStatusXml.append("<Notify>\r\n");
  791 + mediaStatusXml.append("<CmdType>MediaStatus</CmdType>\r\n");
  792 + mediaStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
  793 + mediaStatusXml.append("<DeviceID>" + sendRtpItem.getChannelId() + "</DeviceID>\r\n");
  794 + mediaStatusXml.append("<NotifyType>121</NotifyType>\r\n");
  795 + mediaStatusXml.append("</Notify>\r\n");
  796 + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
  797 + messageRequest.setContent(mediaStatusXml.toString(), contentTypeHeader);
  798 + SipURI sipURI = (SipURI) messageRequest.getRequestURI();
  799 + sipURI.setHost(platform.getServerIP());
  800 + sipURI.setPort(platform.getServerPort());
  801 + ClientTransaction clientTransaction;
  802 + if ("TCP".equals(platform.getTransport())) {
  803 + clientTransaction = tcpSipProvider.getNewClientTransaction(messageRequest);
  804 + }else {
  805 + clientTransaction = udpSipProvider.getNewClientTransaction(messageRequest);
  806 + }
  807 + dialog.sendRequest(clientTransaction);
  808 + } catch (SipException e) {
  809 + e.printStackTrace();
  810 + return false;
  811 + } catch (ParseException e) {
  812 + e.printStackTrace();
  813 + return false;
  814 + } catch (NoSuchFieldException e) {
  815 + throw new RuntimeException(e);
  816 + } catch (IllegalAccessException e) {
  817 + throw new RuntimeException(e);
  818 + }
  819 + return true;
  820 +
  821 +
  822 + }
  823 +
  824 + @Override
754 825 public void streamByeCmd(ParentPlatform platform, String callId) {
755 826 if (platform == null) {
756 827 return;
... ... @@ -766,45 +837,51 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
766 837 byte[] dialogByteArray = sendRtpItem.getDialog();
767 838 if (dialogByteArray != null) {
768 839 SIPDialog dialog = (SIPDialog) SerializeUtils.deSerialize(dialogByteArray);
769   - SipStack sipStack = udpSipProvider.getSipStack();
  840 + SipStack sipStack;
  841 + if ("TCP".equals(platform.getTransport())) {
  842 + sipStack = tcpSipProvider.getSipStack();
  843 + } else {
  844 + sipStack = udpSipProvider.getSipStack();
  845 + }
770 846 SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog);
771 847 if (dialog != sipDialog) {
772 848 dialog = sipDialog;
773   - } else {
774   - try {
  849 + }
  850 + try {
  851 + if ("TCP".equals(platform.getTransport())) {
  852 + dialog.setSipProvider(tcpSipProvider);
  853 + } else {
775 854 dialog.setSipProvider(udpSipProvider);
776   - Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
777   - sipStackField.setAccessible(true);
778   - sipStackField.set(dialog, sipStack);
779   - Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
780   - eventListenersField.setAccessible(true);
781   - eventListenersField.set(dialog, new HashSet<>());
782   -
783   - byte[] transactionByteArray = sendRtpItem.getTransaction();
784   - ClientTransaction clientTransaction = (ClientTransaction) SerializeUtils.deSerialize(transactionByteArray);
785   - Request byeRequest = dialog.createRequest(Request.BYE);
786   -
787   - SipURI byeURI = (SipURI) byeRequest.getRequestURI();
788   - SIPRequest request = (SIPRequest) clientTransaction.getRequest();
789   - byeURI.setHost(request.getRemoteAddress().getHostAddress());
790   - byeURI.setPort(request.getRemotePort());
791   - if ("TCP".equals(platform.getTransport())) {
792   - clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
793   - } else if ("UDP".equals(platform.getTransport())) {
794   - clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
795   - }
796   - dialog.sendRequest(clientTransaction);
797   - } catch (SipException e) {
798   - e.printStackTrace();
799   - } catch (ParseException e) {
800   - e.printStackTrace();
801   - } catch (NoSuchFieldException e) {
802   - e.printStackTrace();
803   - } catch (IllegalAccessException e) {
804   - e.printStackTrace();
805 855 }
806   -
  856 + Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
  857 + sipStackField.setAccessible(true);
  858 + sipStackField.set(dialog, sipStack);
  859 + Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
  860 + eventListenersField.setAccessible(true);
  861 + eventListenersField.set(dialog, new HashSet<>());
  862 +
  863 + Request byeRequest = dialog.createRequest(Request.BYE);
  864 +
  865 + SipURI byeURI = (SipURI) byeRequest.getRequestURI();
  866 + byeURI.setHost(platform.getServerIP());
  867 + byeURI.setPort(platform.getServerPort());
  868 + ClientTransaction clientTransaction;
  869 + if ("TCP".equals(platform.getTransport())) {
  870 + clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
  871 + } else {
  872 + clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
  873 + }
  874 + dialog.sendRequest(clientTransaction);
  875 + } catch (SipException e) {
  876 + e.printStackTrace();
  877 + } catch (ParseException e) {
  878 + e.printStackTrace();
  879 + } catch (NoSuchFieldException e) {
  880 + e.printStackTrace();
  881 + } catch (IllegalAccessException e) {
  882 + e.printStackTrace();
807 883 }
  884 +
808 885 }
809 886 }
810 887 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorAbstract.java deleted 100644 → 0
1   -package com.genersoft.iot.vmp.gb28181.transmit.event.request;
2   -
3   -import gov.nist.javax.sip.SipProviderImpl;
4   -import org.springframework.beans.factory.annotation.Autowired;
5   -import org.springframework.beans.factory.annotation.Qualifier;
6   -
7   -/**
8   - * @description:处理接收IPCamera发来的SIP协议请求消息
9   - * @author: songww
10   - * @date: 2020年5月3日 下午4:42:22
11   - */
12   -public abstract class SIPRequestProcessorAbstract {
13   -
14   -
15   - @Autowired
16   - @Qualifier(value="tcpSipProvider")
17   - private SipProviderImpl tcpSipProvider;
18   -
19   - @Autowired
20   - @Qualifier(value="udpSipProvider")
21   - private SipProviderImpl udpSipProvider;
22   -
23   -}
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;
15 15 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
16 16 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
17 17 import com.genersoft.iot.vmp.service.IMediaServerService;
  18 +import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
  19 +import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener;
18 20 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
19 21 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
20 22 import gov.nist.javax.sip.message.SIPRequest;
21 23 import gov.nist.javax.sip.stack.SIPDialog;
  24 +import com.genersoft.iot.vmp.utils.SerializeUtils;
22 25 import org.ehcache.shadow.org.terracotta.offheapstore.storage.IntegerStorageEngine;
23 26 import org.slf4j.Logger;
24 27 import org.slf4j.LoggerFactory;
... ... @@ -46,7 +49,7 @@ import java.util.*;
46 49 public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
47 50  
48 51 private Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class);
49   - private String method = "ACK";
  52 + private final String method = "ACK";
50 53  
51 54 @Autowired
52 55 private SIPProcessorObserver sipProcessorObserver;
... ... @@ -84,6 +87,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
84 87 @Autowired
85 88 private AudioBroadcastManager audioBroadcastManager;
86 89  
  90 + @Autowired
  91 + private RedisGbPlayMsgListener redisGbPlayMsgListener;
  92 +
87 93  
88 94 /**
89 95 * 处理 ACK请求
... ... @@ -159,60 +165,41 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
159 165 // 向上级平台
160 166 commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
161 167 }
  168 + if (mediaInfo == null) {
  169 + RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
  170 + sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(),
  171 + sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
  172 + sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
  173 + redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, jsonObject->{
  174 + startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
  175 + });
  176 + }else {
  177 + JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
  178 + startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
162 179 }
163 180  
164 181  
165   -// if (streamInfo == null) { // 流还没上来,对方就回复ack
166   -// logger.info("监听流以等待流上线1 rtp/{}", sendRtpItem.getStreamId());
167   -// // 监听流上线
168   -// // 添加订阅
169   -// JSONObject subscribeKey = new JSONObject();
170   -// subscribeKey.put("app", "rtp");
171   -// subscribeKey.put("stream", sendRtpItem.getStreamId());
172   -// subscribeKey.put("regist", true);
173   -// subscribeKey.put("schema", "rtmp");
174   -// subscribeKey.put("mediaServerId", sendRtpItem.getMediaServerId());
175   -// subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
176   -// (MediaServerItem mediaServerItemInUse, JSONObject json)->{
177   -// Map<String, Object> param = new HashMap<>();
178   -// param.put("vhost","__defaultVhost__");
179   -// param.put("app",json.getString("app"));
180   -// param.put("stream",json.getString("stream"));
181   -// param.put("ssrc", sendRtpItem.getSsrc());
182   -// param.put("dst_url",sendRtpItem.getIp());
183   -// param.put("dst_port", sendRtpItem.getPort());
184   -// param.put("is_udp", is_Udp);
185   -// param.put("src_port", sendRtpItem.getLocalPort());
186   -// zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
187   -// });
188   -// }else {
189   -// Map<String, Object> param = new HashMap<>();
190   -// param.put("vhost","__defaultVhost__");
191   -// param.put("app",streamInfo.getApp());
192   -// param.put("stream",streamInfo.getStream());
193   -// param.put("ssrc", sendRtpItem.getSsrc());
194   -// param.put("dst_url",sendRtpItem.getIp());
195   -// param.put("dst_port", sendRtpItem.getPort());
196   -// param.put("is_udp", is_Udp);
197   -// param.put("src_port", sendRtpItem.getLocalPort());
198   -//
199   -// JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
200   -// if (jsonObject.getInteger("code") != 0) {
201   -// logger.info("监听流以等待流上线2 {}/{}", streamInfo.getApp(), streamInfo.getStream());
202   -// // 监听流上线
203   -// // 添加订阅
204   -// JSONObject subscribeKey = new JSONObject();
205   -// subscribeKey.put("app", "rtp");
206   -// subscribeKey.put("stream", streamInfo.getStream());
207   -// subscribeKey.put("regist", true);
208   -// subscribeKey.put("schema", "rtmp");
209   -// subscribeKey.put("mediaServerId", sendRtpItem.getMediaServerId());
210   -// subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
211   -// (MediaServerItem mediaServerItemInUse, JSONObject json)->{
212   -// zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
213   -// });
214   -// }
215   -// }
  182 + }
  183 + }
  184 + private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform,
  185 + JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) {
  186 + if (jsonObject == null) {
  187 + logger.error("RTP推流失败: 请检查ZLM服务");
  188 + } else if (jsonObject.getInteger("code") == 0) {
  189 + logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
  190 + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
  191 + sendRtpItem.setDialog(dialogByteArray);
  192 + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
  193 + sendRtpItem.setTransaction(transactionByteArray);
  194 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  195 + } else {
  196 + logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"),JSONObject.toJSON(param));
  197 + if (sendRtpItem.isOnlyAudio()) {
  198 + // TODO 可能是语音对讲
  199 + }else {
  200 + // 向上级平台
  201 + commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
  202 + }
216 203 }
217 204 }
218 205 }
... ...
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
114 114 playService.stopAudioBroadcast(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
115 115 }
116 116 if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
117   - MessageForPushChannel messageForPushChannel = new MessageForPushChannel();
118   - messageForPushChannel.setType(0);
119   - messageForPushChannel.setGbId(sendRtpItem.getChannelId());
120   - messageForPushChannel.setApp(sendRtpItem.getApp());
121   - messageForPushChannel.setStream(sendRtpItem.getStreamId());
122   - messageForPushChannel.setMediaServerId(sendRtpItem.getMediaServerId());
123   - messageForPushChannel.setPlatFormId(sendRtpItem.getPlatformId());
  117 + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
  118 + sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
  119 + sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId());
124 120 redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
125 121 }
126 122 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java
... ... @@ -15,7 +15,7 @@ import javax.sip.RequestEvent;
15 15 @Component
16 16 public class CancelRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
17 17  
18   - private String method = "CANCEL";
  18 + private final String method = "CANCEL";
19 19  
20 20 @Autowired
21 21 private SIPProcessorObserver sipProcessorObserver;
... ...
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;
26 26 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
27 27 import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
28 28 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  29 +import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
29 30 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItemLite;
30 31 import com.genersoft.iot.vmp.service.IMediaServerService;
31 32 import com.genersoft.iot.vmp.service.IPlayService;
  33 +import com.genersoft.iot.vmp.service.IStreamPushService;
32 34 import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
33 35 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
  36 +import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener;
34 37 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
35 38 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
36 39 import com.genersoft.iot.vmp.utils.DateUtil;
... ... @@ -66,33 +69,42 @@ import java.util.Vector;
66 69 @Component
67 70 public class InviteRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
68 71  
69   - private final static Logger logger = LoggerFactory.getLogger(InviteRequestProcessor.class);
  72 + private final static Logger logger = LoggerFactory.getLogger(InviteRequestProcessor.class);
70 73  
71   - private String method = "INVITE";
  74 + private final String method = "INVITE";
72 75  
73   - @Autowired
74   - private SIPCommanderFroPlatform cmderFroPlatform;
  76 + @Autowired
  77 + private SIPCommanderFroPlatform cmderFroPlatform;
75 78  
76   - @Autowired
77   - private IVideoManagerStorage storager;
  79 + @Autowired
  80 + private IVideoManagerStorage storager;
78 81  
79   - @Autowired
80   - private IRedisCatchStorage redisCatchStorage;
  82 + @Autowired
  83 + private IStreamPushService streamPushService;
81 84  
82   - @Autowired
83   - private DynamicTask dynamicTask;
  85 + @Autowired
  86 + private IRedisCatchStorage redisCatchStorage;
84 87  
85   - @Autowired
86   - private SIPCommander cmder;
  88 + @Autowired
  89 + private DynamicTask dynamicTask;
87 90  
88   - @Autowired
89   - private IPlayService playService;
  91 + @Autowired
  92 + private SIPCommander cmder;
90 93  
  94 + @Autowired
  95 + private IPlayService playService;
  96 +
  97 + @Autowired
  98 + private ISIPCommander commander;
  99 +
91 100 @Autowired
92 101 private AudioBroadcastManager audioBroadcastManager;
93 102  
94   - @Autowired
95   - private ZLMRTPServerFactory zlmrtpServerFactory;
  103 + @Autowired
  104 + private ZLMRTPServerFactory zlmrtpServerFactory;
  105 +
  106 + @Autowired
  107 + private IMediaServerService mediaServerService;
96 108  
97 109 @Autowired
98 110 private ZLMRESTfulUtils zlmresTfulUtils;
... ... @@ -100,17 +112,17 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
100 112 @Autowired
101 113 private IMediaServerService mediaServerService;
102 114  
103   - @Autowired
104   - private SIPProcessorObserver sipProcessorObserver;
  115 + @Autowired
  116 + private SIPProcessorObserver sipProcessorObserver;
105 117  
106   - @Autowired
107   - private VideoStreamSessionManager sessionManager;
  118 + @Autowired
  119 + private VideoStreamSessionManager sessionManager;
108 120  
109   - @Autowired
110   - private UserSetting userSetting;
  121 + @Autowired
  122 + private UserSetting userSetting;
111 123  
112   - @Autowired
113   - private ZLMMediaListManager mediaListManager;
  124 + @Autowired
  125 + private ZLMMediaListManager mediaListManager;
114 126  
115 127 @Autowired
116 128 private DeferredResultHolder resultHolder;
... ... @@ -123,542 +135,664 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
123 135  
124 136  
125 137  
126   - @Override
127   - public void afterPropertiesSet() throws Exception {
128   - // 添加消息处理的订阅
129   - sipProcessorObserver.addRequestProcessor(method, this);
130   - }
131   -
132   - /**
133   - * 处理invite请求
134   - *
135   - * @param evt
136   - * 请求消息
137   - */
138   - @Override
139   - public void process(RequestEvent evt) {
140   - // Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令
141   - try {
142   - Request request = evt.getRequest();
143   - SipURI sipURI = (SipURI) request.getRequestURI();
144   - //从subject读取channelId,不再从request-line读取。 有些平台request-line是平台国标编码,不是设备国标编码。
145   - //String channelId = sipURI.getUser();
146   - String channelId = SipUtils.getChannelIdFromHeader(request);
147   - String requesterId = SipUtils.getUserIdFromFromHeader(request);
148   - CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
149   - if (requesterId == null || channelId == null) {
150   - logger.info("无法从FromHeader的Address中获取到平台id,返回400");
151   - responseAck(evt, Response.BAD_REQUEST); // 参数不全, 发400,请求错误
152   - return;
153   - }
154   -
155   - // 查询请求是否来自上级平台\设备
156   - ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
157   - if (platform == null) {
158   - inviteFromDeviceHandle(evt, requesterId, channelId);
159   - }else {
160   - // 查询平台下是否有该通道
161   - DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId);
162   - GbStream gbStream = storager.queryStreamInParentPlatform(requesterId, channelId);
163   - PlatformCatalog catalog = storager.getCatalog(channelId);
164   - MediaServerItem mediaServerItem = null;
165   - // 不是通道可能是直播流
166   - if (channel != null && gbStream == null ) {
167   - if (channel.getStatus() == 0) {
168   - logger.info("通道离线,返回400");
169   - responseAck(evt, Response.BAD_REQUEST, "channel [" + channel.getChannelId() + "] offline");
170   - return;
171   - }
172   - responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中
173   - }else if(channel == null && gbStream != null){
174   - String mediaServerId = gbStream.getMediaServerId();
175   - mediaServerItem = mediaServerService.getOne(mediaServerId);
176   - if (mediaServerItem == null) {
177   - logger.info("[ app={}, stream={} ]找不到zlm {},返回410",gbStream.getApp(), gbStream.getStream(), mediaServerId);
178   - responseAck(evt, Response.GONE);
179   - return;
180   - }
181   - responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中
182   - }else if (catalog != null) {
183   - responseAck(evt, Response.BAD_REQUEST, "catalog channel can not play"); // 目录不支持点播
184   - return;
185   - } else {
186   - logger.info("通道不存在,返回404");
187   - responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在
188   - return;
189   - }
190   - // 解析sdp消息, 使用jainsip 自带的sdp解析方式
191   - String contentString = new String(request.getRawContent());
192   -
193   - // jainSip不支持y=字段, 移除以解析。
194   - int ssrcIndex = contentString.indexOf("y=");
195   - // 检查是否有y字段
196   - String ssrcDefault = "0000000000";
197   - String ssrc;
198   - SessionDescription sdp;
199   - if (ssrcIndex >= 0) {
200   - //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段
201   - ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
202   - String substring = contentString.substring(0, contentString.indexOf("y="));
203   - sdp = SdpFactory.getInstance().createSessionDescription(substring);
204   - }else {
205   - ssrc = ssrcDefault;
206   - sdp = SdpFactory.getInstance().createSessionDescription(contentString);
207   - }
208   - String sessionName = sdp.getSessionName().getValue();
209   -
210   - Long startTime = null;
211   - Long stopTime = null;
212   - Instant start = null;
213   - Instant end = null;
214   - if (sdp.getTimeDescriptions(false) != null && sdp.getTimeDescriptions(false).size() > 0) {
215   - TimeDescriptionImpl timeDescription = (TimeDescriptionImpl)(sdp.getTimeDescriptions(false).get(0));
216   - TimeField startTimeFiled = (TimeField)timeDescription.getTime();
217   - startTime = startTimeFiled.getStartTime();
218   - stopTime = startTimeFiled.getStopTime();
219   -
220   - start = Instant.ofEpochMilli(startTime*1000);
221   - end = Instant.ofEpochMilli(stopTime*1000);
222   - }
223   - // 获取支持的格式
224   - Vector mediaDescriptions = sdp.getMediaDescriptions(true);
225   - // 查看是否支持PS 负载96
226   - //String ip = null;
227   - int port = -1;
228   - boolean mediaTransmissionTCP = false;
229   - Boolean tcpActive = null;
230   - for (Object description : mediaDescriptions) {
231   - MediaDescription mediaDescription = (MediaDescription) description;
232   - Media media = mediaDescription.getMedia();
233   -
234   - Vector mediaFormats = media.getMediaFormats(false);
235   - if (mediaFormats.contains("96")) {
236   - port = media.getMediaPort();
237   - //String mediaType = media.getMediaType();
238   - String protocol = media.getProtocol();
239   -
240   - // 区分TCP发流还是udp, 当前默认udp
241   - if ("TCP/RTP/AVP".equals(protocol)) {
242   - String setup = mediaDescription.getAttribute("setup");
243   - if (setup != null) {
244   - mediaTransmissionTCP = true;
245   - if ("active".equals(setup)) {
246   - tcpActive = true;
247   - // 不支持tcp主动
248   - responseAck(evt, Response.NOT_IMPLEMENTED, "tcp active not support"); // 目录不支持点播
249   - return;
250   - } else if ("passive".equals(setup)) {
251   - tcpActive = false;
252   - }
253   - }
254   - }
255   - break;
256   - }
257   - }
258   - if (port == -1) {
259   - logger.info("不支持的媒体格式,返回415");
260   - // 回复不支持的格式
261   - responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
262   - return;
263   - }
264   - String username = sdp.getOrigin().getUsername();
265   - String addressStr = sdp.getOrigin().getAddress();
266   -
267   - logger.info("[上级点播]用户:{}, 地址:{}:{}, ssrc:{}", username, addressStr, port, ssrc);
268   - Device device = null;
269   - // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标
270   - if (channel != null) {
271   - device = storager.queryVideoDeviceByPlatformIdAndChannelId(requesterId, channelId);
272   - if (device == null) {
273   - logger.warn("点播平台{}的通道{}时未找到设备信息", requesterId, channel);
274   - responseAck(evt, Response.SERVER_INTERNAL_ERROR);
275   - return;
276   - }
277   - mediaServerItem = playService.getNewMediaServerItem(device);
278   - if (mediaServerItem == null) {
279   - logger.warn("未找到可用的zlm");
280   - responseAck(evt, Response.BUSY_HERE);
281   - return;
282   - }
283   - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
284   - device.getDeviceId(), channelId,
285   - mediaTransmissionTCP);
286   - if (tcpActive != null) {
287   - sendRtpItem.setTcpActive(tcpActive);
288   - }
289   - if (sendRtpItem == null) {
290   - logger.warn("服务器端口资源不足");
291   - responseAck(evt, Response.BUSY_HERE);
292   - return;
293   - }
294   - sendRtpItem.setCallId(callIdHeader.getCallId());
295   - sendRtpItem.setPlayType("Play".equals(sessionName)?InviteStreamType.PLAY:InviteStreamType.PLAYBACK);
296   - byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
297   - sendRtpItem.setDialog(dialogByteArray);
298   - byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
299   - sendRtpItem.setTransaction(transactionByteArray);
300   - Long finalStartTime = startTime;
301   - Long finalStopTime = stopTime;
302   - ZLMHttpHookSubscribe.Event hookEvent = (mediaServerItemInUSe, responseJSON)->{
303   - String app = responseJSON.getString("app");
304   - String stream = responseJSON.getString("stream");
305   - logger.info("[上级点播]下级已经开始推流。 回复200OK(SDP), {}/{}", app, stream);
306   - // * 0 等待设备推流上来
307   - // * 1 下级已经推流,等待上级平台回复ack
308   - // * 2 推流中
309   - sendRtpItem.setStatus(1);
310   - redisCatchStorage.updateSendRTPSever(sendRtpItem);
311   -
312   - StringBuffer content = new StringBuffer(200);
313   - content.append("v=0\r\n");
314   - content.append("o="+ channelId +" 0 0 IN IP4 "+mediaServerItemInUSe.getSdpIp()+"\r\n");
315   - content.append("s=" + sessionName+"\r\n");
316   - content.append("c=IN IP4 "+mediaServerItemInUSe.getSdpIp()+"\r\n");
317   - if ("Playback".equals(sessionName)) {
318   - content.append("t=" + finalStartTime + " " + finalStopTime + "\r\n");
319   - }else {
320   - content.append("t=0 0\r\n");
321   - }
322   - content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n");
323   - content.append("a=sendonly\r\n");
324   - content.append("a=rtpmap:96 PS/90000\r\n");
325   - content.append("y="+ ssrc + "\r\n");
326   - content.append("f=\r\n");
327   -
328   - try {
329   - // 超时未收到Ack应该回复bye,当前等待时间为10秒
330   - dynamicTask.startDelay(callIdHeader.getCallId(), ()->{
331   - logger.info("Ack 等待超时");
332   - mediaServerService.releaseSsrc(mediaServerItemInUSe.getId(), ssrc);
333   - // 回复bye
334   - cmderFroPlatform.streamByeCmd(platform, callIdHeader.getCallId());
335   - }, 60*1000);
336   - responseSdpAck(evt, content.toString(), platform);
337   -
338   - } catch (SipException e) {
339   - e.printStackTrace();
340   - } catch (InvalidArgumentException e) {
341   - e.printStackTrace();
342   - } catch (ParseException e) {
343   - e.printStackTrace();
344   - }
345   - };
346   - SipSubscribe.Event errorEvent = ((event) -> {
347   - // 未知错误。直接转发设备点播的错误
348   - Response response = null;
349   - try {
350   - response = getMessageFactory().createResponse(event.statusCode, evt.getRequest());
351   - ServerTransaction serverTransaction = getServerTransaction(evt);
352   - serverTransaction.sendResponse(response);
353   - if (serverTransaction.getDialog() != null) {
354   - serverTransaction.getDialog().delete();
355   - }
356   - } catch (ParseException | SipException | InvalidArgumentException e) {
357   - e.printStackTrace();
358   - }
359   - });
360   - sendRtpItem.setApp("rtp");
361   - if ("Playback".equals(sessionName)) {
362   - sendRtpItem.setPlayType(InviteStreamType.PLAYBACK);
363   - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true, true);
364   - sendRtpItem.setStreamId(ssrcInfo.getStream());
365   - // 写入redis, 超时时回复
366   - redisCatchStorage.updateSendRTPSever(sendRtpItem);
367   - playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start),
368   - DateUtil.formatter.format(end), null, result -> {
369   - if (result.getCode() != 0){
370   - logger.warn("录像回放失败");
371   - if (result.getEvent() != null) {
372   - errorEvent.response(result.getEvent());
373   - }
374   - redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
375   - try {
376   - responseAck(evt, Response.REQUEST_TIMEOUT);
377   - } catch (SipException e) {
378   - e.printStackTrace();
379   - } catch (InvalidArgumentException e) {
380   - e.printStackTrace();
381   - } catch (ParseException e) {
382   - e.printStackTrace();
383   - }
384   - }else {
385   - if (result.getMediaServerItem() != null) {
386   - hookEvent.response(result.getMediaServerItem(), result.getResponse());
387   - }
388   - }
389   - });
390   - }else {
391   - sendRtpItem.setPlayType(InviteStreamType.PLAY);
392   - SsrcTransaction playTransaction = sessionManager.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
393   - if (playTransaction != null) {
394   - Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream());
395   - if (!streamReady) {
396   - playTransaction = null;
397   - }
398   - }
399   - if (playTransaction == null) {
400   - String streamId = null;
401   - if (mediaServerItem.isRtpEnable()) {
402   - streamId = String.format("%s_%s", device.getDeviceId(), channelId);
403   - }
404   - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, true, false);
405   - sendRtpItem.setStreamId(ssrcInfo.getStream());
406   - // 写入redis, 超时时回复
407   - redisCatchStorage.updateSendRTPSever(sendRtpItem);
408   - playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg)->{
409   - redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
410   - }, null);
411   - }else {
412   - sendRtpItem.setStreamId(playTransaction.getStream());
413   - // 写入redis, 超时时回复
414   - redisCatchStorage.updateSendRTPSever(sendRtpItem);
415   - JSONObject jsonObject = new JSONObject();
416   - jsonObject.put("app", sendRtpItem.getApp());
417   - jsonObject.put("stream", sendRtpItem.getStreamId());
418   - hookEvent.response(mediaServerItem, jsonObject);
419   - }
420   - }
421   - }else if (gbStream != null) {
422   -
423   - Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
424   - if (!streamReady ) {
425   - if ("proxy".equals(gbStream.getStreamType())) {
426   - // TODO 控制启用以使设备上线
427   - logger.info("[ app={}, stream={} ]通道离线,启用流后开始推流",gbStream.getApp(), gbStream.getStream());
428   - responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline");
429   - }else if ("push".equals(gbStream.getStreamType())) {
430   - if (!platform.isStartOfflinePush()) {
431   - responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel unavailable");
432   - return;
433   - }
434   - // 发送redis消息以使设备上线
435   - logger.info("[ app={}, stream={} ]通道离线,发送redis信息控制设备开始推流",gbStream.getApp(), gbStream.getStream());
436   - MessageForPushChannel messageForPushChannel = new MessageForPushChannel();
437   - messageForPushChannel.setType(1);
438   - messageForPushChannel.setGbId(gbStream.getGbId());
439   - messageForPushChannel.setApp(gbStream.getApp());
440   - messageForPushChannel.setStream(gbStream.getStream());
441   - // TODO 获取低负载的节点
442   - messageForPushChannel.setMediaServerId(gbStream.getMediaServerId());
443   - messageForPushChannel.setPlatFormId(platform.getServerGBId());
444   - messageForPushChannel.setPlatFormName(platform.getName());
445   - redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
446   - // 设置超时
447   - dynamicTask.startDelay(callIdHeader.getCallId(), ()->{
448   - logger.info("[ app={}, stream={} ] 等待设备开始推流超时", gbStream.getApp(), gbStream.getStream());
449   - try {
450   - mediaListManager.removedChannelOnlineEventLister(gbStream.getGbId());
451   - responseAck(evt, Response.REQUEST_TIMEOUT); // 超时
452   - } catch (SipException e) {
453   - e.printStackTrace();
454   - } catch (InvalidArgumentException e) {
455   - e.printStackTrace();
456   - } catch (ParseException e) {
457   - e.printStackTrace();
458   - }
459   - }, userSetting.getPlatformPlayTimeout());
460   - // 添加监听
461   - MediaServerItem finalMediaServerItem = mediaServerItem;
462   - int finalPort = port;
463   - boolean finalMediaTransmissionTCP = mediaTransmissionTCP;
464   - Boolean finalTcpActive = tcpActive;
465   - mediaListManager.addChannelOnlineEventLister(gbStream.getGbId(), (app, stream)->{
466   - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(finalMediaServerItem, addressStr, finalPort, ssrc, requesterId,
467   - app, stream, channelId, finalMediaTransmissionTCP);
468   -
469   - if (sendRtpItem == null) {
470   - logger.warn("服务器端口资源不足");
471   - try {
472   - responseAck(evt, Response.BUSY_HERE);
473   - } catch (SipException e) {
474   - e.printStackTrace();
475   - } catch (InvalidArgumentException e) {
476   - e.printStackTrace();
477   - } catch (ParseException e) {
478   - e.printStackTrace();
479   - }
480   - return;
481   - }
482   - if (finalTcpActive != null) {
483   - sendRtpItem.setTcpActive(finalTcpActive);
484   - }
485   - sendRtpItem.setPlayType(InviteStreamType.PUSH);
486   - // 写入redis, 超时时回复
487   - sendRtpItem.setStatus(1);
488   - sendRtpItem.setCallId(callIdHeader.getCallId());
489   - byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
490   - sendRtpItem.setDialog(dialogByteArray);
491   - byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
492   - sendRtpItem.setTransaction(transactionByteArray);
493   - redisCatchStorage.updateSendRTPSever(sendRtpItem);
494   - sendStreamAck(finalMediaServerItem, sendRtpItem, platform, evt);
495   -
496   - });
497   - }
498   - }else {
499   - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
500   - gbStream.getApp(), gbStream.getStream(), channelId,
501   - mediaTransmissionTCP);
502   -
503   - if (sendRtpItem == null) {
504   - logger.warn("服务器端口资源不足");
505   - responseAck(evt, Response.BUSY_HERE);
506   - return;
507   - }
508   - if (tcpActive != null) {
509   - sendRtpItem.setTcpActive(tcpActive);
510   - }
511   - sendRtpItem.setPlayType(InviteStreamType.PUSH);
512   - // 写入redis, 超时时回复
513   - sendRtpItem.setStatus(1);
514   - sendRtpItem.setCallId(callIdHeader.getCallId());
515   - byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
516   - sendRtpItem.setDialog(dialogByteArray);
517   - byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
518   - sendRtpItem.setTransaction(transactionByteArray);
519   - redisCatchStorage.updateSendRTPSever(sendRtpItem);
520   - sendStreamAck(mediaServerItem, sendRtpItem, platform, evt);
521   - }
522   -
523   -
524   - }
525   -
526   - }
527   -
528   - } catch (SipException | InvalidArgumentException | ParseException e) {
529   - e.printStackTrace();
530   - logger.warn("sdp解析错误");
531   - e.printStackTrace();
532   - } catch (SdpParseException e) {
533   - e.printStackTrace();
534   - } catch (SdpException e) {
535   - e.printStackTrace();
536   - }
537   - }
538   -
539   - public void sendStreamAck(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt){
540   -
541   - StringBuffer content = new StringBuffer(200);
542   - content.append("v=0\r\n");
543   - content.append("o="+ sendRtpItem.getChannelId() +" 0 0 IN IP4 "+ mediaServerItem.getSdpIp()+"\r\n");
544   - content.append("s=Play\r\n");
545   - content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
546   - content.append("t=0 0\r\n");
547   - content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n");
548   - content.append("a=sendonly\r\n");
549   - content.append("a=rtpmap:96 PS/90000\r\n");
550   - if (sendRtpItem.isTcp()) {
551   - content.append("a=connection:new\r\n");
552   - if (!sendRtpItem.isTcpActive()) {
553   - content.append("a=setup:active\r\n");
554   - }else {
555   - content.append("a=setup:passive\r\n");
556   - }
557   - }
558   - content.append("y="+ sendRtpItem.getSsrc() + "\r\n");
559   - content.append("f=\r\n");
560   -
561   - try {
562   - responseSdpAck(evt, content.toString(), platform);
563   - } catch (SipException e) {
564   - e.printStackTrace();
565   - } catch (InvalidArgumentException e) {
566   - e.printStackTrace();
567   - } catch (ParseException e) {
568   - e.printStackTrace();
569   - }
570   - }
571   -
572   - public void inviteFromDeviceHandle(RequestEvent evt, String requesterId, String channelId1) throws InvalidArgumentException, ParseException, SipException, SdpException {
573   -
574   - // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备)
575   - Device device = redisCatchStorage.getDevice(requesterId);
576   - AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(requesterId, channelId1);
577   - if (audioBroadcastCatch == null) {
578   - logger.warn("来自设备的Invite请求非语音广播,已忽略");
579   - responseAck(evt, Response.FORBIDDEN);
580   - return;
581   - }
582   - Request request = evt.getRequest();
583   - if (device != null) {
584   - logger.info("收到设备" + requesterId + "的语音广播Invite请求");
585   - responseAck(evt, Response.TRYING);
586   -
587   - String contentString = new String(request.getRawContent());
588   - // jainSip不支持y=字段, 移除移除以解析。
589   - String substring = contentString;
590   - String ssrc = "0000000404";
591   - int ssrcIndex = contentString.indexOf("y=");
592   - if (ssrcIndex > 0) {
593   - substring = contentString.substring(0, ssrcIndex);
594   - ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12).trim();
595   - }
596   - ssrcIndex = substring.indexOf("f=");
597   - if (ssrcIndex > 0) {
598   - substring = contentString.substring(0, ssrcIndex);
599   - }
600   - SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
601   -
602   - // 获取支持的格式
603   - Vector mediaDescriptions = sdp.getMediaDescriptions(true);
604   -
605   - // 查看是否支持PS 负载96
606   - int port = -1;
607   - boolean mediaTransmissionTCP = false;
608   - Boolean tcpActive = null;
609   - for (int i = 0; i < mediaDescriptions.size(); i++) {
610   - MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i);
611   - Media media = mediaDescription.getMedia();
612   -
613   - Vector mediaFormats = media.getMediaFormats(false);
614   - if (mediaFormats.contains("8")) {
615   - port = media.getMediaPort();
616   - String protocol = media.getProtocol();
617   - // 区分TCP发流还是udp, 当前默认udp
618   - if ("TCP/RTP/AVP".equals(protocol)) {
619   - String setup = mediaDescription.getAttribute("setup");
620   - if (setup != null) {
621   - mediaTransmissionTCP = true;
622   - if ("active".equals(setup)) {
623   - tcpActive = true;
624   - } else if ("passive".equals(setup)) {
625   - tcpActive = false;
626   - }
627   - }
628   - }
629   - break;
630   - }
631   - }
632   - if (port == -1) {
633   - logger.info("不支持的媒体格式,返回415");
634   - // 回复不支持的格式
635   - responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
636   - return;
637   - }
638   - String addressStr = sdp.getOrigin().getAddress();
639   - logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", requesterId, addressStr, port, ssrc);
640   -
641   - MediaServerItem mediaServerItem = playService.getNewMediaServerItem(device);
642   - if (mediaServerItem == null) {
643   - logger.warn("未找到可用的zlm");
644   - responseAck(evt, Response.BUSY_HERE);
645   - return;
646   - }
647   - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
648   - device.getDeviceId(), audioBroadcastCatch.getChannelId(),
649   - mediaTransmissionTCP);
650   - if (sendRtpItem == null) {
651   - logger.warn("服务器端口资源不足");
652   - responseAck(evt, Response.BUSY_HERE);
653   - return;
654   - }
655   - sendRtpItem.setTcp(mediaTransmissionTCP);
656   - if (tcpActive != null) {
657   - sendRtpItem.setTcpActive(tcpActive);
658   - }
659   - String app = "broadcast";
660   - String stream = device.getDeviceId() + "_" + audioBroadcastCatch.getChannelId();
661   -
  138 + @Autowired
  139 + private RedisGbPlayMsgListener redisGbPlayMsgListener;
  140 +
  141 +
  142 + @Override
  143 + public void afterPropertiesSet() throws Exception {
  144 + // 添加消息处理的订阅
  145 + sipProcessorObserver.addRequestProcessor(method, this);
  146 + }
  147 +
  148 + /**
  149 + * 处理invite请求
  150 + *
  151 + * @param evt 请求消息
  152 + */
  153 + @Override
  154 + public void process(RequestEvent evt) {
  155 + // Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令
  156 + try {
  157 + Request request = evt.getRequest();
  158 + SipURI sipUri = (SipURI) request.getRequestURI();
  159 + //从subject读取channelId,不再从request-line读取。 有些平台request-line是平台国标编码,不是设备国标编码。
  160 + //String channelId = sipURI.getUser();
  161 + String channelId = SipUtils.getChannelIdFromHeader(request);
  162 + String requesterId = SipUtils.getUserIdFromFromHeader(request);
  163 + CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
  164 + if (requesterId == null || channelId == null) {
  165 + logger.info("无法从FromHeader的Address中获取到平台id,返回400");
  166 + // 参数不全, 发400,请求错误
  167 + responseAck(evt, Response.BAD_REQUEST);
  168 + return;
  169 + }
  170 +
  171 + // 查询请求是否来自上级平台\设备
  172 + ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
  173 + if (platform == null) {
  174 + inviteFromDeviceHandle(evt, requesterId);
  175 + } else {
  176 + // 查询平台下是否有该通道
  177 + DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId);
  178 + GbStream gbStream = storager.queryStreamInParentPlatform(requesterId, channelId);
  179 + PlatformCatalog catalog = storager.getCatalog(channelId);
  180 +
  181 + MediaServerItem mediaServerItem = null;
  182 + StreamPushItem streamPushItem = null;
  183 + // 不是通道可能是直播流
  184 + if (channel != null && gbStream == null) {
  185 + if (channel.getStatus() == 0) {
  186 + logger.info("通道离线,返回400");
  187 + responseAck(evt, Response.BAD_REQUEST, "channel [" + channel.getChannelId() + "] offline");
  188 + return;
  189 + }
  190 + responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中
  191 + } else if (channel == null && gbStream != null) {
  192 +
  193 + String mediaServerId = gbStream.getMediaServerId();
  194 + mediaServerItem = mediaServerService.getOne(mediaServerId);
  195 + if (mediaServerItem == null) {
  196 + if ("proxy".equals(gbStream.getStreamType())) {
  197 + logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId);
  198 + responseAck(evt, Response.GONE);
  199 + return;
  200 + } else {
  201 + streamPushItem = streamPushService.getPush(gbStream.getApp(), gbStream.getStream());
  202 + if (streamPushItem == null || streamPushItem.getServerId().equals(userSetting.getServerId())) {
  203 + logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId);
  204 + responseAck(evt, Response.GONE);
  205 + return;
  206 + }
  207 + }
  208 + } else {
  209 + if ("push".equals(gbStream.getStreamType())) {
  210 + streamPushItem = streamPushService.getPush(gbStream.getApp(), gbStream.getStream());
  211 + if (streamPushItem == null) {
  212 + logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId);
  213 + responseAck(evt, Response.GONE);
  214 + return;
  215 + }
  216 + }
  217 + }
  218 + responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中
  219 + } else if (catalog != null) {
  220 + responseAck(evt, Response.BAD_REQUEST, "catalog channel can not play"); // 目录不支持点播
  221 + return;
  222 + } else {
  223 + logger.info("通道不存在,返回404");
  224 + responseAck(evt, Response.NOT_FOUND); // 通道不存在,发404,资源不存在
  225 + return;
  226 + }
  227 + // 解析sdp消息, 使用jainsip 自带的sdp解析方式
  228 + String contentString = new String(request.getRawContent());
  229 +
  230 + // jainSip不支持y=字段, 移除以解析。
  231 + int ssrcIndex = contentString.indexOf("y=");
  232 + // 检查是否有y字段
  233 + String ssrcDefault = "0000000000";
  234 + String ssrc;
  235 + SessionDescription sdp;
  236 + if (ssrcIndex >= 0) {
  237 + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段
  238 + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  239 + String substring = contentString.substring(0, contentString.indexOf("y="));
  240 + sdp = SdpFactory.getInstance().createSessionDescription(substring);
  241 + } else {
  242 + ssrc = ssrcDefault;
  243 + sdp = SdpFactory.getInstance().createSessionDescription(contentString);
  244 + }
  245 + String sessionName = sdp.getSessionName().getValue();
  246 +
  247 + Long startTime = null;
  248 + Long stopTime = null;
  249 + Instant start = null;
  250 + Instant end = null;
  251 + if (sdp.getTimeDescriptions(false) != null && sdp.getTimeDescriptions(false).size() > 0) {
  252 + TimeDescriptionImpl timeDescription = (TimeDescriptionImpl) (sdp.getTimeDescriptions(false).get(0));
  253 + TimeField startTimeFiled = (TimeField) timeDescription.getTime();
  254 + startTime = startTimeFiled.getStartTime();
  255 + stopTime = startTimeFiled.getStopTime();
  256 +
  257 + start = Instant.ofEpochSecond(startTime);
  258 + end = Instant.ofEpochSecond(stopTime);
  259 + }
  260 + // 获取支持的格式
  261 + Vector mediaDescriptions = sdp.getMediaDescriptions(true);
  262 + // 查看是否支持PS 负载96
  263 + //String ip = null;
  264 + int port = -1;
  265 + boolean mediaTransmissionTCP = false;
  266 + Boolean tcpActive = null;
  267 + for (Object description : mediaDescriptions) {
  268 + MediaDescription mediaDescription = (MediaDescription) description;
  269 + Media media = mediaDescription.getMedia();
  270 +
  271 + Vector mediaFormats = media.getMediaFormats(false);
  272 + if (mediaFormats.contains("96")) {
  273 + port = media.getMediaPort();
  274 + //String mediaType = media.getMediaType();
  275 + String protocol = media.getProtocol();
  276 +
  277 + // 区分TCP发流还是udp, 当前默认udp
  278 + if ("TCP/RTP/AVP".equals(protocol)) {
  279 + String setup = mediaDescription.getAttribute("setup");
  280 + if (setup != null) {
  281 + mediaTransmissionTCP = true;
  282 + if ("active".equals(setup)) {
  283 + tcpActive = true;
  284 + // 不支持tcp主动
  285 + responseAck(evt, Response.NOT_IMPLEMENTED, "tcp active not support"); // 目录不支持点播
  286 + return;
  287 + } else if ("passive".equals(setup)) {
  288 + tcpActive = false;
  289 + }
  290 + }
  291 + }
  292 + break;
  293 + }
  294 + }
  295 + if (port == -1) {
  296 + logger.info("不支持的媒体格式,返回415");
  297 + // 回复不支持的格式
  298 + responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
  299 + return;
  300 + }
  301 + String username = sdp.getOrigin().getUsername();
  302 + String addressStr = sdp.getOrigin().getAddress();
  303 +
  304 + logger.info("[上级点播]用户:{}, 通道:{}, 地址:{}:{}, ssrc:{}", username, channelId, addressStr, port, ssrc);
  305 + Device device = null;
  306 + // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标
  307 + if (channel != null) {
  308 + device = storager.queryVideoDeviceByPlatformIdAndChannelId(requesterId, channelId);
  309 + if (device == null) {
  310 + logger.warn("点播平台{}的通道{}时未找到设备信息", requesterId, channel);
  311 + responseAck(evt, Response.SERVER_INTERNAL_ERROR);
  312 + return;
  313 + }
  314 + mediaServerItem = playService.getNewMediaServerItem(device);
  315 + if (mediaServerItem == null) {
  316 + logger.warn("未找到可用的zlm");
  317 + responseAck(evt, Response.BUSY_HERE);
  318 + return;
  319 + }
  320 + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
  321 + device.getDeviceId(), channelId,
  322 + mediaTransmissionTCP);
  323 + if (tcpActive != null) {
  324 + sendRtpItem.setTcpActive(tcpActive);
  325 + }
  326 + if (sendRtpItem == null) {
  327 + logger.warn("服务器端口资源不足");
  328 + responseAck(evt, Response.BUSY_HERE);
  329 + return;
  330 + }
  331 + sendRtpItem.setCallId(callIdHeader.getCallId());
  332 + sendRtpItem.setPlayType("Play".equals(sessionName) ? InviteStreamType.PLAY : InviteStreamType.PLAYBACK);
  333 +
  334 + Long finalStartTime = startTime;
  335 + Long finalStopTime = stopTime;
  336 + ZLMHttpHookSubscribe.Event hookEvent = (mediaServerItemInUSe, responseJSON) -> {
  337 + String app = responseJSON.getString("app");
  338 + String stream = responseJSON.getString("stream");
  339 + logger.info("[上级点播]下级已经开始推流。 回复200OK(SDP), {}/{}", app, stream);
  340 + // * 0 等待设备推流上来
  341 + // * 1 下级已经推流,等待上级平台回复ack
  342 + // * 2 推流中
  343 + sendRtpItem.setStatus(1);
  344 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  345 +
  346 + StringBuffer content = new StringBuffer(200);
  347 + content.append("v=0\r\n");
  348 + content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n");
  349 + content.append("s=" + sessionName + "\r\n");
  350 + content.append("c=IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n");
  351 + if ("Playback".equals(sessionName)) {
  352 + content.append("t=" + finalStartTime + " " + finalStopTime + "\r\n");
  353 + } else {
  354 + content.append("t=0 0\r\n");
  355 + }
  356 + content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n");
  357 + content.append("a=sendonly\r\n");
  358 + content.append("a=rtpmap:96 PS/90000\r\n");
  359 + content.append("y=" + ssrc + "\r\n");
  360 + content.append("f=\r\n");
  361 +
  362 + try {
  363 + // 超时未收到Ack应该回复bye,当前等待时间为10秒
  364 + dynamicTask.startDelay(callIdHeader.getCallId(), () -> {
  365 + logger.info("Ack 等待超时");
  366 + mediaServerService.releaseSsrc(mediaServerItemInUSe.getId(), ssrc);
  367 + // 回复bye
  368 + cmderFroPlatform.streamByeCmd(platform, callIdHeader.getCallId());
  369 + }, 60 * 1000);
  370 + responseSdpAck(evt, content.toString(), platform);
  371 +
  372 + } catch (SipException e) {
  373 + e.printStackTrace();
  374 + } catch (InvalidArgumentException e) {
  375 + e.printStackTrace();
  376 + } catch (ParseException e) {
  377 + e.printStackTrace();
  378 + }
  379 + };
  380 + SipSubscribe.Event errorEvent = ((event) -> {
  381 + // 未知错误。直接转发设备点播的错误
  382 + Response response = null;
  383 + try {
  384 + response = getMessageFactory().createResponse(event.statusCode, evt.getRequest());
  385 + ServerTransaction serverTransaction = getServerTransaction(evt);
  386 + serverTransaction.sendResponse(response);
  387 + if (serverTransaction.getDialog() != null) {
  388 + serverTransaction.getDialog().delete();
  389 + }
  390 + } catch (ParseException | SipException | InvalidArgumentException e) {
  391 + e.printStackTrace();
  392 + }
  393 + });
  394 + sendRtpItem.setApp("rtp");
  395 + if ("Playback".equals(sessionName)) {
  396 + sendRtpItem.setPlayType(InviteStreamType.PLAYBACK);
  397 + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, true, true);
  398 + sendRtpItem.setStreamId(ssrcInfo.getStream());
  399 + // 写入redis, 超时时回复
  400 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  401 + playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start),
  402 + DateUtil.formatter.format(end), null, result -> {
  403 + if (result.getCode() != 0) {
  404 + logger.warn("录像回放失败");
  405 + if (result.getEvent() != null) {
  406 + errorEvent.response(result.getEvent());
  407 + }
  408 + redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
  409 + try {
  410 + responseAck(evt, Response.REQUEST_TIMEOUT);
  411 + } catch (SipException e) {
  412 + e.printStackTrace();
  413 + } catch (InvalidArgumentException e) {
  414 + e.printStackTrace();
  415 + } catch (ParseException e) {
  416 + e.printStackTrace();
  417 + }
  418 + } else {
  419 + if (result.getMediaServerItem() != null) {
  420 + hookEvent.response(result.getMediaServerItem(), result.getResponse());
  421 + }
  422 + }
  423 + });
  424 + } else {
  425 + sendRtpItem.setPlayType(InviteStreamType.PLAY);
  426 + SsrcTransaction playTransaction = sessionManager.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
  427 + if (playTransaction != null) {
  428 + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream());
  429 + if (!streamReady) {
  430 + playTransaction = null;
  431 + }
  432 + }
  433 + if (playTransaction == null) {
  434 + String streamId = null;
  435 + if (mediaServerItem.isRtpEnable()) {
  436 + streamId = String.format("%s_%s", device.getDeviceId(), channelId);
  437 + }
  438 + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false);
  439 + sendRtpItem.setStreamId(ssrcInfo.getStream());
  440 + // 写入redis, 超时时回复
  441 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  442 + playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg) -> {
  443 + logger.info("[上级点播]超时, 用户:{}, 通道:{}", username, channelId);
  444 + redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null);
  445 + }, null);
  446 + } else {
  447 + sendRtpItem.setStreamId(playTransaction.getStream());
  448 + // 写入redis, 超时时回复
  449 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  450 + JSONObject jsonObject = new JSONObject();
  451 + jsonObject.put("app", sendRtpItem.getApp());
  452 + jsonObject.put("stream", sendRtpItem.getStreamId());
  453 + hookEvent.response(mediaServerItem, jsonObject);
  454 + }
  455 + }
  456 + } else if (gbStream != null) {
  457 + if (streamPushItem.isStatus()) {
  458 + // 在线状态
  459 + pushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
  460 + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
  461 + } else {
  462 + // 不在线 拉起
  463 + notifyStreamOnline(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
  464 + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
  465 + }
  466 +
  467 + }
  468 +
  469 + }
  470 +
  471 + } catch (SipException | InvalidArgumentException | ParseException e) {
  472 + e.printStackTrace();
  473 + logger.warn("sdp解析错误");
  474 + e.printStackTrace();
  475 + } catch (SdpParseException e) {
  476 + e.printStackTrace();
  477 + } catch (SdpException e) {
  478 + e.printStackTrace();
  479 + }
  480 + }
  481 +
  482 + /**
  483 + * 安排推流
  484 + */
  485 +
  486 + private void pushStream(RequestEvent evt, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
  487 + CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
  488 + int port, Boolean tcpActive, boolean mediaTransmissionTCP,
  489 + String channelId, String addressStr, String ssrc, String requesterId) throws InvalidArgumentException, ParseException, SipException {
  490 + // 推流
  491 + if (streamPushItem.getServerId().equals(userSetting.getServerId())) {
  492 + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
  493 + if (streamReady) {
  494 + // 自平台内容
  495 + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
  496 + gbStream.getApp(), gbStream.getStream(), channelId,
  497 + mediaTransmissionTCP);
  498 +
  499 + if (sendRtpItem == null) {
  500 + logger.warn("服务器端口资源不足");
  501 + responseAck(evt, Response.BUSY_HERE);
  502 + return;
  503 + }
  504 + if (tcpActive != null) {
  505 + sendRtpItem.setTcpActive(tcpActive);
  506 + }
  507 + sendRtpItem.setPlayType(InviteStreamType.PUSH);
  508 + // 写入redis, 超时时回复
  509 + sendRtpItem.setStatus(1);
  510 + sendRtpItem.setCallId(callIdHeader.getCallId());
  511 + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
  512 + sendRtpItem.setDialog(dialogByteArray);
  513 + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
  514 + sendRtpItem.setTransaction(transactionByteArray);
  515 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  516 + sendStreamAck(mediaServerItem, sendRtpItem, platform, evt);
  517 + } else {
  518 + // 不在线 拉起
  519 + notifyStreamOnline(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
  520 + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
  521 + }
  522 +
  523 + } else {
  524 + // 其他平台内容
  525 + otherWvpPushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
  526 + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
  527 + }
  528 +
  529 + }
  530 +
  531 + /**
  532 + * 通知流上线
  533 + */
  534 + private void notifyStreamOnline(RequestEvent evt, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
  535 + CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
  536 + int port, Boolean tcpActive, boolean mediaTransmissionTCP,
  537 + String channelId, String addressStr, String ssrc, String requesterId) throws InvalidArgumentException, ParseException, SipException {
  538 + if ("proxy".equals(gbStream.getStreamType())) {
  539 + // TODO 控制启用以使设备上线
  540 + logger.info("[ app={}, stream={} ]通道离线,启用流后开始推流", gbStream.getApp(), gbStream.getStream());
  541 + responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline");
  542 + } else if ("push".equals(gbStream.getStreamType())) {
  543 + if (!platform.isStartOfflinePush()) {
  544 + responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel unavailable");
  545 + return;
  546 + }
  547 + // 发送redis消息以使设备上线
  548 + logger.info("[ app={}, stream={} ]通道离线,发送redis信息控制设备开始推流", gbStream.getApp(), gbStream.getStream());
  549 +
  550 + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(1,
  551 + gbStream.getApp(), gbStream.getStream(), gbStream.getGbId(), gbStream.getPlatformId(),
  552 + platform.getName(), null, gbStream.getMediaServerId());
  553 + redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
  554 + // 设置超时
  555 + dynamicTask.startDelay(callIdHeader.getCallId(), () -> {
  556 + logger.info("[ app={}, stream={} ] 等待设备开始推流超时", gbStream.getApp(), gbStream.getStream());
  557 + try {
  558 + mediaListManager.removedChannelOnlineEventLister(gbStream.getGbId());
  559 + responseAck(evt, Response.REQUEST_TIMEOUT); // 超时
  560 + } catch (SipException e) {
  561 + e.printStackTrace();
  562 + } catch (InvalidArgumentException e) {
  563 + e.printStackTrace();
  564 + } catch (ParseException e) {
  565 + e.printStackTrace();
  566 + }
  567 + }, userSetting.getPlatformPlayTimeout());
  568 + // 添加监听
  569 + int finalPort = port;
  570 + Boolean finalTcpActive = tcpActive;
  571 +
  572 + // 添加在本机上线的通知
  573 + mediaListManager.addChannelOnlineEventLister(gbStream.getGbId(), (app, stream, serverId) -> {
  574 + dynamicTask.stop(callIdHeader.getCallId());
  575 + if (serverId.equals(userSetting.getServerId())) {
  576 + SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, finalPort, ssrc, requesterId,
  577 + app, stream, channelId, mediaTransmissionTCP);
  578 +
  579 + if (sendRtpItem == null) {
  580 + logger.warn("服务器端口资源不足");
  581 + try {
  582 + responseAck(evt, Response.BUSY_HERE);
  583 + } catch (SipException e) {
  584 + e.printStackTrace();
  585 + } catch (InvalidArgumentException e) {
  586 + e.printStackTrace();
  587 + } catch (ParseException e) {
  588 + e.printStackTrace();
  589 + }
  590 + return;
  591 + }
  592 + if (finalTcpActive != null) {
  593 + sendRtpItem.setTcpActive(finalTcpActive);
  594 + }
  595 + sendRtpItem.setPlayType(InviteStreamType.PUSH);
  596 + // 写入redis, 超时时回复
  597 + sendRtpItem.setStatus(1);
  598 + sendRtpItem.setCallId(callIdHeader.getCallId());
  599 + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
  600 + sendRtpItem.setDialog(dialogByteArray);
  601 + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
  602 + sendRtpItem.setTransaction(transactionByteArray);
  603 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  604 + sendStreamAck(mediaServerItem, sendRtpItem, platform, evt);
  605 + } else {
  606 + // 其他平台内容
  607 + otherWvpPushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
  608 + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
  609 + }
  610 + });
  611 + }
  612 + }
  613 +
  614 + /**
  615 + * 来自其他wvp的推流
  616 + */
  617 + private void otherWvpPushStream(RequestEvent evt, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
  618 + CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
  619 + int port, Boolean tcpActive, boolean mediaTransmissionTCP,
  620 + String channelId, String addressStr, String ssrc, String requesterId) {
  621 + logger.info("[级联点播]直播流来自其他平台,发送redis消息");
  622 + // 发送redis消息
  623 + redisGbPlayMsgListener.sendMsg(streamPushItem.getServerId(), streamPushItem.getMediaServerId(),
  624 + streamPushItem.getApp(), streamPushItem.getStream(), addressStr, port, ssrc, requesterId,
  625 + channelId, mediaTransmissionTCP, null, responseSendItemMsg -> {
  626 + SendRtpItem sendRtpItem = responseSendItemMsg.getSendRtpItem();
  627 + if (sendRtpItem == null || responseSendItemMsg.getMediaServerItem() == null) {
  628 + logger.warn("服务器端口资源不足");
  629 + try {
  630 + responseAck(evt, Response.BUSY_HERE);
  631 + } catch (SipException e) {
  632 + e.printStackTrace();
  633 + } catch (InvalidArgumentException e) {
  634 + e.printStackTrace();
  635 + } catch (ParseException e) {
  636 + e.printStackTrace();
  637 + }
  638 + return;
  639 + }
  640 + // 收到sendItem
  641 + if (tcpActive != null) {
  642 + sendRtpItem.setTcpActive(tcpActive);
  643 + }
  644 + sendRtpItem.setPlayType(InviteStreamType.PUSH);
  645 + // 写入redis, 超时时回复
  646 + sendRtpItem.setStatus(1);
  647 + sendRtpItem.setCallId(callIdHeader.getCallId());
  648 + byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
  649 + sendRtpItem.setDialog(dialogByteArray);
  650 + byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
  651 + sendRtpItem.setTransaction(transactionByteArray);
  652 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
  653 + sendStreamAck(responseSendItemMsg.getMediaServerItem(), sendRtpItem, platform, evt);
  654 + }, (wvpResult) -> {
  655 + try {
  656 + // 错误
  657 + if (wvpResult.getCode() == RedisGbPlayMsgListener.ERROR_CODE_OFFLINE) {
  658 + // 离线
  659 + // 查询是否在本机上线了
  660 + StreamPushItem currentStreamPushItem = streamPushService.getPush(streamPushItem.getApp(), streamPushItem.getStream());
  661 + if (currentStreamPushItem.isStatus()) {
  662 + // 在线状态
  663 + pushStream(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
  664 + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
  665 +
  666 + } else {
  667 + // 不在线 拉起
  668 + notifyStreamOnline(evt, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
  669 + mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
  670 + }
  671 + }
  672 + } catch (InvalidArgumentException e) {
  673 + throw new RuntimeException(e);
  674 + } catch (ParseException e) {
  675 + throw new RuntimeException(e);
  676 + } catch (SipException e) {
  677 + throw new RuntimeException(e);
  678 + }
  679 +
  680 +
  681 + try {
  682 + responseAck(evt, Response.BUSY_HERE);
  683 + } catch (SipException e) {
  684 + e.printStackTrace();
  685 + } catch (InvalidArgumentException e) {
  686 + e.printStackTrace();
  687 + } catch (ParseException e) {
  688 + e.printStackTrace();
  689 + }
  690 + return;
  691 + });
  692 + }
  693 +
  694 + public void sendStreamAck(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt) {
  695 +
  696 + StringBuffer content = new StringBuffer(200);
  697 + content.append("v=0\r\n");
  698 + content.append("o=" + sendRtpItem.getChannelId() + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
  699 + content.append("s=Play\r\n");
  700 + content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
  701 + content.append("t=0 0\r\n");
  702 + content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n");
  703 + content.append("a=sendonly\r\n");
  704 + content.append("a=rtpmap:96 PS/90000\r\n");
  705 + if (sendRtpItem.isTcp()) {
  706 + content.append("a=connection:new\r\n");
  707 + if (!sendRtpItem.isTcpActive()) {
  708 + content.append("a=setup:active\r\n");
  709 + } else {
  710 + content.append("a=setup:passive\r\n");
  711 + }
  712 + }
  713 + content.append("y=" + sendRtpItem.getSsrc() + "\r\n");
  714 + content.append("f=\r\n");
  715 +
  716 + try {
  717 + responseSdpAck(evt, content.toString(), platform);
  718 + } catch (SipException e) {
  719 + e.printStackTrace();
  720 + } catch (InvalidArgumentException e) {
  721 + e.printStackTrace();
  722 + } catch (ParseException e) {
  723 + e.printStackTrace();
  724 + }
  725 + }
  726 +
  727 + public void inviteFromDeviceHandle(RequestEvent evt, String requesterId) throws InvalidArgumentException, ParseException, SipException, SdpException {
  728 +
  729 + // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备)
  730 + Device device = redisCatchStorage.getDevice(requesterId);
  731 + Request request = evt.getRequest();
  732 + if (device != null) {
  733 + logger.info("收到设备" + requesterId + "的语音广播Invite请求");
  734 + responseAck(evt, Response.TRYING);
  735 +
  736 + String contentString = new String(request.getRawContent());
  737 + // jainSip不支持y=字段, 移除移除以解析。
  738 + String substring = contentString;
  739 + String ssrc = "0000000404";
  740 + int ssrcIndex = contentString.indexOf("y=");
  741 + if (ssrcIndex > 0) {
  742 + substring = contentString.substring(0, ssrcIndex);
  743 + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  744 + }
  745 + ssrcIndex = substring.indexOf("f=");
  746 + if (ssrcIndex > 0) {
  747 + substring = contentString.substring(0, ssrcIndex);
  748 + }
  749 + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
  750 +
  751 + // 获取支持的格式
  752 + Vector mediaDescriptions = sdp.getMediaDescriptions(true);
  753 + // 查看是否支持PS 负载96
  754 + int port = -1;
  755 + //boolean recvonly = false;
  756 + boolean mediaTransmissionTCP = false;
  757 + Boolean tcpActive = null;
  758 + for (int i = 0; i < mediaDescriptions.size(); i++) {
  759 + MediaDescription mediaDescription = (MediaDescription) mediaDescriptions.get(i);
  760 + Media media = mediaDescription.getMedia();
  761 +
  762 + Vector mediaFormats = media.getMediaFormats(false);
  763 + if (mediaFormats.contains("8")) {
  764 + port = media.getMediaPort();
  765 + String protocol = media.getProtocol();
  766 + // 区分TCP发流还是udp, 当前默认udp
  767 + if ("TCP/RTP/AVP".equals(protocol)) {
  768 + String setup = mediaDescription.getAttribute("setup");
  769 + if (setup != null) {
  770 + mediaTransmissionTCP = true;
  771 + if ("active".equals(setup)) {
  772 + tcpActive = true;
  773 + } else if ("passive".equals(setup)) {
  774 + tcpActive = false;
  775 + }
  776 + }
  777 + }
  778 + break;
  779 + }
  780 + }
  781 + if (port == -1) {
  782 + logger.info("不支持的媒体格式,返回415");
  783 + // 回复不支持的格式
  784 + responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
  785 + return;
  786 + }
  787 + String username = sdp.getOrigin().getUsername();
  788 + String addressStr = sdp.getOrigin().getAddress();
  789 + logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc);
  790 +
  791 + } else {
  792 + logger.warn("来自无效设备/平台的请求");
  793 + responseAck(evt, Response.BAD_REQUEST);
  794 + }
  795 + }
662 796 CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
663 797 sendRtpItem.setPlayType(InviteStreamType.PLAY);
664 798 sendRtpItem.setCallId(callIdHeader.getCallId());
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java
1 1 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
2 2  
3 3 import com.alibaba.fastjson.JSONObject;
4   -import com.genersoft.iot.vmp.common.VideoManagerConstants;
5 4 import com.genersoft.iot.vmp.conf.SipConfig;
6 5 import com.genersoft.iot.vmp.conf.UserSetting;
7 6 import com.genersoft.iot.vmp.gb28181.bean.*;
... ... @@ -18,6 +17,7 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
18 17 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
19 18 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
20 19 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
  20 +import com.genersoft.iot.vmp.utils.DateUtil;
21 21 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
22 22 import org.dom4j.DocumentException;
23 23 import org.dom4j.Element;
... ... @@ -25,6 +25,8 @@ import org.slf4j.Logger;
25 25 import org.slf4j.LoggerFactory;
26 26 import org.springframework.beans.factory.InitializingBean;
27 27 import org.springframework.beans.factory.annotation.Autowired;
  28 +import org.springframework.beans.factory.annotation.Qualifier;
  29 +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
28 30 import org.springframework.stereotype.Component;
29 31 import org.springframework.util.StringUtils;
30 32  
... ... @@ -35,6 +37,7 @@ import javax.sip.header.FromHeader;
35 37 import javax.sip.message.Response;
36 38 import java.text.ParseException;
37 39 import java.util.Iterator;
  40 +import java.util.concurrent.ConcurrentLinkedQueue;
38 41  
39 42 /**
40 43 * SIP命令类型: NOTIFY请求
... ... @@ -63,11 +66,19 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
63 66 @Autowired
64 67 private EventPublisher publisher;
65 68  
66   - private String method = "NOTIFY";
  69 + private final String method = "NOTIFY";
67 70  
68 71 @Autowired
69 72 private SIPProcessorObserver sipProcessorObserver;
70 73  
  74 + private boolean taskQueueHandlerRun = false;
  75 +
  76 + private final ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
  77 +
  78 + @Qualifier("taskExecutor")
  79 + @Autowired
  80 + private ThreadPoolTaskExecutor taskExecutor;
  81 +
71 82 @Override
72 83 public void afterPropertiesSet() throws Exception {
73 84 // 添加消息处理的订阅
... ... @@ -77,23 +88,40 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
77 88 @Override
78 89 public void process(RequestEvent evt) {
79 90 try {
80   - Element rootElement = getRootElement(evt);
81   - String cmd = XmlUtil.getText(rootElement, "CmdType");
82   -
83   - if (CmdType.CATALOG.equals(cmd)) {
84   - logger.info("接收到Catalog通知");
85   - processNotifyCatalogList(evt);
86   - } else if (CmdType.ALARM.equals(cmd)) {
87   - logger.info("接收到Alarm通知");
88   - processNotifyAlarm(evt);
89   - } else if (CmdType.MOBILE_POSITION.equals(cmd)) {
90   - logger.info("接收到MobilePosition通知");
91   - processNotifyMobilePosition(evt);
92   - } else {
93   - logger.info("接收到消息:" + cmd);
94   - responseAck(evt, Response.OK);
  91 +
  92 + taskQueue.offer(new HandlerCatchData(evt, null, null));
  93 + responseAck(evt, Response.OK);
  94 + if (!taskQueueHandlerRun) {
  95 + taskQueueHandlerRun = true;
  96 + taskExecutor.execute(()-> {
  97 + while (!taskQueue.isEmpty()) {
  98 + try {
  99 + HandlerCatchData take = taskQueue.poll();
  100 + Element rootElement = getRootElement(take.getEvt());
  101 + String cmd = XmlUtil.getText(rootElement, "CmdType");
  102 +
  103 + if (CmdType.CATALOG.equals(cmd)) {
  104 + logger.info("接收到Catalog通知");
  105 + processNotifyCatalogList(take.getEvt());
  106 + } else if (CmdType.ALARM.equals(cmd)) {
  107 + logger.info("接收到Alarm通知");
  108 + processNotifyAlarm(take.getEvt());
  109 + } else if (CmdType.MOBILE_POSITION.equals(cmd)) {
  110 + logger.info("接收到MobilePosition通知");
  111 + processNotifyMobilePosition(take.getEvt());
  112 + } else {
  113 + logger.info("接收到消息:" + cmd);
  114 + }
  115 + } catch (DocumentException e) {
  116 + throw new RuntimeException(e);
  117 + }
  118 + }
  119 + taskQueueHandlerRun = false;
  120 + });
95 121 }
96   - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
  122 +
  123 +
  124 + } catch (SipException | InvalidArgumentException | ParseException e) {
97 125 e.printStackTrace();
98 126 }
99 127 }
... ... @@ -166,8 +194,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
166 194 jsonObject.put("direction", mobilePosition.getDirection());
167 195 jsonObject.put("speed", mobilePosition.getSpeed());
168 196 redisCatchStorage.sendMobilePositionMsg(jsonObject);
169   - responseAck(evt, Response.OK);
170   - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
  197 + } catch (DocumentException e) {
171 198 e.printStackTrace();
172 199 }
173 200 }
... ... @@ -188,6 +215,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
188 215  
189 216 Device device = redisCatchStorage.getDevice(deviceId);
190 217 if (device == null) {
  218 + logger.warn("[ NotifyAlarm ] 未找到设备:{}", deviceId);
191 219 return;
192 220 }
193 221 rootElement = getRootElement(evt, device.getCharset());
... ... @@ -195,7 +223,12 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
195 223 deviceAlarm.setDeviceId(deviceId);
196 224 deviceAlarm.setAlarmPriority(XmlUtil.getText(rootElement, "AlarmPriority"));
197 225 deviceAlarm.setAlarmMethod(XmlUtil.getText(rootElement, "AlarmMethod"));
198   - deviceAlarm.setAlarmTime(XmlUtil.getText(rootElement, "AlarmTime"));
  226 + String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
  227 + if (alarmTime == null) {
  228 + logger.warn("[ NotifyAlarm ] AlarmTime cannot be null");
  229 + return;
  230 + }
  231 + deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
199 232 if (XmlUtil.getText(rootElement, "AlarmDescription") == null) {
200 233 deviceAlarm.setAlarmDescription("");
201 234 } else {
... ... @@ -212,7 +245,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
212 245 deviceAlarm.setLatitude(0.00);
213 246 }
214 247 logger.info("[收到Notify-Alarm]:{}/{}", device.getDeviceId(), deviceAlarm.getChannelId());
215   - if (deviceAlarm.getAlarmMethod().equals("4")) {
  248 + if ("4".equals(deviceAlarm.getAlarmMethod())) {
216 249 MobilePosition mobilePosition = new MobilePosition();
217 250 mobilePosition.setDeviceId(deviceAlarm.getDeviceId());
218 251 mobilePosition.setTime(deviceAlarm.getAlarmTime());
... ... @@ -233,11 +266,10 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
233 266 // TODO: 需要实现存储报警信息、报警分类
234 267  
235 268 // 回复200 OK
236   - responseAck(evt, Response.OK);
237 269 if (redisCatchStorage.deviceIsOnline(deviceId)) {
238 270 publisher.deviceAlarmEventPublish(deviceAlarm);
239 271 }
240   - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
  272 + } catch (DocumentException e) {
241 273 e.printStackTrace();
242 274 }
243 275 }
... ... @@ -273,64 +305,60 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
273 305 continue;
274 306 }
275 307 Element eventElement = itemDevice.element("Event");
276   - DeviceChannel channel = XmlUtil.channelContentHander(itemDevice);
  308 + String event;
  309 + if (eventElement == null) {
  310 + logger.warn("[收到 目录订阅]:{}, 但是Event为空, 设为默认值 ADD", (device != null ? device.getDeviceId():"" ));
  311 + event = CatalogEvent.ADD;
  312 + }else {
  313 + event = eventElement.getText().toUpperCase();
  314 + }
  315 + DeviceChannel channel = XmlUtil.channelContentHander(itemDevice, device);
277 316 channel.setDeviceId(device.getDeviceId());
278 317 logger.info("[收到 目录订阅]:{}/{}", device.getDeviceId(), channel.getChannelId());
279   - switch (eventElement.getText().toUpperCase()) {
  318 + switch (event) {
280 319 case CatalogEvent.ON:
281 320 // 上线
282 321 logger.info("收到来自设备【{}】的通道【{}】上线通知", device.getDeviceId(), channel.getChannelId());
283 322 storager.deviceChannelOnline(deviceId, channel.getChannelId());
284   - // 回复200 OK
285   - responseAck(evt, Response.OK);
286 323 break;
287 324 case CatalogEvent.OFF :
288 325 // 离线
289 326 logger.info("收到来自设备【{}】的通道【{}】离线通知", device.getDeviceId(), channel.getChannelId());
290 327 storager.deviceChannelOffline(deviceId, channel.getChannelId());
291   - // 回复200 OK
292   - responseAck(evt, Response.OK);
293 328 break;
294 329 case CatalogEvent.VLOST:
295 330 // 视频丢失
296 331 logger.info("收到来自设备【{}】的通道【{}】视频丢失通知", device.getDeviceId(), channel.getChannelId());
297 332 storager.deviceChannelOffline(deviceId, channel.getChannelId());
298   - // 回复200 OK
299   - responseAck(evt, Response.OK);
300 333 break;
301 334 case CatalogEvent.DEFECT:
302 335 // 故障
303   - // 回复200 OK
304   - responseAck(evt, Response.OK);
305 336 break;
306 337 case CatalogEvent.ADD:
307 338 // 增加
308 339 logger.info("收到来自设备【{}】的增加通道【{}】通知", device.getDeviceId(), channel.getChannelId());
309 340 storager.updateChannel(deviceId, channel);
310   - responseAck(evt, Response.OK);
311 341 break;
312 342 case CatalogEvent.DEL:
313 343 // 删除
314 344 logger.info("收到来自设备【{}】的删除通道【{}】通知", device.getDeviceId(), channel.getChannelId());
315 345 storager.delChannel(deviceId, channel.getChannelId());
316   - responseAck(evt, Response.OK);
317 346 break;
318 347 case CatalogEvent.UPDATE:
319 348 // 更新
320 349 logger.info("收到来自设备【{}】的更新通道【{}】通知", device.getDeviceId(), channel.getChannelId());
321 350 storager.updateChannel(deviceId, channel);
322   - responseAck(evt, Response.OK);
323 351 break;
324 352 default:
325   - responseAck(evt, Response.BAD_REQUEST, "event not found");
  353 + logger.warn("[ NotifyCatalog ] event not found : {}", event );
326 354  
327 355 }
328 356 // 转发变化信息
329   - eventPublisher.catalogEventPublish(null, channel, eventElement.getText().toUpperCase());
  357 + eventPublisher.catalogEventPublish(null, channel, event);
330 358  
331 359 }
332 360 }
333   - } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
  361 + } catch (DocumentException e) {
334 362 e.printStackTrace();
335 363 }
336 364 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
1 1 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
2 2  
3   -import com.genersoft.iot.vmp.common.VideoManagerConstants;
4 3 import com.genersoft.iot.vmp.conf.SipConfig;
5   -import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
6 4 import com.genersoft.iot.vmp.gb28181.bean.Device;
7 5 import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate;
8   -import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
9 6 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
10 7 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
11 8 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
  9 +import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
12 10 import com.genersoft.iot.vmp.service.IDeviceService;
13   -import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
14   -import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
15 11 import com.genersoft.iot.vmp.utils.DateUtil;
16 12 import gov.nist.javax.sip.RequestEventExt;
17 13 import gov.nist.javax.sip.address.AddressImpl;
... ... @@ -45,21 +41,12 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
45 41  
46 42 private final Logger logger = LoggerFactory.getLogger(RegisterRequestProcessor.class);
47 43  
48   - public String method = "REGISTER";
  44 + public final String method = "REGISTER";
49 45  
50 46 @Autowired
51 47 private SipConfig sipConfig;
52 48  
53 49 @Autowired
54   - private IRedisCatchStorage redisCatchStorage;
55   -
56   - @Autowired
57   - private IVideoManagerStorage storager;
58   -
59   - @Autowired
60   - private EventPublisher publisher;
61   -
62   - @Autowired
63 50 private SIPProcessorObserver sipProcessorObserver;
64 51  
65 52 @Autowired
... ... @@ -86,7 +73,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
86 73 ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME);
87 74 Response response = null;
88 75 boolean passwordCorrect = false;
89   - // 注册标志 0:未携带授权头或者密码错误 1:注册成功 2:注销成功
  76 + // 注册标志
90 77 boolean registerFlag = false;
91 78 FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
92 79 AddressImpl address = (AddressImpl) fromHeader.getAddress();
... ... @@ -105,7 +92,6 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
105 92 // 校验密码是否正确
106 93 passwordCorrect = StringUtils.isEmpty(sipConfig.getPassword()) ||
107 94 new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, sipConfig.getPassword());
108   - // 未携带授权头或者密码错误 回复401
109 95  
110 96 if (!passwordCorrect) {
111 97 // 注册失败
... ... @@ -154,6 +140,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
154 140 device = new Device();
155 141 device.setStreamMode("UDP");
156 142 device.setCharset("GB2312");
  143 + device.setGeoCoordSys("WGS84");
157 144 device.setDeviceId(deviceId);
158 145 }
159 146 device.setIp(received);
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java
... ... @@ -82,7 +82,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
82 82 @Override
83 83 public void process(RequestEvent evt) {
84 84 Request request = evt.getRequest();
85   -
86 85 try {
87 86 Element rootElement = getRootElement(evt);
88 87 String cmd = XmlUtil.getText(rootElement, "CmdType");
... ... @@ -176,6 +175,8 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
176 175 }
177 176  
178 177 private void processNotifyCatalogList(RequestEvent evt, Element rootElement) throws SipException {
  178 +
  179 + System.out.println(evt.getRequest().toString());
179 180 String platformId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
180 181 String deviceId = XmlUtil.getText(rootElement, "DeviceID");
181 182 ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java 0 → 100644
  1 +package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info;
  2 +
  3 +import com.genersoft.iot.vmp.common.StreamInfo;
  4 +import com.genersoft.iot.vmp.gb28181.bean.*;
  5 +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
  6 +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
  7 +import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
  8 +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
  9 +import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
  10 +import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
  11 +import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
  12 +import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  13 +import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
  14 +import gov.nist.javax.sip.message.SIPRequest;
  15 +import org.slf4j.Logger;
  16 +import org.slf4j.LoggerFactory;
  17 +import org.springframework.beans.factory.InitializingBean;
  18 +import org.springframework.beans.factory.annotation.Autowired;
  19 +import org.springframework.stereotype.Component;
  20 +import javax.sip.InvalidArgumentException;
  21 +import javax.sip.RequestEvent;
  22 +import javax.sip.SipException;
  23 +import javax.sip.header.*;
  24 +import javax.sip.message.Response;
  25 +import java.text.ParseException;
  26 +
  27 +@Component
  28 +public class InfoRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
  29 +
  30 + private final static Logger logger = LoggerFactory.getLogger(InfoRequestProcessor.class);
  31 +
  32 + private final String method = "INFO";
  33 +
  34 + @Autowired
  35 + private SIPProcessorObserver sipProcessorObserver;
  36 +
  37 + @Autowired
  38 + private IVideoManagerStorage storage;
  39 +
  40 + @Autowired
  41 + private SipSubscribe sipSubscribe;
  42 +
  43 + @Autowired
  44 + private IRedisCatchStorage redisCatchStorage;
  45 +
  46 + @Autowired
  47 + private IVideoManagerStorage storager;
  48 +
  49 + @Autowired
  50 + private SIPCommander cmder;
  51 +
  52 + @Autowired
  53 + private VideoStreamSessionManager sessionManager;
  54 +
  55 + @Override
  56 + public void afterPropertiesSet() throws Exception {
  57 + // 添加消息处理的订阅
  58 + sipProcessorObserver.addRequestProcessor(method, this);
  59 + }
  60 +
  61 + @Override
  62 + public void process(RequestEvent evt) {
  63 + logger.debug("接收到消息:" + evt.getRequest());
  64 + String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
  65 + CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
  66 + // 先从会话内查找
  67 + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
  68 + if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
  69 + deviceId = ssrcTransaction.getDeviceId();
  70 + }
  71 + // 查询设备是否存在
  72 + Device device = redisCatchStorage.getDevice(deviceId);
  73 + // 查询上级平台是否存在
  74 + ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId);
  75 + try {
  76 + if (device != null && parentPlatform != null) {
  77 + logger.warn("[重复]平台与设备编号重复:{}", deviceId);
  78 + SIPRequest request = (SIPRequest) evt.getRequest();
  79 + String hostAddress = request.getRemoteAddress().getHostAddress();
  80 + int remotePort = request.getRemotePort();
  81 + if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) {
  82 + parentPlatform = null;
  83 + }else {
  84 + device = null;
  85 + }
  86 + }
  87 + if (device == null && parentPlatform == null) {
  88 + // 不存在则回复404
  89 + responseAck(evt, Response.NOT_FOUND, "device "+ deviceId +" not found");
  90 + logger.warn("[设备未找到 ]: {}", deviceId);
  91 + if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){
  92 + SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new DeviceNotFoundEvent(evt.getDialog()));
  93 + sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()).response(eventResult);
  94 + };
  95 + }else {
  96 + ContentTypeHeader header = (ContentTypeHeader)evt.getRequest().getHeader(ContentTypeHeader.NAME);
  97 + String contentType = header.getContentType();
  98 + String contentSubType = header.getContentSubType();
  99 + if ("Application".equals(contentType) && "MANSRTSP".equals(contentSubType)) {
  100 + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
  101 + String streamId = sendRtpItem.getStreamId();
  102 + StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null);
  103 + if (null == streamInfo) {
  104 + responseAck(evt, Response.NOT_FOUND, "stream " + streamId + " not found");
  105 + return;
  106 + }
  107 + Device device1 = storager.queryVideoDevice(streamInfo.getDeviceID());
  108 + cmder.playbackControlCmd(device1,streamInfo,new String(evt.getRequest().getRawContent()),eventResult -> {
  109 + // 失败的回复
  110 + try {
  111 + responseAck(evt, eventResult.statusCode, eventResult.msg);
  112 + } catch (SipException e) {
  113 + e.printStackTrace();
  114 + } catch (InvalidArgumentException e) {
  115 + e.printStackTrace();
  116 + } catch (ParseException e) {
  117 + e.printStackTrace();
  118 + }
  119 + }, eventResult -> {
  120 + // 成功的回复
  121 + try {
  122 + responseAck(evt, eventResult.statusCode);
  123 + } catch (SipException e) {
  124 + e.printStackTrace();
  125 + } catch (InvalidArgumentException e) {
  126 + e.printStackTrace();
  127 + } catch (ParseException e) {
  128 + e.printStackTrace();
  129 + }
  130 + });
  131 + }
  132 + }
  133 + } catch (SipException e) {
  134 + logger.warn("SIP 回复错误", e);
  135 + } catch (InvalidArgumentException e) {
  136 + logger.warn("参数无效", e);
  137 + } catch (ParseException e) {
  138 + logger.warn("SIP回复时解析异常", e);
  139 + }
  140 + }
  141 +
  142 +
  143 +}
... ...
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
72 72 String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
73 73 CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
74 74 // 先从会话内查找
75   - SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, null, callIdHeader.getCallId());
  75 + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
76 76 if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
77 77 deviceId = ssrcTransaction.getDeviceId();
78 78 }
... ...
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
9 9 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
10 10 import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
11 11 import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
  12 +import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
12 13 import com.genersoft.iot.vmp.service.IDeviceAlarmService;
13 14 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
14 15 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
  16 +import com.genersoft.iot.vmp.utils.DateUtil;
15 17 import org.dom4j.Element;
16 18 import org.slf4j.Logger;
17 19 import org.slf4j.LoggerFactory;
... ... @@ -84,7 +86,11 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
84 86 deviceAlarm.setChannelId(channelId);
85 87 deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority"));
86 88 deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod"));
87   - deviceAlarm.setAlarmTime(getText(rootElement, "AlarmTime"));
  89 + String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
  90 + if (alarmTime == null) {
  91 + return;
  92 + }
  93 + deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
88 94 String alarmDescription = getText(rootElement, "AlarmDescription");
89 95 if (alarmDescription == null) {
90 96 deviceAlarm.setAlarmDescription("");
... ... @@ -175,7 +181,11 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
175 181 deviceAlarm.setChannelId(channelId);
176 182 deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority"));
177 183 deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod"));
178   - deviceAlarm.setAlarmTime(getText(rootElement, "AlarmTime"));
  184 + String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
  185 + if (alarmTime == null) {
  186 + return;
  187 + }
  188 + deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
179 189 String alarmDescription = getText(rootElement, "AlarmDescription");
180 190 if (alarmDescription == null) {
181 191 deviceAlarm.setAlarmDescription("");
... ...
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
64 64 device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
65 65 }
66 66 device.setKeepaliveTime(DateUtil.getNow());
  67 + // 回复200 OK
  68 + responseAck(evt, Response.OK);
67 69 if (device.getOnline() == 1) {
68   - // 回复200 OK
69   - responseAck(evt, Response.OK);
70 70 deviceService.updateDevice(device);
71 71 }else {
72 72 // 对于已经离线的设备判断他的注册是否已经过期
73 73 if (!deviceService.expire(device)){
74 74 deviceService.online(device);
75   - // 回复200 OK
76   - responseAck(evt, Response.OK);
77 75 }
78 76 }
79 77 } catch (SipException e) {
... ...
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
3 3 import com.genersoft.iot.vmp.common.StreamInfo;
4 4 import com.genersoft.iot.vmp.gb28181.bean.Device;
5 5 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
  6 +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
  7 +import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
  8 +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
6 9 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
  10 +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
7 11 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
8 12 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
9 13 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
10 14 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  15 +import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
11 16 import org.dom4j.Element;
12 17 import org.slf4j.Logger;
13 18 import org.slf4j.LoggerFactory;
... ... @@ -37,8 +42,17 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
37 42 private SIPCommander cmder;
38 43  
39 44 @Autowired
  45 + private SIPCommanderFroPlatform sipCommanderFroPlatform;
  46 +
  47 + @Autowired
40 48 private IRedisCatchStorage redisCatchStorage;
41 49  
  50 + @Autowired
  51 + private IVideoManagerStorage storage;
  52 +
  53 + @Autowired
  54 + private VideoStreamSessionManager sessionManager;
  55 +
42 56 @Override
43 57 public void afterPropertiesSet() throws Exception {
44 58 notifyMessageHandler.addHandler(cmdType, this);
... ... @@ -59,17 +73,32 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
59 73 }
60 74 CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
61 75 String NotifyType =getText(rootElement, "NotifyType");
62   - if (NotifyType.equals("121")){
  76 + if ("121".equals(NotifyType)){
63 77 logger.info("[录像流]推送完毕,收到关流通知");
64   - String channelId =getText(rootElement, "DeviceID");
65 78 // 查询是设备
66   - StreamInfo streamInfo = redisCatchStorage.queryDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
67   - // 设置进度100%
68   - streamInfo.setProgress(1);
69   - redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId());
70   - cmder.streamByeCmd(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
71   - // TODO 如果级联播放,需要给上级发送此通知
  79 + StreamInfo streamInfo = redisCatchStorage.queryDownload(null, null, null, callIdHeader.getCallId());
  80 + if (streamInfo != null) {
  81 + // 设置进度100%
  82 + streamInfo.setProgress(1);
  83 + redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId());
  84 + }
  85 +
  86 + // 先从会话内查找
  87 + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
  88 + if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
  89 + cmder.streamByeCmd(device.getDeviceId(), ssrcTransaction.getChannelId(), null, callIdHeader.getCallId());
72 90  
  91 + // 如果级联播放,需要给上级发送此通知 TODO 多个上级同时观看一个下级 可能存在停错的问题,需要将点播CallId进行上下级绑定
  92 + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, ssrcTransaction.getChannelId(), null, null);
  93 + if (sendRtpItem != null) {
  94 + ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
  95 + if (parentPlatform == null) {
  96 + logger.warn("[级联消息发送]:发送MediaStatus发现上级平台{}不存在", sendRtpItem.getPlatformId());
  97 + return;
  98 + }
  99 + sipCommanderFroPlatform.sendMediaStatusNotify(parentPlatform, sendRtpItem);
  100 + }
  101 + }
73 102 }
74 103 }
75 104  
... ...
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;
20 20 import org.slf4j.LoggerFactory;
21 21 import org.springframework.beans.factory.InitializingBean;
22 22 import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.springframework.beans.factory.annotation.Qualifier;
  24 +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
23 25 import org.springframework.stereotype.Component;
24 26 import org.springframework.util.StringUtils;
25 27  
... ... @@ -31,6 +33,7 @@ import java.text.ParseException;
31 33 import java.util.ArrayList;
32 34 import java.util.Iterator;
33 35 import java.util.List;
  36 +import java.util.concurrent.ConcurrentLinkedQueue;
34 37  
35 38 @Component
36 39 public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
... ... @@ -38,9 +41,13 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
38 41 private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class);
39 42 private final String cmdType = "Catalog";
40 43  
  44 + private boolean taskQueueHandlerRun = false;
  45 +
41 46 @Autowired
42 47 private ResponseMessageHandler responseMessageHandler;
43 48  
  49 + private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
  50 +
44 51 @Autowired
45 52 private IVideoManagerStorage storager;
46 53  
... ... @@ -63,6 +70,10 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
63 70 @Autowired
64 71 private IRedisCatchStorage redisCatchStorage;
65 72  
  73 + @Qualifier("taskExecutor")
  74 + @Autowired
  75 + private ThreadPoolTaskExecutor taskExecutor;
  76 +
66 77 @Override
67 78 public void afterPropertiesSet() throws Exception {
68 79 responseMessageHandler.addHandler(cmdType, this);
... ... @@ -70,68 +81,88 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
70 81  
71 82 @Override
72 83 public void handForDevice(RequestEvent evt, Device device, Element element) {
73   - String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + device.getDeviceId();
74   - Element rootElement = null;
  84 + taskQueue.offer(new HandlerCatchData(evt, device, element));
  85 + // 回复200 OK
75 86 try {
76   - rootElement = getRootElement(evt, device.getCharset());
77   - Element deviceListElement = rootElement.element("DeviceList");
78   - Element sumNumElement = rootElement.element("SumNum");
79   - Element snElement = rootElement.element("SN");
80   - if (snElement == null || sumNumElement == null || deviceListElement == null) {
81   - responseAck(evt, Response.BAD_REQUEST, "xml error");
82   - return;
83   - }
84   - int sumNum = Integer.parseInt(sumNumElement.getText());
85   -
86   - if (sumNum == 0) {
87   - // 数据已经完整接收
88   - storager.cleanChannelsForDevice(device.getDeviceId());
89   - catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null);
90   - }else {
91   - Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
92   - if (deviceListIterator != null) {
93   - List<DeviceChannel> channelList = new ArrayList<>();
94   - // 遍历DeviceList
95   - while (deviceListIterator.hasNext()) {
96   - Element itemDevice = deviceListIterator.next();
97   - Element channelDeviceElement = itemDevice.element("DeviceID");
98   - if (channelDeviceElement == null) {
99   - continue;
  87 + responseAck(evt, Response.OK);
  88 + } catch (SipException e) {
  89 + throw new RuntimeException(e);
  90 + } catch (InvalidArgumentException e) {
  91 + throw new RuntimeException(e);
  92 + } catch (ParseException e) {
  93 + throw new RuntimeException(e);
  94 + }
  95 + if (!taskQueueHandlerRun) {
  96 + taskQueueHandlerRun = true;
  97 + taskExecutor.execute(()-> {
  98 + while (!taskQueue.isEmpty()) {
  99 + HandlerCatchData take = taskQueue.poll();
  100 + String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + take.getDevice().getDeviceId();
  101 + Element rootElement = null;
  102 + try {
  103 + rootElement = getRootElement(take.getEvt(), take.getDevice().getCharset());
  104 + Element deviceListElement = rootElement.element("DeviceList");
  105 + Element sumNumElement = rootElement.element("SumNum");
  106 + Element snElement = rootElement.element("SN");
  107 + if (snElement == null || sumNumElement == null || deviceListElement == null) {
  108 + responseAck(take.getEvt(), Response.BAD_REQUEST, "xml error");
  109 + return;
100 110 }
101   - //by brewswang
102   -// if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {//如果包含位置信息,就更新一下位置
103   -// processNotifyMobilePosition(evt, itemDevice);
104   -// }
105   - DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice);
106   - deviceChannel.setDeviceId(device.getDeviceId());
107   -
108   - channelList.add(deviceChannel);
109   - }
110   - int sn = Integer.parseInt(snElement.getText());
111   - catalogDataCatch.put(device.getDeviceId(), sn, sumNum, device, channelList);
112   - logger.info("收到来自设备【{}】的通道: {}个,{}/{}", device.getDeviceId(), channelList.size(), catalogDataCatch.get(device.getDeviceId()) == null ? 0 :catalogDataCatch.get(device.getDeviceId()).size(), sumNum);
113   - if (catalogDataCatch.get(device.getDeviceId()).size() == sumNum) {
114   - // 数据已经完整接收
115   - boolean resetChannelsResult = storager.resetChannels(device.getDeviceId(), catalogDataCatch.get(device.getDeviceId()));
116   - if (!resetChannelsResult) {
117   - String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(device.getDeviceId()).size() + "条";
118   - catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), errorMsg);
  111 + int sumNum = Integer.parseInt(sumNumElement.getText());
  112 +
  113 + if (sumNum == 0) {
  114 + // 数据已经完整接收
  115 + storager.cleanChannelsForDevice(take.getDevice().getDeviceId());
  116 + catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
119 117 }else {
120   - catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null);
  118 + Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
  119 + if (deviceListIterator != null) {
  120 + List<DeviceChannel> channelList = new ArrayList<>();
  121 + // 遍历DeviceList
  122 + while (deviceListIterator.hasNext()) {
  123 + Element itemDevice = deviceListIterator.next();
  124 + Element channelDeviceElement = itemDevice.element("DeviceID");
  125 + if (channelDeviceElement == null) {
  126 + continue;
  127 + }
  128 + //by brewswang
  129 + // if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {//如果包含位置信息,就更新一下位置
  130 + // processNotifyMobilePosition(evt, itemDevice);
  131 + // }
  132 + DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice, device);
  133 + deviceChannel.setDeviceId(take.getDevice().getDeviceId());
  134 +
  135 + channelList.add(deviceChannel);
  136 + }
  137 + int sn = Integer.parseInt(snElement.getText());
  138 + catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(), channelList);
  139 + logger.info("收到来自设备【{}】的通道: {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.get(take.getDevice().getDeviceId()) == null ? 0 :catalogDataCatch.get(take.getDevice().getDeviceId()).size(), sumNum);
  140 + if (catalogDataCatch.get(take.getDevice().getDeviceId()).size() == sumNum) {
  141 + // 数据已经完整接收
  142 + boolean resetChannelsResult = storager.resetChannels(take.getDevice().getDeviceId(), catalogDataCatch.get(take.getDevice().getDeviceId()));
  143 + if (!resetChannelsResult) {
  144 + String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(take.getDevice().getDeviceId()).size() + "条";
  145 + catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), errorMsg);
  146 + }else {
  147 + catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
  148 + }
  149 + }
  150 + }
  151 +
121 152 }
  153 + } catch (DocumentException e) {
  154 + e.printStackTrace();
  155 + } catch (InvalidArgumentException e) {
  156 + e.printStackTrace();
  157 + } catch (ParseException e) {
  158 + e.printStackTrace();
  159 + } catch (SipException e) {
  160 + e.printStackTrace();
122 161 }
123 162 }
124   - // 回复200 OK
125   - responseAck(evt, Response.OK);
126   - }
127   - } catch (DocumentException e) {
128   - e.printStackTrace();
129   - } catch (InvalidArgumentException e) {
130   - e.printStackTrace();
131   - } catch (ParseException e) {
132   - e.printStackTrace();
133   - } catch (SipException e) {
134   - e.printStackTrace();
  163 + taskQueueHandlerRun = false;
  164 + });
  165 +
135 166 }
136 167 }
137 168  
... ...
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
82 82 deviceService.offline(device.getDeviceId());
83 83 }
84 84 RequestMessage msg = new RequestMessage();
85   - msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId() + channelId);
  85 + msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId());
86 86 msg.setData(json);
87 87 deferredResultHolder.invokeAllResult(msg);
88 88 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
1 1 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
2 2  
3   -import com.genersoft.iot.vmp.gb28181.bean.Device;
4   -import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
5   -import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
6   -import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
  3 +import com.genersoft.iot.vmp.gb28181.bean.*;
7 4 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
8 5 import com.genersoft.iot.vmp.gb28181.session.RecordDataCatch;
9 6 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
... ... @@ -19,6 +16,8 @@ import org.slf4j.Logger;
19 16 import org.slf4j.LoggerFactory;
20 17 import org.springframework.beans.factory.InitializingBean;
21 18 import org.springframework.beans.factory.annotation.Autowired;
  19 +import org.springframework.beans.factory.annotation.Qualifier;
  20 +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
22 21 import org.springframework.stereotype.Component;
23 22 import org.springframework.util.StringUtils;
24 23  
... ... @@ -28,6 +27,9 @@ import javax.sip.SipException;
28 27 import javax.sip.message.Response;
29 28 import java.text.ParseException;
30 29 import java.util.*;
  30 +import java.util.concurrent.BlockingQueue;
  31 +import java.util.concurrent.ConcurrentLinkedQueue;
  32 +import java.util.concurrent.LinkedBlockingQueue;
31 33  
32 34 import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
33 35  
... ... @@ -38,10 +40,11 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
38 40 public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
39 41  
40 42 private Logger logger = LoggerFactory.getLogger(RecordInfoResponseMessageHandler.class);
41   - public static volatile List<String> threadNameList = new ArrayList();
42 43 private final String cmdType = "RecordInfo";
43   - private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_";
44 44  
  45 + private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
  46 +
  47 + private boolean taskQueueHandlerRun = false;
45 48 @Autowired
46 49 private ResponseMessageHandler responseMessageHandler;
47 50  
... ... @@ -51,11 +54,13 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
51 54 @Autowired
52 55 private DeferredResultHolder deferredResultHolder;
53 56  
54   -
55   -
56 57 @Autowired
57 58 private EventPublisher eventPublisher;
58 59  
  60 + @Qualifier("taskExecutor")
  61 + @Autowired
  62 + private ThreadPoolTaskExecutor taskExecutor;
  63 +
59 64 @Override
60 65 public void afterPropertiesSet() throws Exception {
61 66 responseMessageHandler.addHandler(cmdType, this);
... ... @@ -67,67 +72,89 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
67 72 // 回复200 OK
68 73 try {
69 74 responseAck(evt, Response.OK);
70   -
71   - rootElement = getRootElement(evt, device.getCharset());
72   - String sn = getText(rootElement, "SN");
73   -
74   - String sumNumStr = getText(rootElement, "SumNum");
75   - int sumNum = 0;
76   - if (!StringUtils.isEmpty(sumNumStr)) {
77   - sumNum = Integer.parseInt(sumNumStr);
78   - }
79   - Element recordListElement = rootElement.element("RecordList");
80   - if (recordListElement == null || sumNum == 0) {
81   - logger.info("无录像数据");
82   - recordDataCatch.put(device.getDeviceId(), sn, sumNum, new ArrayList<>());
83   - releaseRequest(device.getDeviceId(), sn);
84   - } else {
85   - Iterator<Element> recordListIterator = recordListElement.elementIterator();
86   - if (recordListIterator != null) {
87   - List<RecordItem> recordList = new ArrayList<>();
88   - // 遍历DeviceList
89   - while (recordListIterator.hasNext()) {
90   - Element itemRecord = recordListIterator.next();
91   - Element recordElement = itemRecord.element("DeviceID");
92   - if (recordElement == null) {
93   - logger.info("记录为空,下一个...");
94   - continue;
  75 + taskQueue.offer(new HandlerCatchData(evt, device, rootElement));
  76 + if (!taskQueueHandlerRun) {
  77 + taskQueueHandlerRun = true;
  78 + taskExecutor.execute(()->{
  79 + try {
  80 + while (!taskQueue.isEmpty()) {
  81 + HandlerCatchData take = taskQueue.poll();
  82 + Element rootElementForCharset = getRootElement(take.getEvt(), take.getDevice().getCharset());
  83 + String sn = getText(rootElementForCharset, "SN");
  84 + String channelId = getText(rootElementForCharset, "DeviceID");
  85 + RecordInfo recordInfo = new RecordInfo();
  86 + recordInfo.setChannelId(channelId);
  87 + recordInfo.setDeviceId(take.getDevice().getDeviceId());
  88 + recordInfo.setSn(sn);
  89 + recordInfo.setName(getText(rootElementForCharset, "Name"));
  90 + String sumNumStr = getText(rootElementForCharset, "SumNum");
  91 + int sumNum = 0;
  92 + if (!StringUtils.isEmpty(sumNumStr)) {
  93 + sumNum = Integer.parseInt(sumNumStr);
  94 + }
  95 + recordInfo.setSumNum(sumNum);
  96 + Element recordListElement = rootElementForCharset.element("RecordList");
  97 + if (recordListElement == null || sumNum == 0) {
  98 + logger.info("无录像数据");
  99 + eventPublisher.recordEndEventPush(recordInfo);
  100 + recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, new ArrayList<>());
  101 + releaseRequest(take.getDevice().getDeviceId(), sn);
  102 + } else {
  103 + Iterator<Element> recordListIterator = recordListElement.elementIterator();
  104 + if (recordListIterator != null) {
  105 + List<RecordItem> recordList = new ArrayList<>();
  106 + // 遍历DeviceList
  107 + while (recordListIterator.hasNext()) {
  108 + Element itemRecord = recordListIterator.next();
  109 + Element recordElement = itemRecord.element("DeviceID");
  110 + if (recordElement == null) {
  111 + logger.info("记录为空,下一个...");
  112 + continue;
  113 + }
  114 + RecordItem record = new RecordItem();
  115 + record.setDeviceId(getText(itemRecord, "DeviceID"));
  116 + record.setName(getText(itemRecord, "Name"));
  117 + record.setFilePath(getText(itemRecord, "FilePath"));
  118 + record.setFileSize(getText(itemRecord, "FileSize"));
  119 + record.setAddress(getText(itemRecord, "Address"));
  120 +
  121 + String startTimeStr = getText(itemRecord, "StartTime");
  122 + record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr));
  123 +
  124 + String endTimeStr = getText(itemRecord, "EndTime");
  125 + record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr));
  126 +
  127 + record.setSecrecy(itemRecord.element("Secrecy") == null ? 0
  128 + : Integer.parseInt(getText(itemRecord, "Secrecy")));
  129 + record.setType(getText(itemRecord, "Type"));
  130 + record.setRecorderId(getText(itemRecord, "RecorderID"));
  131 + recordList.add(record);
  132 + }
  133 + recordInfo.setRecordList(recordList);
  134 + // 发送消息,如果是上级查询此录像,则会通过这里通知给上级
  135 + eventPublisher.recordEndEventPush(recordInfo);
  136 + int count = recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, recordList);
  137 + logger.info("[国标录像], {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum);
  138 + }
  139 +
  140 + if (recordDataCatch.isComplete(take.getDevice().getDeviceId(), sn)){
  141 + releaseRequest(take.getDevice().getDeviceId(), sn);
  142 + }
  143 + }
95 144 }
96   - RecordItem record = new RecordItem();
97   - record.setDeviceId(getText(itemRecord, "DeviceID"));
98   - record.setName(getText(itemRecord, "Name"));
99   - record.setFilePath(getText(itemRecord, "FilePath"));
100   - record.setFileSize(getText(itemRecord, "FileSize"));
101   - record.setAddress(getText(itemRecord, "Address"));
102   -
103   - String startTimeStr = getText(itemRecord, "StartTime");
104   - record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr));
105   -
106   - String endTimeStr = getText(itemRecord, "EndTime");
107   - record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr));
108   -
109   - record.setSecrecy(itemRecord.element("Secrecy") == null ? 0
110   - : Integer.parseInt(getText(itemRecord, "Secrecy")));
111   - record.setType(getText(itemRecord, "Type"));
112   - record.setRecorderId(getText(itemRecord, "RecorderID"));
113   - recordList.add(record);
  145 + taskQueueHandlerRun = false;
  146 + }catch (DocumentException e) {
  147 + throw new RuntimeException(e);
114 148 }
115   - int count = recordDataCatch.put(device.getDeviceId(), sn, sumNum, recordList);
116   - logger.info("[国标录像], {}->{}: {}/{}", device.getDeviceId(), sn, count, sumNum);
117   - }
118   -
119   - if (recordDataCatch.isComplete(device.getDeviceId(), sn)){
120   - releaseRequest(device.getDeviceId(), sn);
121   - }
  149 + });
122 150 }
  151 +
123 152 } catch (SipException e) {
124 153 e.printStackTrace();
125 154 } catch (InvalidArgumentException e) {
126 155 e.printStackTrace();
127 156 } catch (ParseException e) {
128 157 e.printStackTrace();
129   - } catch (DocumentException e) {
130   - e.printStackTrace();
131 158 }
132 159 }
133 160  
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java
... ... @@ -17,7 +17,7 @@ import javax.sip.ResponseEvent;
17 17 @Component
18 18 public class ByeResponseProcessor extends SIPResponseProcessorAbstract {
19 19  
20   - private String method = "BYE";
  20 + private final String method = "BYE";
21 21  
22 22 @Autowired
23 23 private SipLayer sipLayer;
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/CancelResponseProcessor.java
... ... @@ -17,7 +17,7 @@ import javax.sip.ResponseEvent;
17 17 @Component
18 18 public class CancelResponseProcessor extends SIPResponseProcessorAbstract {
19 19  
20   - private String method = "CANCEL";
  20 + private final String method = "CANCEL";
21 21  
22 22 @Autowired
23 23 private SipLayer sipLayer;
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
... ... @@ -31,7 +31,7 @@ import java.text.ParseException;
31 31 public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
32 32  
33 33 private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class);
34   - private String method = "INVITE";
  34 + private final String method = "INVITE";
35 35  
36 36 @Autowired
37 37 private SipLayer sipLayer;
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java
... ... @@ -27,7 +27,7 @@ import javax.sip.message.Response;
27 27 public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
28 28  
29 29 private Logger logger = LoggerFactory.getLogger(RegisterResponseProcessor.class);
30   - private String method = "REGISTER";
  30 + private final String method = "REGISTER";
31 31  
32 32 @Autowired
33 33 private ISIPCommanderForPlatform sipCommanderForPlatform;
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
... ... @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.utils;
2 2  
3 3 import com.alibaba.fastjson.JSONArray;
4 4 import com.alibaba.fastjson.JSONObject;
  5 +import com.genersoft.iot.vmp.gb28181.bean.Device;
5 6 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
6 7 import org.dom4j.Attribute;
7 8 import org.dom4j.Document;
... ... @@ -180,7 +181,7 @@ public class XmlUtil {
180 181 return xml.getRootElement();
181 182 }
182 183  
183   - public static DeviceChannel channelContentHander(Element itemDevice){
  184 + public static DeviceChannel channelContentHander(Element itemDevice, Device device){
184 185 Element channdelNameElement = itemDevice.element("Name");
185 186 String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim().toString() : "";
186 187 Element statusElement = itemDevice.element("Status");
... ... @@ -254,6 +255,8 @@ public class XmlUtil {
254 255 }else if (deviceChannel.getChannelId().length() == 20) {
255 256 if (Integer.parseInt(deviceChannel.getChannelId().substring(10, 13)) == 216) { // 虚拟组织
256 257 deviceChannel.setParentId(businessGroupID);
  258 + }else if (Integer.parseInt(device.getDeviceId().substring(10, 13) )== 118) {//NVR 如果上级设备编号是NVR则直接将NVR的编号设置给通道的上级编号
  259 + deviceChannel.setParentId(device.getDeviceId());
257 260 }else if (deviceChannel.getCivilCode() != null) {
258 261 // 设备, 无parentId的20位是使用CivilCode表示上级的设备,
259 262 // 注:215 业务分组是需要有parentId的
... ... @@ -308,6 +311,31 @@ public class XmlUtil {
308 311 } else {
309 312 deviceChannel.setLatitude(0.00);
310 313 }
  314 + if (deviceChannel.getLongitude()*deviceChannel.getLatitude() > 0) {
  315 + if ("WGS84".equals(device.getGeoCoordSys())) {
  316 + deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude());
  317 + deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude());
  318 + Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude());
  319 + deviceChannel.setLongitudeGcj02(position[0]);
  320 + deviceChannel.setLatitudeGcj02(position[1]);
  321 + }else if ("GCJ02".equals(device.getGeoCoordSys())) {
  322 + deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude());
  323 + deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude());
  324 + Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude());
  325 + deviceChannel.setLongitudeWgs84(position[0]);
  326 + deviceChannel.setLatitudeWgs84(position[1]);
  327 + }else {
  328 + deviceChannel.setLongitudeGcj02(0.00);
  329 + deviceChannel.setLatitudeGcj02(0.00);
  330 + deviceChannel.setLongitudeWgs84(0.00);
  331 + deviceChannel.setLatitudeWgs84(0.00);
  332 + }
  333 + }else {
  334 + deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude());
  335 + deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude());
  336 + deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude());
  337 + deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude());
  338 + }
311 339 if (XmlUtil.getText(itemDevice, "PTZType") == null || "".equals(XmlUtil.getText(itemDevice, "PTZType"))) {
312 340 //兼容INFO中的信息
313 341 Element info = itemDevice.element("Info");
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
... ... @@ -11,7 +11,6 @@ import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
11 11 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
12 12 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
13 13 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
14   -import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
15 14 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
16 15 import com.genersoft.iot.vmp.media.zlm.dto.*;
17 16 import com.genersoft.iot.vmp.service.*;
... ... @@ -92,10 +91,9 @@ public class ZLMHttpHookListener {
92 91 public ResponseEntity<String> onServerKeepalive(@RequestBody JSONObject json){
93 92  
94 93 if (logger.isDebugEnabled()) {
95   - logger.debug("[ ZLM HOOK ]on_server_keepalive API调用,参数:" + json.toString());
  94 + logger.debug("[ ZLM HOOK ] on_server_keepalive API调用,参数:" + json.toString());
96 95 }
97 96 String mediaServerId = json.getString("mediaServerId");
98   -
99 97 List<ZLMHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(ZLMHttpHookSubscribe.HookType.on_server_keepalive);
100 98 if (subscribes != null && subscribes.size() > 0) {
101 99 for (ZLMHttpHookSubscribe.Event subscribe : subscribes) {
... ... @@ -165,7 +163,6 @@ public class ZLMHttpHookListener {
165 163 if (mediaInfo != null) {
166 164 subscribe.response(mediaInfo, json);
167 165 }
168   -
169 166 }
170 167 JSONObject ret = new JSONObject();
171 168 ret.put("code", 0);
... ... @@ -248,6 +245,23 @@ public class ZLMHttpHookListener {
248 245 ret.put("msg", "success");
249 246 return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
250 247 }
  248 + /**
  249 + * 录制hls完成后通知事件;此事件对回复不敏感。
  250 + *
  251 + */
  252 + @ResponseBody
  253 + @PostMapping(value = "/on_record_ts", produces = "application/json;charset=UTF-8")
  254 + public ResponseEntity<String> onRecordTs(@RequestBody JSONObject json){
  255 +
  256 + if (logger.isDebugEnabled()) {
  257 + logger.debug("[ ZLM HOOK ]on_record_ts API调用,参数:" + json.toString());
  258 + }
  259 + String mediaServerId = json.getString("mediaServerId");
  260 + JSONObject ret = new JSONObject();
  261 + ret.put("code", 0);
  262 + ret.put("msg", "success");
  263 + return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
  264 + }
251 265  
252 266 /**
253 267 * rtsp专用的鉴权事件,先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件。
... ... @@ -383,21 +397,22 @@ public class ZLMHttpHookListener {
383 397 if (item.getOriginType() == OriginType.RTSP_PUSH.ordinal()
384 398 || item.getOriginType() == OriginType.RTMP_PUSH.ordinal()
385 399 || item.getOriginType() == OriginType.RTC_PUSH.ordinal() ) {
386   - streamPushItem = zlmMediaListManager.addPush(item);
  400 + item.setSeverId(userSetting.getServerId());
  401 + zlmMediaListManager.addPush(item);
387 402 }
388 403  
389   - List<GbStream> gbStreams = new ArrayList<>();
390   - if (streamPushItem == null || streamPushItem.getGbId() == null) {
391   - GbStream gbStream = storager.getGbStream(app, streamId);
392   - gbStreams.add(gbStream);
393   - }else {
394   - if (streamPushItem.getGbId() != null) {
395   - gbStreams.add(streamPushItem);
396   - }
397   - }
398   - if (gbStreams.size() > 0) {
  404 +// List<GbStream> gbStreams = new ArrayList<>();
  405 +// if (streamPushItem == null || streamPushItem.getGbId() == null) {
  406 +// GbStream gbStream = storager.getGbStream(app, streamId);
  407 +// gbStreams.add(gbStream);
  408 +// }else {
  409 +// if (streamPushItem.getGbId() != null) {
  410 +// gbStreams.add(streamPushItem);
  411 +// }
  412 +// }
  413 +// if (gbStreams.size() > 0) {
399 414 // eventPublisher.catalogEventPublishForStream(null, gbStreams, CatalogEvent.ON);
400   - }
  415 +// }
401 416  
402 417 }else {
403 418 // 兼容流注销时类型从redis记录获取
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
... ... @@ -24,6 +24,9 @@ import java.util.concurrent.ConcurrentHashMap;
24 24 import java.util.regex.Matcher;
25 25 import java.util.regex.Pattern;
26 26  
  27 +/**
  28 + * @author lin
  29 + */
27 30 @Component
28 31 public class ZLMMediaListManager {
29 32  
... ... @@ -147,7 +150,6 @@ public class ZLMMediaListManager {
147 150 }
148 151 }
149 152 }
150   - // StreamProxyItem streamProxyItem = gbStreamMapper.selectOne(transform.getApp(), transform.getStream());
151 153 List<GbStream> gbStreamList = gbStreamMapper.selectByGBId(transform.getGbId());
152 154 if (gbStreamList != null && gbStreamList.size() == 1) {
153 155 transform.setGbStreamId(gbStreamList.get(0).getGbStreamId());
... ... @@ -162,13 +164,12 @@ public class ZLMMediaListManager {
162 164 }
163 165 if (transform != null) {
164 166 if (channelOnlineEvents.get(transform.getGbId()) != null) {
165   - channelOnlineEvents.get(transform.getGbId()).run(transform.getApp(), transform.getStream());
  167 + channelOnlineEvents.get(transform.getGbId()).run(transform.getApp(), transform.getStream(), transform.getServerId());
166 168 channelOnlineEvents.remove(transform.getGbId());
167 169 }
168 170 }
169 171 }
170 172  
171   -
172 173 storager.updateMedia(transform);
173 174 return transform;
174 175 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
... ... @@ -12,6 +12,7 @@ import org.springframework.stereotype.Component;
12 12  
13 13 import java.io.*;
14 14 import java.net.ConnectException;
  15 +import java.net.SocketTimeoutException;
15 16 import java.util.HashMap;
16 17 import java.util.Map;
17 18 import java.util.Objects;
... ... @@ -28,6 +29,9 @@ public class ZLMRESTfulUtils {
28 29  
29 30 private OkHttpClient getClient(){
30 31 OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
  32 + //todo 暂时写死超时时间 均为5s
  33 + httpClientBuilder.connectTimeout(5,TimeUnit.SECONDS); //设置连接超时时间
  34 + httpClientBuilder.readTimeout(5,TimeUnit.SECONDS); //设置读取超时时间
31 35 if (logger.isDebugEnabled()) {
32 36 HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> {
33 37 logger.debug("http请求参数:" + message);
... ... @@ -47,7 +51,10 @@ public class ZLMRESTfulUtils {
47 51 return null;
48 52 }
49 53 String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
50   - JSONObject responseJSON = null;
  54 + JSONObject responseJSON = new JSONObject();
  55 + //-2自定义流媒体 调用错误码
  56 + responseJSON.put("code",-2);
  57 + responseJSON.put("msg","流媒体调用失败");
51 58  
52 59 FormBody.Builder builder = new FormBody.Builder();
53 60 builder.add("secret",mediaServerItem.getSecret());
... ... @@ -78,11 +85,20 @@ public class ZLMRESTfulUtils {
78 85 response.close();
79 86 Objects.requireNonNull(response.body()).close();
80 87 }
81   - } catch (ConnectException e) {
82   - logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
83   - logger.info("请检查media配置并确认ZLM已启动...");
84 88 }catch (IOException e) {
85 89 logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
  90 +
  91 + if(e instanceof SocketTimeoutException){
  92 + //读取超时超时异常
  93 + logger.error(String.format("读取ZLM数据失败: %s, %s", url, e.getMessage()));
  94 + }
  95 + if(e instanceof ConnectException){
  96 + //判断连接异常,我这里是报Failed to connect to 10.7.5.144
  97 + logger.error(String.format("连接ZLM失败: %s, %s", url, e.getMessage()));
  98 + }
  99 +
  100 + }catch (Exception e){
  101 + logger.error(String.format("访问ZLM失败: %s, %s", url, e.getMessage()));
86 102 }
87 103 }else {
88 104 client.newCall(request).enqueue(new Callback(){
... ... @@ -105,8 +121,16 @@ public class ZLMRESTfulUtils {
105 121  
106 122 @Override
107 123 public void onFailure(@NotNull Call call, @NotNull IOException e) {
108   - logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
109   - logger.info("请检查media配置并确认ZLM已启动...");
  124 + logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage()));
  125 +
  126 + if(e instanceof SocketTimeoutException){
  127 + //读取超时超时异常
  128 + logger.error(String.format("读取ZLM数据失败: %s, %s", call.request().toString(), e.getMessage()));
  129 + }
  130 + if(e instanceof ConnectException){
  131 + //判断连接异常,我这里是报Failed to connect to 10.7.5.144
  132 + logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage()));
  133 + }
110 134 }
111 135 });
112 136 }
... ... @@ -151,7 +175,7 @@ public class ZLMRESTfulUtils {
151 175 }
152 176  
153 177 }
154   - File snapFile = new File(targetPath + "/" + fileName);
  178 + File snapFile = new File(targetPath + File.separator + fileName);
155 179 FileOutputStream outStream = new FileOutputStream(snapFile);
156 180  
157 181 outStream.write(Objects.requireNonNull(response.body()).bytes());
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
... ... @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.media.zlm;
2 2  
3 3 import com.alibaba.fastjson.JSONArray;
4 4 import com.alibaba.fastjson.JSONObject;
  5 +import com.genersoft.iot.vmp.conf.UserSetting;
5 6 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
6 7 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
7 8 import org.slf4j.Logger;
... ... @@ -20,6 +21,9 @@ public class ZLMRTPServerFactory {
20 21 @Autowired
21 22 private ZLMRESTfulUtils zlmresTfulUtils;
22 23  
  24 + @Autowired
  25 + private UserSetting userSetting;
  26 +
23 27 private int[] portRangeArray = new int[2];
24 28  
25 29 public int getFreePort(MediaServerItem mediaServerItem, int startPort, int endPort, List<Integer> usedFreelist) {
... ... @@ -87,10 +91,15 @@ public class ZLMRTPServerFactory {
87 91 int result = -1;
88 92 // 查询此rtp server 是否已经存在
89 93 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
90   - if (rtpInfo != null && rtpInfo.getInteger("code") == 0 && rtpInfo.getBoolean("exist")) {
91   - result = rtpInfo.getInteger("local_port");
  94 + if(rtpInfo.getInteger("code") == 0){
  95 + if (rtpInfo.getBoolean("exist")) {
  96 + result = rtpInfo.getInteger("local_port");
  97 + return result;
  98 + }
  99 + }else if(rtpInfo.getInteger("code") == -2){
92 100 return result;
93 101 }
  102 +
94 103 Map<String, Object> param = new HashMap<>();
95 104 // 推流端口设置0则使用随机端口
96 105 param.put("enable_tcp", 1);
... ... @@ -197,6 +206,7 @@ public class ZLMRTPServerFactory {
197 206 sendRtpItem.setTcp(tcp);
198 207 sendRtpItem.setApp("rtp");
199 208 sendRtpItem.setLocalPort(localPort);
  209 + sendRtpItem.setServerId(userSetting.getServerId());
200 210 sendRtpItem.setMediaServerId(serverItem.getId());
201 211 return sendRtpItem;
202 212 }
... ... @@ -238,6 +248,7 @@ public class ZLMRTPServerFactory {
238 248 sendRtpItem.setChannelId(channelId);
239 249 sendRtpItem.setTcp(tcp);
240 250 sendRtpItem.setLocalPort(localPort);
  251 + sendRtpItem.setServerId(userSetting.getServerId());
241 252 sendRtpItem.setMediaServerId(serverItem.getId());
242 253 return sendRtpItem;
243 254 }
... ... @@ -279,10 +290,10 @@ public class ZLMRTPServerFactory {
279 290 */
280 291 public int totalReaderCount(MediaServerItem mediaServerItem, String app, String streamId) {
281 292 JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
282   - Integer code = mediaInfo.getInteger("code");
283 293 if (mediaInfo == null) {
284 294 return 0;
285 295 }
  296 + Integer code = mediaInfo.getInteger("code");
286 297 if ( code < 0) {
287 298 logger.warn("查询流({}/{})是否有其它观看者时得到: {}", app, streamId, mediaInfo.getString("msg"));
288 299 return -1;
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java
1 1 package com.genersoft.iot.vmp.media.zlm.dto;
2 2  
  3 +/**
  4 + * @author lin
  5 + */
3 6 public interface ChannelOnlineEvent {
4 7  
5   - void run(String app, String stream);
  8 + void run(String app, String stream, String serverId);
6 9 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java
... ... @@ -61,11 +61,16 @@ public class MediaItem {
61 61 private String originUrl;
62 62  
63 63 /**
64   - * 服务器id
  64 + * 流媒体服务器id
65 65 */
66 66 private String mediaServerId;
67 67  
68 68 /**
  69 + * 服务器id
  70 + */
  71 + private String severId;
  72 +
  73 + /**
69 74 * GMT unix系统时间戳,单位秒
70 75 */
71 76 private Long createStamp;
... ... @@ -414,4 +419,12 @@ public class MediaItem {
414 419 public void setStreamInfo(StreamInfo streamInfo) {
415 420 this.streamInfo = streamInfo;
416 421 }
  422 +
  423 + public String getSeverId() {
  424 + return severId;
  425 + }
  426 +
  427 + public void setSeverId(String severId) {
  428 + this.severId = severId;
  429 + }
417 430 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java
... ... @@ -81,6 +81,11 @@ public class StreamPushItem extends GbStream implements Comparable&lt;StreamPushIte
81 81 */
82 82 private String mediaServerId;
83 83  
  84 + /**
  85 + * 使用的服务ID
  86 + */
  87 + private String serverId;
  88 +
84 89 public String getVhost() {
85 90 return vhost;
86 91 }
... ... @@ -219,5 +224,13 @@ public class StreamPushItem extends GbStream implements Comparable&lt;StreamPushIte
219 224 public void setMediaServerId(String mediaServerId) {
220 225 this.mediaServerId = mediaServerId;
221 226 }
  227 +
  228 + public String getServerId() {
  229 + return serverId;
  230 + }
  231 +
  232 + public void setServerId(String serverId) {
  233 + this.serverId = serverId;
  234 + }
222 235 }
223 236  
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMKeepliveTimeoutListener.java
... ... @@ -61,13 +61,12 @@ public class ZLMKeepliveTimeoutListener extends RedisKeyExpirationEventMessageLi
61 61 // 发起http请求验证zlm是否确实无法连接,如果确实无法连接则发送离线事件,否则不作处理
62 62 MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
63 63 JSONObject mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
64   - if (mediaServerConfig == null) {
65   - publisher.zlmOfflineEventPublish(mediaServerId);
66   - }else {
  64 + if (mediaServerConfig != null && mediaServerConfig.getInteger("code") == 0) {
67 65 logger.info("[zlm心跳到期]:{}验证后zlm仍在线,恢复心跳信息", mediaServerId);
68 66 // 添加zlm信息
69 67 mediaServerService.updateMediaServerKeepalive(mediaServerId, mediaServerConfig);
  68 + }else {
  69 + publisher.zlmOfflineEventPublish(mediaServerId);
70 70 }
71   -
72 71 }
73 72 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMStatusEventListener.java
... ... @@ -42,7 +42,7 @@ public class ZLMStatusEventListener {
42 42 logger.info("[ZLM] 上线 ID:" + event.getMediaServerId());
43 43 streamPushService.zlmServerOnline(event.getMediaServerId());
44 44 streamProxyService.zlmServerOnline(event.getMediaServerId());
45   -
  45 + playService.zlmServerOnline(event.getMediaServerId());
46 46 }
47 47  
48 48 @Async
... ...
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
... ... @@ -42,6 +42,8 @@ public interface IPlayService {
42 42  
43 43 StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream);
44 44  
  45 + void zlmServerOnline(String mediaServerId);
  46 +
45 47 void audioBroadcast(Device device, String channelId, int timeout, AudioBroadcastEvent event);
46 48 void stopAudioBroadcast(String deviceId, String channelId);
47 49 }
... ...
src/main/java/com/genersoft/iot/vmp/service/StreamGPSSubscribeTask.java
... ... @@ -23,7 +23,6 @@ public class StreamGPSSubscribeTask {
23 23 private IVideoManagerStorage storager;
24 24  
25 25  
26   -
27 26 @Scheduled(fixedRate = 30 * 1000) //每30秒执行一次
28 27 public void execute(){
29 28 List<GPSMsgInfo> gpsMsgInfo = redisCatchStorage.getAllGpsMsgInfo();
... ...
src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java
1 1 package com.genersoft.iot.vmp.service.bean;
2 2  
  3 +import java.util.stream.Stream;
  4 +
3 5 /**
4 6 * 当上级平台
  7 + * @author lin
5 8 */
6 9 public class MessageForPushChannel {
7 10 /**
... ... @@ -45,6 +48,20 @@ public class MessageForPushChannel {
45 48 */
46 49 private String mediaServerId;
47 50  
  51 + public static MessageForPushChannel getInstance(int type, String app, String stream, String gbId,
  52 + String platFormId, String platFormName, String serverId,
  53 + String mediaServerId){
  54 + MessageForPushChannel messageForPushChannel = new MessageForPushChannel();
  55 + messageForPushChannel.setType(type);
  56 + messageForPushChannel.setGbId(gbId);
  57 + messageForPushChannel.setApp(app);
  58 + messageForPushChannel.setStream(stream);
  59 + messageForPushChannel.setMediaServerId(mediaServerId);
  60 + messageForPushChannel.setPlatFormId(platFormId);
  61 + messageForPushChannel.setPlatFormName(platFormName);
  62 + return messageForPushChannel;
  63 + }
  64 +
48 65  
49 66 public int getType() {
50 67 return type;
... ...
src/main/java/com/genersoft/iot/vmp/service/bean/RequestPushStreamMsg.java 0 → 100644
  1 +package com.genersoft.iot.vmp.service.bean;
  2 +
  3 +/**
  4 + * redis消息:请求下级推送流信息
  5 + * @author lin
  6 + */
  7 +public class RequestPushStreamMsg {
  8 +
  9 +
  10 + /**
  11 + * 下级服务ID
  12 + */
  13 + private String mediaServerId;
  14 +
  15 + /**
  16 + * 流ID
  17 + */
  18 + private String app;
  19 +
  20 + /**
  21 + * 应用名
  22 + */
  23 + private String stream;
  24 +
  25 + /**
  26 + * 目标IP
  27 + */
  28 + private String ip;
  29 +
  30 + /**
  31 + * 目标端口
  32 + */
  33 + private int port;
  34 +
  35 + /**
  36 + * ssrc
  37 + */
  38 + private String ssrc;
  39 +
  40 + /**
  41 + * 是否使用TCP方式
  42 + */
  43 + private boolean tcp;
  44 +
  45 + /**
  46 + * 本地使用的端口
  47 + */
  48 + private int srcPort;
  49 +
  50 + /**
  51 + * 发送时,rtp的pt(uint8_t),不传时默认为96
  52 + */
  53 + private int pt;
  54 +
  55 + /**
  56 + * 发送时,rtp的负载类型。为true时,负载为ps;为false时,为es;
  57 + */
  58 + private boolean ps;
  59 +
  60 + /**
  61 + * 是否只有音频
  62 + */
  63 + private boolean onlyAudio;
  64 +
  65 +
  66 + public static RequestPushStreamMsg getInstance(String mediaServerId, String app, String stream, String ip, int port, String ssrc,
  67 + boolean tcp, int srcPort, int pt, boolean ps, boolean onlyAudio) {
  68 + RequestPushStreamMsg requestPushStreamMsg = new RequestPushStreamMsg();
  69 + requestPushStreamMsg.setMediaServerId(mediaServerId);
  70 + requestPushStreamMsg.setApp(app);
  71 + requestPushStreamMsg.setStream(stream);
  72 + requestPushStreamMsg.setIp(ip);
  73 + requestPushStreamMsg.setPort(port);
  74 + requestPushStreamMsg.setSsrc(ssrc);
  75 + requestPushStreamMsg.setTcp(tcp);
  76 + requestPushStreamMsg.setSrcPort(srcPort);
  77 + requestPushStreamMsg.setPt(pt);
  78 + requestPushStreamMsg.setPs(ps);
  79 + requestPushStreamMsg.setOnlyAudio(onlyAudio);
  80 + return requestPushStreamMsg;
  81 + }
  82 +
  83 + public String getMediaServerId() {
  84 + return mediaServerId;
  85 + }
  86 +
  87 + public void setMediaServerId(String mediaServerId) {
  88 + this.mediaServerId = mediaServerId;
  89 + }
  90 +
  91 + public String getApp() {
  92 + return app;
  93 + }
  94 +
  95 + public void setApp(String app) {
  96 + this.app = app;
  97 + }
  98 +
  99 + public String getStream() {
  100 + return stream;
  101 + }
  102 +
  103 + public void setStream(String stream) {
  104 + this.stream = stream;
  105 + }
  106 +
  107 + public String getIp() {
  108 + return ip;
  109 + }
  110 +
  111 + public void setIp(String ip) {
  112 + this.ip = ip;
  113 + }
  114 +
  115 + public int getPort() {
  116 + return port;
  117 + }
  118 +
  119 + public void setPort(int port) {
  120 + this.port = port;
  121 + }
  122 +
  123 + public String getSsrc() {
  124 + return ssrc;
  125 + }
  126 +
  127 + public void setSsrc(String ssrc) {
  128 + this.ssrc = ssrc;
  129 + }
  130 +
  131 + public boolean isTcp() {
  132 + return tcp;
  133 + }
  134 +
  135 + public void setTcp(boolean tcp) {
  136 + this.tcp = tcp;
  137 + }
  138 +
  139 + public int getSrcPort() {
  140 + return srcPort;
  141 + }
  142 +
  143 + public void setSrcPort(int srcPort) {
  144 + this.srcPort = srcPort;
  145 + }
  146 +
  147 + public int getPt() {
  148 + return pt;
  149 + }
  150 +
  151 + public void setPt(int pt) {
  152 + this.pt = pt;
  153 + }
  154 +
  155 + public boolean isPs() {
  156 + return ps;
  157 + }
  158 +
  159 + public void setPs(boolean ps) {
  160 + this.ps = ps;
  161 + }
  162 +
  163 + public boolean isOnlyAudio() {
  164 + return onlyAudio;
  165 + }
  166 +
  167 + public void setOnlyAudio(boolean onlyAudio) {
  168 + this.onlyAudio = onlyAudio;
  169 + }
  170 +}
... ...
src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java 0 → 100644
  1 +package com.genersoft.iot.vmp.service.bean;
  2 +
  3 +/**
  4 + * redis消息:请求下级回复推送信息
  5 + * @author lin
  6 + */
  7 +public class RequestSendItemMsg {
  8 +
  9 + /**
  10 + * 下级服务ID
  11 + */
  12 + private String serverId;
  13 +
  14 + /**
  15 + * 下级服务ID
  16 + */
  17 + private String mediaServerId;
  18 +
  19 + /**
  20 + * 流ID
  21 + */
  22 + private String app;
  23 +
  24 + /**
  25 + * 应用名
  26 + */
  27 + private String stream;
  28 +
  29 + /**
  30 + * 目标IP
  31 + */
  32 + private String ip;
  33 +
  34 + /**
  35 + * 目标端口
  36 + */
  37 + private int port;
  38 +
  39 + /**
  40 + * ssrc
  41 + */
  42 + private String ssrc;
  43 +
  44 + /**
  45 + * 平台国标编号
  46 + */
  47 + private String platformId;
  48 +
  49 + /**
  50 + * 平台名称
  51 + */
  52 + private String platformName;
  53 +
  54 + /**
  55 + * 通道ID
  56 + */
  57 + private String channelId;
  58 +
  59 +
  60 + /**
  61 + * 是否使用TCP
  62 + */
  63 + private Boolean isTcp;
  64 +
  65 +
  66 +
  67 +
  68 + public static RequestSendItemMsg getInstance(String serverId, String mediaServerId, String app, String stream, String ip, int port,
  69 + String ssrc, String platformId, String channelId, Boolean isTcp, String platformName) {
  70 + RequestSendItemMsg requestSendItemMsg = new RequestSendItemMsg();
  71 + requestSendItemMsg.setServerId(serverId);
  72 + requestSendItemMsg.setMediaServerId(mediaServerId);
  73 + requestSendItemMsg.setApp(app);
  74 + requestSendItemMsg.setStream(stream);
  75 + requestSendItemMsg.setIp(ip);
  76 + requestSendItemMsg.setPort(port);
  77 + requestSendItemMsg.setSsrc(ssrc);
  78 + requestSendItemMsg.setPlatformId(platformId);
  79 + requestSendItemMsg.setPlatformName(platformName);
  80 + requestSendItemMsg.setChannelId(channelId);
  81 + requestSendItemMsg.setTcp(isTcp);
  82 +
  83 + return requestSendItemMsg;
  84 + }
  85 +
  86 + public String getServerId() {
  87 + return serverId;
  88 + }
  89 +
  90 + public void setServerId(String serverId) {
  91 + this.serverId = serverId;
  92 + }
  93 +
  94 + public String getMediaServerId() {
  95 + return mediaServerId;
  96 + }
  97 +
  98 + public void setMediaServerId(String mediaServerId) {
  99 + this.mediaServerId = mediaServerId;
  100 + }
  101 +
  102 + public String getApp() {
  103 + return app;
  104 + }
  105 +
  106 + public void setApp(String app) {
  107 + this.app = app;
  108 + }
  109 +
  110 + public String getStream() {
  111 + return stream;
  112 + }
  113 +
  114 + public void setStream(String stream) {
  115 + this.stream = stream;
  116 + }
  117 +
  118 + public String getIp() {
  119 + return ip;
  120 + }
  121 +
  122 + public void setIp(String ip) {
  123 + this.ip = ip;
  124 + }
  125 +
  126 + public int getPort() {
  127 + return port;
  128 + }
  129 +
  130 + public void setPort(int port) {
  131 + this.port = port;
  132 + }
  133 +
  134 + public String getSsrc() {
  135 + return ssrc;
  136 + }
  137 +
  138 + public void setSsrc(String ssrc) {
  139 + this.ssrc = ssrc;
  140 + }
  141 +
  142 + public String getPlatformId() {
  143 + return platformId;
  144 + }
  145 +
  146 + public void setPlatformId(String platformId) {
  147 + this.platformId = platformId;
  148 + }
  149 +
  150 + public String getPlatformName() {
  151 + return platformName;
  152 + }
  153 +
  154 + public void setPlatformName(String platformName) {
  155 + this.platformName = platformName;
  156 + }
  157 +
  158 + public String getChannelId() {
  159 + return channelId;
  160 + }
  161 +
  162 + public void setChannelId(String channelId) {
  163 + this.channelId = channelId;
  164 + }
  165 +
  166 + public Boolean getTcp() {
  167 + return isTcp;
  168 + }
  169 +
  170 + public void setTcp(Boolean tcp) {
  171 + isTcp = tcp;
  172 + }
  173 +}
... ...
src/main/java/com/genersoft/iot/vmp/service/bean/ResponseSendItemMsg.java 0 → 100644
  1 +package com.genersoft.iot.vmp.service.bean;
  2 +
  3 +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
  4 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  5 +
  6 +/**
  7 + * redis消息:下级回复推送信息
  8 + * @author lin
  9 + */
  10 +public class ResponseSendItemMsg {
  11 +
  12 + private SendRtpItem sendRtpItem;
  13 +
  14 + private MediaServerItem mediaServerItem;
  15 +
  16 + public SendRtpItem getSendRtpItem() {
  17 + return sendRtpItem;
  18 + }
  19 +
  20 + public void setSendRtpItem(SendRtpItem sendRtpItem) {
  21 + this.sendRtpItem = sendRtpItem;
  22 + }
  23 +
  24 + public MediaServerItem getMediaServerItem() {
  25 + return mediaServerItem;
  26 + }
  27 +
  28 + public void setMediaServerItem(MediaServerItem mediaServerItem) {
  29 + this.mediaServerItem = mediaServerItem;
  30 + }
  31 +}
... ...
src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java 0 → 100644
  1 +package com.genersoft.iot.vmp.service.bean;
  2 +
  3 +/**
  4 + * @author lin
  5 + */
  6 +public class WvpRedisMsg {
  7 +
  8 + public static WvpRedisMsg getInstance(String fromId, String toId, String type, String cmd, String serial, String content){
  9 + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
  10 + wvpRedisMsg.setFromId(fromId);
  11 + wvpRedisMsg.setToId(toId);
  12 + wvpRedisMsg.setType(type);
  13 + wvpRedisMsg.setCmd(cmd);
  14 + wvpRedisMsg.setSerial(serial);
  15 + wvpRedisMsg.setContent(content);
  16 + return wvpRedisMsg;
  17 + }
  18 +
  19 + private String fromId;
  20 +
  21 + private String toId;
  22 + /**
  23 + * req 请求, res 回复
  24 + */
  25 + private String type;
  26 + private String cmd;
  27 +
  28 + /**
  29 + * 消息的ID
  30 + */
  31 + private String serial;
  32 + private Object content;
  33 +
  34 + private final static String requestTag = "req";
  35 + private final static String responseTag = "res";
  36 +
  37 + public static WvpRedisMsg getRequestInstance(String fromId, String toId, String cmd, String serial, Object content) {
  38 + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
  39 + wvpRedisMsg.setType(requestTag);
  40 + wvpRedisMsg.setFromId(fromId);
  41 + wvpRedisMsg.setToId(toId);
  42 + wvpRedisMsg.setCmd(cmd);
  43 + wvpRedisMsg.setSerial(serial);
  44 + wvpRedisMsg.setContent(content);
  45 + return wvpRedisMsg;
  46 + }
  47 +
  48 + public static WvpRedisMsg getResponseInstance() {
  49 + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
  50 + wvpRedisMsg.setType(responseTag);
  51 + return wvpRedisMsg;
  52 + }
  53 +
  54 + public static WvpRedisMsg getResponseInstance(String fromId, String toId, String cmd, String serial, Object content) {
  55 + WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
  56 + wvpRedisMsg.setType(responseTag);
  57 + wvpRedisMsg.setFromId(fromId);
  58 + wvpRedisMsg.setToId(toId);
  59 + wvpRedisMsg.setCmd(cmd);
  60 + wvpRedisMsg.setSerial(serial);
  61 + wvpRedisMsg.setContent(content);
  62 + return wvpRedisMsg;
  63 + }
  64 +
  65 + public static boolean isRequest(WvpRedisMsg wvpRedisMsg) {
  66 + return requestTag.equals(wvpRedisMsg.getType());
  67 + }
  68 +
  69 + public String getSerial() {
  70 + return serial;
  71 + }
  72 +
  73 + public void setSerial(String serial) {
  74 + this.serial = serial;
  75 + }
  76 +
  77 + public String getFromId() {
  78 + return fromId;
  79 + }
  80 +
  81 + public void setFromId(String fromId) {
  82 + this.fromId = fromId;
  83 + }
  84 +
  85 + public String getToId() {
  86 + return toId;
  87 + }
  88 +
  89 + public void setToId(String toId) {
  90 + this.toId = toId;
  91 + }
  92 +
  93 + public String getType() {
  94 + return type;
  95 + }
  96 +
  97 + public void setType(String type) {
  98 + this.type = type;
  99 + }
  100 +
  101 + public String getCmd() {
  102 + return cmd;
  103 + }
  104 +
  105 + public void setCmd(String cmd) {
  106 + this.cmd = cmd;
  107 + }
  108 +
  109 + public Object getContent() {
  110 + return content;
  111 + }
  112 +
  113 + public void setContent(Object content) {
  114 + this.content = content;
  115 + }
  116 +}
... ...
src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsgCmd.java 0 → 100644
  1 +package com.genersoft.iot.vmp.service.bean;
  2 +
  3 +/**
  4 + * @author lin
  5 + */
  6 +
  7 +public class WvpRedisMsgCmd {
  8 +
  9 + public static final String GET_SEND_ITEM = "GetSendItem";
  10 + public static final String REQUEST_PUSH_STREAM = "RequestPushStream";
  11 +
  12 +}
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
... ... @@ -2,16 +2,21 @@ package com.genersoft.iot.vmp.service.impl;
2 2  
3 3 import com.genersoft.iot.vmp.conf.DynamicTask;
4 4 import com.genersoft.iot.vmp.gb28181.bean.Device;
  5 +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
5 6 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
6 7 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
  8 +import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
7 9 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
8 10 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
  11 +import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
9 12 import com.genersoft.iot.vmp.service.IDeviceService;
10 13 import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
11 14 import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
12 15 import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
13 16 import com.genersoft.iot.vmp.service.IMediaServerService;
14 17 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  18 +import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
  19 +import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
15 20 import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
16 21 import com.genersoft.iot.vmp.utils.DateUtil;
17 22 import org.slf4j.Logger;
... ... @@ -50,6 +55,12 @@ public class DeviceServiceImpl implements IDeviceService {
50 55 private DeviceMapper deviceMapper;
51 56  
52 57 @Autowired
  58 + private DeviceChannelMapper deviceChannelMapper;
  59 +
  60 + @Autowired
  61 + private IVideoManagerStorage storage;
  62 +
  63 + @Autowired
53 64 private ISIPCommander commander;
54 65  
55 66 @Autowired
... ... @@ -68,7 +79,6 @@ public class DeviceServiceImpl implements IDeviceService {
68 79 if (deviceInRedis != null && deviceInDb == null) {
69 80 // redis 存在脏数据
70 81 redisCatchStorage.clearCatchByDeviceId(device.getDeviceId());
71   -
72 82 }
73 83 device.setUpdateTime(now);
74 84 device.setOnline(1);
... ... @@ -77,13 +87,15 @@ public class DeviceServiceImpl implements IDeviceService {
77 87 if (device.getCreateTime() == null) {
78 88 device.setCreateTime(now);
79 89 logger.info("[设备上线,首次注册]: {},查询设备信息以及通道信息", device.getDeviceId());
  90 + deviceMapper.add(device);
  91 + redisCatchStorage.updateDevice(device);
80 92 commander.deviceInfoQuery(device);
81 93 sync(device);
82   - deviceMapper.add(device);
83 94 }else {
84 95 deviceMapper.update(device);
  96 + redisCatchStorage.updateDevice(device);
85 97 }
86   - redisCatchStorage.updateDevice(device);
  98 +
87 99 // 上线添加订阅
88 100 if (device.getSubscribeCycleForCatalog() > 0) {
89 101 // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
... ... @@ -94,7 +106,6 @@ public class DeviceServiceImpl implements IDeviceService {
94 106 }
95 107 // 刷新过期任务
96 108 String registerExpireTaskKey = registerExpireTaskKeyPrefix + device.getDeviceId();
97   - dynamicTask.stop(registerExpireTaskKey);
98 109 dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId()), device.getExpires() * 1000);
99 110 }
100 111  
... ... @@ -143,8 +154,16 @@ public class DeviceServiceImpl implements IDeviceService {
143 154 if (device == null || device.getSubscribeCycleForCatalog() < 0) {
144 155 return false;
145 156 }
146   - logger.info("移除目录订阅: {}", device.getDeviceId());
147   - dynamicTask.stop(device.getDeviceId() + "catalog");
  157 + logger.info("[移除目录订阅]: {}", device.getDeviceId());
  158 + String taskKey = device.getDeviceId() + "catalog";
  159 + if (device.getOnline() == 1) {
  160 + Runnable runnable = dynamicTask.get(taskKey);
  161 + if (runnable instanceof ISubscribeTask) {
  162 + ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
  163 + subscribeTask.stop();
  164 + }
  165 + }
  166 + dynamicTask.stop(taskKey);
148 167 return true;
149 168 }
150 169  
... ... @@ -168,8 +187,16 @@ public class DeviceServiceImpl implements IDeviceService {
168 187 if (device == null || device.getSubscribeCycleForCatalog() < 0) {
169 188 return false;
170 189 }
171   - logger.info("移除移动位置订阅: {}", device.getDeviceId());
172   - dynamicTask.stop(device.getDeviceId() + "mobile_position");
  190 + logger.info("[移除移动位置订阅]: {}", device.getDeviceId());
  191 + String taskKey = device.getDeviceId() + "mobile_position";
  192 + if (device.getOnline() == 1) {
  193 + Runnable runnable = dynamicTask.get(taskKey);
  194 + if (runnable instanceof ISubscribeTask) {
  195 + ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
  196 + subscribeTask.stop();
  197 + }
  198 + }
  199 + dynamicTask.stop(taskKey);
173 200 return true;
174 201 }
175 202  
... ... @@ -275,6 +302,10 @@ public class DeviceServiceImpl implements IDeviceService {
275 302 removeMobilePositionSubscribe(deviceInStore);
276 303 }
277 304 }
  305 + // 坐标系变化,需要重新计算GCJ02坐标和WGS84坐标
  306 + if (!deviceInStore.getGeoCoordSys().equals(device.getGeoCoordSys())) {
  307 + updateDeviceChannelGeoCoordSys(device);
  308 + }
278 309  
279 310 String now = DateUtil.getNow();
280 311 device.setUpdateTime(now);
... ... @@ -282,6 +313,32 @@ public class DeviceServiceImpl implements IDeviceService {
282 313 device.setUpdateTime(DateUtil.getNow());
283 314 if (deviceMapper.update(device) > 0) {
284 315 redisCatchStorage.updateDevice(device);
  316 +
285 317 }
286 318 }
  319 +
  320 + /**
  321 + * 更新通道坐标系
  322 + */
  323 + private void updateDeviceChannelGeoCoordSys(Device device) {
  324 + List<DeviceChannel> deviceChannels = deviceChannelMapper.getAllChannelWithCoordinate(device.getDeviceId());
  325 + if (deviceChannels.size() > 0) {
  326 + for (DeviceChannel deviceChannel : deviceChannels) {
  327 + if ("WGS84".equals(device.getGeoCoordSys())) {
  328 + deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude());
  329 + deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude());
  330 + Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude());
  331 + deviceChannel.setLongitudeGcj02(position[0]);
  332 + deviceChannel.setLatitudeGcj02(position[1]);
  333 + }else if ("GCJ02".equals(device.getGeoCoordSys())) {
  334 + deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude());
  335 + deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude());
  336 + Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude());
  337 + deviceChannel.setLongitudeWgs84(position[0]);
  338 + deviceChannel.setLatitudeWgs84(position[1]);
  339 + }
  340 + }
  341 + }
  342 + storage.updateChannels(device.getDeviceId(), deviceChannels);
  343 + }
287 344 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java
... ... @@ -106,7 +106,8 @@ public class GbStreamServiceImpl implements IGbStreamService {
106 106 deviceChannel.setStatus(1);
107 107 deviceChannel.setParentId(catalogId ==null?gbStream.getCatalogId():catalogId);
108 108 deviceChannel.setRegisterWay(1);
109   - if (catalogId.length() <= 10) { // 父节点是行政区划,则设置CivilCode使用此行政区划
  109 + if (catalogId.length() > 0 && catalogId.length() <= 10) {
  110 + // 父节点是行政区划,则设置CivilCode使用此行政区划
110 111 deviceChannel.setCivilCode(catalogId);
111 112 }else {
112 113 deviceChannel.setCivilCode(platform.getAdministrativeDivision());
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
... ... @@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject;
6 6 import com.genersoft.iot.vmp.common.VideoManagerConstants;
7 7 import com.genersoft.iot.vmp.conf.SipConfig;
8 8 import com.genersoft.iot.vmp.conf.UserSetting;
  9 +import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
9 10 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
10 11 import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
11 12 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
... ... @@ -35,7 +36,9 @@ import org.springframework.util.StringUtils;
35 36  
36 37 import java.text.ParseException;
37 38 import java.text.SimpleDateFormat;
  39 +import java.time.LocalDateTime;
38 40 import java.util.*;
  41 +import java.util.stream.Collectors;
39 42  
40 43 /**
41 44 * 媒体服务器节点管理
... ... @@ -189,6 +192,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
189 192 public void clearRTPServer(MediaServerItem mediaServerItem) {
190 193 mediaServerItem.setSsrcConfig(new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getDomain()));
191 194 redisUtil.zAdd(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId(), mediaServerItem.getId(), 0);
  195 +
192 196 }
193 197  
194 198  
... ... @@ -229,11 +233,10 @@ public class MediaServerServiceImpl implements IMediaServerService {
229 233 }
230 234 result.sort((serverItem1, serverItem2)->{
231 235 int sortResult = 0;
232   - try {
233   - sortResult = DateUtil.format.parse(serverItem1.getCreateTime()).compareTo(DateUtil.format.parse(serverItem2.getCreateTime()));
234   - } catch (ParseException e) {
235   - e.printStackTrace();
236   - }
  236 + LocalDateTime localDateTime1 = LocalDateTime.parse(serverItem1.getCreateTime(), DateUtil.formatter);
  237 + LocalDateTime localDateTime2 = LocalDateTime.parse(serverItem2.getCreateTime(), DateUtil.formatter);
  238 +
  239 + sortResult = localDateTime1.compareTo(localDateTime2);
237 240 return sortResult;
238 241 });
239 242 return result;
... ... @@ -495,14 +498,14 @@ public class MediaServerServiceImpl implements IMediaServerService {
495 498 param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline
496 499 param.put("ffmpeg.cmd","%s -fflags nobuffer -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s");
497 500 param.put("hook.enable","1");
498   - param.put("hook.on_flow_report","");
  501 + param.put("hook.on_flow_report",String.format("%s/on_flow_report", hookPrex));
499 502 param.put("hook.on_play",String.format("%s/on_play", hookPrex));
500   - param.put("hook.on_http_access","");
  503 + param.put("hook.on_http_access",String.format("%s/on_http_access", hookPrex));
501 504 param.put("hook.on_publish", String.format("%s/on_publish", hookPrex));
502 505 param.put("hook.on_record_mp4",recordHookPrex != null? String.format("%s/on_record_mp4", recordHookPrex): "");
503   - param.put("hook.on_record_ts","");
504   - param.put("hook.on_rtsp_auth","");
505   - param.put("hook.on_rtsp_realm","");
  506 + param.put("hook.on_record_ts",String.format("%s/on_record_ts", hookPrex));
  507 + param.put("hook.on_rtsp_auth",String.format("%s/on_rtsp_auth", hookPrex));
  508 + param.put("hook.on_rtsp_realm",String.format("%s/on_rtsp_realm", hookPrex));
506 509 param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex));
507 510 param.put("hook.on_shell_login",String.format("%s/on_shell_login", hookPrex));
508 511 param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex));
... ...