Просмотр исходного кода

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
648540858 3 лет назад
Родитель
Сommit
26739237e2
100 измененных файлов с 3338 добавлено и 1349 удалено
  1. 3 0
      DOCKERFILE
  2. 4 1
      README.md
  3. 5 4
      pom.xml
  4. 1 0
      sql/mysql.sql
  5. 25 1
      sql/update.sql
  6. 1 0
      src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
  7. 6 6
      src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java
  8. 23 1
      src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
  9. 12 2
      src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java
  10. 3 0
      src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java
  11. 12 7
      src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
  12. 18 1
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  13. 52 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
  14. 44 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/HandlerCatchData.java
  15. 13 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java
  16. 13 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java
  17. 8 2
      src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
  18. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java
  19. 11 5
      src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
  20. 3 1
      src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java
  21. 6 12
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java
  22. 8 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  23. 8 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
  24. 69 16
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  25. 120 43
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  26. 0 23
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorAbstract.java
  27. 39 52
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
  28. 3 7
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
  29. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java
  30. 694 560
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  31. 68 40
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java
  32. 4 17
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
  33. 2 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java
  34. 143 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java
  35. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java
  36. 12 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java
  37. 2 4
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
  38. 37 8
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java
  39. 87 56
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java
  40. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java
  41. 87 60
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
  42. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java
  43. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/CancelResponseProcessor.java
  44. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
  45. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java
  46. 29 1
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
  47. 31 16
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  48. 4 3
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
  49. 31 7
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
  50. 14 3
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
  51. 4 1
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java
  52. 14 1
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java
  53. 13 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java
  54. 3 4
      src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMKeepliveTimeoutListener.java
  55. 1 1
      src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMStatusEventListener.java
  56. 2 0
      src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
  57. 0 1
      src/main/java/com/genersoft/iot/vmp/service/StreamGPSSubscribeTask.java
  58. 17 0
      src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java
  59. 170 0
      src/main/java/com/genersoft/iot/vmp/service/bean/RequestPushStreamMsg.java
  60. 173 0
      src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java
  61. 31 0
      src/main/java/com/genersoft/iot/vmp/service/bean/ResponseSendItemMsg.java
  62. 116 0
      src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java
  63. 12 0
      src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsgCmd.java
  64. 65 8
      src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
  65. 2 1
      src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java
  66. 13 10
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
  67. 55 50
      src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
  68. 377 0
      src/main/java/com/genersoft/iot/vmp/service/impl/RedisGbPlayMsgListener.java
  69. 11 4
      src/main/java/com/genersoft/iot/vmp/service/impl/RedisGPSMsgListener.java
  70. 83 0
      src/main/java/com/genersoft/iot/vmp/service/impl/RedisStreamMsgListener.java
  71. 1 0
      src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
  72. 9 0
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
  73. 23 7
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
  74. 3 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
  75. 2 2
      src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java
  76. 4 4
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  77. 15 2
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  78. 36 9
      src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java
  79. 6 16
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java
  80. 29 6
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java
  81. 2 2
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java
  82. 1 8
      src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java
  83. 2 2
      src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java
  84. 1 1
      src/main/resources/all-application.yml
  85. 1 1
      src/main/resources/application-dev.yml
  86. 1 1
      src/main/resources/application-docker.yml
  87. 15 8
      src/test/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImplTest.java
  88. 1 1
      web_src/package.json
  89. 5 1
      web_src/src/App.vue
  90. 7 9
      web_src/src/components/CloudRecord.vue
  91. 30 28
      web_src/src/components/DeviceList.vue
  92. 1 1
      web_src/src/components/MediaServerManger.vue
  93. 18 14
      web_src/src/components/ParentPlatformList.vue
  94. 28 23
      web_src/src/components/PushVideoList.vue
  95. 27 23
      web_src/src/components/StreamProxyList.vue
  96. 85 78
      web_src/src/components/channelList.vue
  97. 18 6
      web_src/src/components/common/DeviceTree.vue
  98. 40 39
      web_src/src/components/common/jessibuca.vue
  99. 2 4
      web_src/src/components/control.vue
  100. 0 0
      web_src/src/components/dialog/deviceEdit.vue

+ 3 - 0
DOCKERFILE

@@ -1,3 +1,6 @@
+#很久没维护了,已经与定前版本不匹配
+
+
 FROM ubuntu:20.04 AS build
 
 ARG DEBIAN_FRONTEND=noninteractive

+ 4 - 1
README.md

@@ -107,6 +107,8 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
 - [X] 云端录像(需要部署单独服务配合使用)
 - [X] 多流媒体节点,自动选择负载最低的节点使用。
 - [X] WEB端支持播放H264与H265,音频支持G.711A/G.711U/AAC,覆盖国标常用编码格式。
+- [X] 支持电子地图。
+- [X] 支持接入WGS84和GCJ02两种坐标系。
 
 [//]: # (# docker快速体验)
 
@@ -143,7 +145,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
 
 # 合作
 目前很多打着合作的幌子来私聊的,其实大家大可不必,目前作者没有精力,你有问题可以付费找我解答,也可以提PR
-,如果对代码有建议可以提ISSUE;也可以加群一起聊聊。我们欢迎所有有兴趣但遇到项目中来的人。
+,如果对代码有建议可以提ISSUE;也可以加群一起聊聊。我们欢迎所有有兴趣参与到项目中来的人。
 
 
 
@@ -163,6 +165,7 @@ QQ私信一般不回, 精力有限.欢迎大家在群里讨论.觉得项目对
 [hotcoffie](https://github.com/hotcoffie) [xiaomu](https://github.com/nikmu) [TristingChen](https://github.com/TristingChen)
 [chenparty](https://github.com/chenparty) [Hotleave](https://github.com/hotleave) [ydwxb](https://github.com/ydwxb)
 [ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666)
+[mk1990](https://github.com/mk1990)
 
 ps: 刚增加了这个名单,肯定遗漏了一些大佬,欢迎大佬联系我添加。
 

+ 5 - 4
pom.xml

@@ -11,7 +11,7 @@
 
 	<groupId>com.genersoft</groupId>
 	<artifactId>wvp-pro</artifactId>
-	<version>2.2.1</version>
+	<version>2.3.1</version>
 	<name>web video platform</name>
 	<description>国标28181视频平台</description>
 
@@ -159,9 +159,10 @@
 		<dependency>
 			<groupId>com.alibaba</groupId>
 			<artifactId>fastjson</artifactId>
-			<version>1.2.73</version>
+			<version>1.2.83</version>
 		</dependency>
 
+
 		<!-- okhttp -->
 		<dependency>
 			<groupId>com.squareup.okhttp3</groupId>
@@ -180,9 +181,9 @@
 
 		<!-- okhttp-digest -->
 		<dependency>
-			<groupId>com.burgstaller</groupId>
+			<groupId>io.github.rburgst</groupId>
 			<artifactId>okhttp-digest</artifactId>
-			<version>2.1</version>
+			<version>2.5</version>
 		</dependency>
 
 		<!-- https://mvnrepository.com/artifact/net.sf.kxml/kxml2 -->

+ 1 - 0
sql/mysql.sql

@@ -471,6 +471,7 @@ CREATE TABLE `stream_push` (
                                `createStamp` bigint(20) DEFAULT NULL,
                                `aliveSecond` int(11) DEFAULT NULL,
                                `mediaServerId` varchar(50) DEFAULT NULL,
+                               `serverId` varchar(50) not NULL,
                                PRIMARY KEY (`id`),
                                UNIQUE KEY `stream_push_pk` (`app`,`stream`)
 ) ENGINE=InnoDB AUTO_INCREMENT=300838 DEFAULT CHARSET=utf8mb4;

+ 25 - 1
sql/update.sql

@@ -1,5 +1,29 @@
+alter table parent_platform
+    add startOfflinePush int default 0 null;
+
+alter table parent_platform
+    add administrativeDivision varchar(50) not null;
+
+alter table parent_platform
+    add catalogGroup int default 1 null;
+
 alter table device
     add audioChannelForReceive VARCHAR(50) null;
 
 alter table device
-    add audioChannelForSend VARCHAR(50) null;
+    add audioChannelForSend VARCHAR(50) null;
+
+alter table stream_push
+    add serverId varchar(50) not null;
+alter table device
+    add geoCoordSys varchar(50) not null;
+update device set device.geoCoordSys='WGS84';
+alter table device_channel
+    add longitudeGcj02 double default null;
+alter table device_channel
+    add latitudeGcj02 double default null;
+alter table device_channel
+    add longitudeWgs84 double default null;
+alter table device_channel
+    add latitudeWgs84 double default null;
+

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java

@@ -97,4 +97,5 @@ public class VideoManagerConstants {
 	//**************************    第三方  ****************************************
 	public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_";
 	public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_";
+
 }

+ 6 - 6
src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java

@@ -103,12 +103,12 @@ public class DynamicTask {
 
     public void stop(String key) {
         if (futureMap.get(key) != null && !futureMap.get(key).isCancelled()) {
-            futureMap.get(key).cancel(true);
-            Runnable runnable = runnableMap.get(key);
-            if (runnable instanceof ISubscribeTask) {
-                ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
-                subscribeTask.stop();
-            }
+//            Runnable runnable = runnableMap.get(key);
+//            if (runnable instanceof ISubscribeTask) {
+//                ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
+//                subscribeTask.stop();
+//            }
+            futureMap.get(key).cancel(false);
         }
     }
 

+ 23 - 1
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java

@@ -6,6 +6,10 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.util.StringUtils;
 
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.regex.Pattern;
+
 
 @Configuration("mediaConfig")
 public class MediaConfig{
@@ -161,7 +165,18 @@ public class MediaConfig{
         if (StringUtils.isEmpty(sdpIp)){
             return ip;
         }else {
-            return sdpIp;
+            if (isValidIPAddress(sdpIp)) {
+                return sdpIp;
+            }else {
+                // 按照域名解析
+                String hostAddress = null;
+                try {
+                    hostAddress = InetAddress.getByName(sdpIp).getHostAddress();
+                } catch (UnknownHostException e) {
+                    throw new RuntimeException(e);
+                }
+                return hostAddress;
+            }
         }
     }
 
@@ -211,4 +226,11 @@ public class MediaConfig{
         return mediaServerItem;
     }
 
+    private boolean isValidIPAddress(String ipAddress) {
+        if ((ipAddress != null) && (!ipAddress.isEmpty())) {
+            return Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$", ipAddress);
+        }
+        return false;
+    }
+
 }

+ 12 - 2
src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java

@@ -2,7 +2,9 @@ package com.genersoft.iot.vmp.conf;
 
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.service.impl.RedisAlarmMsgListener;
-import com.genersoft.iot.vmp.service.impl.RedisGPSMsgListener;
+import com.genersoft.iot.vmp.service.impl.RedisGpsMsgListener;
+import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener;
+import com.genersoft.iot.vmp.service.impl.RedisStreamMsgListener;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
@@ -47,11 +49,17 @@ public class RedisConfig extends CachingConfigurerSupport {
 	private int poolMaxWait;
 
 	@Autowired
-	private RedisGPSMsgListener redisGPSMsgListener;
+	private RedisGpsMsgListener redisGPSMsgListener;
 
 	@Autowired
 	private RedisAlarmMsgListener redisAlarmMsgListener;
 
+	@Autowired
+	private RedisStreamMsgListener redisStreamMsgListener;
+
+	@Autowired
+	private RedisGbPlayMsgListener redisGbPlayMsgListener;
+
 	@Bean
 	public JedisPool jedisPool() {
 		if (StringUtils.isBlank(password)) {
@@ -98,6 +106,8 @@ public class RedisConfig extends CachingConfigurerSupport {
         container.setConnectionFactory(connectionFactory);
 		container.addMessageListener(redisGPSMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_GPS));
 		container.addMessageListener(redisAlarmMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE));
+		container.addMessageListener(redisStreamMsgListener, new PatternTopic(VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + "PUSH"));
+		container.addMessageListener(redisGbPlayMsgListener, new PatternTopic(RedisGbPlayMsgListener.WVP_PUSH_STREAM_KEY));
         return container;
     }
 

+ 3 - 0
src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java

@@ -11,6 +11,9 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 
+/**
+ * @author lin
+ */
 @Component
 public class LoginSuccessHandler implements AuthenticationSuccessHandler {
 

+ 12 - 7
src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java

@@ -20,6 +20,7 @@ import java.util.List;
 
 /**
  * 配置Spring Security
+ * @author lin
  */
 @Configuration
 @EnableWebSecurity
@@ -132,15 +133,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .anyRequest().authenticated()
                 // 异常处理(权限拒绝、登录失效等)
                 .and().exceptionHandling()
-                .authenticationEntryPoint(anonymousAuthenticationEntryPoint)//匿名用户访问无权限资源时的异常处理
+                //匿名用户访问无权限资源时的异常处理
+                .authenticationEntryPoint(anonymousAuthenticationEntryPoint)
 //                .accessDeniedHandler(accessDeniedHandler)//登录用户没有权限访问资源
-                // 登入
-                .and().formLogin().permitAll()//允许所有用户
-                .successHandler(loginSuccessHandler)//登录成功处理逻辑
-                .failureHandler(loginFailureHandler)//登录失败处理逻辑
+                // 登入 允许所有用户
+                .and().formLogin().permitAll()
+                //登录成功处理逻辑
+                .successHandler(loginSuccessHandler)
+                //登录失败处理逻辑
+                .failureHandler(loginFailureHandler)
                 // 登出
-                .and().logout().logoutUrl("/api/user/logout").permitAll()//允许所有用户
-                .logoutSuccessHandler(logoutHandler)//登出成功处理逻辑
+                .and().logout().logoutUrl("/api/user/logout").permitAll()
+                //登出成功处理逻辑
+                .logoutSuccessHandler(logoutHandler)
                 .deleteCookies("JSESSIONID")
                 // 会话管理
 //                .and().sessionManagement().invalidSessionStrategy(invalidSessionHandler) // 超时处理

+ 18 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java

@@ -1,6 +1,10 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
 
+/**
+ * 国标设备/平台
+ * @author lin
+ */
 public class Device {
 
 	/**
@@ -127,7 +131,12 @@ public class Device {
 	/**
 	 * 是否开启ssrc校验,默认关闭,开启可以防止串流
 	 */
-	private boolean ssrcCheck;
+	private boolean ssrcCheck = true;
+
+	/**
+	 * 地理坐标系, 目前支持 WGS84,GCJ02 TODO CGCS2000
+	 */
+	private String geoCoordSys;
 
 
 	public String getDeviceId() {
@@ -322,4 +331,12 @@ public class Device {
 		this.ssrcCheck = ssrcCheck;
 	}
 
+	public String getGeoCoordSys() {
+		return geoCoordSys;
+	}
+
+	public void setGeoCoordSys(String geoCoordSys) {
+		this.geoCoordSys = geoCoordSys;
+	}
+
 }

+ 52 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java

@@ -154,6 +154,26 @@ public class DeviceChannel {
 	 */
 	private double latitude;
 
+	/**
+	 * 经度 GCJ02
+	 */
+	private double longitudeGcj02;
+
+	/**
+	 * 纬度 GCJ02
+	 */
+	private double latitudeGcj02;
+
+	/**
+	 * 经度 WGS84
+	 */
+	private double longitudeWgs84;
+
+	/**
+	 * 纬度 WGS84
+	 */
+	private double latitudeWgs84;
+
 	/**
 	 * 子设备数
 	 */
@@ -407,6 +427,38 @@ public class DeviceChannel {
 		this.latitude = latitude;
 	}
 
+	public double getLongitudeGcj02() {
+		return longitudeGcj02;
+	}
+
+	public void setLongitudeGcj02(double longitudeGcj02) {
+		this.longitudeGcj02 = longitudeGcj02;
+	}
+
+	public double getLatitudeGcj02() {
+		return latitudeGcj02;
+	}
+
+	public void setLatitudeGcj02(double latitudeGcj02) {
+		this.latitudeGcj02 = latitudeGcj02;
+	}
+
+	public double getLongitudeWgs84() {
+		return longitudeWgs84;
+	}
+
+	public void setLongitudeWgs84(double longitudeWgs84) {
+		this.longitudeWgs84 = longitudeWgs84;
+	}
+
+	public double getLatitudeWgs84() {
+		return latitudeWgs84;
+	}
+
+	public void setLatitudeWgs84(double latitudeWgs84) {
+		this.latitudeWgs84 = latitudeWgs84;
+	}
+
 	public int getSubCount() {
 		return subCount;
 	}

+ 44 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/HandlerCatchData.java

@@ -0,0 +1,44 @@
+package com.genersoft.iot.vmp.gb28181.bean;
+
+import org.dom4j.Element;
+
+import javax.sip.RequestEvent;
+
+/**
+ * @author lin
+ */
+public class HandlerCatchData {
+    private RequestEvent evt;
+    private Device device;
+    private Element rootElement;
+
+    public HandlerCatchData(RequestEvent evt, Device device, Element rootElement) {
+        this.evt = evt;
+        this.device = device;
+        this.rootElement = rootElement;
+    }
+
+    public RequestEvent getEvt() {
+        return evt;
+    }
+
+    public void setEvt(RequestEvent evt) {
+        this.evt = evt;
+    }
+
+    public Device getDevice() {
+        return device;
+    }
+
+    public void setDevice(Device device) {
+        this.device = device;
+    }
+
+    public Element getRootElement() {
+        return rootElement;
+    }
+
+    public void setRootElement(Element rootElement) {
+        this.rootElement = rootElement;
+    }
+}

+ 13 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java

@@ -71,6 +71,11 @@ public class SendRtpItem {
      */
     private String mediaServerId;
 
+    /**
+     * 使用的服务的ID
+     */
+    private String serverId;
+
     /**
      *  invite的callId
      */
@@ -259,4 +264,12 @@ public class SendRtpItem {
     public void setOnlyAudio(boolean onlyAudio) {
         this.onlyAudio = onlyAudio;
     }
+
+    public String getServerId() {
+        return serverId;
+    }
+
+    public void setServerId(String serverId) {
+        this.serverId = serverId;
+    }
 }

+ 13 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean;
 
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
 import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeHandlerTask;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -38,7 +39,6 @@ public class SubscribeHolder {
         catalogMap.put(platformId, subscribeInfo);
         // 添加订阅到期
         String taskOverdueKey = taskOverduePrefix +  "catalog_" + platformId;
-        dynamicTask.stop(taskOverdueKey);
         // 添加任务处理订阅过期
         dynamicTask.startDelay(taskOverdueKey, () -> removeCatalogSubscribe(subscribeInfo.getId()),
                 subscribeInfo.getExpires() * 1000);
@@ -49,10 +49,17 @@ public class SubscribeHolder {
     }
 
     public void removeCatalogSubscribe(String platformId) {
+
         catalogMap.remove(platformId);
         String taskOverdueKey = taskOverduePrefix +  "catalog_" + platformId;
+        Runnable runnable = dynamicTask.get(taskOverdueKey);
+        if (runnable instanceof ISubscribeTask) {
+            ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
+            subscribeTask.stop();
+        }
         // 添加任务处理订阅过期
         dynamicTask.stop(taskOverdueKey);
+
     }
 
     public void putMobilePositionSubscribe(String platformId, SubscribeInfo subscribeInfo) {
@@ -63,7 +70,6 @@ public class SubscribeHolder {
                 storager,  platformId, subscribeInfo.getSn(), key, this, dynamicTask),
                 subscribeInfo.getGpsInterval() * 1000);
         String taskOverdueKey = taskOverduePrefix +  "MobilePosition_" + platformId;
-        dynamicTask.stop(taskOverdueKey);
         // 添加任务处理订阅过期
         dynamicTask.startDelay(taskOverdueKey, () -> {
                     removeMobilePositionSubscribe(subscribeInfo.getId());
@@ -81,6 +87,11 @@ public class SubscribeHolder {
         // 结束任务处理GPS定时推送
         dynamicTask.stop(key);
         String taskOverdueKey = taskOverduePrefix +  "MobilePosition_" + platformId;
+        Runnable runnable = dynamicTask.get(taskOverdueKey);
+        if (runnable instanceof ISubscribeTask) {
+            ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
+            subscribeTask.stop();
+        }
         // 添加任务处理订阅过期
         dynamicTask.stop(taskOverdueKey);
     }

+ 8 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java

@@ -88,8 +88,8 @@ public class SipSubscribe {
                 this.type = "timeout";
                 this.msg = "消息超时未回复";
                 this.statusCode = -1024;
-                this.callId = timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId();
                 this.dialog = timeoutEvent.getClientTransaction().getDialog();
+                this.callId = this.dialog != null?timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId(): null;
             }else if (event instanceof TransactionTerminatedEvent) {
                 TransactionTerminatedEvent transactionTerminatedEvent = (TransactionTerminatedEvent)event;
                 this.type = "transactionTerminated";
@@ -109,8 +109,8 @@ public class SipSubscribe {
                 this.type = "deviceNotFoundEvent";
                 this.msg = "设备未找到";
                 this.statusCode = -1024;
-                this.callId = deviceNotFoundEvent.getDialog().getCallId().getCallId();
                 this.dialog = deviceNotFoundEvent.getDialog();
+                this.callId = this.dialog != null ?deviceNotFoundEvent.getDialog().getCallId().getCallId() : null;
             }
         }
     }
@@ -130,6 +130,9 @@ public class SipSubscribe {
     }
 
     public void removeErrorSubscribe(String key) {
+        if(key == null){
+            return;
+        }
         errorSubscribes.remove(key);
         errorTimeSubscribes.remove(key);
     }
@@ -139,6 +142,9 @@ public class SipSubscribe {
     }
 
     public void removeOkSubscribe(String key) {
+        if(key == null){
+            return;
+        }
         okSubscribes.remove(key);
         okTimeSubscribes.remove(key);
     }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java

@@ -66,7 +66,7 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
             subscribe = subscribeHolder.getCatalogSubscribe(event.getPlatformId());
 
             if (subscribe == null) {
-                logger.info("发送订阅消息时发现订阅信息已经不存在");
+                logger.info("发送订阅消息时发现订阅信息已经不存在: {}", event.getPlatformId());
                 return;
             }
         }else {

+ 11 - 5
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java

@@ -99,8 +99,8 @@ public class VideoStreamSessionManager {
 		return dialog;
 	}
 
-	public SIPDialog getDialogByCallId(String deviceId, String channelId, String callID){
-		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callID, null);
+	public SIPDialog getDialogByCallId(String deviceId, String channelId, String callId){
+		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, null);
 		if (ssrcTransaction == null) {
 			return null;
 		}
@@ -108,11 +108,17 @@ public class VideoStreamSessionManager {
 		if (dialogByteArray == null) {
 			return null;
 		}
-		SIPDialog dialog = (SIPDialog)SerializeUtils.deSerialize(dialogByteArray);
-		return dialog;
+		return (SIPDialog)SerializeUtils.deSerialize(dialogByteArray);
 	}
 
 	public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){
+
+		if (StringUtils.isEmpty(deviceId)) {
+			deviceId ="*";
+		}
+		if (StringUtils.isEmpty(channelId)) {
+			channelId ="*";
+		}
 		if (StringUtils.isEmpty(callId)) {
 			callId ="*";
 		}
@@ -179,7 +185,7 @@ public class VideoStreamSessionManager {
 
 
 	public List<SsrcTransaction> getAllSsrc() {
-		List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId() + "_" ));
+		List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId()));
 		List<SsrcTransaction> result= new ArrayList<>();
 		for (int i = 0; i < ssrcTransactionKeys.size(); i++) {
 			String key = (String)ssrcTransactionKeys.get(i);

+ 3 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java

@@ -71,7 +71,9 @@ public class MobilePositionSubscribeHandlerTask implements ISubscribeTask {
                 String gbId = gbStream.getGbId();
                 GPSMsgInfo gpsMsgInfo = redisCatchStorage.getGpsMsgInfo(gbId);
                 if (gpsMsgInfo != null) { // 无最新位置不发送
-                    logger.info("无最新位置不发送");
+                   if (logger.isDebugEnabled()) {
+                       logger.debug("无最新位置不发送");
+                   }
                     // 经纬度都为0不发送
                     if (gpsMsgInfo.getLng() == 0 && gpsMsgInfo.getLat() == 0) {
                         continue;

+ 6 - 12
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorObserver.java

@@ -150,30 +150,24 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
     public void processTimeout(TimeoutEvent timeoutEvent) {
         logger.info("[消息发送超时]");
         ClientTransaction clientTransaction = timeoutEvent.getClientTransaction();
-        eventPublisher.requestTimeOut(timeoutEvent);
+
         if (clientTransaction != null) {
+            logger.info("[发送错误订阅] clientTransaction != null");
             Request request = clientTransaction.getRequest();
             if (request != null) {
+                logger.info("[发送错误订阅] request != null");
                 CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
                 if (callIdHeader != null) {
+                    logger.info("[发送错误订阅]");
                     SipSubscribe.Event subscribe = sipSubscribe.getErrorSubscribe(callIdHeader.getCallId());
                     SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(timeoutEvent);
                     subscribe.response(eventResult);
+                    sipSubscribe.removeOkSubscribe(callIdHeader.getCallId());
                     sipSubscribe.removeErrorSubscribe(callIdHeader.getCallId());
                 }
             }
         }
-
-//        Timeout timeout = timeoutEvent.getTimeout();
-//        ServerTransaction serverTransaction = timeoutEvent.getServerTransaction();
-//        if (serverTransaction != null) {
-//            Request request = serverTransaction.getRequest();
-//            URI requestURI = request.getRequestURI();
-//            Header header = request.getHeader(FromHeader.NAME);
-//        }
-//        if(timeoutProcessor != null) {
-//            timeoutProcessor.process(timeoutEvent);
-//        }
+        eventPublisher.requestTimeOut(timeoutEvent);
     }
 
     @Override

+ 8 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -148,6 +148,14 @@ public interface ISIPCommander {
 	 * 回放倍速播放
 	 */
 	void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed);
+	
+	/**
+	 * 回放控制
+	 * @param device
+	 * @param streamInfo
+	 * @param content
+	 */
+	void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent);
 
 	
 	/**

+ 8 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java

@@ -103,6 +103,14 @@ public interface ISIPCommanderForPlatform {
      */
     boolean recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo);
 
+    /**
+     * 录像播放推送完成时发送MediaStatus消息
+     * @param platform
+     * @param sendRtpItem
+     * @return
+     */
+    boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem);
+
     /**
      * 向发起点播的上级回复bye
      * @param platform 平台信息

+ 69 - 16
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java

@@ -32,7 +32,9 @@ import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 
 import javax.sip.*;
+import javax.sip.address.Address;
 import javax.sip.address.SipURI;
+import javax.sip.address.URI;
 import javax.sip.header.*;
 import javax.sip.message.Request;
 import java.lang.reflect.Field;
@@ -708,22 +710,19 @@ public class SIPCommander implements ISIPCommander {
 			}
 			SIPDialog dialog;
 			if (callId != null) {
-				dialog = streamSession.getDialogByCallId(deviceId, channelId, callId);
+				dialog = streamSession.getDialogByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), callId);
 			}else {
-				if (stream == null) {
+				if (stream == null && ssrcTransaction == null && ssrcTransaction.getStream() == null) {
 					return;
 				}
-				dialog = streamSession.getDialogByStream(deviceId, channelId, stream);
-			}
-			if (ssrcTransaction != null) {
-				MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId());
-				mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
-				mediaServerService.closeRTPServer(deviceId, channelId, ssrcTransaction.getStream());
-				streamSession.remove(deviceId, channelId, ssrcTransaction.getStream());
+				dialog = streamSession.getDialogByStream(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
 			}
+			mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
+			mediaServerService.closeRTPServer(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
+			streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
 
 			if (dialog == null) {
-				logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", deviceId, channelId);
+				logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
 				return;
 			}
 			SipStack sipStack = udpSipProvider.getSipStack();
@@ -1456,12 +1455,20 @@ public class SIPCommander implements ISIPCommander {
 
 			Request request;
 			if (dialog != null) {
-				logger.info("发送移动位置订阅消息时 dialog的状态为: {}", dialog.getState());
+				SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
 				request = dialog.createRequest(Request.SUBSCRIBE);
+				ExpiresHeader expiresHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForCatalog());
+				request.setExpires(expiresHeader);
+
+				request.setRequestURI(requestURI);
+
 				ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 				request.setContent(subscribePostitionXml.toString(), contentTypeHeader);
-				ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition());
-				request.addHeader(expireHeader);
+
+				CSeqHeader cSeqHeader = (CSeqHeader)request.getHeader(CSeqHeader.NAME);
+				cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ(Request.SUBSCRIBE));
+				request.removeHeader(CSeqHeader.NAME);
+				request.addHeader(cSeqHeader);
 			}else {
 				String tm = Long.toString(System.currentTimeMillis());
 				CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
@@ -1552,12 +1559,21 @@ public class SIPCommander implements ISIPCommander {
 
 			Request request;
 			if (dialog != null) {
-				logger.info("发送目录订阅消息时 dialog的状态为: {}", dialog.getState());
+				SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
 				request = dialog.createRequest(Request.SUBSCRIBE);
+				ExpiresHeader expiresHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForCatalog());
+				request.setExpires(expiresHeader);
+
+				request.setRequestURI(requestURI);
+
 				ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
 				request.setContent(cmdXml.toString(), contentTypeHeader);
-				ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition());
-				request.addHeader(expireHeader);
+
+				CSeqHeader cSeqHeader = (CSeqHeader)request.getHeader(CSeqHeader.NAME);
+				cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ(Request.SUBSCRIBE));
+				request.removeHeader(CSeqHeader.NAME);
+				request.addHeader(cSeqHeader);
+
 			}else {
 				String tm = Long.toString(System.currentTimeMillis());
 
@@ -1779,6 +1795,43 @@ public class SIPCommander implements ISIPCommander {
 			e.printStackTrace();
 		}
 	}
+	
+	@Override
+	public void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) {
+		try {
+			Request request = headerProvider.createInfoRequest(device, streamInfo, content);
+			if (request == null) {
+				return;
+			}
+			logger.info(request.toString());
+			ClientTransaction clientTransaction = null;
+			if ("TCP".equals(device.getTransport())) {
+				clientTransaction = tcpSipProvider.getNewClientTransaction(request);
+			} else if ("UDP".equals(device.getTransport())) {
+				clientTransaction = udpSipProvider.getNewClientTransaction(request);
+			}
+			CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
+			if(errorEvent != null) {
+				sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
+					errorEvent.response(eventResult);
+					sipSubscribe.removeErrorSubscribe(eventResult.callId);
+					sipSubscribe.removeOkSubscribe(eventResult.callId);
+				}));
+			}
+			
+			if(okEvent != null) {
+				sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
+					okEvent.response(eventResult);
+					sipSubscribe.removeOkSubscribe(eventResult.callId);
+					sipSubscribe.removeErrorSubscribe(eventResult.callId);
+				});
+			}
+			clientTransaction.sendRequest();
+			
+		} catch (SipException | ParseException | InvalidArgumentException e) {
+			e.printStackTrace();
+		}
+	}
 
 	@Override
 	public boolean sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) {

+ 120 - 43
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java

@@ -31,6 +31,7 @@ import javax.sip.address.SipURI;
 import javax.sip.header.*;
 import javax.sip.message.Request;
 import java.lang.reflect.Field;
+import java.net.InetAddress;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -276,8 +277,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                         catalogXml.append("<Owner>" + channel.getOwner() + "</Owner>\r\n");
                         catalogXml.append("<CivilCode>" + channel.getCivilCode() + "</CivilCode>\r\n");
                         catalogXml.append("<Address>" + channel.getAddress() + "</Address>\r\n");
-                        catalogXml.append("<Longitude>" + channel.getLongitude() + "</Longitude>\r\n");
-                        catalogXml.append("<Latitude>" + channel.getLatitude() + "</Latitude>\r\n");
+                        catalogXml.append("<Longitude>" + channel.getLongitudeWgs84() + "</Longitude>\r\n");
+                        catalogXml.append("<Latitude>" + channel.getLatitudeWgs84() + "</Latitude>\r\n");
                         catalogXml.append("<IPAddress>" + channel.getIpAddress() + "</IPAddress>\r\n");
                         catalogXml.append("<Port>" + channel.getPort() + "</Port>\r\n");
                         catalogXml.append("<Info>\r\n");
@@ -546,14 +547,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         }
         notifyRequest.addHeader(event);
         SipURI sipURI = (SipURI) notifyRequest.getRequestURI();
-        if (subscribeInfo.getTransaction() != null) {
-            SIPRequest request = (SIPRequest) subscribeInfo.getTransaction().getRequest();
-            sipURI.setHost(request.getRemoteAddress().getHostAddress());
-            sipURI.setPort(request.getRemotePort());
-        }else {
-            sipURI.setHost(parentPlatform.getServerIP());
-            sipURI.setPort(parentPlatform.getServerPort());
-        }
+        sipURI.setHost(parentPlatform.getServerIP());
+        sipURI.setPort(parentPlatform.getServerPort());
 
         ClientTransaction transaction = null;
         if ("TCP".equals(parentPlatform.getTransport())) {
@@ -750,6 +745,82 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         return true;
     }
 
+    @Override
+    public boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem) {
+        if (sendRtpItem == null) {
+            return false;
+        }
+        if (platform == null) {
+            return false;
+        }
+
+        byte[] dialogByteArray = sendRtpItem.getDialog();
+        if (dialogByteArray == null) {
+            return false;
+        }
+        try{
+            SIPDialog dialog = (SIPDialog) SerializeUtils.deSerialize(dialogByteArray);
+            SipStack sipStack;
+            if ("TCP".equals(platform.getTransport())) {
+                sipStack = tcpSipProvider.getSipStack();
+            } else {
+                sipStack = udpSipProvider.getSipStack();
+            }
+            SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog);
+            if (dialog != sipDialog) {
+                dialog = sipDialog;
+            }
+            if ("TCP".equals(platform.getTransport())) {
+                dialog.setSipProvider(tcpSipProvider);
+            } else {
+                dialog.setSipProvider(udpSipProvider);
+            }
+
+            Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
+            sipStackField.setAccessible(true);
+            sipStackField.set(dialog, sipStack);
+            Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
+            eventListenersField.setAccessible(true);
+            eventListenersField.set(dialog, new HashSet<>());
+
+            SIPRequest messageRequest = (SIPRequest)dialog.createRequest(Request.MESSAGE);
+            String characterSet = platform.getCharacterSet();
+            StringBuffer mediaStatusXml = new StringBuffer(200);
+            mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
+            mediaStatusXml.append("<Notify>\r\n");
+            mediaStatusXml.append("<CmdType>MediaStatus</CmdType>\r\n");
+            mediaStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
+            mediaStatusXml.append("<DeviceID>" + sendRtpItem.getChannelId() + "</DeviceID>\r\n");
+            mediaStatusXml.append("<NotifyType>121</NotifyType>\r\n");
+            mediaStatusXml.append("</Notify>\r\n");
+            ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+            messageRequest.setContent(mediaStatusXml.toString(), contentTypeHeader);
+            SipURI sipURI = (SipURI) messageRequest.getRequestURI();
+            sipURI.setHost(platform.getServerIP());
+            sipURI.setPort(platform.getServerPort());
+            ClientTransaction clientTransaction;
+            if ("TCP".equals(platform.getTransport())) {
+                clientTransaction = tcpSipProvider.getNewClientTransaction(messageRequest);
+            }else {
+                clientTransaction = udpSipProvider.getNewClientTransaction(messageRequest);
+            }
+            dialog.sendRequest(clientTransaction);
+        } catch (SipException e) {
+            e.printStackTrace();
+            return false;
+        } catch (ParseException e) {
+            e.printStackTrace();
+            return false;
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+        return true;
+
+
+    }
+
     @Override
     public void streamByeCmd(ParentPlatform platform, String callId) {
         if (platform == null) {
@@ -766,45 +837,51 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
             byte[] dialogByteArray = sendRtpItem.getDialog();
             if (dialogByteArray != null) {
                 SIPDialog dialog = (SIPDialog) SerializeUtils.deSerialize(dialogByteArray);
-                SipStack sipStack = udpSipProvider.getSipStack();
+                SipStack sipStack;
+                if ("TCP".equals(platform.getTransport())) {
+                    sipStack = tcpSipProvider.getSipStack();
+                } else {
+                    sipStack = udpSipProvider.getSipStack();
+                }
                 SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog);
                 if (dialog != sipDialog) {
                     dialog = sipDialog;
-                } else {
-                    try {
+                }
+                try {
+                    if ("TCP".equals(platform.getTransport())) {
+                        dialog.setSipProvider(tcpSipProvider);
+                    } else {
                         dialog.setSipProvider(udpSipProvider);
-                        Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
-                        sipStackField.setAccessible(true);
-                        sipStackField.set(dialog, sipStack);
-                        Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
-                        eventListenersField.setAccessible(true);
-                        eventListenersField.set(dialog, new HashSet<>());
-
-                        byte[] transactionByteArray = sendRtpItem.getTransaction();
-                        ClientTransaction clientTransaction = (ClientTransaction) SerializeUtils.deSerialize(transactionByteArray);
-                        Request byeRequest = dialog.createRequest(Request.BYE);
-
-                        SipURI byeURI = (SipURI) byeRequest.getRequestURI();
-                        SIPRequest request = (SIPRequest) clientTransaction.getRequest();
-                        byeURI.setHost(request.getRemoteAddress().getHostAddress());
-                        byeURI.setPort(request.getRemotePort());
-                        if ("TCP".equals(platform.getTransport())) {
-                            clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
-                        } else if ("UDP".equals(platform.getTransport())) {
-                            clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
-                        }
-                        dialog.sendRequest(clientTransaction);
-                    } catch (SipException e) {
-                        e.printStackTrace();
-                    } catch (ParseException e) {
-                        e.printStackTrace();
-                    } catch (NoSuchFieldException e) {
-                        e.printStackTrace();
-                    } catch (IllegalAccessException e) {
-                        e.printStackTrace();
                     }
-
+                    Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
+                    sipStackField.setAccessible(true);
+                    sipStackField.set(dialog, sipStack);
+                    Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
+                    eventListenersField.setAccessible(true);
+                    eventListenersField.set(dialog, new HashSet<>());
+
+                    Request byeRequest = dialog.createRequest(Request.BYE);
+
+                    SipURI byeURI = (SipURI) byeRequest.getRequestURI();
+                    byeURI.setHost(platform.getServerIP());
+                    byeURI.setPort(platform.getServerPort());
+                    ClientTransaction clientTransaction;
+                    if ("TCP".equals(platform.getTransport())) {
+                        clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
+                    } else {
+                        clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
+                    }
+                    dialog.sendRequest(clientTransaction);
+                } catch (SipException e) {
+                    e.printStackTrace();
+                } catch (ParseException e) {
+                    e.printStackTrace();
+                } catch (NoSuchFieldException e) {
+                    e.printStackTrace();
+                } catch (IllegalAccessException e) {
+                    e.printStackTrace();
                 }
+
             }
         }
     }

+ 0 - 23
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorAbstract.java

@@ -1,23 +0,0 @@
-package com.genersoft.iot.vmp.gb28181.transmit.event.request;
-
-import gov.nist.javax.sip.SipProviderImpl;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-
-/**    
- * @description:处理接收IPCamera发来的SIP协议请求消息
- * @author: songww
- * @date:   2020年5月3日 下午4:42:22     
- */
-public abstract class SIPRequestProcessorAbstract  {
-
-
-	@Autowired
-	@Qualifier(value="tcpSipProvider")
-	private SipProviderImpl tcpSipProvider;
-
-	@Autowired
-	@Qualifier(value="udpSipProvider")
-	private SipProviderImpl udpSipProvider;
-
-}

+ 39 - 52
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java

@@ -15,10 +15,13 @@ import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
+import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
+import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.stack.SIPDialog;
+import com.genersoft.iot.vmp.utils.SerializeUtils;
 import org.ehcache.shadow.org.terracotta.offheapstore.storage.IntegerStorageEngine;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,7 +49,7 @@ import java.util.*;
 public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
 
 	private Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class);
-	private String method = "ACK";
+	private final String method = "ACK";
 
 	@Autowired
 	private SIPProcessorObserver sipProcessorObserver;
@@ -84,6 +87,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 	@Autowired
 	private AudioBroadcastManager audioBroadcastManager;
 
+	@Autowired
+	private RedisGbPlayMsgListener redisGbPlayMsgListener;
+
 
 	/**   
 	 * 处理  ACK请求
@@ -159,60 +165,41 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 					// 向上级平台
 					commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
 				}
+			if (mediaInfo == null) {
+				RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
+						sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(),
+						sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
+						sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
+				redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, jsonObject->{
+					startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
+				});
+			}else {
+				JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
+				startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
 			}
 
 
-//			if (streamInfo == null) { // 流还没上来,对方就回复ack
-//				logger.info("监听流以等待流上线1 rtp/{}", sendRtpItem.getStreamId());
-//				// 监听流上线
-//				// 添加订阅
-//				JSONObject subscribeKey = new JSONObject();
-//				subscribeKey.put("app", "rtp");
-//				subscribeKey.put("stream", sendRtpItem.getStreamId());
-//				subscribeKey.put("regist", true);
-//				subscribeKey.put("schema", "rtmp");
-//				subscribeKey.put("mediaServerId", sendRtpItem.getMediaServerId());
-//				subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
-//						(MediaServerItem mediaServerItemInUse, JSONObject json)->{
-//							Map<String, Object> param = new HashMap<>();
-//							param.put("vhost","__defaultVhost__");
-//							param.put("app",json.getString("app"));
-//							param.put("stream",json.getString("stream"));
-//							param.put("ssrc", sendRtpItem.getSsrc());
-//							param.put("dst_url",sendRtpItem.getIp());
-//							param.put("dst_port", sendRtpItem.getPort());
-//							param.put("is_udp", is_Udp);
-//							param.put("src_port", sendRtpItem.getLocalPort());
-//							zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
-//						});
-//			}else {
-//				Map<String, Object> param = new HashMap<>();
-//				param.put("vhost","__defaultVhost__");
-//				param.put("app",streamInfo.getApp());
-//				param.put("stream",streamInfo.getStream());
-//				param.put("ssrc", sendRtpItem.getSsrc());
-//				param.put("dst_url",sendRtpItem.getIp());
-//				param.put("dst_port", sendRtpItem.getPort());
-//				param.put("is_udp", is_Udp);
-//				param.put("src_port", sendRtpItem.getLocalPort());
-//
-//				JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
-//				if (jsonObject.getInteger("code") != 0) {
-//					logger.info("监听流以等待流上线2 {}/{}", streamInfo.getApp(), streamInfo.getStream());
-//					// 监听流上线
-//					// 添加订阅
-//					JSONObject subscribeKey = new JSONObject();
-//					subscribeKey.put("app", "rtp");
-//					subscribeKey.put("stream", streamInfo.getStream());
-//					subscribeKey.put("regist", true);
-//					subscribeKey.put("schema", "rtmp");
-//					subscribeKey.put("mediaServerId", sendRtpItem.getMediaServerId());
-//					subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
-//							(MediaServerItem mediaServerItemInUse, JSONObject json)->{
-//								zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
-//							});
-//				}
-//			}
+		}
+	}
+	private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform,
+										JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) {
+		if (jsonObject == null) {
+			logger.error("RTP推流失败: 请检查ZLM服务");
+		} else if (jsonObject.getInteger("code") == 0) {
+			logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
+			byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
+			sendRtpItem.setDialog(dialogByteArray);
+			byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
+			sendRtpItem.setTransaction(transactionByteArray);
+			redisCatchStorage.updateSendRTPSever(sendRtpItem);
+		} else {
+			logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"),JSONObject.toJSON(param));
+			if (sendRtpItem.isOnlyAudio()) {
+				// TODO 可能是语音对讲
+			}else {
+				// 向上级平台
+				commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
+			}
 		}
 	}
 }

+ 3 - 7
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java

@@ -114,13 +114,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 							playService.stopAudioBroadcast(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
 						}
 						if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
-							MessageForPushChannel messageForPushChannel = new MessageForPushChannel();
-							messageForPushChannel.setType(0);
-							messageForPushChannel.setGbId(sendRtpItem.getChannelId());
-							messageForPushChannel.setApp(sendRtpItem.getApp());
-							messageForPushChannel.setStream(sendRtpItem.getStreamId());
-							messageForPushChannel.setMediaServerId(sendRtpItem.getMediaServerId());
-							messageForPushChannel.setPlatFormId(sendRtpItem.getPlatformId());
+							MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
+									sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
+									sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId());
 							redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
 						}
 					}

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java

@@ -15,7 +15,7 @@ import javax.sip.RequestEvent;
 @Component
 public class CancelRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
 
-	private String method = "CANCEL";
+	private final String method = "CANCEL";
 
 	@Autowired
 	private SIPProcessorObserver sipProcessorObserver;

Разница между файлами не показана из-за своего большого размера
+ 694 - 560
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java


+ 68 - 40
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java

@@ -1,7 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
 import com.alibaba.fastjson.JSONObject;
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
@@ -18,6 +17,7 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
@@ -25,6 +25,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 
@@ -35,6 +37,7 @@ import javax.sip.header.FromHeader;
 import javax.sip.message.Response;
 import java.text.ParseException;
 import java.util.Iterator;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 /**
  * SIP命令类型: NOTIFY请求
@@ -63,11 +66,19 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 	@Autowired
 	private EventPublisher publisher;
 
-	private String method = "NOTIFY";
+	private final String method = "NOTIFY";
 
 	@Autowired
 	private SIPProcessorObserver sipProcessorObserver;
 
+	private boolean taskQueueHandlerRun = false;
+
+	private final ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
+
+	@Qualifier("taskExecutor")
+	@Autowired
+	private ThreadPoolTaskExecutor taskExecutor;
+
 	@Override
 	public void afterPropertiesSet() throws Exception {
 		// 添加消息处理的订阅
@@ -77,23 +88,40 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 	@Override
 	public void process(RequestEvent evt) {
 		try {
-			Element rootElement = getRootElement(evt);
-			String cmd = XmlUtil.getText(rootElement, "CmdType");
-
-			if (CmdType.CATALOG.equals(cmd)) {
-				logger.info("接收到Catalog通知");
-				processNotifyCatalogList(evt);
-			} else if (CmdType.ALARM.equals(cmd)) {
-				logger.info("接收到Alarm通知");
-				processNotifyAlarm(evt);
-			} else if (CmdType.MOBILE_POSITION.equals(cmd)) {
-				logger.info("接收到MobilePosition通知");
-				processNotifyMobilePosition(evt);
-			} else {
-				logger.info("接收到消息:" + cmd);
-				responseAck(evt, Response.OK);
+
+			taskQueue.offer(new HandlerCatchData(evt, null, null));
+			responseAck(evt, Response.OK);
+			if (!taskQueueHandlerRun) {
+				taskQueueHandlerRun = true;
+				taskExecutor.execute(()-> {
+							while (!taskQueue.isEmpty()) {
+								try {
+									HandlerCatchData take = taskQueue.poll();
+									Element rootElement = getRootElement(take.getEvt());
+									String cmd = XmlUtil.getText(rootElement, "CmdType");
+
+									if (CmdType.CATALOG.equals(cmd)) {
+										logger.info("接收到Catalog通知");
+										processNotifyCatalogList(take.getEvt());
+									} else if (CmdType.ALARM.equals(cmd)) {
+										logger.info("接收到Alarm通知");
+										processNotifyAlarm(take.getEvt());
+									} else if (CmdType.MOBILE_POSITION.equals(cmd)) {
+										logger.info("接收到MobilePosition通知");
+										processNotifyMobilePosition(take.getEvt());
+									} else {
+										logger.info("接收到消息:" + cmd);
+									}
+								} catch (DocumentException e) {
+									throw new RuntimeException(e);
+								}
+							}
+						taskQueueHandlerRun = false;
+						});
 			}
-		} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
+
+
+		} catch (SipException | InvalidArgumentException | ParseException e) {
 			e.printStackTrace();
 		}
 	}
@@ -166,8 +194,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 			jsonObject.put("direction", mobilePosition.getDirection());
 			jsonObject.put("speed", mobilePosition.getSpeed());
 			redisCatchStorage.sendMobilePositionMsg(jsonObject);
-			responseAck(evt, Response.OK);
-		} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
+		} catch (DocumentException  e) {
 			e.printStackTrace();
 		}
 	}
@@ -188,6 +215,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 
 			Device device = redisCatchStorage.getDevice(deviceId);
 			if (device == null) {
+				logger.warn("[ NotifyAlarm ] 未找到设备:{}", deviceId);
 				return;
 			}
 			rootElement = getRootElement(evt, device.getCharset());
@@ -195,7 +223,12 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 			deviceAlarm.setDeviceId(deviceId);
 			deviceAlarm.setAlarmPriority(XmlUtil.getText(rootElement, "AlarmPriority"));
 			deviceAlarm.setAlarmMethod(XmlUtil.getText(rootElement, "AlarmMethod"));
-			deviceAlarm.setAlarmTime(XmlUtil.getText(rootElement, "AlarmTime"));
+			String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
+			if (alarmTime == null) {
+				logger.warn("[ NotifyAlarm ] AlarmTime cannot be null");
+				return;
+			}
+			deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
 			if (XmlUtil.getText(rootElement, "AlarmDescription") == null) {
 				deviceAlarm.setAlarmDescription("");
 			} else {
@@ -212,7 +245,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 				deviceAlarm.setLatitude(0.00);
 			}
 			logger.info("[收到Notify-Alarm]:{}/{}", device.getDeviceId(), deviceAlarm.getChannelId());
-			if (deviceAlarm.getAlarmMethod().equals("4")) {
+			if ("4".equals(deviceAlarm.getAlarmMethod())) {
 				MobilePosition mobilePosition = new MobilePosition();
 				mobilePosition.setDeviceId(deviceAlarm.getDeviceId());
 				mobilePosition.setTime(deviceAlarm.getAlarmTime());
@@ -233,11 +266,10 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 			// TODO: 需要实现存储报警信息、报警分类
 
 			// 回复200 OK
-			responseAck(evt, Response.OK);
 			if (redisCatchStorage.deviceIsOnline(deviceId)) {
 				publisher.deviceAlarmEventPublish(deviceAlarm);
 			}
-		} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
+		} catch (DocumentException e) {
 			e.printStackTrace();
 		}
 	}
@@ -273,64 +305,60 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
 						continue;
 					}
 					Element eventElement = itemDevice.element("Event");
-					DeviceChannel channel = XmlUtil.channelContentHander(itemDevice);
+					String event;
+					if (eventElement == null) {
+						logger.warn("[收到 目录订阅]:{}, 但是Event为空, 设为默认值 ADD", (device != null ? device.getDeviceId():"" ));
+						event = CatalogEvent.ADD;
+					}else {
+						event = eventElement.getText().toUpperCase();
+					}
+					DeviceChannel channel = XmlUtil.channelContentHander(itemDevice, device);
 					channel.setDeviceId(device.getDeviceId());
 					logger.info("[收到 目录订阅]:{}/{}", device.getDeviceId(), channel.getChannelId());
-					switch (eventElement.getText().toUpperCase()) {
+					switch (event) {
 						case CatalogEvent.ON:
 							// 上线
 							logger.info("收到来自设备【{}】的通道【{}】上线通知", device.getDeviceId(), channel.getChannelId());
 							storager.deviceChannelOnline(deviceId, channel.getChannelId());
-							// 回复200 OK
-							responseAck(evt, Response.OK);
 							break;
 						case CatalogEvent.OFF :
 							// 离线
 							logger.info("收到来自设备【{}】的通道【{}】离线通知", device.getDeviceId(), channel.getChannelId());
 							storager.deviceChannelOffline(deviceId, channel.getChannelId());
-							// 回复200 OK
-							responseAck(evt, Response.OK);
 							break;
 						case CatalogEvent.VLOST:
 							// 视频丢失
 							logger.info("收到来自设备【{}】的通道【{}】视频丢失通知", device.getDeviceId(), channel.getChannelId());
 							storager.deviceChannelOffline(deviceId, channel.getChannelId());
-							// 回复200 OK
-							responseAck(evt, Response.OK);
 							break;
 						case CatalogEvent.DEFECT:
 							// 故障
-							// 回复200 OK
-							responseAck(evt, Response.OK);
 							break;
 						case CatalogEvent.ADD:
 							// 增加
 							logger.info("收到来自设备【{}】的增加通道【{}】通知", device.getDeviceId(), channel.getChannelId());
 							storager.updateChannel(deviceId, channel);
-							responseAck(evt, Response.OK);
 							break;
 						case CatalogEvent.DEL:
 							// 删除
 							logger.info("收到来自设备【{}】的删除通道【{}】通知", device.getDeviceId(), channel.getChannelId());
 							storager.delChannel(deviceId, channel.getChannelId());
-							responseAck(evt, Response.OK);
 							break;
 						case CatalogEvent.UPDATE:
 							// 更新
 							logger.info("收到来自设备【{}】的更新通道【{}】通知", device.getDeviceId(), channel.getChannelId());
 							storager.updateChannel(deviceId, channel);
-							responseAck(evt, Response.OK);
 							break;
 						default:
-							responseAck(evt, Response.BAD_REQUEST, "event not found");
+							logger.warn("[ NotifyCatalog ] event not found : {}", event );
 
 					}
 					// 转发变化信息
-					eventPublisher.catalogEventPublish(null, channel, eventElement.getText().toUpperCase());
+					eventPublisher.catalogEventPublish(null, channel, event);
 
 				}
 			}
-		} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
+		} catch (DocumentException e) {
 			e.printStackTrace();
 		}
 	}

+ 4 - 17
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java

@@ -1,17 +1,13 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
 
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate;
-import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
+import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
 import com.genersoft.iot.vmp.service.IDeviceService;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import gov.nist.javax.sip.RequestEventExt;
 import gov.nist.javax.sip.address.AddressImpl;
@@ -45,20 +41,11 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
 
     private final Logger logger = LoggerFactory.getLogger(RegisterRequestProcessor.class);
 
-    public String method = "REGISTER";
+    public final String method = "REGISTER";
 
     @Autowired
     private SipConfig sipConfig;
 
-    @Autowired
-    private IRedisCatchStorage redisCatchStorage;
-
-    @Autowired
-    private IVideoManagerStorage storager;
-
-    @Autowired
-    private EventPublisher publisher;
-
     @Autowired
     private SIPProcessorObserver sipProcessorObserver;
 
@@ -86,7 +73,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME);
             Response response = null;
             boolean passwordCorrect = false;
-            // 注册标志  0:未携带授权头或者密码错误  1:注册成功   2:注销成功
+            // 注册标志
             boolean registerFlag = false;
             FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
             AddressImpl address = (AddressImpl) fromHeader.getAddress();
@@ -105,7 +92,6 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
             // 校验密码是否正确
             passwordCorrect = StringUtils.isEmpty(sipConfig.getPassword()) ||
                     new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, sipConfig.getPassword());
-            // 未携带授权头或者密码错误 回复401
 
             if (!passwordCorrect) {
                 // 注册失败
@@ -154,6 +140,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
                 device = new Device();
                 device.setStreamMode("UDP");
                 device.setCharset("GB2312");
+                device.setGeoCoordSys("WGS84");
                 device.setDeviceId(deviceId);
             }
             device.setIp(received);

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java

@@ -82,7 +82,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 	@Override
 	public void process(RequestEvent evt) {
 		Request request = evt.getRequest();
-
 		try {
 			Element rootElement = getRootElement(evt);
 			String cmd = XmlUtil.getText(rootElement, "CmdType");
@@ -176,6 +175,8 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 	}
 
 	private void processNotifyCatalogList(RequestEvent evt, Element rootElement) throws SipException {
+
+		System.out.println(evt.getRequest().toString());
 		String platformId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
 		String deviceId = XmlUtil.getText(rootElement, "DeviceID");
 		ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);

+ 143 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java

@@ -0,0 +1,143 @@
+package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info;
+
+import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
+import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.SipException;
+import javax.sip.header.*;
+import javax.sip.message.Response;
+import java.text.ParseException;
+
+@Component
+public class InfoRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
+
+    private final static Logger logger = LoggerFactory.getLogger(InfoRequestProcessor.class);
+
+    private final String method = "INFO";
+
+    @Autowired
+    private SIPProcessorObserver sipProcessorObserver;
+
+    @Autowired
+    private IVideoManagerStorage storage;
+
+    @Autowired
+    private SipSubscribe sipSubscribe;
+
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
+    @Autowired
+    private IVideoManagerStorage storager;
+
+    @Autowired
+    private SIPCommander cmder;
+
+    @Autowired
+    private VideoStreamSessionManager sessionManager;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        // 添加消息处理的订阅
+        sipProcessorObserver.addRequestProcessor(method, this);
+    }
+
+    @Override
+    public void process(RequestEvent evt) {
+        logger.debug("接收到消息:" + evt.getRequest());
+        String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
+        CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
+        // 先从会话内查找
+        SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
+        if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
+            deviceId = ssrcTransaction.getDeviceId();
+        }
+        // 查询设备是否存在
+        Device device = redisCatchStorage.getDevice(deviceId);
+        // 查询上级平台是否存在
+        ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId);
+        try {
+            if (device != null && parentPlatform != null) {
+                logger.warn("[重复]平台与设备编号重复:{}", deviceId);
+                SIPRequest request = (SIPRequest) evt.getRequest();
+                String hostAddress = request.getRemoteAddress().getHostAddress();
+                int remotePort = request.getRemotePort();
+                if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) {
+                    parentPlatform = null;
+                }else {
+                    device = null;
+                }
+            }
+            if (device == null && parentPlatform == null) {
+                // 不存在则回复404
+                responseAck(evt, Response.NOT_FOUND, "device "+ deviceId +" not found");
+                logger.warn("[设备未找到 ]: {}", deviceId);
+                if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){
+                    SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new DeviceNotFoundEvent(evt.getDialog()));
+                    sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()).response(eventResult);
+                };
+            }else {
+                ContentTypeHeader header = (ContentTypeHeader)evt.getRequest().getHeader(ContentTypeHeader.NAME);
+                String contentType = header.getContentType();
+                String contentSubType = header.getContentSubType();
+                if ("Application".equals(contentType) && "MANSRTSP".equals(contentSubType)) {
+                    SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
+                    String streamId = sendRtpItem.getStreamId();
+                    StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null);
+                    if (null == streamInfo) {
+                        responseAck(evt, Response.NOT_FOUND, "stream " + streamId + " not found");
+                        return;
+                    }
+                    Device device1 = storager.queryVideoDevice(streamInfo.getDeviceID());
+                    cmder.playbackControlCmd(device1,streamInfo,new String(evt.getRequest().getRawContent()),eventResult -> {
+                        // 失败的回复
+                        try {
+                            responseAck(evt, eventResult.statusCode, eventResult.msg);
+                        } catch (SipException e) {
+                            e.printStackTrace();
+                        } catch (InvalidArgumentException e) {
+                            e.printStackTrace();
+                        } catch (ParseException e) {
+                            e.printStackTrace();
+                        }
+                    }, eventResult -> {
+                        // 成功的回复
+                        try {
+                            responseAck(evt, eventResult.statusCode);
+                        } catch (SipException e) {
+                            e.printStackTrace();
+                        } catch (InvalidArgumentException e) {
+                            e.printStackTrace();
+                        } catch (ParseException e) {
+                            e.printStackTrace();
+                        }
+                    });
+                }
+            }
+        } catch (SipException e) {
+            logger.warn("SIP 回复错误", e);
+        } catch (InvalidArgumentException e) {
+            logger.warn("参数无效", e);
+        } catch (ParseException e) {
+            logger.warn("SIP回复时解析异常", e);
+        }
+    }
+
+
+}

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java

@@ -72,7 +72,7 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
         String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
         CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
         // 先从会话内查找
-        SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, null, callIdHeader.getCallId());
+        SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
         if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
             deviceId = ssrcTransaction.getDeviceId();
         }

+ 12 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java

@@ -9,9 +9,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessag
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
 import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
 import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
+import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
 import com.genersoft.iot.vmp.service.IDeviceAlarmService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.utils.DateUtil;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -84,7 +86,11 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
         deviceAlarm.setChannelId(channelId);
         deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority"));
         deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod"));
-        deviceAlarm.setAlarmTime(getText(rootElement, "AlarmTime"));
+        String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
+        if (alarmTime == null) {
+            return;
+        }
+        deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
         String alarmDescription = getText(rootElement, "AlarmDescription");
         if (alarmDescription == null) {
             deviceAlarm.setAlarmDescription("");
@@ -175,7 +181,11 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
         deviceAlarm.setChannelId(channelId);
         deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority"));
         deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod"));
-        deviceAlarm.setAlarmTime(getText(rootElement, "AlarmTime"));
+        String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
+        if (alarmTime == null) {
+            return;
+        }
+        deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
         String alarmDescription = getText(rootElement, "AlarmDescription");
         if (alarmDescription == null) {
             deviceAlarm.setAlarmDescription("");

+ 2 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java

@@ -64,16 +64,14 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
                 device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
             }
             device.setKeepaliveTime(DateUtil.getNow());
+            // 回复200 OK
+            responseAck(evt, Response.OK);
             if (device.getOnline() == 1) {
-                // 回复200 OK
-                responseAck(evt, Response.OK);
                 deviceService.updateDevice(device);
             }else {
                 // 对于已经离线的设备判断他的注册是否已经过期
                 if (!deviceService.expire(device)){
                     deviceService.online(device);
-                    // 回复200 OK
-                    responseAck(evt, Response.OK);
                 }
             }
         } catch (SipException e) {

+ 37 - 8
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java

@@ -3,11 +3,16 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
+import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -36,9 +41,18 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
     @Autowired
     private SIPCommander cmder;
 
+    @Autowired
+    private SIPCommanderFroPlatform sipCommanderFroPlatform;
+
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
+    @Autowired
+    private IVideoManagerStorage storage;
+
+    @Autowired
+    private VideoStreamSessionManager sessionManager;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         notifyMessageHandler.addHandler(cmdType, this);
@@ -59,17 +73,32 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
         }
         CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
         String NotifyType =getText(rootElement, "NotifyType");
-        if (NotifyType.equals("121")){
+        if ("121".equals(NotifyType)){
             logger.info("[录像流]推送完毕,收到关流通知");
-            String channelId =getText(rootElement, "DeviceID");
             // 查询是设备
-            StreamInfo streamInfo = redisCatchStorage.queryDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
-            // 设置进度100%
-            streamInfo.setProgress(1);
-            redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId());
-            cmder.streamByeCmd(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
-            // TODO 如果级联播放,需要给上级发送此通知
+            StreamInfo streamInfo = redisCatchStorage.queryDownload(null, null, null, callIdHeader.getCallId());
+            if (streamInfo != null) {
+                // 设置进度100%
+                streamInfo.setProgress(1);
+                redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId());
+            }
+
+            // 先从会话内查找
+            SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
+            if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
+                cmder.streamByeCmd(device.getDeviceId(), ssrcTransaction.getChannelId(), null, callIdHeader.getCallId());
 
+                // 如果级联播放,需要给上级发送此通知 TODO 多个上级同时观看一个下级 可能存在停错的问题,需要将点播CallId进行上下级绑定
+                SendRtpItem sendRtpItem =  redisCatchStorage.querySendRTPServer(null, ssrcTransaction.getChannelId(), null, null);
+                if (sendRtpItem != null) {
+                    ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
+                    if (parentPlatform == null) {
+                        logger.warn("[级联消息发送]:发送MediaStatus发现上级平台{}不存在", sendRtpItem.getPlatformId());
+                        return;
+                    }
+                    sipCommanderFroPlatform.sendMediaStatusNotify(parentPlatform, sendRtpItem);
+                }
+            }
         }
     }
 

+ 87 - 56
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java

@@ -20,6 +20,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 
@@ -31,6 +33,7 @@ import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 @Component
 public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
@@ -38,9 +41,13 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
     private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class);
     private final String cmdType = "Catalog";
 
+    private boolean taskQueueHandlerRun = false;
+
     @Autowired
     private ResponseMessageHandler responseMessageHandler;
 
+    private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
+
     @Autowired
     private IVideoManagerStorage storager;
 
@@ -63,6 +70,10 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
+    @Qualifier("taskExecutor")
+    @Autowired
+    private ThreadPoolTaskExecutor taskExecutor;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         responseMessageHandler.addHandler(cmdType, this);
@@ -70,68 +81,88 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
 
     @Override
     public void handForDevice(RequestEvent evt, Device device, Element element) {
-        String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + device.getDeviceId();
-        Element rootElement = null;
+        taskQueue.offer(new HandlerCatchData(evt, device, element));
+        // 回复200 OK
         try {
-            rootElement = getRootElement(evt, device.getCharset());
-            Element deviceListElement = rootElement.element("DeviceList");
-            Element sumNumElement = rootElement.element("SumNum");
-            Element snElement = rootElement.element("SN");
-            if (snElement == null || sumNumElement == null || deviceListElement == null) {
-                responseAck(evt, Response.BAD_REQUEST, "xml error");
-                return;
-            }
-            int sumNum = Integer.parseInt(sumNumElement.getText());
-
-            if (sumNum == 0) {
-                // 数据已经完整接收
-                storager.cleanChannelsForDevice(device.getDeviceId());
-                catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null);
-            }else {
-                Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
-                if (deviceListIterator != null) {
-                    List<DeviceChannel> channelList = new ArrayList<>();
-                    // 遍历DeviceList
-                    while (deviceListIterator.hasNext()) {
-                        Element itemDevice = deviceListIterator.next();
-                        Element channelDeviceElement = itemDevice.element("DeviceID");
-                        if (channelDeviceElement == null) {
-                            continue;
+            responseAck(evt, Response.OK);
+        } catch (SipException e) {
+            throw new RuntimeException(e);
+        } catch (InvalidArgumentException e) {
+            throw new RuntimeException(e);
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
+        if (!taskQueueHandlerRun) {
+            taskQueueHandlerRun = true;
+            taskExecutor.execute(()-> {
+                while (!taskQueue.isEmpty()) {
+                    HandlerCatchData take = taskQueue.poll();
+                    String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + take.getDevice().getDeviceId();
+                    Element rootElement = null;
+                    try {
+                        rootElement = getRootElement(take.getEvt(), take.getDevice().getCharset());
+                        Element deviceListElement = rootElement.element("DeviceList");
+                        Element sumNumElement = rootElement.element("SumNum");
+                        Element snElement = rootElement.element("SN");
+                        if (snElement == null || sumNumElement == null || deviceListElement == null) {
+                            responseAck(take.getEvt(), Response.BAD_REQUEST, "xml error");
+                            return;
                         }
-                        //by brewswang
-//                        if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {//如果包含位置信息,就更新一下位置
-//                            processNotifyMobilePosition(evt, itemDevice);
-//                        }
-                        DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice);
-                        deviceChannel.setDeviceId(device.getDeviceId());
-
-                        channelList.add(deviceChannel);
-                    }
-                    int sn = Integer.parseInt(snElement.getText());
-                    catalogDataCatch.put(device.getDeviceId(), sn, sumNum, device, channelList);
-                    logger.info("收到来自设备【{}】的通道: {}个,{}/{}", device.getDeviceId(), channelList.size(), catalogDataCatch.get(device.getDeviceId()) == null ? 0 :catalogDataCatch.get(device.getDeviceId()).size(), sumNum);
-                    if (catalogDataCatch.get(device.getDeviceId()).size() == sumNum) {
-                        // 数据已经完整接收
-                        boolean resetChannelsResult = storager.resetChannels(device.getDeviceId(), catalogDataCatch.get(device.getDeviceId()));
-                        if (!resetChannelsResult) {
-                            String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(device.getDeviceId()).size() + "条";
-                            catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), errorMsg);
+                        int sumNum = Integer.parseInt(sumNumElement.getText());
+
+                        if (sumNum == 0) {
+                            // 数据已经完整接收
+                            storager.cleanChannelsForDevice(take.getDevice().getDeviceId());
+                            catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
                         }else {
-                            catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null);
+                            Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
+                            if (deviceListIterator != null) {
+                                List<DeviceChannel> channelList = new ArrayList<>();
+                                // 遍历DeviceList
+                                while (deviceListIterator.hasNext()) {
+                                    Element itemDevice = deviceListIterator.next();
+                                    Element channelDeviceElement = itemDevice.element("DeviceID");
+                                    if (channelDeviceElement == null) {
+                                        continue;
+                                    }
+                                    //by brewswang
+    //                        if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {//如果包含位置信息,就更新一下位置
+    //                            processNotifyMobilePosition(evt, itemDevice);
+    //                        }
+                                    DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice, device);
+                                    deviceChannel.setDeviceId(take.getDevice().getDeviceId());
+
+                                    channelList.add(deviceChannel);
+                                }
+                                int sn = Integer.parseInt(snElement.getText());
+                                catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(), channelList);
+                                logger.info("收到来自设备【{}】的通道: {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.get(take.getDevice().getDeviceId()) == null ? 0 :catalogDataCatch.get(take.getDevice().getDeviceId()).size(), sumNum);
+                                if (catalogDataCatch.get(take.getDevice().getDeviceId()).size() == sumNum) {
+                                    // 数据已经完整接收
+                                    boolean resetChannelsResult = storager.resetChannels(take.getDevice().getDeviceId(), catalogDataCatch.get(take.getDevice().getDeviceId()));
+                                    if (!resetChannelsResult) {
+                                        String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(take.getDevice().getDeviceId()).size() + "条";
+                                        catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), errorMsg);
+                                    }else {
+                                        catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
+                                    }
+                                }
+                            }
+
                         }
+                    } catch (DocumentException e) {
+                        e.printStackTrace();
+                    } catch (InvalidArgumentException e) {
+                        e.printStackTrace();
+                    } catch (ParseException e) {
+                        e.printStackTrace();
+                    } catch (SipException e) {
+                        e.printStackTrace();
                     }
                 }
-                // 回复200 OK
-                responseAck(evt, Response.OK);
-            }
-        } catch (DocumentException e) {
-            e.printStackTrace();
-        } catch (InvalidArgumentException e) {
-            e.printStackTrace();
-        } catch (ParseException e) {
-            e.printStackTrace();
-        } catch (SipException e) {
-            e.printStackTrace();
+                taskQueueHandlerRun = false;
+            });
+
         }
     }
 

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java

@@ -82,7 +82,7 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
             deviceService.offline(device.getDeviceId());
         }
         RequestMessage msg = new RequestMessage();
-        msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId() + channelId);
+        msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId());
         msg.setData(json);
         deferredResultHolder.invokeAllResult(msg);
     }

+ 87 - 60
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java

@@ -1,9 +1,6 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
-import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
-import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
+import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.session.RecordDataCatch;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
@@ -19,6 +16,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 
@@ -28,6 +27,9 @@ import javax.sip.SipException;
 import javax.sip.message.Response;
 import java.text.ParseException;
 import java.util.*;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.LinkedBlockingQueue;
 
 import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
 
@@ -38,10 +40,11 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
 public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
 
     private Logger logger = LoggerFactory.getLogger(RecordInfoResponseMessageHandler.class);
-    public static volatile List<String> threadNameList = new ArrayList();
     private final String cmdType = "RecordInfo";
-    private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_";
 
+    private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
+
+    private boolean taskQueueHandlerRun = false;
     @Autowired
     private ResponseMessageHandler responseMessageHandler;
 
@@ -51,11 +54,13 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
     @Autowired
     private DeferredResultHolder deferredResultHolder;
 
-
-
     @Autowired
     private EventPublisher eventPublisher;
 
+    @Qualifier("taskExecutor")
+    @Autowired
+    private ThreadPoolTaskExecutor taskExecutor;
+
     @Override
     public void afterPropertiesSet() throws Exception {
         responseMessageHandler.addHandler(cmdType, this);
@@ -67,67 +72,89 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
         // 回复200 OK
         try {
             responseAck(evt, Response.OK);
-
-            rootElement = getRootElement(evt, device.getCharset());
-            String sn = getText(rootElement, "SN");
-
-            String sumNumStr = getText(rootElement, "SumNum");
-            int sumNum = 0;
-            if (!StringUtils.isEmpty(sumNumStr)) {
-                sumNum = Integer.parseInt(sumNumStr);
-            }
-            Element recordListElement = rootElement.element("RecordList");
-            if (recordListElement == null || sumNum == 0) {
-                logger.info("无录像数据");
-                recordDataCatch.put(device.getDeviceId(), sn, sumNum, new ArrayList<>());
-                releaseRequest(device.getDeviceId(), sn);
-            } else {
-                Iterator<Element> recordListIterator = recordListElement.elementIterator();
-                if (recordListIterator != null) {
-                    List<RecordItem> recordList = new ArrayList<>();
-                    // 遍历DeviceList
-                    while (recordListIterator.hasNext()) {
-                        Element itemRecord = recordListIterator.next();
-                        Element recordElement = itemRecord.element("DeviceID");
-                        if (recordElement == null) {
-                            logger.info("记录为空,下一个...");
-                            continue;
+            taskQueue.offer(new HandlerCatchData(evt, device, rootElement));
+            if (!taskQueueHandlerRun) {
+                taskQueueHandlerRun = true;
+                taskExecutor.execute(()->{
+                    try {
+                        while (!taskQueue.isEmpty()) {
+                            HandlerCatchData take = taskQueue.poll();
+                            Element rootElementForCharset = getRootElement(take.getEvt(), take.getDevice().getCharset());
+                            String sn = getText(rootElementForCharset, "SN");
+                            String channelId = getText(rootElementForCharset, "DeviceID");
+                            RecordInfo recordInfo = new RecordInfo();
+                            recordInfo.setChannelId(channelId);
+                            recordInfo.setDeviceId(take.getDevice().getDeviceId());
+                            recordInfo.setSn(sn);
+                            recordInfo.setName(getText(rootElementForCharset, "Name"));
+                            String sumNumStr = getText(rootElementForCharset, "SumNum");
+                            int sumNum = 0;
+                            if (!StringUtils.isEmpty(sumNumStr)) {
+                                sumNum = Integer.parseInt(sumNumStr);
+                            }
+                            recordInfo.setSumNum(sumNum);
+                            Element recordListElement = rootElementForCharset.element("RecordList");
+                            if (recordListElement == null || sumNum == 0) {
+                                logger.info("无录像数据");
+                                eventPublisher.recordEndEventPush(recordInfo);
+                                recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, new ArrayList<>());
+                                releaseRequest(take.getDevice().getDeviceId(), sn);
+                            } else {
+                                Iterator<Element> recordListIterator = recordListElement.elementIterator();
+                                if (recordListIterator != null) {
+                                    List<RecordItem> recordList = new ArrayList<>();
+                                    // 遍历DeviceList
+                                    while (recordListIterator.hasNext()) {
+                                        Element itemRecord = recordListIterator.next();
+                                        Element recordElement = itemRecord.element("DeviceID");
+                                        if (recordElement == null) {
+                                            logger.info("记录为空,下一个...");
+                                            continue;
+                                        }
+                                        RecordItem record = new RecordItem();
+                                        record.setDeviceId(getText(itemRecord, "DeviceID"));
+                                        record.setName(getText(itemRecord, "Name"));
+                                        record.setFilePath(getText(itemRecord, "FilePath"));
+                                        record.setFileSize(getText(itemRecord, "FileSize"));
+                                        record.setAddress(getText(itemRecord, "Address"));
+
+                                        String startTimeStr = getText(itemRecord, "StartTime");
+                                        record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr));
+
+                                        String endTimeStr = getText(itemRecord, "EndTime");
+                                        record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr));
+
+                                        record.setSecrecy(itemRecord.element("Secrecy") == null ? 0
+                                                : Integer.parseInt(getText(itemRecord, "Secrecy")));
+                                        record.setType(getText(itemRecord, "Type"));
+                                        record.setRecorderId(getText(itemRecord, "RecorderID"));
+                                        recordList.add(record);
+                                    }
+                                    recordInfo.setRecordList(recordList);
+                                    // 发送消息,如果是上级查询此录像,则会通过这里通知给上级
+                                    eventPublisher.recordEndEventPush(recordInfo);
+                                    int count = recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, recordList);
+                                    logger.info("[国标录像], {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum);
+                                }
+
+                                if (recordDataCatch.isComplete(take.getDevice().getDeviceId(), sn)){
+                                    releaseRequest(take.getDevice().getDeviceId(), sn);
+                                }
+                            }
                         }
-                        RecordItem record = new RecordItem();
-                        record.setDeviceId(getText(itemRecord, "DeviceID"));
-                        record.setName(getText(itemRecord, "Name"));
-                        record.setFilePath(getText(itemRecord, "FilePath"));
-                        record.setFileSize(getText(itemRecord, "FileSize"));
-                        record.setAddress(getText(itemRecord, "Address"));
-
-                        String startTimeStr = getText(itemRecord, "StartTime");
-                        record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr));
-
-                        String endTimeStr = getText(itemRecord, "EndTime");
-                        record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr));
-
-                        record.setSecrecy(itemRecord.element("Secrecy") == null ? 0
-                                : Integer.parseInt(getText(itemRecord, "Secrecy")));
-                        record.setType(getText(itemRecord, "Type"));
-                        record.setRecorderId(getText(itemRecord, "RecorderID"));
-                        recordList.add(record);
+                        taskQueueHandlerRun = false;
+                    }catch (DocumentException e) {
+                        throw new RuntimeException(e);
                     }
-                    int count = recordDataCatch.put(device.getDeviceId(), sn, sumNum, recordList);
-                    logger.info("[国标录像], {}->{}: {}/{}", device.getDeviceId(), sn, count, sumNum);
-                }
-
-                if (recordDataCatch.isComplete(device.getDeviceId(), sn)){
-                    releaseRequest(device.getDeviceId(), sn);
-                }
+                });
             }
+
         } catch (SipException e) {
             e.printStackTrace();
         } catch (InvalidArgumentException e) {
             e.printStackTrace();
         } catch (ParseException e) {
             e.printStackTrace();
-        } catch (DocumentException e) {
-            e.printStackTrace();
         }
     }
 

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java

@@ -17,7 +17,7 @@ import javax.sip.ResponseEvent;
 @Component
 public class ByeResponseProcessor extends SIPResponseProcessorAbstract {
 
-	private String method = "BYE";
+	private final String method = "BYE";
 
 	@Autowired
 	private SipLayer sipLayer;

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/CancelResponseProcessor.java

@@ -17,7 +17,7 @@ import javax.sip.ResponseEvent;
 @Component
 public class CancelResponseProcessor extends SIPResponseProcessorAbstract {
 
-	private String method = "CANCEL";
+	private final String method = "CANCEL";
 
 	@Autowired
 	private SipLayer sipLayer;

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java

@@ -31,7 +31,7 @@ import java.text.ParseException;
 public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
 
 	private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class);
-	private String method = "INVITE";
+	private final String method = "INVITE";
 
 	@Autowired
 	private SipLayer sipLayer;

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java

@@ -27,7 +27,7 @@ import javax.sip.message.Response;
 public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
 
 	private Logger logger = LoggerFactory.getLogger(RegisterResponseProcessor.class);
-	private String method = "REGISTER";
+	private final String method = "REGISTER";
 
 	@Autowired
 	private ISIPCommanderForPlatform sipCommanderForPlatform;

+ 29 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.utils;
 
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
 import org.dom4j.Attribute;
 import org.dom4j.Document;
@@ -180,7 +181,7 @@ public class XmlUtil {
         return xml.getRootElement();
     }
 
-    public static DeviceChannel channelContentHander(Element itemDevice){
+    public static DeviceChannel channelContentHander(Element itemDevice, Device device){
         Element channdelNameElement = itemDevice.element("Name");
         String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim().toString() : "";
         Element statusElement = itemDevice.element("Status");
@@ -254,6 +255,8 @@ public class XmlUtil {
             }else if (deviceChannel.getChannelId().length() == 20) {
                 if (Integer.parseInt(deviceChannel.getChannelId().substring(10, 13)) == 216) { // 虚拟组织
                     deviceChannel.setParentId(businessGroupID);
+                }else if (Integer.parseInt(device.getDeviceId().substring(10, 13) )== 118) {//NVR 如果上级设备编号是NVR则直接将NVR的编号设置给通道的上级编号
+                    deviceChannel.setParentId(device.getDeviceId());
                 }else if (deviceChannel.getCivilCode() != null) {
                     // 设备, 无parentId的20位是使用CivilCode表示上级的设备,
                     // 注:215 业务分组是需要有parentId的
@@ -308,6 +311,31 @@ public class XmlUtil {
         } else {
             deviceChannel.setLatitude(0.00);
         }
+        if (deviceChannel.getLongitude()*deviceChannel.getLatitude() > 0) {
+            if ("WGS84".equals(device.getGeoCoordSys())) {
+                deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude());
+                deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude());
+                Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude());
+                deviceChannel.setLongitudeGcj02(position[0]);
+                deviceChannel.setLatitudeGcj02(position[1]);
+            }else if ("GCJ02".equals(device.getGeoCoordSys())) {
+                deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude());
+                deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude());
+                Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude());
+                deviceChannel.setLongitudeWgs84(position[0]);
+                deviceChannel.setLatitudeWgs84(position[1]);
+            }else {
+                deviceChannel.setLongitudeGcj02(0.00);
+                deviceChannel.setLatitudeGcj02(0.00);
+                deviceChannel.setLongitudeWgs84(0.00);
+                deviceChannel.setLatitudeWgs84(0.00);
+            }
+        }else {
+            deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude());
+            deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude());
+            deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude());
+            deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude());
+        }
         if (XmlUtil.getText(itemDevice, "PTZType") == null || "".equals(XmlUtil.getText(itemDevice, "PTZType"))) {
             //兼容INFO中的信息
             Element info = itemDevice.element("Info");

+ 31 - 16
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@@ -11,7 +11,6 @@ import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
-import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.media.zlm.dto.*;
 import com.genersoft.iot.vmp.service.*;
@@ -92,10 +91,9 @@ public class ZLMHttpHookListener {
 	public ResponseEntity<String> onServerKeepalive(@RequestBody JSONObject json){
 
 		if (logger.isDebugEnabled()) {
-			logger.debug("[ ZLM HOOK ]on_server_keepalive API调用,参数:" + json.toString());
+			logger.debug("[ ZLM HOOK ] on_server_keepalive API调用,参数:" + json.toString());
 		}
 		String mediaServerId = json.getString("mediaServerId");
-
 		List<ZLMHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(ZLMHttpHookSubscribe.HookType.on_server_keepalive);
 		if (subscribes != null  && subscribes.size() > 0) {
 			for (ZLMHttpHookSubscribe.Event subscribe : subscribes) {
@@ -165,7 +163,6 @@ public class ZLMHttpHookListener {
 			if (mediaInfo != null) {
 				subscribe.response(mediaInfo, json);
 			}
-
 		}
 		JSONObject ret = new JSONObject();
 		ret.put("code", 0);
@@ -248,6 +245,23 @@ public class ZLMHttpHookListener {
 		ret.put("msg", "success");
 		return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
 	}
+	/**
+	 * 录制hls完成后通知事件;此事件对回复不敏感。
+	 *
+	 */
+	@ResponseBody
+	@PostMapping(value = "/on_record_ts", produces = "application/json;charset=UTF-8")
+	public ResponseEntity<String> onRecordTs(@RequestBody JSONObject json){
+
+		if (logger.isDebugEnabled()) {
+			logger.debug("[ ZLM HOOK ]on_record_ts API调用,参数:" + json.toString());
+		}
+		String mediaServerId = json.getString("mediaServerId");
+		JSONObject ret = new JSONObject();
+		ret.put("code", 0);
+		ret.put("msg", "success");
+		return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
+	}
 	
 	/**
 	 * rtsp专用的鉴权事件,先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件。
@@ -383,21 +397,22 @@ public class ZLMHttpHookListener {
 							if (item.getOriginType() == OriginType.RTSP_PUSH.ordinal()
 									|| item.getOriginType() == OriginType.RTMP_PUSH.ordinal()
 									|| item.getOriginType() == OriginType.RTC_PUSH.ordinal() ) {
-								streamPushItem = zlmMediaListManager.addPush(item);
+								item.setSeverId(userSetting.getServerId());
+								zlmMediaListManager.addPush(item);
 							}
 
-							List<GbStream> gbStreams = new ArrayList<>();
-							if (streamPushItem == null || streamPushItem.getGbId() == null) {
-								GbStream gbStream = storager.getGbStream(app, streamId);
-								gbStreams.add(gbStream);
-							}else {
-								if (streamPushItem.getGbId() != null) {
-									gbStreams.add(streamPushItem);
-								}
-							}
-							if (gbStreams.size() > 0) {
+//							List<GbStream> gbStreams = new ArrayList<>();
+//							if (streamPushItem == null || streamPushItem.getGbId() == null) {
+//								GbStream gbStream = storager.getGbStream(app, streamId);
+//								gbStreams.add(gbStream);
+//							}else {
+//								if (streamPushItem.getGbId() != null) {
+//									gbStreams.add(streamPushItem);
+//								}
+//							}
+//							if (gbStreams.size() > 0) {
 //								eventPublisher.catalogEventPublishForStream(null, gbStreams, CatalogEvent.ON);
-							}
+//							}
 
 						}else {
 							// 兼容流注销时类型从redis记录获取

+ 4 - 3
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java

@@ -24,6 +24,9 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+/**
+ * @author lin
+ */
 @Component
 public class ZLMMediaListManager {
 
@@ -147,7 +150,6 @@ public class ZLMMediaListManager {
                     }
                 }
             }
-            //            StreamProxyItem streamProxyItem = gbStreamMapper.selectOne(transform.getApp(), transform.getStream());
             List<GbStream> gbStreamList = gbStreamMapper.selectByGBId(transform.getGbId());
             if (gbStreamList != null && gbStreamList.size() == 1) {
                 transform.setGbStreamId(gbStreamList.get(0).getGbStreamId());
@@ -162,13 +164,12 @@ public class ZLMMediaListManager {
             }
             if (transform != null) {
                 if (channelOnlineEvents.get(transform.getGbId()) != null)  {
-                    channelOnlineEvents.get(transform.getGbId()).run(transform.getApp(), transform.getStream());
+                    channelOnlineEvents.get(transform.getGbId()).run(transform.getApp(), transform.getStream(), transform.getServerId());
                     channelOnlineEvents.remove(transform.getGbId());
                 }
             }
         }
 
-
         storager.updateMedia(transform);
         return transform;
     }

+ 31 - 7
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java

@@ -12,6 +12,7 @@ import org.springframework.stereotype.Component;
 
 import java.io.*;
 import java.net.ConnectException;
+import java.net.SocketTimeoutException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -28,6 +29,9 @@ public class ZLMRESTfulUtils {
 
     private OkHttpClient getClient(){
         OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
+        //todo 暂时写死超时时间 均为5s
+        httpClientBuilder.connectTimeout(5,TimeUnit.SECONDS);  //设置连接超时时间
+        httpClientBuilder.readTimeout(5,TimeUnit.SECONDS);     //设置读取超时时间
         if (logger.isDebugEnabled()) {
             HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> {
                 logger.debug("http请求参数:" + message);
@@ -47,7 +51,10 @@ public class ZLMRESTfulUtils {
             return null;
         }
         String url = String.format("http://%s:%s/index/api/%s",  mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
-        JSONObject responseJSON = null;
+        JSONObject responseJSON = new JSONObject();
+        //-2自定义流媒体 调用错误码
+        responseJSON.put("code",-2);
+        responseJSON.put("msg","流媒体调用失败");
 
         FormBody.Builder builder = new FormBody.Builder();
         builder.add("secret",mediaServerItem.getSecret());
@@ -78,11 +85,20 @@ public class ZLMRESTfulUtils {
                         response.close();
                         Objects.requireNonNull(response.body()).close();
                     }
-                } catch (ConnectException e) {
-                    logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
-                    logger.info("请检查media配置并确认ZLM已启动...");
                 }catch (IOException e) {
                     logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
+
+                    if(e instanceof SocketTimeoutException){
+                        //读取超时超时异常
+                        logger.error(String.format("读取ZLM数据失败: %s, %s", url, e.getMessage()));
+                    }
+                    if(e instanceof ConnectException){
+                        //判断连接异常,我这里是报Failed to connect to 10.7.5.144
+                        logger.error(String.format("连接ZLM失败: %s, %s", url, e.getMessage()));
+                    }
+
+                }catch (Exception e){
+                    logger.error(String.format("访问ZLM失败: %s, %s", url, e.getMessage()));
                 }
             }else {
                 client.newCall(request).enqueue(new Callback(){
@@ -105,8 +121,16 @@ public class ZLMRESTfulUtils {
 
                     @Override
                     public void onFailure(@NotNull Call call, @NotNull IOException e) {
-                        logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
-                        logger.info("请检查media配置并确认ZLM已启动...");
+                        logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage()));
+
+                        if(e instanceof SocketTimeoutException){
+                            //读取超时超时异常
+                            logger.error(String.format("读取ZLM数据失败: %s, %s", call.request().toString(), e.getMessage()));
+                        }
+                        if(e instanceof ConnectException){
+                            //判断连接异常,我这里是报Failed to connect to 10.7.5.144
+                            logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage()));
+                        }
                     }
                 });
             }
@@ -151,7 +175,7 @@ public class ZLMRESTfulUtils {
                         }
 
                     }
-                    File snapFile = new File(targetPath + "/" + fileName);
+                    File snapFile = new File(targetPath + File.separator + fileName);
                     FileOutputStream outStream = new FileOutputStream(snapFile);
 
                     outStream.write(Objects.requireNonNull(response.body()).bytes());

+ 14 - 3
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.media.zlm;
 
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import org.slf4j.Logger;
@@ -20,6 +21,9 @@ public class ZLMRTPServerFactory {
     @Autowired
     private ZLMRESTfulUtils zlmresTfulUtils;
 
+    @Autowired
+    private UserSetting userSetting;
+
     private int[] portRangeArray = new int[2];
 
     public int getFreePort(MediaServerItem mediaServerItem, int startPort, int endPort, List<Integer> usedFreelist) {
@@ -87,10 +91,15 @@ public class ZLMRTPServerFactory {
         int result = -1;
         // 查询此rtp server 是否已经存在
         JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
-        if (rtpInfo != null && rtpInfo.getInteger("code") == 0 && rtpInfo.getBoolean("exist")) {
-            result = rtpInfo.getInteger("local_port");
+        if(rtpInfo.getInteger("code") == 0){
+            if (rtpInfo.getBoolean("exist")) {
+                result = rtpInfo.getInteger("local_port");
+                return result;
+            }
+        }else if(rtpInfo.getInteger("code") == -2){
             return result;
         }
+
         Map<String, Object> param = new HashMap<>();
         // 推流端口设置0则使用随机端口
         param.put("enable_tcp", 1);
@@ -197,6 +206,7 @@ public class ZLMRTPServerFactory {
         sendRtpItem.setTcp(tcp);
         sendRtpItem.setApp("rtp");
         sendRtpItem.setLocalPort(localPort);
+        sendRtpItem.setServerId(userSetting.getServerId());
         sendRtpItem.setMediaServerId(serverItem.getId());
         return sendRtpItem;
     }
@@ -238,6 +248,7 @@ public class ZLMRTPServerFactory {
         sendRtpItem.setChannelId(channelId);
         sendRtpItem.setTcp(tcp);
         sendRtpItem.setLocalPort(localPort);
+        sendRtpItem.setServerId(userSetting.getServerId());
         sendRtpItem.setMediaServerId(serverItem.getId());
         return sendRtpItem;
     }
@@ -279,10 +290,10 @@ public class ZLMRTPServerFactory {
      */
     public int totalReaderCount(MediaServerItem mediaServerItem, String app, String streamId) {
         JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
-        Integer code = mediaInfo.getInteger("code");
         if (mediaInfo == null) {
             return 0;
         }
+        Integer code = mediaInfo.getInteger("code");
         if ( code < 0) {
             logger.warn("查询流({}/{})是否有其它观看者时得到: {}", app, streamId, mediaInfo.getString("msg"));
             return -1;

+ 4 - 1
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java

@@ -1,6 +1,9 @@
 package com.genersoft.iot.vmp.media.zlm.dto;
 
+/**
+ * @author lin
+ */
 public interface ChannelOnlineEvent {
 
-    void run(String app, String stream);
+    void run(String app, String stream, String serverId);
 }

+ 14 - 1
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java

@@ -61,10 +61,15 @@ public class MediaItem {
     private String originUrl;
 
     /**
-     * 服务器id
+     * 流媒体服务器id
      */
     private String mediaServerId;
 
+    /**
+     * 服务器id
+     */
+    private String severId;
+
     /**
      * GMT unix系统时间戳,单位秒
      */
@@ -414,4 +419,12 @@ public class MediaItem {
     public void setStreamInfo(StreamInfo streamInfo) {
         this.streamInfo = streamInfo;
     }
+
+    public String getSeverId() {
+        return severId;
+    }
+
+    public void setSeverId(String severId) {
+        this.severId = severId;
+    }
 }

+ 13 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java

@@ -81,6 +81,11 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte
      */
     private String mediaServerId;
 
+    /**
+     * 使用的服务ID
+     */
+    private String serverId;
+
     public String getVhost() {
         return vhost;
     }
@@ -219,5 +224,13 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte
     public void setMediaServerId(String mediaServerId) {
         this.mediaServerId = mediaServerId;
     }
+
+    public String getServerId() {
+        return serverId;
+    }
+
+    public void setServerId(String serverId) {
+        this.serverId = serverId;
+    }
 }
 

+ 3 - 4
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMKeepliveTimeoutListener.java

@@ -61,13 +61,12 @@ public class ZLMKeepliveTimeoutListener extends RedisKeyExpirationEventMessageLi
         // 发起http请求验证zlm是否确实无法连接,如果确实无法连接则发送离线事件,否则不作处理
         MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
         JSONObject mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
-        if (mediaServerConfig == null) {
-            publisher.zlmOfflineEventPublish(mediaServerId);
-        }else {
+        if (mediaServerConfig != null && mediaServerConfig.getInteger("code") == 0) {
             logger.info("[zlm心跳到期]:{}验证后zlm仍在线,恢复心跳信息", mediaServerId);
             // 添加zlm信息
             mediaServerService.updateMediaServerKeepalive(mediaServerId, mediaServerConfig);
+        }else {
+            publisher.zlmOfflineEventPublish(mediaServerId);
         }
-
     }
 }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMStatusEventListener.java

@@ -42,7 +42,7 @@ public class ZLMStatusEventListener {
 		logger.info("[ZLM] 上线 ID:" + event.getMediaServerId());
 		streamPushService.zlmServerOnline(event.getMediaServerId());
 		streamProxyService.zlmServerOnline(event.getMediaServerId());
-
+		playService.zlmServerOnline(event.getMediaServerId());
 	}
 
 	@Async

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java

@@ -42,6 +42,8 @@ public interface IPlayService {
 
     StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream);
 
+    void zlmServerOnline(String mediaServerId);
+
     void audioBroadcast(Device device, String channelId, int timeout, AudioBroadcastEvent event);
     void stopAudioBroadcast(String deviceId, String channelId);
 }

+ 0 - 1
src/main/java/com/genersoft/iot/vmp/service/StreamGPSSubscribeTask.java

@@ -23,7 +23,6 @@ public class StreamGPSSubscribeTask {
     private IVideoManagerStorage storager;
 
 
-
     @Scheduled(fixedRate = 30 * 1000)   //每30秒执行一次
     public void execute(){
         List<GPSMsgInfo> gpsMsgInfo = redisCatchStorage.getAllGpsMsgInfo();

+ 17 - 0
src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java

@@ -1,7 +1,10 @@
 package com.genersoft.iot.vmp.service.bean;
 
+import java.util.stream.Stream;
+
 /**
  * 当上级平台
+ * @author lin
  */
 public class MessageForPushChannel {
     /**
@@ -45,6 +48,20 @@ public class MessageForPushChannel {
      */
     private String mediaServerId;
 
+    public static MessageForPushChannel getInstance(int type, String app, String stream, String gbId,
+                                                    String platFormId, String platFormName, String serverId,
+                                                    String mediaServerId){
+        MessageForPushChannel messageForPushChannel = new MessageForPushChannel();
+        messageForPushChannel.setType(type);
+        messageForPushChannel.setGbId(gbId);
+        messageForPushChannel.setApp(app);
+        messageForPushChannel.setStream(stream);
+        messageForPushChannel.setMediaServerId(mediaServerId);
+        messageForPushChannel.setPlatFormId(platFormId);
+        messageForPushChannel.setPlatFormName(platFormName);
+        return messageForPushChannel;
+    }
+
 
     public int getType() {
         return type;

+ 170 - 0
src/main/java/com/genersoft/iot/vmp/service/bean/RequestPushStreamMsg.java

@@ -0,0 +1,170 @@
+package com.genersoft.iot.vmp.service.bean;
+
+/**
+ * redis消息:请求下级推送流信息
+ * @author lin
+ */
+public class RequestPushStreamMsg {
+
+
+    /**
+     * 下级服务ID
+     */
+    private String mediaServerId;
+
+    /**
+     * 流ID
+     */
+    private String app;
+
+    /**
+     * 应用名
+     */
+    private String stream;
+
+    /**
+     * 目标IP
+     */
+    private String ip;
+
+    /**
+     * 目标端口
+     */
+    private int port;
+
+    /**
+     * ssrc
+     */
+    private String ssrc;
+
+    /**
+     * 是否使用TCP方式
+     */
+    private boolean tcp;
+
+    /**
+     * 本地使用的端口
+     */
+    private int srcPort;
+
+    /**
+     * 发送时,rtp的pt(uint8_t),不传时默认为96
+     */
+    private int pt;
+
+    /**
+     * 发送时,rtp的负载类型。为true时,负载为ps;为false时,为es;
+     */
+    private boolean ps;
+
+    /**
+     * 是否只有音频
+     */
+    private boolean onlyAudio;
+
+
+    public static RequestPushStreamMsg getInstance(String mediaServerId, String app, String stream, String ip, int port, String ssrc,
+                                boolean tcp, int srcPort, int pt, boolean ps, boolean onlyAudio) {
+        RequestPushStreamMsg requestPushStreamMsg = new RequestPushStreamMsg();
+        requestPushStreamMsg.setMediaServerId(mediaServerId);
+        requestPushStreamMsg.setApp(app);
+        requestPushStreamMsg.setStream(stream);
+        requestPushStreamMsg.setIp(ip);
+        requestPushStreamMsg.setPort(port);
+        requestPushStreamMsg.setSsrc(ssrc);
+        requestPushStreamMsg.setTcp(tcp);
+        requestPushStreamMsg.setSrcPort(srcPort);
+        requestPushStreamMsg.setPt(pt);
+        requestPushStreamMsg.setPs(ps);
+        requestPushStreamMsg.setOnlyAudio(onlyAudio);
+        return requestPushStreamMsg;
+    }
+
+    public String getMediaServerId() {
+        return mediaServerId;
+    }
+
+    public void setMediaServerId(String mediaServerId) {
+        this.mediaServerId = mediaServerId;
+    }
+
+    public String getApp() {
+        return app;
+    }
+
+    public void setApp(String app) {
+        this.app = app;
+    }
+
+    public String getStream() {
+        return stream;
+    }
+
+    public void setStream(String stream) {
+        this.stream = stream;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public String getSsrc() {
+        return ssrc;
+    }
+
+    public void setSsrc(String ssrc) {
+        this.ssrc = ssrc;
+    }
+
+    public boolean isTcp() {
+        return tcp;
+    }
+
+    public void setTcp(boolean tcp) {
+        this.tcp = tcp;
+    }
+
+    public int getSrcPort() {
+        return srcPort;
+    }
+
+    public void setSrcPort(int srcPort) {
+        this.srcPort = srcPort;
+    }
+
+    public int getPt() {
+        return pt;
+    }
+
+    public void setPt(int pt) {
+        this.pt = pt;
+    }
+
+    public boolean isPs() {
+        return ps;
+    }
+
+    public void setPs(boolean ps) {
+        this.ps = ps;
+    }
+
+    public boolean isOnlyAudio() {
+        return onlyAudio;
+    }
+
+    public void setOnlyAudio(boolean onlyAudio) {
+        this.onlyAudio = onlyAudio;
+    }
+}

+ 173 - 0
src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java

@@ -0,0 +1,173 @@
+package com.genersoft.iot.vmp.service.bean;
+
+/**
+ * redis消息:请求下级回复推送信息
+ * @author lin
+ */
+public class RequestSendItemMsg {
+
+    /**
+     * 下级服务ID
+     */
+    private String serverId;
+
+    /**
+     * 下级服务ID
+     */
+    private String mediaServerId;
+
+    /**
+     * 流ID
+     */
+    private String app;
+
+    /**
+     * 应用名
+     */
+    private String stream;
+
+    /**
+     * 目标IP
+     */
+    private String ip;
+
+    /**
+     * 目标端口
+     */
+    private int port;
+
+    /**
+     * ssrc
+     */
+    private String ssrc;
+
+    /**
+     * 平台国标编号
+     */
+    private String platformId;
+
+    /**
+     * 平台名称
+     */
+    private String platformName;
+
+    /**
+     * 通道ID
+     */
+    private String channelId;
+
+
+    /**
+     * 是否使用TCP
+     */
+    private Boolean isTcp;
+
+
+
+
+    public static RequestSendItemMsg getInstance(String serverId, String mediaServerId, String app, String stream, String ip, int port,
+                                                          String ssrc, String platformId, String channelId, Boolean isTcp, String platformName) {
+        RequestSendItemMsg requestSendItemMsg = new RequestSendItemMsg();
+        requestSendItemMsg.setServerId(serverId);
+        requestSendItemMsg.setMediaServerId(mediaServerId);
+        requestSendItemMsg.setApp(app);
+        requestSendItemMsg.setStream(stream);
+        requestSendItemMsg.setIp(ip);
+        requestSendItemMsg.setPort(port);
+        requestSendItemMsg.setSsrc(ssrc);
+        requestSendItemMsg.setPlatformId(platformId);
+        requestSendItemMsg.setPlatformName(platformName);
+        requestSendItemMsg.setChannelId(channelId);
+        requestSendItemMsg.setTcp(isTcp);
+
+        return  requestSendItemMsg;
+    }
+
+    public String getServerId() {
+        return serverId;
+    }
+
+    public void setServerId(String serverId) {
+        this.serverId = serverId;
+    }
+
+    public String getMediaServerId() {
+        return mediaServerId;
+    }
+
+    public void setMediaServerId(String mediaServerId) {
+        this.mediaServerId = mediaServerId;
+    }
+
+    public String getApp() {
+        return app;
+    }
+
+    public void setApp(String app) {
+        this.app = app;
+    }
+
+    public String getStream() {
+        return stream;
+    }
+
+    public void setStream(String stream) {
+        this.stream = stream;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public String getSsrc() {
+        return ssrc;
+    }
+
+    public void setSsrc(String ssrc) {
+        this.ssrc = ssrc;
+    }
+
+    public String getPlatformId() {
+        return platformId;
+    }
+
+    public void setPlatformId(String platformId) {
+        this.platformId = platformId;
+    }
+
+    public String getPlatformName() {
+        return platformName;
+    }
+
+    public void setPlatformName(String platformName) {
+        this.platformName = platformName;
+    }
+
+    public String getChannelId() {
+        return channelId;
+    }
+
+    public void setChannelId(String channelId) {
+        this.channelId = channelId;
+    }
+
+    public Boolean getTcp() {
+        return isTcp;
+    }
+
+    public void setTcp(Boolean tcp) {
+        isTcp = tcp;
+    }
+}

+ 31 - 0
src/main/java/com/genersoft/iot/vmp/service/bean/ResponseSendItemMsg.java

@@ -0,0 +1,31 @@
+package com.genersoft.iot.vmp.service.bean;
+
+import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+
+/**
+ * redis消息:下级回复推送信息
+ * @author lin
+ */
+public class ResponseSendItemMsg {
+
+    private SendRtpItem sendRtpItem;
+
+    private MediaServerItem mediaServerItem;
+
+    public SendRtpItem getSendRtpItem() {
+        return sendRtpItem;
+    }
+
+    public void setSendRtpItem(SendRtpItem sendRtpItem) {
+        this.sendRtpItem = sendRtpItem;
+    }
+
+    public MediaServerItem getMediaServerItem() {
+        return mediaServerItem;
+    }
+
+    public void setMediaServerItem(MediaServerItem mediaServerItem) {
+        this.mediaServerItem = mediaServerItem;
+    }
+}

+ 116 - 0
src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java

@@ -0,0 +1,116 @@
+package com.genersoft.iot.vmp.service.bean;
+
+/**
+ * @author lin
+ */
+public class WvpRedisMsg {
+
+    public static WvpRedisMsg getInstance(String fromId, String toId, String type, String cmd, String serial, String content){
+        WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
+        wvpRedisMsg.setFromId(fromId);
+        wvpRedisMsg.setToId(toId);
+        wvpRedisMsg.setType(type);
+        wvpRedisMsg.setCmd(cmd);
+        wvpRedisMsg.setSerial(serial);
+        wvpRedisMsg.setContent(content);
+        return wvpRedisMsg;
+    }
+
+    private String fromId;
+
+    private String toId;
+    /**
+     * req 请求, res 回复
+     */
+    private String type;
+    private String cmd;
+
+    /**
+     * 消息的ID
+     */
+    private String serial;
+    private Object content;
+
+    private final static String requestTag = "req";
+    private final static String responseTag = "res";
+
+    public static WvpRedisMsg getRequestInstance(String fromId, String toId, String cmd, String serial, Object content) {
+        WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
+        wvpRedisMsg.setType(requestTag);
+        wvpRedisMsg.setFromId(fromId);
+        wvpRedisMsg.setToId(toId);
+        wvpRedisMsg.setCmd(cmd);
+        wvpRedisMsg.setSerial(serial);
+        wvpRedisMsg.setContent(content);
+        return wvpRedisMsg;
+    }
+
+    public static WvpRedisMsg getResponseInstance() {
+        WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
+        wvpRedisMsg.setType(responseTag);
+        return wvpRedisMsg;
+    }
+
+    public static WvpRedisMsg getResponseInstance(String fromId, String toId, String cmd, String serial, Object content) {
+        WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
+        wvpRedisMsg.setType(responseTag);
+        wvpRedisMsg.setFromId(fromId);
+        wvpRedisMsg.setToId(toId);
+        wvpRedisMsg.setCmd(cmd);
+        wvpRedisMsg.setSerial(serial);
+        wvpRedisMsg.setContent(content);
+        return wvpRedisMsg;
+    }
+
+    public static boolean isRequest(WvpRedisMsg wvpRedisMsg) {
+        return requestTag.equals(wvpRedisMsg.getType());
+    }
+
+    public String getSerial() {
+        return serial;
+    }
+
+    public void setSerial(String serial) {
+        this.serial = serial;
+    }
+
+    public String getFromId() {
+        return fromId;
+    }
+
+    public void setFromId(String fromId) {
+        this.fromId = fromId;
+    }
+
+    public String getToId() {
+        return toId;
+    }
+
+    public void setToId(String toId) {
+        this.toId = toId;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getCmd() {
+        return cmd;
+    }
+
+    public void setCmd(String cmd) {
+        this.cmd = cmd;
+    }
+
+    public Object getContent() {
+        return content;
+    }
+
+    public void setContent(Object content) {
+        this.content = content;
+    }
+}

+ 12 - 0
src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsgCmd.java

@@ -0,0 +1,12 @@
+package com.genersoft.iot.vmp.service.bean;
+
+/**
+ * @author lin
+ */
+
+public class WvpRedisMsgCmd {
+
+    public static final String GET_SEND_ITEM = "GetSendItem";
+    public static final String REQUEST_PUSH_STREAM = "RequestPushStream";
+
+}

+ 65 - 8
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java

@@ -2,16 +2,21 @@ package com.genersoft.iot.vmp.service.impl;
 
 import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
 import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
+import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
 import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
 import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
 import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import org.slf4j.Logger;
@@ -49,6 +54,12 @@ public class DeviceServiceImpl implements IDeviceService {
     @Autowired
     private DeviceMapper deviceMapper;
 
+    @Autowired
+    private DeviceChannelMapper deviceChannelMapper;
+
+    @Autowired
+    private IVideoManagerStorage storage;
+
     @Autowired
     private ISIPCommander commander;
 
@@ -68,7 +79,6 @@ public class DeviceServiceImpl implements IDeviceService {
         if (deviceInRedis != null && deviceInDb == null) {
             // redis 存在脏数据
             redisCatchStorage.clearCatchByDeviceId(device.getDeviceId());
-
         }
         device.setUpdateTime(now);
         device.setOnline(1);
@@ -77,13 +87,15 @@ public class DeviceServiceImpl implements IDeviceService {
         if (device.getCreateTime() == null) {
             device.setCreateTime(now);
             logger.info("[设备上线,首次注册]: {},查询设备信息以及通道信息", device.getDeviceId());
+            deviceMapper.add(device);
+            redisCatchStorage.updateDevice(device);
             commander.deviceInfoQuery(device);
             sync(device);
-            deviceMapper.add(device);
         }else {
             deviceMapper.update(device);
+            redisCatchStorage.updateDevice(device);
         }
-        redisCatchStorage.updateDevice(device);
+
         // 上线添加订阅
         if (device.getSubscribeCycleForCatalog() > 0) {
             // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
@@ -94,7 +106,6 @@ public class DeviceServiceImpl implements IDeviceService {
         }
         // 刷新过期任务
         String registerExpireTaskKey = registerExpireTaskKeyPrefix + device.getDeviceId();
-        dynamicTask.stop(registerExpireTaskKey);
         dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId()), device.getExpires() * 1000);
     }
 
@@ -143,8 +154,16 @@ public class DeviceServiceImpl implements IDeviceService {
         if (device == null || device.getSubscribeCycleForCatalog() < 0) {
             return false;
         }
-        logger.info("移除目录订阅: {}", device.getDeviceId());
-        dynamicTask.stop(device.getDeviceId() + "catalog");
+        logger.info("[移除目录订阅]: {}", device.getDeviceId());
+        String taskKey = device.getDeviceId() + "catalog";
+        if (device.getOnline() == 1) {
+            Runnable runnable = dynamicTask.get(taskKey);
+            if (runnable instanceof ISubscribeTask) {
+                ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
+                subscribeTask.stop();
+            }
+        }
+        dynamicTask.stop(taskKey);
         return true;
     }
 
@@ -168,8 +187,16 @@ public class DeviceServiceImpl implements IDeviceService {
         if (device == null || device.getSubscribeCycleForCatalog() < 0) {
             return false;
         }
-        logger.info("移除移动位置订阅: {}", device.getDeviceId());
-        dynamicTask.stop(device.getDeviceId() + "mobile_position");
+        logger.info("[移除移动位置订阅]: {}", device.getDeviceId());
+        String taskKey = device.getDeviceId() + "mobile_position";
+        if (device.getOnline() == 1) {
+            Runnable runnable = dynamicTask.get(taskKey);
+            if (runnable instanceof ISubscribeTask) {
+                ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
+                subscribeTask.stop();
+            }
+        }
+        dynamicTask.stop(taskKey);
         return true;
     }
 
@@ -275,6 +302,10 @@ public class DeviceServiceImpl implements IDeviceService {
                 removeMobilePositionSubscribe(deviceInStore);
             }
         }
+        // 坐标系变化,需要重新计算GCJ02坐标和WGS84坐标
+        if (!deviceInStore.getGeoCoordSys().equals(device.getGeoCoordSys())) {
+            updateDeviceChannelGeoCoordSys(device);
+        }
 
         String now = DateUtil.getNow();
         device.setUpdateTime(now);
@@ -282,6 +313,32 @@ public class DeviceServiceImpl implements IDeviceService {
         device.setUpdateTime(DateUtil.getNow());
         if (deviceMapper.update(device) > 0) {
             redisCatchStorage.updateDevice(device);
+
         }
     }
+
+    /**
+     * 更新通道坐标系
+     */
+    private void updateDeviceChannelGeoCoordSys(Device device) {
+       List<DeviceChannel> deviceChannels =  deviceChannelMapper.getAllChannelWithCoordinate(device.getDeviceId());
+       if (deviceChannels.size() > 0) {
+           for (DeviceChannel deviceChannel : deviceChannels) {
+               if ("WGS84".equals(device.getGeoCoordSys())) {
+                   deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude());
+                   deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude());
+                   Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude());
+                   deviceChannel.setLongitudeGcj02(position[0]);
+                   deviceChannel.setLatitudeGcj02(position[1]);
+               }else if ("GCJ02".equals(device.getGeoCoordSys())) {
+                   deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude());
+                   deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude());
+                   Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude());
+                   deviceChannel.setLongitudeWgs84(position[0]);
+                   deviceChannel.setLatitudeWgs84(position[1]);
+               }
+           }
+       }
+        storage.updateChannels(device.getDeviceId(), deviceChannels);
+    }
 }

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java

@@ -106,7 +106,8 @@ public class GbStreamServiceImpl implements IGbStreamService {
         deviceChannel.setStatus(1);
         deviceChannel.setParentId(catalogId ==null?gbStream.getCatalogId():catalogId);
         deviceChannel.setRegisterWay(1);
-        if (catalogId.length() <= 10) { // 父节点是行政区划,则设置CivilCode使用此行政区划
+        if (catalogId.length() > 0 && catalogId.length() <= 10) {
+            // 父节点是行政区划,则设置CivilCode使用此行政区划
             deviceChannel.setCivilCode(catalogId);
         }else {
             deviceChannel.setCivilCode(platform.getAdministrativeDivision());

+ 13 - 10
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java

@@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
 import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
@@ -35,7 +36,9 @@ import org.springframework.util.StringUtils;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 媒体服务器节点管理
@@ -189,6 +192,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
     public void clearRTPServer(MediaServerItem mediaServerItem) {
         mediaServerItem.setSsrcConfig(new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getDomain()));
         redisUtil.zAdd(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId(), mediaServerItem.getId(), 0);
+
     }
 
 
@@ -229,11 +233,10 @@ public class MediaServerServiceImpl implements IMediaServerService {
         }
         result.sort((serverItem1, serverItem2)->{
             int sortResult = 0;
-            try {
-                sortResult = DateUtil.format.parse(serverItem1.getCreateTime()).compareTo(DateUtil.format.parse(serverItem2.getCreateTime()));
-            } catch (ParseException e) {
-                e.printStackTrace();
-            }
+            LocalDateTime localDateTime1 = LocalDateTime.parse(serverItem1.getCreateTime(), DateUtil.formatter);
+            LocalDateTime localDateTime2 = LocalDateTime.parse(serverItem2.getCreateTime(), DateUtil.formatter);
+
+            sortResult = localDateTime1.compareTo(localDateTime2);
             return  sortResult;
         });
         return result;
@@ -495,14 +498,14 @@ public class MediaServerServiceImpl implements IMediaServerService {
         param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline
         param.put("ffmpeg.cmd","%s -fflags nobuffer -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264  -f flv %s");
         param.put("hook.enable","1");
-        param.put("hook.on_flow_report","");
+        param.put("hook.on_flow_report",String.format("%s/on_flow_report", hookPrex));
         param.put("hook.on_play",String.format("%s/on_play", hookPrex));
-        param.put("hook.on_http_access","");
+        param.put("hook.on_http_access",String.format("%s/on_http_access", hookPrex));
         param.put("hook.on_publish", String.format("%s/on_publish", hookPrex));
         param.put("hook.on_record_mp4",recordHookPrex != null? String.format("%s/on_record_mp4", recordHookPrex): "");
-        param.put("hook.on_record_ts","");
-        param.put("hook.on_rtsp_auth","");
-        param.put("hook.on_rtsp_realm","");
+        param.put("hook.on_record_ts",String.format("%s/on_record_ts", hookPrex));
+        param.put("hook.on_rtsp_auth",String.format("%s/on_rtsp_auth", hookPrex));
+        param.put("hook.on_rtsp_realm",String.format("%s/on_rtsp_realm", hookPrex));
         param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex));
         param.put("hook.on_shell_login",String.format("%s/on_shell_login", hookPrex));
         param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex));

+ 55 - 50
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java

@@ -21,6 +21,8 @@ import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
+import com.genersoft.iot.vmp.service.IMediaService;
+import com.genersoft.iot.vmp.service.IPlayService;
 import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
 import com.genersoft.iot.vmp.service.bean.PlayBackCallback;
 import com.genersoft.iot.vmp.service.bean.PlayBackResult;
@@ -32,8 +34,6 @@ import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
 import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
-import com.genersoft.iot.vmp.service.IMediaService;
-import com.genersoft.iot.vmp.service.IPlayService;
 import gov.nist.javax.sip.stack.SIPDialog;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -49,6 +49,7 @@ import javax.sip.SipException;
 import java.io.FileNotFoundException;
 import java.math.BigDecimal;
 import java.text.ParseException;
+import java.math.RoundingMode;
 import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -74,9 +75,6 @@ public class PlayServiceImpl implements IPlayService {
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
-    @Autowired
-    private RedisUtil redis;
-
     @Autowired
     private DeferredResultHolder resultHolder;
 
@@ -140,36 +138,19 @@ public class PlayServiceImpl implements IPlayService {
         result.onCompletion(()->{
             // 点播结束时调用截图接口
             // TODO 应该在上流时调用更好,结束也可能是错误结束
-            try {
-                String classPath = ResourceUtils.getURL("classpath:").getPath();
-                // 兼容打包为jar的class路径
-                if(classPath.contains("jar")) {
-                    classPath = classPath.substring(0, classPath.lastIndexOf("."));
-                    classPath = classPath.substring(0, classPath.lastIndexOf("/") + 1);
-                }
-                if (classPath.startsWith("file:")) {
-                    classPath = classPath.substring(classPath.indexOf(":") + 1);
+            String path =  "static/static/snap/";
+            String fileName =  deviceId + "_" + channelId + ".jpg";
+            ResponseEntity responseEntity =  (ResponseEntity)result.getResult();
+            if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
+                WVPResult wvpResult = (WVPResult)responseEntity.getBody();
+                if (Objects.requireNonNull(wvpResult).getCode() == 0) {
+                    StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
+                    MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId());
+                    String streamUrl = streamInfoForSuccess.getFmp4();
+                    // 请求截图
+                    logger.info("[请求截图]: " + fileName);
+                    zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName);
                 }
-                String path = classPath + "static/static/snap/";
-                // 兼容Windows系统路径(去除前面的“/”)
-                if(System.getProperty("os.name").contains("indows")) {
-                    path = path.substring(1);
-                }
-                String fileName =  deviceId + "_" + channelId + ".jpg";
-                ResponseEntity responseEntity =  (ResponseEntity)result.getResult();
-                if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
-                    WVPResult wvpResult = (WVPResult)responseEntity.getBody();
-                    if (Objects.requireNonNull(wvpResult).getCode() == 0) {
-                        StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
-                        MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId());
-                        String streamUrl = streamInfoForSuccess.getFmp4();
-                        // 请求截图
-                        logger.info("[请求截图]: " + fileName);
-                        zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName);
-                    }
-                }
-            } catch (FileNotFoundException e) {
-                e.printStackTrace();
             }
         });
         if (streamInfo != null) {
@@ -186,24 +167,33 @@ public class PlayServiceImpl implements IPlayService {
             MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
 
             JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
-            if (rtpInfo != null && rtpInfo.getBoolean("exist")) {
+            if(rtpInfo.getInteger("code") == 0){
+                if (rtpInfo.getBoolean("exist")) {
 
-                WVPResult wvpResult = new WVPResult();
-                wvpResult.setCode(0);
-                wvpResult.setMsg("success");
-                wvpResult.setData(streamInfo);
-                msg.setData(wvpResult);
+                    WVPResult wvpResult = new WVPResult();
+                    wvpResult.setCode(0);
+                    wvpResult.setMsg("success");
+                    wvpResult.setData(streamInfo);
+                    msg.setData(wvpResult);
 
-                resultHolder.invokeAllResult(msg);
-                if (hookEvent != null) {
-                    hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo)));
+                    resultHolder.invokeAllResult(msg);
+                    if (hookEvent != null) {
+                        hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo)));
+                    }
+                }else {
+                    redisCatchStorage.stopPlay(streamInfo);
+                    storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
+                    streamInfo = null;
                 }
             }else {
+                //zlm连接失败
                 redisCatchStorage.stopPlay(streamInfo);
                 storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
                 streamInfo = null;
+
             }
 
+
         }
         if (streamInfo == null) {
             String streamId = null;
@@ -256,33 +246,41 @@ public class PlayServiceImpl implements IPlayService {
         if (ssrcInfo == null) {
             ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
         }
-
+        logger.info("[点播开始] deviceId: {}, channelId: {}, SSRC: {}", device.getDeviceId(), channelId, ssrcInfo.getSsrc() );
         // 超时处理
         String timeOutTaskKey = UUID.randomUUID().toString();
         SSRCInfo finalSsrcInfo = ssrcInfo;
         dynamicTask.startDelay( timeOutTaskKey,()->{
-            logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", device.getDeviceId(), channelId));
 
             SIPDialog dialog = streamSession.getDialogByStream(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
             if (dialog != null) {
+                logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
                 timeoutCallback.run(1, "收流超时");
                 // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
                 cmder.streamByeCmd(device.getDeviceId(), channelId, finalSsrcInfo.getStream(), null);
             }else {
+                logger.info("[点播超时] 消息未响应 deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
                 timeoutCallback.run(0, "点播超时");
                 mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
                 mediaServerService.closeRTPServer(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
                 streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
             }
-        }, userSetting.getPlayTimeout()*1000);
+        }, userSetting.getPlayTimeout());
         final String ssrc = ssrcInfo.getSsrc();
         final String stream = ssrcInfo.getStream();
+        //端口获取失败的ssrcInfo 没有必要发送点播指令
+        if(ssrcInfo.getPort() <= 0){
+            logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
+            return;
+        }
         cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
             logger.info("收到订阅消息: " + response.toJSONString());
             dynamicTask.stop(timeOutTaskKey);
             // hook响应
             onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId, uuid);
             hookEvent.response(mediaServerItemInuse, response);
+            logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
+
         }, (event) -> {
             ResponseEvent responseEvent = (ResponseEvent)event.event;
             String contentString = new String(responseEvent.getResponse().getRawContent());
@@ -296,8 +294,10 @@ public class PlayServiceImpl implements IPlayService {
                 if (ssrc.equals(ssrcInResponse)) {
                     return;
                 }
-                logger.info("[SIP 消息] 收到invite 200, 发现下级自定义了ssrc 开启修正");
+                logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse );
                 if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
+                    logger.info("[SIP 消息] SSRC修正 {}->{}", ssrc, ssrcInResponse);
+
                     if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
                         // ssrc 不可用
                         // 释放ssrc
@@ -450,7 +450,7 @@ public class PlayServiceImpl implements IPlayService {
             cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null);
             // 回复之前所有的点播请求
             playBackCallback.call(playBackResult);
-        }, userSetting.getPlayTimeout()*1000);
+        }, userSetting.getPlayTimeout());
 
         cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack,
                 (InviteStreamInfo inviteStreamInfo) -> {
@@ -539,7 +539,7 @@ public class PlayServiceImpl implements IPlayService {
             cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null);
             // 回复之前所有的点播请求
             hookCallBack.call(downloadResult);
-        }, userSetting.getPlayTimeout()*1000);
+        }, userSetting.getPlayTimeout());
         cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack,
                 inviteStreamInfo -> {
                     logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
@@ -605,7 +605,7 @@ public class PlayServiceImpl implements IPlayService {
 
                         BigDecimal currentCount = new BigDecimal(duration/1000);
                         BigDecimal totalCount = new BigDecimal(end-start);
-                        BigDecimal divide = currentCount.divide(totalCount,2, BigDecimal.ROUND_HALF_UP);
+                        BigDecimal divide = currentCount.divide(totalCount,2, RoundingMode.HALF_UP);
                         double process = divide.doubleValue();
                         streamInfo.setProgress(process);
                     }
@@ -728,4 +728,9 @@ public class PlayServiceImpl implements IPlayService {
 
 
     }
+
+    @Override
+    public void zlmServerOnline(String mediaServerId) {
+        // 似乎没啥需要做的
+    }
 }

+ 377 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/RedisGbPlayMsgListener.java

@@ -0,0 +1,377 @@
+package com.genersoft.iot.vmp.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.conf.DynamicTask;
+import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
+import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
+import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
+import com.genersoft.iot.vmp.service.bean.*;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.utils.redis.RedisUtil;
+import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.connection.Message;
+import org.springframework.data.redis.connection.MessageListener;
+import org.springframework.stereotype.Component;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+/**
+ * 监听下级发送推送信息,并发送国标推流消息上级
+ * @author lin
+ */
+@Component
+public class RedisGbPlayMsgListener implements MessageListener {
+
+    private final static Logger logger = LoggerFactory.getLogger(RedisGbPlayMsgListener.class);
+
+    public static final String WVP_PUSH_STREAM_KEY = "WVP_PUSH_STREAM";
+
+    /**
+     * 流媒体不存在的错误玛
+     */
+    public static final  int ERROR_CODE_MEDIA_SERVER_NOT_FOUND = -1;
+
+    /**
+     * 离线的错误玛
+     */
+    public static final  int ERROR_CODE_OFFLINE = -2;
+
+    /**
+     * 超时的错误玛
+     */
+    public static final  int ERROR_CODE_TIMEOUT = -3;
+
+    private Map<String, PlayMsgCallback> callbacks = new ConcurrentHashMap<>();
+    private Map<String, PlayMsgCallbackForStartSendRtpStream> callbacksForStartSendRtpStream = new ConcurrentHashMap<>();
+    private Map<String, PlayMsgErrorCallback> callbacksForError = new ConcurrentHashMap<>();
+
+    @Autowired
+    private UserSetting userSetting;
+
+    @Autowired
+    private RedisUtil redis;
+
+    @Autowired
+    private ZLMMediaListManager zlmMediaListManager;
+
+    @Autowired
+    private ZLMRTPServerFactory zlmrtpServerFactory;
+
+    @Autowired
+    private IMediaServerService mediaServerService;
+
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
+    @Autowired
+    private DynamicTask dynamicTask;
+
+    @Autowired
+    private ZLMMediaListManager mediaListManager;
+
+    @Autowired
+    private ZLMHttpHookSubscribe subscribe;
+
+
+    public interface PlayMsgCallback{
+        void handler(ResponseSendItemMsg responseSendItemMsg);
+    }
+
+    public interface PlayMsgCallbackForStartSendRtpStream{
+        void handler(JSONObject jsonObject);
+    }
+
+    public interface PlayMsgErrorCallback{
+        void handler(WVPResult wvpResult);
+    }
+
+    @Override
+    public void onMessage(Message message, byte[] bytes) {
+        JSONObject msgJSON = JSON.parseObject(message.getBody(), JSONObject.class);
+        WvpRedisMsg wvpRedisMsg = JSON.toJavaObject(msgJSON, WvpRedisMsg.class);
+        if (!userSetting.getServerId().equals(wvpRedisMsg.getToId())) {
+            return;
+        }
+        if (WvpRedisMsg.isRequest(wvpRedisMsg)) {
+            logger.info("[收到REDIS通知] 请求: {}", new String(message.getBody()));
+
+            switch (wvpRedisMsg.getCmd()){
+                case WvpRedisMsgCmd.GET_SEND_ITEM:
+                    RequestSendItemMsg content = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), RequestSendItemMsg.class);
+                    requestSendItemMsgHand(content, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial());
+                    break;
+                case WvpRedisMsgCmd.REQUEST_PUSH_STREAM:
+                    RequestPushStreamMsg param = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), RequestPushStreamMsg.class);;
+                    requestPushStreamMsgHand(param, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial());
+                    break;
+                default:
+                    break;
+            }
+
+        }else {
+            logger.info("[收到REDIS通知] 回复: {}", new String(message.getBody()));
+            switch (wvpRedisMsg.getCmd()){
+                case WvpRedisMsgCmd.GET_SEND_ITEM:
+
+                    WVPResult content  = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), WVPResult.class);
+
+                    String key = wvpRedisMsg.getSerial();
+                    switch (content.getCode()) {
+                        case 0:
+                            ResponseSendItemMsg responseSendItemMsg =JSON.toJavaObject((JSONObject)content.getData(), ResponseSendItemMsg.class);
+                            PlayMsgCallback playMsgCallback = callbacks.get(key);
+                            if (playMsgCallback != null) {
+                                callbacksForError.remove(key);
+                                playMsgCallback.handler(responseSendItemMsg);
+                            }
+                            break;
+                        case ERROR_CODE_MEDIA_SERVER_NOT_FOUND:
+                        case ERROR_CODE_OFFLINE:
+                        case ERROR_CODE_TIMEOUT:
+                            PlayMsgErrorCallback errorCallback = callbacksForError.get(key);
+                            if (errorCallback != null) {
+                                callbacks.remove(key);
+                                errorCallback.handler(content);
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
+                case WvpRedisMsgCmd.REQUEST_PUSH_STREAM:
+                    WVPResult wvpResult  = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), WVPResult.class);
+                    String serial = wvpRedisMsg.getSerial();
+                    switch (wvpResult.getCode()) {
+                        case 0:
+                            JSONObject jsonObject = (JSONObject)wvpResult.getData();
+                            PlayMsgCallbackForStartSendRtpStream playMsgCallback = callbacksForStartSendRtpStream.get(serial);
+                            if (playMsgCallback != null) {
+                                callbacksForError.remove(serial);
+                                playMsgCallback.handler(jsonObject);
+                            }
+                            break;
+                        case ERROR_CODE_MEDIA_SERVER_NOT_FOUND:
+                        case ERROR_CODE_OFFLINE:
+                        case ERROR_CODE_TIMEOUT:
+                            PlayMsgErrorCallback errorCallback = callbacksForError.get(serial);
+                            if (errorCallback != null) {
+                                callbacks.remove(serial);
+                                errorCallback.handler(wvpResult);
+                            }
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+
+
+
+
+    }
+
+    /**
+     * 处理收到的请求推流的请求
+     */
+    private void requestPushStreamMsgHand(RequestPushStreamMsg requestPushStreamMsg, String fromId, String serial) {
+        MediaServerItem mediaInfo = mediaServerService.getOne(requestPushStreamMsg.getMediaServerId());
+        if (mediaInfo == null) {
+            // TODO 回复错误
+            return;
+        }
+        String is_Udp = requestPushStreamMsg.isTcp() ? "0" : "1";
+        Map<String, Object> param = new HashMap<>();
+        param.put("vhost","__defaultVhost__");
+        param.put("app",requestPushStreamMsg.getApp());
+        param.put("stream",requestPushStreamMsg.getStream());
+        param.put("ssrc", requestPushStreamMsg.getSsrc());
+        param.put("dst_url",requestPushStreamMsg.getIp());
+        param.put("dst_port", requestPushStreamMsg.getPort());
+        param.put("is_udp", is_Udp);
+        param.put("src_port", requestPushStreamMsg.getSrcPort());
+        param.put("pt", requestPushStreamMsg.getPt());
+        param.put("use_ps", requestPushStreamMsg.isPs() ? "1" : "0");
+        param.put("only_audio", requestPushStreamMsg.isOnlyAudio() ? "1" : "0");
+        JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
+        // 回复消息
+        responsePushStream(jsonObject, fromId, serial);
+    }
+
+    private void responsePushStream(JSONObject content, String toId, String serial) {
+
+        WVPResult<JSONObject> result = new WVPResult<>();
+        result.setCode(0);
+        result.setData(content);
+
+        WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), toId,
+                WvpRedisMsgCmd.REQUEST_PUSH_STREAM, serial, result);
+        JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
+        redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
+    }
+
+    /**
+     * 处理收到的请求sendItem的请求
+     */
+    private void requestSendItemMsgHand(RequestSendItemMsg content, String toId, String serial) {
+        MediaServerItem mediaServerItem = mediaServerService.getOne(content.getMediaServerId());
+        if (mediaServerItem == null) {
+            logger.info("[回复推流信息] 流媒体{}不存在 ", content.getMediaServerId());
+
+            WVPResult<SendRtpItem> result = new WVPResult<>();
+            result.setCode(ERROR_CODE_MEDIA_SERVER_NOT_FOUND);
+            result.setMsg("流媒体不存在");
+
+            WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), toId,
+                    WvpRedisMsgCmd.GET_SEND_ITEM, serial, result);
+
+            JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
+            redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
+            return;
+        }
+        // 确定流是否在线
+        boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, content.getApp(), content.getStream());
+        if (streamReady) {
+            logger.info("[回复推流信息]  {}/{}", content.getApp(), content.getStream());
+            responseSendItem(mediaServerItem, content, toId, serial);
+        }else {
+            // 流已经离线
+            // 发送redis消息以使设备上线
+            logger.info("[ app={}, stream={} ]通道离线,发送redis信息控制设备开始推流",content.getApp(), content.getStream());
+
+            String taskKey = UUID.randomUUID().toString();
+            // 设置超时
+            dynamicTask.startDelay(taskKey, ()->{
+                logger.info("[ app={}, stream={} ] 等待设备开始推流超时", content.getApp(), content.getStream());
+                WVPResult<SendRtpItem> result = new WVPResult<>();
+                result.setCode(ERROR_CODE_TIMEOUT);
+                WvpRedisMsg response = WvpRedisMsg.getResponseInstance(
+                        userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, result
+                );
+                JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
+                redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
+            }, userSetting.getPlatformPlayTimeout());
+
+            // 添加订阅
+            JSONObject subscribeKey = new JSONObject();
+            subscribeKey.put("app", content.getApp());
+            subscribeKey.put("stream", content.getStream());
+            subscribeKey.put("regist", true);
+            subscribeKey.put("schema", "rtmp");
+            subscribeKey.put("mediaServerId", mediaServerItem.getId());
+            subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
+                    (MediaServerItem mediaServerItemInUse, JSONObject json)->{
+                        dynamicTask.stop(taskKey);
+                        responseSendItem(mediaServerItem, content, toId, serial);
+                    });
+
+            MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(1, content.getApp(), content.getStream(),
+                    content.getChannelId(), content.getPlatformId(), content.getPlatformName(), content.getServerId(),
+                    content.getMediaServerId());
+            redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
+
+        }
+    }
+
+    /**
+     * 将获取到的sendItem发送出去
+     */
+    private void responseSendItem(MediaServerItem mediaServerItem, RequestSendItemMsg content, String toId, String serial) {
+        SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, content.getIp(),
+                content.getPort(), content.getSsrc(), content.getPlatformId(),
+                content.getApp(), content.getStream(), content.getChannelId(),
+                content.getTcp());
+
+        WVPResult<ResponseSendItemMsg> result = new WVPResult<>();
+        result.setCode(0);
+        ResponseSendItemMsg responseSendItemMsg = new ResponseSendItemMsg();
+        responseSendItemMsg.setSendRtpItem(sendRtpItem);
+        responseSendItemMsg.setMediaServerItem(mediaServerItem);
+        result.setData(responseSendItemMsg);
+
+        WvpRedisMsg response = WvpRedisMsg.getResponseInstance(
+                userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, result
+        );
+        JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
+        redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
+    }
+
+    /**
+     * 发送消息要求下级生成推流信息
+     * @param serverId 下级服务ID
+     * @param app 应用名
+     * @param stream 流ID
+     * @param ip 目标IP
+     * @param port 目标端口
+     * @param ssrc  ssrc
+     * @param platformId 平台国标编号
+     * @param channelId 通道ID
+     * @param isTcp 是否使用TCP
+     * @param callback 得到信息的回调
+     */
+    public void sendMsg(String serverId, String mediaServerId, String app, String stream, String ip, int port, String ssrc,
+                        String platformId, String channelId, boolean isTcp, String platformName, PlayMsgCallback callback, PlayMsgErrorCallback errorCallback) {
+        RequestSendItemMsg requestSendItemMsg = RequestSendItemMsg.getInstance(
+                serverId, mediaServerId, app, stream, ip, port, ssrc, platformId, channelId, isTcp, platformName);
+        requestSendItemMsg.setServerId(serverId);
+        String key = UUID.randomUUID().toString();
+        WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId, WvpRedisMsgCmd.GET_SEND_ITEM,
+                key, requestSendItemMsg);
+
+        JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg);
+        logger.info("[请求推流SendItem] {}: {}", serverId, jsonObject);
+        callbacks.put(key, callback);
+        callbacksForError.put(key, errorCallback);
+        dynamicTask.startDelay(key, ()->{
+            callbacks.remove(key);
+            callbacksForError.remove(key);
+            WVPResult<Object> wvpResult = new WVPResult<>();
+            wvpResult.setCode(ERROR_CODE_TIMEOUT);
+            wvpResult.setMsg("timeout");
+            errorCallback.handler(wvpResult);
+        }, userSetting.getPlatformPlayTimeout());
+        redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
+    }
+
+    /**
+     * 发送请求推流的消息
+     * @param param 推流参数
+     * @param callback 回调
+     */
+    public void sendMsgForStartSendRtpStream(String serverId, RequestPushStreamMsg param, PlayMsgCallbackForStartSendRtpStream callback) {
+        String key = UUID.randomUUID().toString();
+        WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId,
+                WvpRedisMsgCmd.REQUEST_PUSH_STREAM, key, param);
+
+        JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg);
+        logger.info("[REDIS 请求其他平台推流] {}: {}", serverId, jsonObject);
+        dynamicTask.startDelay(key, ()->{
+            callbacksForStartSendRtpStream.remove(key);
+            callbacksForError.remove(key);
+        }, userSetting.getPlatformPlayTimeout());
+        callbacksForStartSendRtpStream.put(key, callback);
+        callbacksForError.put(key, (wvpResult)->{
+            logger.info("[REDIS 请求其他平台推流] 失败: {}", wvpResult.getMsg());
+            callbacksForStartSendRtpStream.remove(key);
+            callbacksForError.remove(key);
+        });
+        redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
+    }
+}

+ 11 - 4
src/main/java/com/genersoft/iot/vmp/service/impl/RedisGPSMsgListener.java

@@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.service.impl;
 import com.alibaba.fastjson.JSON;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -10,17 +11,23 @@ import org.springframework.data.redis.connection.Message;
 import org.springframework.data.redis.connection.MessageListener;
 import org.springframework.stereotype.Component;
 
+/**
+ * 接收来自redis的GPS更新通知
+ * @author lin
+ */
 @Component
-public class RedisGPSMsgListener implements MessageListener {
+public class RedisGpsMsgListener implements MessageListener {
 
-    private final static Logger logger = LoggerFactory.getLogger(RedisGPSMsgListener.class);
+    private final static Logger logger = LoggerFactory.getLogger(RedisGpsMsgListener.class);
 
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
     @Override
-    public void onMessage(Message message, byte[] bytes) {
-        logger.info("收到来自REDIS的GPS通知: {}", new String(message.getBody()));
+    public void onMessage(@NotNull Message message, byte[] bytes) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("收到来自REDIS的GPS通知: {}", new String(message.getBody()));
+        }
         GPSMsgInfo gpsMsgInfo = JSON.parseObject(message.getBody(), GPSMsgInfo.class);
         redisCatchStorage.updateGpsMsgInfo(gpsMsgInfo);
     }

+ 83 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/RedisStreamMsgListener.java

@@ -0,0 +1,83 @@
+package com.genersoft.iot.vmp.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.gb28181.bean.AlarmChannelMessage;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
+import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
+import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.utils.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.connection.Message;
+import org.springframework.data.redis.connection.MessageListener;
+import org.springframework.stereotype.Component;
+
+
+/**
+ * @author lin
+ */
+@Component
+public class RedisStreamMsgListener implements MessageListener {
+
+    private final static Logger logger = LoggerFactory.getLogger(RedisStreamMsgListener.class);
+
+    @Autowired
+    private ISIPCommander commander;
+
+    @Autowired
+    private ISIPCommanderForPlatform commanderForPlatform;
+
+    @Autowired
+    private IVideoManagerStorage storage;
+
+    @Autowired
+    private UserSetting userSetting;
+
+    @Autowired
+    private ZLMMediaListManager zlmMediaListManager;
+
+    @Override
+    public void onMessage(Message message, byte[] bytes) {
+
+        JSONObject steamMsgJson = JSON.parseObject(message.getBody(), JSONObject.class);
+        if (steamMsgJson == null) {
+            logger.warn("[REDIS的ALARM通知]消息解析失败");
+            return;
+        }
+        String serverId = steamMsgJson.getString("serverId");
+
+        if (userSetting.getServerId().equals(serverId)) {
+            // 自己发送的消息忽略即可
+            return;
+        }
+        logger.info("[REDIS通知] 流变化: {}", new String(message.getBody()));
+        String app = steamMsgJson.getString("app");
+        String stream = steamMsgJson.getString("stream");
+        boolean register = steamMsgJson.getBoolean("register");
+        String mediaServerId = steamMsgJson.getString("mediaServerId");
+        MediaItem mediaItem = new MediaItem();
+        mediaItem.setSeverId(serverId);
+        mediaItem.setApp(app);
+        mediaItem.setStream(stream);
+        mediaItem.setRegist(register);
+        mediaItem.setMediaServerId(mediaServerId);
+        mediaItem.setCreateStamp(System.currentTimeMillis()/1000);
+        mediaItem.setAliveSecond(0L);
+        mediaItem.setTotalReaderCount("0");
+        mediaItem.setOriginType(0);
+        mediaItem.setOriginTypeStr("0");
+        mediaItem.setOriginTypeStr("unknown");
+
+        zlmMediaListManager.addPush(mediaItem);
+
+
+    }
+}

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java

@@ -107,6 +107,7 @@ public class StreamPushServiceImpl implements IStreamPushService {
         streamPushItem.setStatus(true);
         streamPushItem.setStreamType("push");
         streamPushItem.setVhost(item.getVhost());
+        streamPushItem.setServerId(item.getSeverId());
         return streamPushItem;
     }
 

+ 9 - 0
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java

@@ -356,6 +356,15 @@ public interface IVideoManagerStorage {
 	int removeMedia(String app, String stream);
 
 
+	/**
+	 * 获取但个推流
+	 * @param app
+	 * @param stream
+	 * @return
+	 */
+	StreamPushItem getMedia(String app, String stream);
+
+
 	/**
 	 * 清空推流列表
 	 */

+ 23 - 7
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java

@@ -17,10 +17,10 @@ public interface DeviceChannelMapper {
 
     @Insert("INSERT INTO device_channel (channelId, deviceId, name, manufacture, model, owner, civilCode, block, " +
             "address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " +
-            "ipAddress, port, password, PTZType, status, streamId, longitude, latitude, createTime, updateTime) " +
+            "ipAddress, port, password, PTZType, status, streamId, longitude, latitude, longitudeGcj02, latitudeGcj02, longitudeWgs84, latitudeWgs84, createTime, updateTime) " +
             "VALUES ('${channelId}', '${deviceId}', '${name}', '${manufacture}', '${model}', '${owner}', '${civilCode}', '${block}'," +
             "'${address}', ${parental}, '${parentId}', ${safetyWay}, ${registerWay}, '${certNum}', ${certifiable}, ${errCode}, '${secrecy}', " +
-            "'${ipAddress}', ${port}, '${password}', ${PTZType}, ${status}, '${streamId}', ${longitude}, ${latitude},'${createTime}', '${updateTime}')")
+            "'${ipAddress}', ${port}, '${password}', ${PTZType}, ${status}, '${streamId}', ${longitude}, ${latitude}, ${longitudeGcj02}, ${latitudeGcj02}, ${longitudeWgs84}, ${latitudeWgs84},'${createTime}', '${updateTime}')")
     int add(DeviceChannel channel);
 
     @Update(value = {" <script>" +
@@ -50,6 +50,10 @@ public interface DeviceChannelMapper {
             "<if test='hasAudio != null'>, hasAudio=${hasAudio}</if>" +
             "<if test='longitude != null'>, longitude=${longitude}</if>" +
             "<if test='latitude != null'>, latitude=${latitude}</if>" +
+            "<if test='longitudeGcj02 != null'>, longitudeGcj02=${longitudeGcj02}</if>" +
+            "<if test='latitudeGcj02 != null'>, latitudeGcj02=${latitudeGcj02}</if>" +
+            "<if test='longitudeWgs84 != null'>, longitudeWgs84=${longitudeWgs84}</if>" +
+            "<if test='latitudeWgs84 != null'>, latitudeWgs84=${latitudeWgs84}</if>" +
             "WHERE deviceId='${deviceId}' AND channelId='${channelId}'"+
             " </script>"})
     int update(DeviceChannel channel);
@@ -67,7 +71,7 @@ public interface DeviceChannelMapper {
             " <if test='online == false' > AND dc.status=0</if>" +
             " <if test='hasSubChannel == true' >  AND dc.subCount > 0 </if>" +
             " <if test='hasSubChannel == false' >  AND dc.subCount = 0 </if>" +
-            "GROUP BY dc.channelId " +
+            "ORDER BY dc.channelId " +
             " </script>"})
     List<DeviceChannel> queryChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online);
 
@@ -138,7 +142,8 @@ public interface DeviceChannelMapper {
             "insert into device_channel " +
             "(channelId, deviceId, name, manufacture, model, owner, civilCode, block, subCount, " +
             "  address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " +
-            "  ipAddress, port, password, PTZType, status, streamId, longitude, latitude, createTime, updateTime) " +
+            "  ipAddress, port, password, PTZType, status, streamId, longitude, latitude, longitudeGcj02, latitudeGcj02, " +
+            "  longitudeWgs84, latitudeWgs84, createTime, updateTime) " +
             "values " +
             "<foreach collection='addChannels' index='index' item='item' separator=','> " +
             "('${item.channelId}', '${item.deviceId}', '${item.name}', '${item.manufacture}', '${item.model}', " +
@@ -146,7 +151,8 @@ public interface DeviceChannelMapper {
             "'${item.address}', ${item.parental}, '${item.parentId}', ${item.safetyWay}, ${item.registerWay}, " +
             "'${item.certNum}', ${item.certifiable}, ${item.errCode}, '${item.secrecy}', " +
             "'${item.ipAddress}', ${item.port}, '${item.password}', ${item.PTZType}, ${item.status}, " +
-            "'${item.streamId}', ${item.longitude}, ${item.latitude},'${item.createTime}', '${item.updateTime}')" +
+            "'${item.streamId}', ${item.longitude}, ${item.latitude},${item.longitudeGcj02}, " +
+            "${item.latitudeGcj02},${item.longitudeWgs84}, ${item.latitudeWgs84},'${item.createTime}', '${item.updateTime}')" +
             "</foreach> " +
             "ON DUPLICATE KEY UPDATE " +
             "updateTime=VALUES(updateTime), " +
@@ -173,7 +179,11 @@ public interface DeviceChannelMapper {
             "status=VALUES(status), " +
             "streamId=VALUES(streamId), " +
             "longitude=VALUES(longitude), " +
-            "latitude=VALUES(latitude)" +
+            "latitude=VALUES(latitude), " +
+            "longitudeGcj02=VALUES(longitudeGcj02), " +
+            "latitudeGcj02=VALUES(latitudeGcj02), " +
+            "longitudeWgs84=VALUES(longitudeWgs84), " +
+            "latitudeWgs84=VALUES(latitudeWgs84) " +
             "</script>")
     int batchAdd(List<DeviceChannel> addChannels);
 
@@ -207,7 +217,11 @@ public interface DeviceChannelMapper {
             "<if test='item.hasAudio != null'>, hasAudio=${item.hasAudio}</if>" +
             "<if test='item.longitude != null'>, longitude=${item.longitude}</if>" +
             "<if test='item.latitude != null'>, latitude=${item.latitude}</if>" +
-            "WHERE deviceId=#{item.deviceId} AND channelId=#{item.channelId}"+
+            "<if test='item.longitudeGcj02 != null'>, longitudeGcj02=${item.longitudeGcj02}</if>" +
+            "<if test='item.latitudeGcj02 != null'>, latitudeGcj02=${item.latitudeGcj02}</if>" +
+            "<if test='item.longitudeWgs84 != null'>, longitudeWgs84=${item.longitudeWgs84}</if>" +
+            "<if test='item.latitudeWgs84 != null'>, latitudeWgs84=${item.latitudeWgs84}</if>" +
+            "WHERE deviceId='${item.deviceId}' AND channelId='${item.channelId}'"+
             "</foreach>" +
             "</script>"})
     int batchUpdate(List<DeviceChannel> updateChannels);
@@ -261,4 +275,6 @@ public interface DeviceChannelMapper {
     @Select("SELECT * FROM device_channel WHERE length(trim(streamId)) > 0")
     List<DeviceChannel> getAllChannelInPlay();
 
+    @Select("select * from device_channel where longitude*latitude > 0 and deviceId = #{deviceId}")
+    List<DeviceChannel> getAllChannelWithCoordinate(String deviceId);
 }

+ 3 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java

@@ -38,6 +38,7 @@ public interface DeviceMapper {
                 "mobilePositionSubmissionInterval," +
                 "subscribeCycleForAlarm," +
                 "ssrcCheck," +
+                "geoCoordSys," +
                 "online" +
             ") VALUES (" +
                 "#{deviceId}," +
@@ -61,6 +62,7 @@ public interface DeviceMapper {
                 "#{mobilePositionSubmissionInterval}," +
                 "#{subscribeCycleForAlarm}," +
                 "#{ssrcCheck}," +
+                "#{geoCoordSys}," +
                 "#{online}" +
             ")")
     int add(Device device);
@@ -87,6 +89,7 @@ public interface DeviceMapper {
                 "<if test=\"mobilePositionSubmissionInterval != null\">, mobilePositionSubmissionInterval=${mobilePositionSubmissionInterval}</if>" +
                 "<if test=\"subscribeCycleForAlarm != null\">, subscribeCycleForAlarm=${subscribeCycleForAlarm}</if>" +
                 "<if test=\"ssrcCheck != null\">, ssrcCheck=${ssrcCheck}</if>" +
+                "<if test=\"geoCoordSys != null\">, geoCoordSys=#{geoCoordSys}</if>" +
                 "WHERE deviceId='${deviceId}'"+
             " </script>"})
     int update(Device device);

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java

@@ -14,9 +14,9 @@ import java.util.List;
 public interface StreamPushMapper {
 
     @Insert("INSERT INTO stream_push (app, stream, totalReaderCount, originType, originTypeStr, " +
-            "createStamp, aliveSecond, mediaServerId) VALUES" +
+            "createStamp, aliveSecond, mediaServerId, serverId) VALUES" +
             "('${app}', '${stream}', '${totalReaderCount}', '${originType}', '${originTypeStr}', " +
-            "'${createStamp}', '${aliveSecond}', '${mediaServerId}' )")
+            "'${createStamp}', '${aliveSecond}', '${mediaServerId}' , '${serverId}' )")
     int add(StreamPushItem streamPushItem);
 
     @Update("UPDATE stream_push " +

+ 4 - 4
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@@ -587,11 +587,11 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
         String scanKey = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetting.getServerId() + "_*";
         List<GPSMsgInfo> result = new ArrayList<>();
         List<Object> keys = redis.scan(scanKey);
-        for (int i = 0; i < keys.size(); i++) {
-            String key = (String) keys.get(i);
+        for (Object o : keys) {
+            String key = (String) o;
             GPSMsgInfo gpsMsgInfo = (GPSMsgInfo) redis.get(key);
             if (!gpsMsgInfo.isStored()) { // 只取没有存过得
-                result.add((GPSMsgInfo)redis.get(key));
+                result.add((GPSMsgInfo) redis.get(key));
             }
         }
 
@@ -667,7 +667,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
     @Override
     public void sendStreamPushRequestedMsg(MessageForPushChannel msg) {
         String key = VideoManagerConstants.VM_MSG_STREAM_PUSH_REQUESTED;
-        logger.info("[redis 推流被请求通知] {}: {}-{}", key, msg.getApp(), msg.getStream());
+        logger.info("[redis 推流被请求通知] {}: {}/{}", key, msg.getApp(), msg.getStream());
         redis.convertAndSend(key, (JSONObject)JSON.toJSON(msg));
     }
 

+ 15 - 2
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java

@@ -25,12 +25,13 @@ import org.springframework.stereotype.Component;
 import org.springframework.transaction.TransactionDefinition;
 import org.springframework.transaction.TransactionStatus;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 
-/**    
+/**
  * 视频设备数据存储-jdbc实现
  * swwheihei
  * 2020年5月6日 下午2:31:42
@@ -195,7 +196,7 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 
 	@Override
 	public boolean resetChannels(String deviceId, List<DeviceChannel> deviceChannelList) {
-		if (deviceChannelList == null) {
+		if (CollectionUtils.isEmpty(deviceChannelList)) {
 			return false;
 		}
 		List<DeviceChannel> allChannelInPlay = deviceChannelMapper.getAllChannelInPlay();
@@ -246,6 +247,10 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		if (stringBuilder.length() > 0) {
 			logger.info("[目录查询]收到的数据存在重复: {}" , stringBuilder);
 		}
+		if(CollectionUtils.isEmpty(channels)){
+			logger.info("通道重设,数据为空={}" , deviceChannelList);
+			return false;
+		}
 		try {
 			int cleanChannelsResult = deviceChannelMapper.cleanChannelsNotInList(deviceId, channels);
 			int limitCount = 300;
@@ -315,6 +320,9 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		List<DeviceChannel> all;
 		if (catalogUnderDevice != null && catalogUnderDevice) {
 			all = deviceChannelMapper.queryChannels(deviceId, deviceId, query, hasSubChannel, online);
+			// 海康设备的parentId是SIP id
+			List<DeviceChannel> deviceChannels = deviceChannelMapper.queryChannels(deviceId, sipConfig.getId(), query, hasSubChannel, online);
+			all.addAll(deviceChannels);
 		}else {
 			all = deviceChannelMapper.queryChannels(deviceId, null, query, hasSubChannel, online);
 		}
@@ -876,6 +884,11 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		return streamPushMapper.del(app, stream);
 	}
 
+	@Override
+	public StreamPushItem getMedia(String app, String stream) {
+		return streamPushMapper.selectOne(app, stream);
+	}
+
 	@Override
 	public void clearMediaList() {
 		streamPushMapper.clear();

+ 36 - 9
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java

@@ -1,7 +1,6 @@
 package com.genersoft.iot.vmp.utils;
 
 
-import java.text.SimpleDateFormat;
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -18,35 +17,63 @@ import java.util.Locale;
  */
 public class DateUtil {
 
-	private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss";
-    public static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
+    /**
+     * 兼容不规范的iso8601时间格式
+     */
+	private static final String ISO8601_COMPATIBLE_PATTERN = "yyyy-M-d'T'H:m:s";
 
-    public static final SimpleDateFormat formatISO8601 = new SimpleDateFormat(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault());
-    public static final SimpleDateFormat format = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss, Locale.getDefault());
+    /**
+     * 用以输出标准的iso8601时间格式
+     */
+	private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
 
-    public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault()).withZone(ZoneId.systemDefault());
-    public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(yyyy_MM_dd_HH_mm_ss, Locale.getDefault()).withZone(ZoneId.systemDefault());
+    /**
+     * wvp内部统一时间格式
+     */
+    public static final String PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+    public static final String zoneStr = "Asia/Shanghai";
+
+    public static final DateTimeFormatter formatterCompatibleISO8601 = DateTimeFormatter.ofPattern(ISO8601_COMPATIBLE_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr));
+    public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(ISO8601_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr));
+    public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr));
 
 	public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) {
+
         return formatterISO8601.format(formatter.parse(formatTime));
     }
 	
 	public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) {
-        return formatter.format(formatterISO8601.parse(formatTime));
+        return formatter.format(formatterCompatibleISO8601.parse(formatTime));
 
     }
-	
+
+    /**
+     * yyyy_MM_dd_HH_mm_ss 转时间戳
+     * @param formatTime
+     * @return
+     */
 	public static long yyyy_MM_dd_HH_mm_ssToTimestamp(String formatTime) {
         TemporalAccessor temporalAccessor = formatter.parse(formatTime);
         Instant instant = Instant.from(temporalAccessor);
         return instant.getEpochSecond();
 	}
 
+    /**
+     * 获取当前时间
+     * @return
+     */
     public static String getNow() {
         LocalDateTime nowDateTime = LocalDateTime.now();
         return formatter.format(nowDateTime);
     }
 
+    /**
+     * 格式校验
+     * @param timeStr 时间字符串
+     * @param dateTimeFormatter 待校验的格式
+     * @return
+     */
     public static boolean verification(String timeStr, DateTimeFormatter dateTimeFormatter) {
         try {
             LocalDate.parse(timeStr, dateTimeFormatter);

+ 6 - 16
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/alarm/AlarmController.java

@@ -24,6 +24,7 @@ import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
 import java.text.ParseException;
+import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.List;
 
@@ -68,8 +69,8 @@ public class AlarmController {
             @ApiImplicitParam(name="alarmMethod", value = "查询内容" ,dataTypeClass = String.class),
             @ApiImplicitParam(name="alarmMethod", value = "查询内容" ,dataTypeClass = String.class),
             @ApiImplicitParam(name="alarmType", value = "查询内容" ,dataTypeClass = String.class),
-            @ApiImplicitParam(name="startTime", value = "查询内容" ,dataTypeClass = String.class),
-            @ApiImplicitParam(name="endTime", value = "查询内容" ,dataTypeClass = String.class),
+            @ApiImplicitParam(name="startTime", value = "开始时间" ,dataTypeClass = String.class),
+            @ApiImplicitParam(name="endTime", value = "结束时间" ,dataTypeClass = String.class),
     })
     public ResponseEntity<PageInfo<DeviceAlarm>> getAll(
                                              @RequestParam int page,
@@ -98,14 +99,7 @@ public class AlarmController {
         }
 
 
-        try {
-            if (startTime != null) {
-                DateUtil.format.parse(startTime);
-            }
-            if (endTime != null) {
-                DateUtil.format.parse(endTime);
-            }
-        } catch (ParseException e) {
+        if (!DateUtil.verification(startTime, DateUtil.formatter) || !DateUtil.verification(endTime, DateUtil.formatter)){
             return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
         }
 
@@ -144,11 +138,7 @@ public class AlarmController {
         if (StringUtils.isEmpty(time)) {
             time = null;
         }
-        try {
-            if (time != null) {
-                DateUtil.format.parse(time);
-            }
-        } catch (ParseException e) {
+        if (!DateUtil.verification(time, DateUtil.formatter) ){
             return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
         }
         List<String> deviceIdList = null;
@@ -189,7 +179,7 @@ public class AlarmController {
         deviceAlarm.setAlarmDescription("test");
         deviceAlarm.setAlarmMethod("1");
         deviceAlarm.setAlarmPriority("1");
-        deviceAlarm.setAlarmTime(DateUtil.formatISO8601.format(System.currentTimeMillis()));
+        deviceAlarm.setAlarmTime(DateUtil.formatterISO8601.format(LocalDateTime.now()));
         deviceAlarm.setAlarmType("1");
         deviceAlarm.setLongitude(115.33333);
         deviceAlarm.setLatitude(39.33333);

+ 29 - 6
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java

@@ -21,16 +21,22 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
+import org.apache.commons.compress.utils.IOUtils;
+import org.apache.http.HttpResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.async.DeferredResult;
 
+import javax.servlet.http.HttpServletResponse;
 import javax.sip.DialogState;
+import java.io.*;
+import java.nio.file.Files;
 import java.util.*;
 
 @Api(tags = "国标设备查询", value = "国标设备查询")
@@ -200,6 +206,11 @@ public class DeviceQuery {
 			Set<String> allKeys = dynamicTask.getAllKeys();
 			for (String key : allKeys) {
 				if (key.startsWith(deviceId)) {
+					Runnable runnable = dynamicTask.get(key);
+					if (runnable instanceof ISubscribeTask) {
+						ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
+						subscribeTask.stop();
+					}
 					dynamicTask.stop(key);
 				}
 			}
@@ -306,12 +317,7 @@ public class DeviceQuery {
 	public ResponseEntity<WVPResult<String>> updateDevice(Device device){
 
 		if (device != null && device.getDeviceId() != null) {
-
-
-			// TODO 报警订阅相关的信息
-
 			deviceService.updateDevice(device);
-//			cmder.deviceInfoQuery(device);
 		}
 		WVPResult<String> result = new WVPResult<>();
 		result.setCode(0);
@@ -336,6 +342,11 @@ public class DeviceQuery {
 		Device device = storager.queryVideoDevice(deviceId);
 		String uuid = UUID.randomUUID().toString();
 		String key = DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId;
+		DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(2*1000L);
+		if(device == null) {
+			result.setResult(new ResponseEntity(String.format("设备%s不存在", deviceId),HttpStatus.OK));
+			return result;
+		}
 		cmder.deviceStatusQuery(device, event -> {
 			RequestMessage msg = new RequestMessage();
 			msg.setId(uuid);
@@ -343,7 +354,6 @@ public class DeviceQuery {
 			msg.setData(String.format("获取设备状态失败,错误码: %s, %s", event.statusCode, event.msg));
 			resultHolder.invokeResult(msg);
 		});
-        DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(2*1000L);
 		result.onTimeout(()->{
 			logger.warn(String.format("获取设备状态超时"));
 			// 释放rtpserver
@@ -456,4 +466,17 @@ public class DeviceQuery {
 		wvpResult.setData(dialogStateMap);
 		return wvpResult;
 	}
+
+	@GetMapping("/snap/{deviceId}/{channelId}")
+	@ApiOperation(value = "请求截图", notes = "请求截图")
+	public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId) {
+
+		try {
+			final InputStream in = Files.newInputStream(new File("snap" + File.separator + deviceId + "_" + channelId + ".jpg").toPath());
+			resp.setContentType(MediaType.IMAGE_PNG_VALUE);
+			IOUtils.copy(in, resp.getOutputStream());
+		} catch (IOException e) {
+			resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
+		}
+	}
 }

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java

@@ -72,7 +72,7 @@ public class GBRecordController {
 		if (!DateUtil.verification(startTime, DateUtil.formatter)){
 			WVPResult<RecordInfo> wvpResult = new WVPResult<>();
 			wvpResult.setCode(-1);
-			wvpResult.setMsg("startTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss);
+			wvpResult.setMsg("startTime error, format is " + DateUtil.PATTERN);
 
 			ResponseEntity<WVPResult<RecordInfo>> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK);
 			result.setResult(resultResponseEntity);
@@ -81,7 +81,7 @@ public class GBRecordController {
 		if (!DateUtil.verification(endTime, DateUtil.formatter)){
 			WVPResult<RecordInfo> wvpResult = new WVPResult<>();
 			wvpResult.setCode(-1);
-			wvpResult.setMsg("endTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss);
+			wvpResult.setMsg("endTime error, format is " + DateUtil.PATTERN);
 			ResponseEntity<WVPResult<RecordInfo>> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK);
 			result.setResult(resultResponseEntity);
 			return result;

+ 1 - 8
src/main/java/com/genersoft/iot/vmp/vmanager/log/LogController.java

@@ -76,14 +76,7 @@ public class LogController {
             logger.warn("自动记录日志功能已关闭,查询结果可能不完整。");
         }
 
-        try {
-            if (startTime != null) {
-                DateUtil.format.parse(startTime);
-            }
-            if (endTime != null) {
-                DateUtil.format.parse(endTime);
-            }
-        } catch (ParseException e) {
+        if (!DateUtil.verification(startTime, DateUtil.formatter) || !DateUtil.verification(endTime, DateUtil.formatter)){
             return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
         }
 

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java

@@ -146,8 +146,8 @@ public class ApiDeviceController {
                                                      // 2-基于口令的双向认证,
                                                      // 3-基于数字证书的双向认证
             deviceJOSNChannel.put("Status", deviceChannel.getStatus());
-            deviceJOSNChannel.put("Longitude", deviceChannel.getLongitude());
-            deviceJOSNChannel.put("Latitude", deviceChannel.getLatitude());
+            deviceJOSNChannel.put("Longitude", deviceChannel.getLongitudeWgs84());
+            deviceJOSNChannel.put("Latitude", deviceChannel.getLatitudeWgs84());
             deviceJOSNChannel.put("PTZType ", deviceChannel.getPTZType()); // 云台类型, 0 - 未知, 1 - 球机, 2 - 半球,
                                                                             //   3 - 固定枪机, 4 - 遥控枪机
             deviceJOSNChannel.put("CustomPTZType", "");

+ 1 - 1
src/main/resources/all-application.yml

@@ -32,7 +32,7 @@ spring:
     datasource:
         type: com.alibaba.druid.pool.DruidDataSource
         driver-class-name: com.mysql.cj.jdbc.Driver
-        url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false
+        url: jdbc:mysql://127.0.0.1:3306/wvp2?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true
         username: root
         password: root123
         druid:

+ 1 - 1
src/main/resources/application-dev.yml

@@ -20,7 +20,7 @@ spring:
     datasource:
         type: com.alibaba.druid.pool.DruidDataSource
         driver-class-name: com.mysql.cj.jdbc.Driver
-        url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false
+        url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true
         username: root
         password: 123456
         druid:

+ 1 - 1
src/main/resources/application-docker.yml

@@ -20,7 +20,7 @@ spring:
     datasource:
         # 使用mysql 打开23-28行注释, 删除29-36行
          name: wvp
-         url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&allowMultiQueries=true&useSSL=false
+         url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&allowMultiQueries=true&useSSL=false&allowMultiQueries=true
          username: root
          password: root
          type: com.alibaba.druid.pool.DruidDataSource

+ 15 - 8
src/test/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImplTest.java

@@ -8,6 +8,10 @@ import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit4.SpringRunner;
 
 import javax.annotation.Resource;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.temporal.TemporalAccessor;
 import java.util.Date;
 
 
@@ -64,8 +68,8 @@ class DeviceAlarmServiceImplTest {
              * 	 * 7其他报警;可以为直接组合如12为电话报警或 设备报警-
              */
             deviceAlarm.setAlarmMethod((int)(Math.random()*7 + 1) + "");
-            Date date = randomDate("2021-01-01 00:00:00", "2021-06-01 00:00:00");
-            deviceAlarm.setAlarmTime(DateUtil.format.format(date));
+            Instant date = randomDate("2021-01-01 00:00:00", "2021-06-01 00:00:00");
+            deviceAlarm.setAlarmTime(DateUtil.formatter.format(date));
             /**
              * 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级 警情-
              */
@@ -85,17 +89,20 @@ class DeviceAlarmServiceImplTest {
 
 
 
-    private Date randomDate(String beginDate, String endDate) {
+    private Instant randomDate(String beginDate, String endDate) {
         try {
 
-            Date start = DateUtil.format.parse(beginDate);//构造开始日期
-            Date end = DateUtil.format.parse(endDate);//构造结束日期
+            //构造开始日期
+            LocalDateTime start = LocalDateTime.parse(beginDate, DateUtil.formatter);
+
+            //构造结束日期
+            LocalDateTime end = LocalDateTime.parse(endDate, DateUtil.formatter);
             //getTime()表示返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
-            if (start.getTime() >= end.getTime()) {
+            if (start.isAfter(end)) {
                 return null;
             }
-            long date = random(start.getTime(), end.getTime());
-            return new Date(date);
+            long date = random(start.toInstant(ZoneOffset.of("+8")).toEpochMilli(), end.toInstant(ZoneOffset.of("+8")).toEpochMilli());
+            return Instant.ofEpochMilli(date);
         } catch (Exception e) {
             e.printStackTrace();
         }

+ 1 - 1
web_src/package.json

@@ -52,7 +52,7 @@
     "postcss-url": "^7.2.1",
     "rimraf": "^2.6.0",
     "semver": "^5.3.0",
-    "shelljs": "^0.7.6",
+    "shelljs": "^0.8.5",
     "uglifyjs-webpack-plugin": "^1.1.1",
     "url-loader": "^0.5.8",
     "vue-loader": "^13.3.0",

+ 5 - 1
web_src/src/App.vue

@@ -76,7 +76,7 @@ body,
   line-height: 60px;
 }
 .el-main {
-  background-color: #e9eef3;
+  background-color: #f0f2f5;
   color: #333;
   text-align: center;
   padding-top: 0px !important;
@@ -101,4 +101,8 @@ body,
   box-shadow: inset 0 0 6px rgba(0, 0, 0, .1);
   -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .1);
 }
+.table-header {
+  color: #727272;
+  font-weight: 600;
+}
 </style>

+ 7 - 9
web_src/src/components/CloudRecord.vue

@@ -18,19 +18,17 @@
     <div v-if="!recordDetail">
 
       <!--设备列表-->
-      <el-table :data="recordList" border style="width: 100%" :height="winHeight">
-        <el-table-column prop="app" label="应用名" align="center">
+      <el-table :data="recordList" style="width: 100%" :height="winHeight">
+        <el-table-column prop="app" label="应用名" >
         </el-table-column>
-        <el-table-column prop="stream" label="流ID" align="center">
+        <el-table-column prop="stream" label="流ID" >
         </el-table-column>
-        <el-table-column prop="time" label="时间" align="center">
+        <el-table-column prop="time" label="时间" >
         </el-table-column>
-        <el-table-column label="操作" width="360" align="center" fixed="right">
+        <el-table-column label="操作" width="360"  fixed="right">
           <template slot-scope="scope">
-            <el-button-group>
-              <el-button size="mini" icon="el-icon-video-camera-solid" type="primary" @click="showRecordDetail(scope.row)">查看</el-button>
-              <!--                  <el-button size="mini" icon="el-icon-delete" type="danger"  @click="deleteRecord(scope.row)">删除</el-button>-->
-            </el-button-group>
+            <el-button size="medium" icon="el-icon-folder-opened" type="text" @click="showRecordDetail(scope.row)">查看</el-button>
+            <!--                  <el-button size="mini" icon="el-icon-delete" type="danger"  @click="deleteRecord(scope.row)">删除</el-button>-->
           </template>
         </el-table-column>
       </el-table>

+ 30 - 28
web_src/src/components/DeviceList.vue

@@ -7,34 +7,33 @@
                    @click="getDeviceList()"></el-button>
       </div>
     </div>
-    <!-- <devicePlayer ref="devicePlayer"></devicePlayer> -->
     <!--设备列表-->
-    <el-table :data="deviceList" border style="width: 100%;font-size: 12px;" :height="winHeight">
-      <el-table-column prop="name" label="名称" align="center">
+    <el-table :data="deviceList" style="width: 100%;font-size: 12px;" :height="winHeight" header-row-class-name="table-header">
+      <el-table-column prop="name" label="名称" min-width="160">
       </el-table-column>
-      <el-table-column prop="deviceId" label="设备编号" width="180" align="center">
+      <el-table-column prop="deviceId" label="设备编号" min-width="200" >
       </el-table-column>
-      <el-table-column label="地址" width="180" align="center">
+      <el-table-column label="地址" min-width="160" >
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
             <el-tag size="medium">{{ scope.row.hostAddress }}</el-tag>
           </div>
         </template>
       </el-table-column>
-      <el-table-column prop="manufacturer" label="厂家" align="center">
+      <el-table-column prop="manufacturer" label="厂家" min-width="120" >
       </el-table-column>
-      <el-table-column label="流传输模式" align="center" width="120">
+      <el-table-column label="流传输模式"  min-width="160" >
         <template slot-scope="scope">
-          <el-select size="mini" @change="transportChange(scope.row)" v-model="scope.row.streamMode" placeholder="请选择">
+          <el-select size="mini" @change="transportChange(scope.row)" v-model="scope.row.streamMode" placeholder="请选择" style="width: 120px">
             <el-option key="UDP" label="UDP" value="UDP"></el-option>
             <el-option key="TCP-ACTIVE" label="TCP主动模式" :disabled="true" value="TCP-ACTIVE"></el-option>
             <el-option key="TCP-PASSIVE" label="TCP被动模式" value="TCP-PASSIVE"></el-option>
           </el-select>
         </template>
       </el-table-column>
-      <el-table-column prop="channelCount" label="通道数" align="center">
+      <el-table-column prop="channelCount" label="通道数" min-width="120" >
       </el-table-column>
-      <el-table-column label="状态" width="120" align="center">
+      <el-table-column label="状态" min-width="120">
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
             <el-tag size="medium" v-if="scope.row.online == 1">在线</el-tag>
@@ -42,30 +41,32 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column prop="keepaliveTime" label="最近心跳" align="center" width="140">
+      <el-table-column prop="keepaliveTime" label="最近心跳" min-width="160" >
       </el-table-column>
-      <el-table-column prop="registerTime" label="最近注册" align="center" width="140">
-      </el-table-column>
-      <el-table-column prop="updateTime" label="更新时间" align="center" width="140">
-      </el-table-column>
-      <el-table-column prop="createTime" label="创建时间" align="center" width="140">
+      <el-table-column prop="registerTime" label="最近注册"  min-width="160">
       </el-table-column>
+<!--      <el-table-column prop="updateTime" label="更新时间"  width="140">-->
+<!--      </el-table-column>-->
+<!--      <el-table-column prop="createTime" label="创建时间"  width="140">-->
+<!--      </el-table-column>-->
 
-      <el-table-column label="操作" width="450" align="center" fixed="right">
+      <el-table-column label="操作" min-width="450" fixed="right">
         <template slot-scope="scope">
-          <el-button size="mini" v-if="scope.row.online!=0" icon="el-icon-refresh" @click="refDevice(scope.row)"
+          <el-button type="text" size="medium" v-bind:disabled="scope.row.online==0" icon="el-icon-refresh" @click="refDevice(scope.row)"
                      @mouseover="getTooltipContent(scope.row.deviceId)">刷新
           </el-button>
-          <el-button-group>
-            <el-button size="mini" icon="el-icon-video-camera-solid" v-bind:disabled="scope.row.online==0"
-                       type="primary" @click="showChannelList(scope.row)">通道
-            </el-button>
-            <el-button size="mini" icon="el-icon-location" v-bind:disabled="scope.row.online==0" type="primary"
-                       @click="showDevicePosition(scope.row)">定位
-            </el-button>
-            <el-button size="mini" icon="el-icon-edit" type="primary" @click="edit(scope.row)">编辑</el-button>
-            <el-button size="mini" icon="el-icon-delete" type="danger" @click="deleteDevice(scope.row)">删除</el-button>
-          </el-button-group>
+          <el-divider direction="vertical"></el-divider>
+          <el-button type="text" size="medium" icon="el-icon-video-camera" v-bind:disabled="scope.row.online==0"
+                     @click="showChannelList(scope.row)">通道
+          </el-button>
+          <el-divider direction="vertical"></el-divider>
+          <el-button size="medium" icon="el-icon-location" v-bind:disabled="scope.row.online==0" type="text"
+                     @click="showDevicePosition(scope.row)">定位
+          </el-button>
+          <el-divider direction="vertical"></el-divider>
+          <el-button size="medium" icon="el-icon-edit" type="text" @click="edit(scope.row)">编辑</el-button>
+          <el-divider direction="vertical"></el-divider>
+          <el-button size="medium" icon="el-icon-delete" type="text" @click="deleteDevice(scope.row)" style="color: #f56c6c">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -347,4 +348,5 @@ export default {
   padding: 0.3rem;
   width: 14.4rem;
 }
+
 </style>

+ 1 - 1
web_src/src/components/MediaServerManger.vue

@@ -15,7 +15,7 @@
             <span style="font-size: 16px">{{item.id}}</span>
             <el-button v-if="!item.defaultServer" icon="el-icon-edit" style="padding: 0;float: right;" type="text" @click="edit(item)">编辑</el-button>
             <el-button v-if="item.defaultServer" icon="el-icon-edit" style="padding: 0;float: right;" type="text" @click="edit(item)">查看</el-button>
-            <el-button icon="el-icon-delete" style="margin-right: 10px;padding: 0;float: right;" type="text" @click="del(item)">移除</el-button>
+            <el-button v-if="!item.defaultServer" icon="el-icon-delete" style="margin-right: 10px;padding: 0;float: right;" type="text" @click="del(item)">移除</el-button>
             <div style="margin-top: 13px; line-height: 12px; ">
               <span style="font-size: 14px; color: #999; margin-top: 5px; ">{{item.ip}}</span>
               <span style="font-size: 14px; color: #999; margin-top: 5px; float: right;">{{item.createTime}}</span>

+ 18 - 14
web_src/src/components/ParentPlatformList.vue

@@ -4,14 +4,15 @@
       <div class="page-title">上级平台列表</div>
       <div class="page-header-btn">
         <el-button icon="el-icon-plus" size="mini" style="margin-right: 1rem;" type="primary" @click="addParentPlatform">添加</el-button>
+        <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button>
       </div>
     </div>
 
     <!--设备列表-->
-    <el-table :data="platformList" border style="width: 100%" :height="winHeight">
-      <el-table-column prop="name" label="名称" align="center"></el-table-column>
-      <el-table-column prop="serverGBId" label="平台编号" align="center"></el-table-column>
-      <el-table-column label="是否启用" width="120" align="center">
+    <el-table :data="platformList" style="width: 100%" :height="winHeight">
+      <el-table-column prop="name" label="名称" ></el-table-column>
+      <el-table-column prop="serverGBId" label="平台编号" min-width="200"></el-table-column>
+      <el-table-column label="是否启用" min-width="80" >
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
             <el-tag size="medium" v-if="scope.row.enable">已启用</el-tag>
@@ -19,7 +20,7 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column label="状态" width="120" align="center">
+      <el-table-column label="状态" min-width="80" >
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
             <el-tag size="medium" v-if="scope.row.status">在线</el-tag>
@@ -27,17 +28,17 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column label="地址" width="180" align="center">
+      <el-table-column label="地址" min-width="160" >
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
             <el-tag size="medium">{{ scope.row.serverIP}}:{{scope.row.serverPort }}</el-tag>
           </div>
         </template>
       </el-table-column>
-      <el-table-column prop="deviceGBId" label="设备国标编号" width="200" align="center"></el-table-column>
-      <el-table-column prop="transport" label="信令传输模式" width="120" align="center"></el-table-column>
-      <el-table-column prop="channelCount" label="通道数" width="120" align="center"></el-table-column>
-      <el-table-column label="订阅信息" width="240" align="center" fixed="right">
+      <el-table-column prop="deviceGBId" label="设备国标编号" min-width="200" ></el-table-column>
+      <el-table-column prop="transport" label="信令传输模式" min-width="120" ></el-table-column>
+      <el-table-column prop="channelCount" label="通道数" min-width="120" ></el-table-column>
+      <el-table-column label="订阅信息" min-width="120"  fixed="right">
         <template slot-scope="scope">
           <i v-if="scope.row.alarmSubscribe" style="font-size: 20px" title="报警订阅" class="iconfont icon-gbaojings subscribe-on " ></i>
           <i v-if="!scope.row.alarmSubscribe" style="font-size: 20px" title="报警订阅" class="iconfont icon-gbaojings subscribe-off " ></i>
@@ -48,11 +49,11 @@
         </template>
       </el-table-column>
 
-      <el-table-column label="操作" width="300" align="center" fixed="right">
+      <el-table-column label="操作" min-width="240" fixed="right">
         <template slot-scope="scope">
-          <el-button size="mini" icon="el-icon-edit" @click="editPlatform(scope.row)">编辑</el-button>
-          <el-button size="mini" icon="el-icon-share"  type="primary"  @click="chooseChannel(scope.row)">选择通道</el-button>
-          <el-button size="mini" icon="el-icon-delete"  type="danger" @click="deletePlatform(scope.row)">删除</el-button>
+          <el-button size="medium" icon="el-icon-edit" type="text" @click="editPlatform(scope.row)">编辑</el-button>
+          <el-button size="medium" icon="el-icon-share"  type="text"  @click="chooseChannel(scope.row)">选择通道</el-button>
+          <el-button size="medium" icon="el-icon-delete"  type="text" style="color: #f56c6c" @click="deletePlatform(scope.row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -168,6 +169,9 @@ export default {
         console.log(error);
       });
 
+    },
+    refresh: function (){
+      this.initData();
     }
 
   }

+ 28 - 23
web_src/src/components/PushVideoList.vue

@@ -34,52 +34,54 @@
         <el-button icon="el-icon-delete" size="mini" style="margin-right: 1rem;"
                    :disabled="multipleSelection.length === 0" type="danger" @click="batchDel">批量移除
         </el-button>
+        <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button>
       </div>
     </div>
     <devicePlayer ref="devicePlayer"></devicePlayer>
     <addStreamTOGB ref="addStreamTOGB"></addStreamTOGB>
-    <el-table ref="pushListTable" :data="pushList" border style="width: 100%" :height="winHeight"
+    <el-table ref="pushListTable" :data="pushList" style="width: 100%" :height="winHeight"
               @selection-change="handleSelectionChange" :row-key="(row)=> row.app + row.stream">
-      <el-table-column align="center" type="selection" :reserve-selection="true" width="55">
+      <el-table-column  type="selection" :reserve-selection="true" min-width="55">
       </el-table-column>
-      <el-table-column prop="name" label="名称" align="center">
+      <el-table-column prop="name" label="名称" min-width="200">
       </el-table-column>
-      <el-table-column prop="app" label="APP" align="center">
+      <el-table-column prop="app" label="APP" min-width="200">
       </el-table-column>
-      <el-table-column prop="stream" label="流ID" align="center">
+      <el-table-column prop="stream" label="流ID" min-width="200">
       </el-table-column>
-      <el-table-column prop="gbId" label="国标编码" width="200" align="center">
+      <el-table-column prop="gbId" label="国标编码" min-width="200" >
       </el-table-column>
-      <el-table-column prop="mediaServerId" label="流媒体" width="200" align="center">
+      <el-table-column prop="mediaServerId" label="流媒体" min-width="200" >
       </el-table-column>
-      <el-table-column label="开始时间" align="center" width="200">
+      <el-table-column label="开始时间"  min-width="200">
         <template slot-scope="scope">
           <el-button-group>
             {{ dateFormat(parseInt(scope.row.createStamp)) }}
           </el-button-group>
         </template>
       </el-table-column>
-      <el-table-column label="正在推流" align="center" width="100">
+      <el-table-column label="正在推流"  min-width="100">
         <template slot-scope="scope">
           {{ (scope.row.status == false && scope.row.gbId == null) || scope.row.status ? '是' : '否' }}
         </template>
       </el-table-column>
 
-      <el-table-column label="操作" width="360" align="center" fixed="right">
+      <el-table-column label="操作" min-width="360"  fixed="right">
         <template slot-scope="scope">
-          <el-button-group>
-            <el-button size="mini" icon="el-icon-video-play"
-                       v-if="(scope.row.status == false && scope.row.gbId == null) || scope.row.status"
-                       @click="playPush(scope.row)">播放
-            </el-button>
-            <el-button size="mini" icon="el-icon-delete" type="danger" @click="stopPush(scope.row)">移除</el-button>
-            <el-button size="mini" icon="el-icon-position" type="primary" v-if="!!!scope.row.gbId"
-                       @click="addToGB(scope.row)">加入国标
-            </el-button>
-            <el-button size="mini" icon="el-icon-position" type="primary" v-if="!!scope.row.gbId"
-                       @click="removeFromGB(scope.row)">移出国标
-            </el-button>
-          </el-button-group>
+          <el-button size="medium" icon="el-icon-video-play"
+                     v-if="(scope.row.status == false && scope.row.gbId == null) || scope.row.status"
+                     @click="playPush(scope.row)" type="text">播放
+          </el-button>
+          <el-divider direction="vertical"></el-divider>
+          <el-button size="medium" icon="el-icon-delete" type="text" @click="stopPush(scope.row)" style="color: #f56c6c" >移除</el-button>
+          <el-divider direction="vertical"></el-divider>
+          <el-button size="medium" icon="el-icon-position" type="text" v-if="!!!scope.row.gbId"
+                     @click="addToGB(scope.row)">加入国标
+          </el-button>
+          <el-divider v-if="!!!scope.row.gbId" direction="vertical"></el-divider>
+          <el-button size="medium" icon="el-icon-position" type="text" v-if="!!scope.row.gbId"
+                     @click="removeFromGB(scope.row)">移出国标
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -284,6 +286,9 @@ export default {
     handleSelectionChange: function (val) {
       this.multipleSelection = val;
     },
+    refresh: function () {
+      this.initData();
+    },
   }
 };
 </script>

+ 27 - 23
web_src/src/components/StreamProxyList.vue

@@ -5,14 +5,15 @@
       <div class="page-header-btn">
         <el-button icon="el-icon-plus" size="mini" style="margin-right: 1rem;" type="primary" @click="addStreamProxy">添加代理</el-button>
         <el-button v-if="false" icon="el-icon-search" size="mini" style="margin-right: 1rem;" type="primary" @click="addOnvif">搜索ONVIF</el-button>
+        <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button>
       </div>
     </div>
     <devicePlayer ref="devicePlayer"></devicePlayer>
-    <el-table :data="streamProxyList" border style="width: 100%" :height="winHeight">
-      <el-table-column prop="name" label="名称" align="center" show-overflow-tooltip/>
-      <el-table-column prop="app" label="流应用名" align="center" show-overflow-tooltip/>
-      <el-table-column prop="stream" label="流ID" align="center" show-overflow-tooltip/>
-      <el-table-column label="流地址" width="400" align="center" show-overflow-tooltip >
+    <el-table :data="streamProxyList" style="width: 100%" :height="winHeight">
+      <el-table-column prop="name" label="名称" min-width="120" show-overflow-tooltip/>
+      <el-table-column prop="app" label="流应用名" min-width="120" show-overflow-tooltip/>
+      <el-table-column prop="stream" label="流ID" min-width="120" show-overflow-tooltip/>
+      <el-table-column label="流地址" min-width="400"  show-overflow-tooltip >
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
 
@@ -27,8 +28,8 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column prop="mediaServerId" label="流媒体" width="150" align="center"></el-table-column>
-      <el-table-column label="类型" width="100" align="center">
+      <el-table-column prop="mediaServerId" label="流媒体" min-width="180" ></el-table-column>
+      <el-table-column label="类型" width="100" >
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
             <el-tag size="medium">{{scope.row.type}}</el-tag>
@@ -36,8 +37,8 @@
         </template>
       </el-table-column>
 
-      <el-table-column prop="gbId" label="国标编码" width="180" align="center" show-overflow-tooltip/>
-      <el-table-column label="状态" width="120" align="center">
+      <el-table-column prop="gbId" label="国标编码" min-width="180"  show-overflow-tooltip/>
+      <el-table-column label="状态" min-width="120" >
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
             <el-tag size="medium" v-if="scope.row.status">在线</el-tag>
@@ -45,7 +46,7 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column label="启用" width="120" align="center">
+      <el-table-column label="启用" min-width="120" >
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
             <el-tag size="medium" v-if="scope.row.enable">已启用</el-tag>
@@ -53,8 +54,8 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column prop="createTime" label="创建时间" align="center" width="150" show-overflow-tooltip/>
-      <el-table-column label="转HLS" width="120" align="center">
+      <el-table-column prop="createTime" label="创建时间"  min-width="150" show-overflow-tooltip/>
+      <el-table-column label="转HLS" min-width="120" >
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
             <el-tag size="medium" v-if="scope.row.enable_hls">已启用</el-tag>
@@ -62,7 +63,7 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column label="MP4录制" width="120" align="center">
+      <el-table-column label="MP4录制" min-width="120" >
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
             <el-tag size="medium" v-if="scope.row.enable_mp4">已启用</el-tag>
@@ -70,7 +71,7 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column label="无人观看自动删除" width="160" align="center">
+      <el-table-column label="无人观看自动删除" min-width="160" >
         <template slot-scope="scope">
           <div slot="reference" class="name-wrapper">
             <el-tag size="medium" v-if="scope.row.enable_remove_none_reader">已启用</el-tag>
@@ -80,14 +81,15 @@
       </el-table-column>
 
 
-      <el-table-column label="操作" width="360" align="center" fixed="right">
+      <el-table-column label="操作" width="360"  fixed="right">
         <template slot-scope="scope">
-          <el-button-group>
-            <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.enable" @click="play(scope.row)">播放</el-button>
-            <el-button size="mini" icon="el-icon-close" type="success" v-if="scope.row.enable" @click="stop(scope.row)">停用</el-button>
-            <el-button size="mini" icon="el-icon-check" type="primary" :loading="startBtnLaoding" v-if="!scope.row.enable" @click="start(scope.row)">启用</el-button>
-            <el-button size="mini" icon="el-icon-delete" type="danger"  @click="deleteStreamProxy(scope.row)">删除</el-button>
-          </el-button-group>
+          <el-button size="medium" icon="el-icon-video-play" type="text" v-if="scope.row.enable" @click="play(scope.row)">播放</el-button>
+          <el-divider direction="vertical"></el-divider>
+          <el-button size="medium" icon="el-icon-switch-button" type="text" v-if="scope.row.enable" @click="stop(scope.row)">停用</el-button>
+          <el-divider direction="vertical"></el-divider>
+          <el-button size="medium" icon="el-icon-check" type="text" :loading="startBtnLaoding" v-if="!scope.row.enable" @click="start(scope.row)">启用</el-button>
+          <el-divider v-if="!scope.row.enable" direction="vertical"></el-divider>
+          <el-button size="medium" icon="el-icon-delete" type="text" style="color: #f56c6c" @click="deleteStreamProxy(scope.row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -305,8 +307,10 @@
 					console.log(error);
 					that.getListLoading = false;
 				});
-			}
-
+			},
+      refresh: function (){
+        this.initData();
+      }
 		}
 	};
 </script>

+ 85 - 78
web_src/src/components/channelList.vue

@@ -2,10 +2,10 @@
   <div id="channelList" style="width: 100%">
     <div class="page-header">
       <div class="page-title">
-        <el-button icon="el-icon-arrow-left" size="mini" style="margin-right: 1rem;" type="primary" @click="showDevice">
-          返回
-        </el-button>
-        通道列表({{ parentChannelId == 0 ? deviceId : parentChannelId }})</div>
+        <el-button icon="el-icon-back" size="mini" style="font-size: 20px; color: #000;" type="text" @click="showDevice" ></el-button>
+        <el-divider direction="vertical"></el-divider>
+        通道列表
+      </div>
       <div class="page-header-btn">
       搜索:
       <el-input @input="search" style="margin-right: 1rem; width: auto;" size="mini" placeholder="关键字"
@@ -25,84 +25,85 @@
         <el-option label="在线" value="true"></el-option>
         <el-option label="离线" value="false"></el-option>
       </el-select>
-      <el-checkbox size="mini" v-model="autoList" @change="autoListChange">
-        自动刷新
-      </el-checkbox>
+      <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button>
     </div>
   </div>
   <devicePlayer ref="devicePlayer" v-loading="isLoging"></devicePlayer>
   <!--设备列表-->
-  <el-table ref="channelListTable" :data="deviceChannelList" :height="winHeight" border style="width: 100%">
-    <el-table-column prop="channelId" label="通道编号" width="200">
+  <el-table ref="channelListTable" :data="deviceChannelList" :height="winHeight" style="width: 100%" header-row-class-name="table-header">
+    <el-table-column prop="channelId" label="通道编号" min-width="200">
     </el-table-column>
-    <el-table-column prop="name" label="通道名称">
+    <el-table-column prop="deviceId" label="设备编号" min-width="200">
     </el-table-column>
-    <el-table-column label="快照" width="80" align="center">
-      <template slot-scope="scope">
-        <img style="max-height: 3rem;max-width: 4rem;"
-             v-if="scope.row.subCount === 0 && scope.row.parental === 0"
-             :id="scope.row.deviceId + '_' + scope.row.channelId"
-             :src="getSnap(scope.row)"
-             @error="getSnapErrorEvent($event.target.id)"
-             alt="">
-        <!--                    <el-image-->
-        <!--                      :id="'snapImg_' + scope.row.deviceId + '_' + scope.row.channelId"-->
-        <!--                      :src="getSnap(scope.row)"-->
-        <!--                      @error="getSnapErrorEvent($event, scope.row)"-->
-        <!--                      :fit="'contain'">-->
-        <!--                      <div slot="error" class="image-slot">-->
-        <!--                        <i class="el-icon-picture-outline"></i>-->
-        <!--                      </div>-->
-        <!--                    </el-image>-->
+    <el-table-column prop="name" label="通道名称" min-width="200">
+    </el-table-column>
+    <el-table-column label="快照" min-width="120">
+      <template v-slot:default="scope">
+        <el-image
+          :src="getSnap(scope.row)"
+          :preview-src-list="getBigSnap(scope.row)"
+          @error="getSnapErrorEvent(scope.row.deviceId, scope.row.channelId)"
+          :fit="'contain'"
+          style="width: 60px">
+          <div slot="error" class="image-slot">
+            <i class="el-icon-picture-outline"></i>
+          </div>
+        </el-image>
       </template>
     </el-table-column>
-    <el-table-column prop="subCount" label="子节点数">
+    <el-table-column prop="subCount" label="子节点数" min-width="120">
     </el-table-column>
-    <el-table-column prop="manufacture" label="厂家">
+    <el-table-column prop="manufacture" label="厂家" min-width="120">
     </el-table-column>
-    <el-table-column label="位置信息" align="center">
+    <el-table-column label="位置信息"  min-width="200">
       <template slot-scope="scope">
-        <span>{{ scope.row.longitude }},{{ scope.row.latitude }}</span>
+        <span v-if="scope.row.longitude*scope.row.latitude > 0">{{ scope.row.longitude }},<br>{{ scope.row.latitude }}</span>
+        <span v-if="scope.row.longitude*scope.row.latitude === 0">无</span>
       </template>
     </el-table-column>
-    <el-table-column prop="ptztypeText" label="云台类型"/>
-    <el-table-column label="开启音频" align="center">
+    <el-table-column prop="ptztypeText" label="云台类型" min-width="120"/>
+    <el-table-column label="开启音频" min-width="120">
       <template slot-scope="scope">
         <el-switch @change="updateChannel(scope.row)" v-model="scope.row.hasAudio" active-color="#409EFF">
         </el-switch>
       </template>
     </el-table-column>
-    <el-table-column label="状态" width="180" align="center">
+    <el-table-column label="状态" min-width="120">
       <template slot-scope="scope">
         <div slot="reference" class="name-wrapper">
-          <el-tag size="medium" v-if="scope.row.status == 1">开启</el-tag>
-          <el-tag size="medium" type="info" v-if="scope.row.status == 0">关闭</el-tag>
+          <el-tag size="medium" v-if="scope.row.status === 1">在线</el-tag>
+          <el-tag size="medium" type="info" v-if="scope.row.status === 0">离线</el-tag>
         </div>
       </template>
     </el-table-column>
 
 
-    <el-table-column label="操作" width="280" align="center" fixed="right">
+    <el-table-column label="操作" min-width="280" fixed="right">
       <template slot-scope="scope">
-        <el-button-group>
-          <!-- <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">播放</el-button> -->
-          <el-button size="mini" icon="el-icon-video-play" @click="sendDevicePush(scope.row)">播放</el-button>
-          <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="!!scope.row.streamId"
-                     @click="stopDevicePush(scope.row)">停止
-          </el-button>
-          <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.subCount > 0 || scope.row.parental === 1"
-                     @click="changeSubchannel(scope.row)">查看
-          </el-button>
-          <el-button size="mini" icon="el-icon-video-camera" type="primary" @click="queryRecords(scope.row)">设备录像
-          </el-button>
-          <!--                             <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> -->
-        </el-button-group>
+        <!-- <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">播放</el-button> -->
+        <el-button size="medium" icon="el-icon-video-play" type="text" @click="sendDevicePush(scope.row)">播放</el-button>
+        <el-button size="medium" icon="el-icon-switch-button" type="text"  style="color: #f56c6c" v-if="!!scope.row.streamId"
+                   @click="stopDevicePush(scope.row)">停止
+        </el-button>
+        <el-divider direction="vertical"></el-divider>
+        <el-button size="medium" icon="el-icon-s-open" type="text" v-if="scope.row.subCount > 0 || scope.row.parental === 1"
+                   @click="changeSubchannel(scope.row)">查看
+        </el-button>
+        <el-divider v-if="scope.row.subCount > 0 || scope.row.parental === 1" direction="vertical"></el-divider>
+        <el-button size="medium" icon="el-icon-video-camera" type="text" @click="queryRecords(scope.row)">设备录像
+        </el-button>
       </template>
     </el-table-column>
   </el-table>
-  <el-pagination style="float: right" @size-change="handleSizeChange" @current-change="currentChange"
-                 :current-page="currentPage" :page-size="count" :page-sizes="[15, 20, 30, 50]"
-                 layout="total, sizes, prev, pager, next" :total="total">
+  <el-pagination
+    style="float: right"
+    @size-change="handleSizeChange"
+    @current-change="currentChange"
+    :current-page="currentPage"
+    :page-size="count"
+    :page-sizes="[15, 25, 35, 50]"
+    layout="total, sizes, prev, pager, next"
+    :total="total">
   </el-pagination>
   </div>
 </template>
@@ -111,6 +112,8 @@
 import devicePlayer from './dialog/devicePlayer.vue'
 import uiHeader from '../layout/UiHeader.vue'
 import moment from "moment";
+import DviceService from "./service/DeviceService";
+import DeviceService from "./service/DeviceService";
 
 export default {
   name: 'channelList',
@@ -120,6 +123,8 @@ export default {
   },
   data() {
     return {
+      deviceService: new DeviceService(),
+      device: null,
       deviceId: this.$route.params.deviceId,
       parentChannelId: this.$route.params.parentChannelId,
       deviceChannelList: [],
@@ -135,16 +140,21 @@ export default {
       total: 0,
       beforeUrl: "/deviceList",
       isLoging: false,
-      autoList: true,
       loadSnap: {}
     };
   },
 
   mounted() {
-    this.initData();
-    if (this.autoList) {
-      this.updateLooper = setInterval(this.initData, 5000);
+    if (this.deviceId) {
+      this.deviceService.getDevice(this.deviceId, (result)=>{
+          this.device = result;
+
+      }, (error)=>{
+        console.log("获取设备信息失败")
+        console.error(error)
+      })
     }
+    this.initData();
 
   },
   destroyed() {
@@ -177,12 +187,8 @@ export default {
       })
     },
     handleSizeChange: function (val) {
-      var url = `/${this.$router.currentRoute.name}/${this.$router.params.deviceId}/${this.$router.params.parentChannelId}/${val}/1`
-      this.$router.push(url).then(() => {
-        this.initParam();
-        this.initData();
-      })
-
+      this.count = val;
+      this.getDeviceChannelList();
     },
     getDeviceChannelList: function () {
       let that = this;
@@ -227,7 +233,7 @@ export default {
           setTimeout(() => {
 
             let snapId = deviceId + "_" + channelId;
-            that.loadSnap[snapId] = 0;
+            that.loadSnap[deviceId + channelId] = 0;
             that.getSnapErrorEvent(snapId)
           }, 5000)
           that.$refs.devicePlayer.openDialog("media", deviceId, channelId, {
@@ -269,19 +275,24 @@ export default {
       });
     },
     getSnap: function (row) {
-      return '/static/snap/' + row.deviceId + '_' + row.channelId + '.jpg'
+      let url = (process.env.NODE_ENV === 'development'? "debug": "") + '/api/device/query/snap/' + row.deviceId + '/' + row.channelId
+      return url
     },
-    getSnapErrorEvent: function (id) {
+    getBigSnap: function (row) {
+      return [this.getSnap(row)]
+    },
+    getSnapErrorEvent: function (deviceId, channelId) {
 
-      if (typeof (this.loadSnap[id]) != "undefined") {
-        console.log("下载截图" + this.loadSnap[id])
-        if (this.loadSnap[id] > 5) {
-          delete this.loadSnap[id];
+      if (typeof (this.loadSnap[deviceId + channelId]) != "undefined") {
+        console.log("下载截图" + this.loadSnap[deviceId + channelId])
+        if (this.loadSnap[deviceId + channelId] > 5) {
+          delete this.loadSnap[deviceId + channelId];
           return;
         }
         setTimeout(() => {
-          this.loadSnap[id]++
-          document.getElementById(id).setAttribute("src", '/static/snap/' + id + '.jpg?' + new Date().getTime())
+          let url = (process.env.NODE_ENV === 'development'? "debug": "") + '/api/device/query/snap/' + deviceId + '/' + channelId
+          this.loadSnap[deviceId + channelId]++
+          document.getElementById(deviceId + channelId).setAttribute("src", url + '?' + new Date().getTime())
         }, 1000)
 
       }
@@ -342,12 +353,8 @@ export default {
         console.log(JSON.stringify(res));
       });
     },
-    autoListChange: function () {
-      if (this.autoList) {
-        this.updateLooper = setInterval(this.initData, 1500);
-      } else {
-        window.clearInterval(this.updateLooper);
-      }
+    refresh: function () {
+      this.initData();
     }
 
   }

+ 18 - 6
web_src/src/components/common/DeviceTree.vue

@@ -84,22 +84,34 @@ export default {
             }else {
               resolve([])
             }
+          }, (list)=>{
+              console.log("设备加载完成")
           }, (error)=>{
 
           })
         }
         if (node.level === 1) {
-          this.deviceService.getAllChannel(true, true, node.data.id, (catalogData) => {
-            this.deviceService.getAllChannel(false, true, node.data.id, (channelData) => {
-              let data = catalogData.concat(channelData)
-              this.channelDataHandler(data, resolve)
+          let channelArray = []
+          this.deviceService.getAllChannel(true, true, node.data.id, catalogData =>{
+            channelArray = channelArray.concat(catalogData)
+            this.channelDataHandler(channelArray, resolve)
+          },(endCatalogData) => {
+            this.deviceService.getAllChannel(false, true, node.data.id, channelData => {
+              channelArray = channelArray.concat(channelData)
+              this.channelDataHandler(channelArray, resolve)
+            }, endChannelList => {
+
             })
           })
         }else if (node.level > 1){
+          let channelArray = []
           this.deviceService.getAllSubChannel(true, node.data.deviceId, node.data.id, (catalogData)=>{
+            channelArray = channelArray.concat(catalogData)
+            this.channelDataHandler(channelArray, resolve)
+          }, (endCatalogData)=>{
             this.deviceService.getAllSubChannel(false, node.data.deviceId, node.data.id, (channelData)=>{
-              let data = catalogData.concat(channelData)
-              this.channelDataHandler(data, resolve)
+              channelArray = channelArray.concat(channelData)
+              this.channelDataHandler(channelArray, resolve)
             })
           })
         }

+ 40 - 39
web_src/src/components/common/jessibuca.vue

@@ -23,11 +23,11 @@
 </template>
 
 <script>
+let jessibucaPlayer = {};
 export default {
   name: 'jessibuca',
   data() {
     return {
-      jessibuca: null,
       playing: false,
       isNotMute: false,
       quieting: false,
@@ -49,6 +49,7 @@ export default {
     window.onerror = (msg) => {
       // console.error(msg)
     };
+    console.log(this._uid)
     let paramUrl = decodeURIComponent(this.$route.params.url)
     this.$nextTick(() => {
       this.updatePlayerDomSize()
@@ -88,7 +89,7 @@ export default {
       let options = {};
       console.log("hasAudio  " + this.hasAudio)
 
-      this.jessibuca = new window.Jessibuca(Object.assign(
+      jessibucaPlayer[this._uid] = new window.Jessibuca(Object.assign(
         {
           container: this.$refs.container,
           videoBuffer: 0.2, // 最大缓冲时长,单位秒
@@ -117,70 +118,70 @@ export default {
         },
         options
       ));
-
+      let jessibuca = jessibucaPlayer[this._uid];
       let _this = this;
-      this.jessibuca.on("load", function () {
+      jessibuca.on("load", function () {
         console.log("on load init");
       });
 
-      this.jessibuca.on("log", function (msg) {
+      jessibuca.on("log", function (msg) {
         console.log("on log", msg);
       });
-      this.jessibuca.on("record", function (msg) {
+      jessibuca.on("record", function (msg) {
         console.log("on record:", msg);
       });
-      this.jessibuca.on("pause", function () {
+      jessibuca.on("pause", function () {
         _this.playing = false;
       });
-      this.jessibuca.on("play", function () {
+      jessibuca.on("play", function () {
         _this.playing = true;
       });
-      this.jessibuca.on("fullscreen", function (msg) {
+      jessibuca.on("fullscreen", function (msg) {
         console.log("on fullscreen", msg);
         _this.fullscreen = msg
       });
 
-      this.jessibuca.on("mute", function (msg) {
+      jessibuca.on("mute", function (msg) {
         console.log("on mute", msg);
         _this.isNotMute = !msg;
       });
-      this.jessibuca.on("audioInfo", function (msg) {
+      jessibuca.on("audioInfo", function (msg) {
         // console.log("audioInfo", msg);
       });
 
-      this.jessibuca.on("videoInfo", function (msg) {
+      jessibuca.on("videoInfo", function (msg) {
         // this.videoInfo = msg;
         console.log("videoInfo", msg);
 
       });
 
-      this.jessibuca.on("bps", function (bps) {
+      jessibuca.on("bps", function (bps) {
         // console.log('bps', bps);
 
       });
       let _ts = 0;
-      this.jessibuca.on("timeUpdate", function (ts) {
+      jessibuca.on("timeUpdate", function (ts) {
         // console.log('timeUpdate,old,new,timestamp', _ts, ts, ts - _ts);
         _ts = ts;
       });
 
-      this.jessibuca.on("videoInfo", function (info) {
+      jessibuca.on("videoInfo", function (info) {
         console.log("videoInfo", info);
       });
 
-      this.jessibuca.on("error", function (error) {
+      jessibuca.on("error", function (error) {
         console.log("error", error);
       });
 
-      this.jessibuca.on("timeout", function () {
+      jessibuca.on("timeout", function () {
         console.log("timeout");
       });
 
-      this.jessibuca.on('start', function () {
+      jessibuca.on('start', function () {
         console.log('start');
       })
 
-      this.jessibuca.on("performance", function (performance) {
+      jessibuca.on("performance", function (performance) {
         let show = "卡顿";
         if (performance === 2) {
           show = "非常流畅";
@@ -189,25 +190,25 @@ export default {
         }
         _this.performance = show;
       });
-      this.jessibuca.on('buffer', function (buffer) {
+      jessibuca.on('buffer', function (buffer) {
         // console.log('buffer', buffer);
       })
 
-      this.jessibuca.on('stats', function (stats) {
+      jessibuca.on('stats', function (stats) {
         // console.log('stats', stats);
       })
 
-      this.jessibuca.on('kBps', function (kBps) {
+      jessibuca.on('kBps', function (kBps) {
         _this.kBps = Math.round(kBps);
       });
 
       // 显示时间戳 PTS
-      this.jessibuca.on('videoFrame', function () {
+      jessibuca.on('videoFrame', function () {
 
       })
 
       //
-      this.jessibuca.on('metadata', function () {
+      jessibuca.on('metadata', function () {
 
       });
     },
@@ -216,40 +217,40 @@ export default {
     },
     play: function (url) {
       console.log(url)
-      if (this.jessibuca) {
+      if (jessibucaPlayer[this._uid]) {
         this.destroy();
       }
       this.create();
-      this.jessibuca.on("play", () => {
+      jessibucaPlayer[this._uid].on("play", () => {
         this.playing = true;
         this.loaded = true;
-        this.quieting = this.jessibuca.quieting;
+        this.quieting = jessibuca.quieting;
       });
-      if (this.jessibuca.hasLoaded()) {
-        this.jessibuca.play(url);
+      if (jessibucaPlayer[this._uid].hasLoaded()) {
+        jessibucaPlayer[this._uid].play(url);
       } else {
-        this.jessibuca.on("load", () => {
+        jessibucaPlayer[this._uid].on("load", () => {
           console.log("load 播放")
-          this.jessibuca.play(url);
+          jessibucaPlayer[this._uid].play(url);
         });
       }
     },
     pause: function () {
-      if (this.jessibuca) {
-        this.jessibuca.pause();
+      if (jessibucaPlayer[this._uid]) {
+        jessibucaPlayer[this._uid].pause();
       }
       this.playing = false;
       this.err = "";
       this.performance = "";
     },
     destroy: function () {
-      if (this.jessibuca) {
-        this.jessibuca.destroy();
+      if (jessibucaPlayer[this._uid]) {
+        jessibucaPlayer[this._uid].destroy();
       }
       if (document.getElementById("buttonsBox") == null) {
         this.$refs.container.appendChild(this.btnDom)
       }
-      this.jessibuca = null;
+      jessibucaPlayer[this._uid] = null;
       this.playing = false;
       this.err = "";
       this.performance = "";
@@ -262,7 +263,7 @@ export default {
     },
     fullscreenSwich: function () {
       let isFull = this.isFullscreen()
-      this.jessibuca.setFullscreen(!isFull)
+      jessibucaPlayer[this._uid].setFullscreen(!isFull)
       this.fullscreen = !isFull;
     },
     isFullscreen: function () {
@@ -273,8 +274,8 @@ export default {
     }
   },
   destroyed() {
-    if (this.jessibuca) {
-      this.jessibuca.destroy();
+    if (jessibucaPlayer[this._uid]) {
+      jessibucaPlayer[this._uid].destroy();
     }
     this.playing = false;
     this.loaded = false;

+ 2 - 4
web_src/src/components/control.vue

@@ -235,10 +235,8 @@
       <el-table-column prop="local_ip" label="本地"></el-table-column>
       <el-table-column prop="typeid" label="类型"></el-table-column>
       <el-table-column align="right">
-        <template slot="header" slot-scope="scope">
-          <el-button icon="el-icon-refresh-right" circle @click="getAllSession()"></el-button>
-        </template>
-        <template slot-scope="scope">
+        <template v-slot:default="scope">
+          <el-button size="mini" icon="el-icon-refresh-right" circle @click="getAllSession()"></el-button>
           <el-button @click.native.prevent="deleteRow(scope.$index, allSessionData)" type="text" size="small">移除
           </el-button>
         </template>

+ 0 - 0
web_src/src/components/dialog/deviceEdit.vue


Некоторые файлы не были показаны из-за большого количества измененных файлов