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

Merge branch 'wvp-28181-2.0' into wvp-28181-2.0

648540858 2 лет назад
Родитель
Сommit
bdf799fb64
26 измененных файлов с 1071 добавлено и 917 удалено
  1. 59 100
      README.md
  2. 2 1
      doc/_content/introduction/config.md
  3. BIN
      doc/_media/2.png
  4. BIN
      doc/_media/3-1.png
  5. BIN
      doc/_media/3-2.png
  6. BIN
      doc/_media/3-3.png
  7. BIN
      doc/_media/3.png
  8. BIN
      doc/_media/index.png
  9. 1 0
      sql/mysql.sql
  10. 10 4
      src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java
  11. 151 113
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  12. 3 62
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java
  13. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  14. 3 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java
  15. 631 619
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  16. 36 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java
  17. 44 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java
  18. 4 0
      src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java
  19. 3 1
      src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
  20. 2 1
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
  21. 1 1
      src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java
  22. 113 0
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  23. 3 4
      src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
  24. 1 5
      src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
  25. 1 1
      src/main/resources/all-application.yml
  26. 2 1
      web_src/src/components/GBRecordDetail.vue

+ 59 - 100
README.md

@@ -1,4 +1,4 @@
-![logo](https://raw.githubusercontent.com/648540858/wvp-GB28181-pro/wvp-28181-2.0/web_src/static/logo.png)
+![logo](doc/_media/logo.png)
 # 开箱即用的28181协议视频平台
 
 [![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit)
@@ -17,7 +17,7 @@ WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网
 # 应用场景:
 支持浏览器无插件播放摄像头视频。  
 支持摄像机、平台、NVR等设备接入。 
-支持国标级联。  
+支持国标级联。多平台级联。跨网视频预览。
 支持rtsp/rtmp等视频流转发到国标平台。  
 支持rtsp/rtmp等推流转发到国标平台。  
 
@@ -31,62 +31,49 @@ WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网
 https://gitee.com/pan648540858/wvp-GB28181-pro.git
 
 # 截图
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101513_79632720_1018729.png "2022-03-04_09-51.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/103025_5df016f9_1018729.png "2022-03-04_10-27.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101706_088fbafa_1018729.png "2022-03-04_09-52_1.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101756_3d662828_1018729.png "2022-03-04_10-00_1.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101823_19050c66_1018729.png "2022-03-04_10-12_1.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101848_e5a39557_1018729.png "2022-03-04_10-12_2.png")
-![build_1.png](https://images.gitee.com/uploads/images/2022/0304/101919_ee5b8c79_1018729.png "2022-03-04_10-13.png")
-
-# 1.0 基础特性  
-1. 视频预览;  
-2. 云台控制(方向、缩放控制);  
-3. 视频设备信息同步;   
-4. 离在线监控;  
-5. 录像查询与回放(基于NVR\DVR,暂不支持快进、seek操作);  
-6. 无人观看自动断流;    
-7. 支持UDP和TCP两种国标信令传输模式; 
-8. 集成web界面, 不需要单独部署前端服务, 直接利用wvp内置文件服务部署, 随wvp一起部署;   
-9. 支持平台接入, 针对大平台大量设备的情况进行优化;  
-10. 支持检索,通道筛选;  
-11. 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题;  
-12. 支持启用udp多端口模式, 提高udp模式下媒体传输性能;  
-13. 支持通道是否含有音频的设置;  
-14. 支持通道子目录查询;  
-15. 支持udp/tcp国标流传输模式;  
-16. 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址  
-17. 支持国标网络校时  
-18. 支持公网部署, 支持wvp与zlm分开部署   
-19. 支持播放h265, g.711格式的流(需要将closeWaitRTPInfo设为false)
-20. 报警信息处理,支持向前端推送报警信息
-
-# 1.0 新支持特性  
-1. 集成web界面, 不需要单独部署前端服务, 直接利用wvp内置文件服务部署, 随wvp一起部署;   
-2. 支持平台接入, 针对大平台大量设备的情况进行优化;  
-3. 支持检索,通道筛选;  
-4. 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题;  
-5. 支持启用udp多端口模式, 提高udp模式下媒体传输性能;  
-6. 支持通道是否含有音频的设置;  
-7. 支持通道子目录查询;  
-8. 支持udp/tcp国标流传输模式;  
-9. 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址  
-10. 支持国标网络校时  
-11. 支持公网部署, 支持wvp与zlm分开部署   
-12. 支持播放h265, g.711格式的流   
-13. 支持固定流地址和自动点播,同时支持未点播时直接播放流地址,代码自动发起点播.  ( [查看WIKI](https://github.com/648540858/wvp-GB28181-pro/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E5%9B%BA%E5%AE%9A%E6%92%AD%E6%94%BE%E5%9C%B0%E5%9D%80%E4%B8%8E%E8%87%AA%E5%8A%A8%E7%82%B9%E6%92%AD))
-14. 报警信息处理,支持向前端推送报警信息
-15. 支持订阅与通知方法
-   -  [X] 移动位置订阅
-   -  [X] 移动位置通知处理
-   -  [X] 报警事件订阅
-   -  [X] 报警事件通知处理
-   -  [X] 设备目录订阅
-   -  [X] 设备目录通知处理
-16. 移动位置查询和显示,可通过配置文件设置移动位置历史是否存储
-
-# 2.0 支持特性
-- [X] 国标通道向上级联
+![index](doc/_media/index.png "index.png")
+![2](doc/_media/2.png "2.png")
+![3](doc/_media/3.png "3.png")
+![3-1](doc/_media/3-1.png "3-1.png")
+![3-2](doc/_media/3-2.png "3-2.png")
+![3-3](doc/_media/3-3.png "3-3.png")
+![build_1](https://images.gitee.com/uploads/images/2022/0304/101919_ee5b8c79_1018729.png "2022-03-04_10-13.png")
+
+# 功能特性 
+- [X] 集成web界面
+- [X] 兼容性良好
+- [X] 支持电子地图,支持接入WGS84和GCJ02两种坐标系,并且自动转化为合适的坐标系进行展示和分发
+- [X] 接入设备
+  - [X] 视频预览
+  - [X] 无限制接入路数,能接入多少设备只取决于你的服务器性能
+  - [X] 云台控制,控制设备转向,拉近,拉远
+  - [X] 预置位查询,使用与设置
+  - [X] 查询NVR/IPC上的录像与播放,支持指定时间播放与下载
+  - [X] 无人观看自动断流,节省流量
+  - [X] 视频设备信息同步
+  - [X] 离在线监控
+  - [X] 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址
+  - [X] 支持通过一个流地址直接观看摄像头,无需登录以及调用任何接口
+  - [X] 支持UDP和TCP两种国标信令传输模式
+  - [X] 支持UDP和TCP两种国标流传输模式
+  - [X] 支持检索,通道筛选
+  - [X] 支持通道子目录查询
+  - [X] 支持过滤音频,防止杂音影响观看
+  - [X] 支持国标网络校时
+  - [X] 支持播放H264和H265
+  - [X] 报警信息处理,支持向前端推送报警信息
+  - [X] 支持订阅与通知方法
+    - [X] 移动位置订阅
+    - [X] 移动位置通知处理
+    - [X] 报警事件订阅
+    - [X] 报警事件通知处理
+    - [X] 设备目录订阅
+    - [X] 设备目录通知处理
+  -  [X] 移动位置查询和显示
+  - [X] 支持手动添加设备和给设备设置单独的密码
+-  [X] 支持平台对接接入
+-  [X] 支持国标级联
+  - [X] 国标通道向上级联
     - [X] WEB添加上级平台
     - [X] 注册
     - [X] 心跳保活
@@ -101,61 +88,33 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
     - [X] 目录订阅与通知
     - [X] 录像查看与播放
     - [X] GPS订阅与通知(直播推流)
-- [X] 支持手动添加设备和给设备设置单独的密码
-- [X] 添加RTSP视频
-- [X] 添加接口鉴权
-- [X] 添加RTMP视频
-- [X] 云端录像(需要部署单独服务配合使用)
+- [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题;  
 - [X] 多流媒体节点,自动选择负载最低的节点使用。
-- [X] WEB端支持播放H264与H265,音频支持G.711A/G.711U/AAC,覆盖国标常用编码格式。
-- [X] 支持电子地图。
-- [X] 支持接入WGS84和GCJ02两种坐标系。
-
-[//]: # (# docker快速体验)
-
-[//]: # (目前作者的docker-compose因为时间有限维护不及时,这里提供第三方提供的供大家使用,维护不易,大家记得给这位小伙伴点个star。  )
-
-[//]: # (https://github.com/SaltFish001/wvp_pro_compose)
-
-[//]: # ([https://github.com/SaltFish001/wvp_pro_compose](https://github.com/SaltFish001/wvp_pro_compose))
-
-[//]: # (这是作者维护的一个镜像,可能存在不及时的问题。)
-
-[//]: # (```shell)
-
-[//]: # (docker pull 648540858/wvp_pro)
-
-[//]: # ()
-[//]: # (docker run  --env WVP_IP="你的IP" -it -p 18080:18080 -p 30000-30500:30000-30500/udp -p 30000-30500:30000-30500/tcp -p 80:80 -p 5060:5060 -p 5060:5060/udp 648540858/wvp_pro)
-
-[//]: # (```)
-
-[//]: # (docker使用详情查看:[https://hub.docker.com/r/648540858/wvp_pro](https://hub.docker.com/r/648540858/wvp_pro))
-
-# gitee同步仓库
-https://gitee.com/pan648540858/wvp-GB28181-pro.git  
-
-# 遇到问题
+- [X] 支持启用udp多端口模式, 提高udp模式下媒体传输性能;
+- [X] 支持公网部署; 
+- [X] 支持wvp与zlm分开部署,提升平台并发能力
+- [X] 支持拉流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台
+- [X] 支持推流RTSP/RTMP,分发为各种流格式,或者推送到其他国标平台
+- [X] 支持推流鉴权
+- [X] 支持接口鉴权
+- [X] 云端录像,推流/代理/国标视频均可以录制在云端服务器,支持预览和下载
+ 
+
+# 遇到问题如何解决
 国标最麻烦的地方在于设备的兼容性,所以需要大量的设备来测试,目前作者手里的设备有限,再加上作者水平有限,所以遇到问题在所难免;
 1. 查看wiki,仔细的阅读可以帮你避免几乎所有的问题
 2. 搜索issues,这里有大部分的答案
-3. 加QQ群,这里有大量热心的小伙伴,但是前提新希望你已经仔细阅读了wiki和搜索了issues。
+3. 加QQ群(901799015),这里有大量热心的小伙伴,但是前提新希望你已经仔细阅读了wiki和搜索了issues。
 4. 你可以请作者为你解答,但是我不是免费的。
 5. 你可以把遇到问题的设备寄给我,可以更容易的复现问题。
 
-
-# 合作
-目前很多打着合作的幌子来私聊的,其实大家大可不必,目前作者没有精力,你有问题可以付费找我解答,也可以提PR
-,如果对代码有建议可以提ISSUE;也可以加群一起聊聊。我们欢迎所有有兴趣参与到项目中来的人。
-
-
-
 # 使用帮助
 QQ群: 901799015, ZLM使用文档[https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)  
 QQ私信一般不回, 精力有限.欢迎大家在群里讨论.觉得项目对你有帮助,欢迎star和提交pr。
 
 # 授权协议
 本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。 在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议
+
 # 致谢
 感谢作者[夏楚](https://github.com/xia-chu) 提供这么棒的开源流媒体服务框架,并在开发过程中给予支持与帮助。     
 感谢作者[dexter langhuihui](https://github.com/langhuihui) 开源这么好用的WEB播放器。     

+ 2 - 1
doc/_content/introduction/config.md

@@ -31,6 +31,7 @@ java -jar wvp-pro-*.jar
 ## 2 配置WVP-PRO
 ### 2.1 Mysql数据库配置
 首先你需要创建一个名为wvp(也可使用其他名字)的数据库,并使用sql/mysql.sql导入数据库,初始化数据库结构。
+(这里注意,取决于版本,新版的sql文件夹下有update.sql,补丁包,一定要注意运行导入)
 在application-dev.yml中配置(使用1.2方式的是在jar包的同级目录的application.yml)配置数据库连接,包括数据库连接信息,密码。
 ### 2.2 Redis数据库配置
 配置wvp中的redis连接信息,建议wvp自己单独使用一个db。
@@ -116,4 +117,4 @@ user-settings:
 
 
 如果配置信息无误,你可以启动zlm,再启动wvp来测试了,启动成功的话,你可以在wvp的日志下看到zlm已连接的提示。
-接下来[部署到服务器](./_content/introduction/deployment.md), 如何你只是本地运行直接再本地运行即可。
+接下来[部署到服务器](./_content/introduction/deployment.md), 如何你只是本地运行直接再本地运行即可。

BIN
doc/_media/2.png


BIN
doc/_media/3-1.png


BIN
doc/_media/3-2.png


BIN
doc/_media/3-3.png


BIN
doc/_media/3.png


BIN
doc/_media/index.png


+ 1 - 0
sql/mysql.sql

@@ -39,6 +39,7 @@ CREATE TABLE `device` (
                           `updateTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                           `port` int DEFAULT NULL,
                           `expires` int DEFAULT NULL,
+                          `keepaliveIntervalTime` int DEFAULT NULL,
                           `subscribeCycleForCatalog` int DEFAULT NULL,
                           `hostAddress` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
                           `charset` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,

+ 10 - 4
src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java

@@ -109,12 +109,18 @@ public class CatalogDataCatch {
 
         for (String deviceId : keys) {
             CatalogData catalogData = data.get(deviceId);
-            if ( catalogData.getLastTime().isBefore(instantBefore5S)) { // 超过五秒收不到消息任务超时, 只更新这一部分数据
+            if ( catalogData.getLastTime().isBefore(instantBefore5S)) {
+                // 超过五秒收不到消息任务超时, 只更新这一部分数据, 收到数据与声明的总数一致,则重置通道数据,数据不全则只对收到的数据做更新操作
                 if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.runIng)) {
-                    storager.resetChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList());
+                    if (catalogData.getTotal() == catalogData.getChannelList().size()) {
+                        storager.resetChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList());
+                    }else {
+                        storager.updateChannels(catalogData.getDevice().getDeviceId(), catalogData.getChannelList());
+                    }
+                    String errorMsg = "更新成功,共" + catalogData.getTotal() + "条,已更新" + catalogData.getChannelList().size() + "条";
+                    catalogData.setErrorMsg(errorMsg);
                     if (catalogData.getTotal() != catalogData.getChannelList().size()) {
-                        String errorMsg = "更新成功,共" + catalogData.getTotal() + "条,已更新" + catalogData.getChannelList().size() + "条";
-                        catalogData.setErrorMsg(errorMsg);
+
                     }
                 }else if (catalogData.getStatus().equals(CatalogData.CatalogDataStatus.ready)) {
                     String errorMsg = "同步失败,等待回复超时";

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

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
 
 import com.alibaba.fastjson2.JSON;
+import com.genersoft.iot.vmp.conf.DynamicTask;
 import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
@@ -61,6 +62,9 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     @Autowired
     private SIPSender sipSender;
 
+    @Autowired
+    private DynamicTask dynamicTask;
+
     @Override
     public void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
         register(parentPlatform, null, null, errorEvent, okEvent, false, true);
@@ -109,13 +113,14 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
     public String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException {
             String characterSet = parentPlatform.getCharacterSet();
             StringBuffer keepaliveXml = new StringBuffer(200);
-            keepaliveXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-            keepaliveXml.append("<Notify>\r\n");
-            keepaliveXml.append("<CmdType>Keepalive</CmdType>\r\n");
-            keepaliveXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-            keepaliveXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-            keepaliveXml.append("<Status>OK</Status>\r\n");
-            keepaliveXml.append("</Notify>\r\n");
+            keepaliveXml.append("<?xml version=\"1.0\" encoding=\"")
+                    .append(characterSet).append("\"?>\r\n")
+                    .append("<Notify>\r\n")
+                    .append("<CmdType>Keepalive</CmdType>\r\n")
+                    .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                    .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                    .append("<Status>OK</Status>\r\n")
+                    .append("</Notify>\r\n");
 
         CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
@@ -133,7 +138,6 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
      * 向上级回复通道信息
      * @param channel 通道信息
      * @param parentPlatform 平台信息
-     * @return
      */
     @Override
     public void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException {
@@ -160,18 +164,18 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         if ( parentPlatform ==null) {
             return ;
         }
-        sendCatalogResponse(channels, parentPlatform, sn, fromTag, 0);
+        sendCatalogResponse(channels, parentPlatform, sn, fromTag, 0, true);
     }
     private String getCatalogXml(List<DeviceChannel> channels, String sn, ParentPlatform parentPlatform, int size) {
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer catalogXml = new StringBuffer(600);
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet +"\"?>\r\n");
-        catalogXml.append("<Response>\r\n");
-        catalogXml.append("<CmdType>Catalog</CmdType>\r\n");
-        catalogXml.append("<SN>" +sn + "</SN>\r\n");
-        catalogXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        catalogXml.append("<SumNum>" + size + "</SumNum>\r\n");
-        catalogXml.append("<DeviceList Num=\"" + channels.size() +"\">\r\n");
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet +"\"?>\r\n")
+                .append("<Response>\r\n")
+                .append("<CmdType>Catalog</CmdType>\r\n")
+                .append("<SN>" +sn + "</SN>\r\n")
+                .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                .append("<SumNum>" + size + "</SumNum>\r\n")
+                .append("<DeviceList Num=\"" + channels.size() +"\">\r\n");
         if (channels.size() > 0) {
             for (DeviceChannel channel : channels) {
                 if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
@@ -222,7 +226,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         return catalogXml.toString();
     }
 
-    private void sendCatalogResponse(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag, int index) throws SipException, InvalidArgumentException, ParseException {
+    private void sendCatalogResponse(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag, int index, boolean sendAfterResponse) throws SipException, InvalidArgumentException, ParseException {
         if (index >= channels.size()) {
             return;
         }
@@ -236,15 +240,49 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         // callid
         CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
-        Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, SipUtils.getNewViaTag(), callIdHeader);
-        sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, null, eventResult -> {
-            int indexNext = index + parentPlatform.getCatalogGroup();
-            try {
-                sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext);
-            } catch (SipException | InvalidArgumentException | ParseException e) {
-                logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
-            }
-        });
+        SIPRequest request = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(parentPlatform, catalogXml, fromTag, SipUtils.getNewViaTag(), callIdHeader);
+
+        String timeoutTaskKey = "catalog_task_" + parentPlatform.getServerGBId() + sn;
+
+        String callId = request.getCallIdHeader().getCallId();
+
+        if (sendAfterResponse) {
+            // 默认按照收到200回复后发送下一条, 如果超时收不到回复,就以30毫秒的间隔直接发送。
+            dynamicTask.startDelay(timeoutTaskKey, ()->{
+                sipSubscribe.removeOkSubscribe(callId);
+                int indexNext = index + parentPlatform.getCatalogGroup();
+                try {
+                    sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, false);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
+                }
+            }, 3000);
+            sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, eventResult -> {
+                logger.error("[目录推送失败] 国标级联 platform : {}, code: {}, msg: {}, 停止发送", parentPlatform.getServerGBId(), eventResult.statusCode, eventResult.msg);
+                dynamicTask.stop(timeoutTaskKey);
+            }, eventResult -> {
+                dynamicTask.stop(timeoutTaskKey);
+                int indexNext = index + parentPlatform.getCatalogGroup();
+                try {
+                    sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, true);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
+                }
+            });
+        }else {
+            sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, eventResult -> {
+                logger.error("[目录推送失败] 国标级联 platform : {}, code: {}, msg: {}, 停止发送", parentPlatform.getServerGBId(), eventResult.statusCode, eventResult.msg);
+                dynamicTask.stop(timeoutTaskKey);
+            }, null);
+            dynamicTask.startDelay(timeoutTaskKey, ()->{
+                int indexNext = index + parentPlatform.getCatalogGroup();
+                try {
+                    sendCatalogResponse(channels, parentPlatform, sn, fromTag, indexNext, false);
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());
+                }
+            }, 30);
+        }
     }
 
     /**
@@ -294,15 +332,15 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         String statusStr = (status==1)?"ONLINE":"OFFLINE";
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceStatusXml = new StringBuffer(600);
-        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        deviceStatusXml.append("<Response>\r\n");
-        deviceStatusXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
-        deviceStatusXml.append("<SN>" +sn + "</SN>\r\n");
-        deviceStatusXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        deviceStatusXml.append("<Result>OK</Result>\r\n");
-        deviceStatusXml.append("<Online>"+statusStr+"</Online>\r\n");
-        deviceStatusXml.append("<Status>OK</Status>\r\n");
-        deviceStatusXml.append("</Response>\r\n");
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Response>\r\n")
+                .append("<CmdType>DeviceStatus</CmdType>\r\n")
+                .append("<SN>" +sn + "</SN>\r\n")
+                .append("<DeviceID>" + channelId + "</DeviceID>\r\n")
+                .append("<Result>OK</Result>\r\n")
+                .append("<Online>"+statusStr+"</Online>\r\n")
+                .append("<Status>OK</Status>\r\n")
+                .append("</Response>\r\n");
 
         CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
@@ -321,18 +359,18 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceStatusXml = new StringBuffer(600);
-        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        deviceStatusXml.append("<Notify>\r\n");
-        deviceStatusXml.append("<CmdType>MobilePosition</CmdType>\r\n");
-        deviceStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-        deviceStatusXml.append("<DeviceID>" + gpsMsgInfo.getId() + "</DeviceID>\r\n");
-        deviceStatusXml.append("<Time>" + gpsMsgInfo.getTime() + "</Time>\r\n");
-        deviceStatusXml.append("<Longitude>" + gpsMsgInfo.getLng() + "</Longitude>\r\n");
-        deviceStatusXml.append("<Latitude>" + gpsMsgInfo.getLat() + "</Latitude>\r\n");
-        deviceStatusXml.append("<Speed>" + gpsMsgInfo.getSpeed() + "</Speed>\r\n");
-        deviceStatusXml.append("<Direction>" + gpsMsgInfo.getDirection() + "</Direction>\r\n");
-        deviceStatusXml.append("<Altitude>" + gpsMsgInfo.getAltitude() + "</Altitude>\r\n");
-        deviceStatusXml.append("</Notify>\r\n");
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>MobilePosition</CmdType>\r\n")
+                .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                .append("<DeviceID>" + gpsMsgInfo.getId() + "</DeviceID>\r\n")
+                .append("<Time>" + gpsMsgInfo.getTime() + "</Time>\r\n")
+                .append("<Longitude>" + gpsMsgInfo.getLng() + "</Longitude>\r\n")
+                .append("<Latitude>" + gpsMsgInfo.getLat() + "</Latitude>\r\n")
+                .append("<Speed>" + gpsMsgInfo.getSpeed() + "</Speed>\r\n")
+                .append("<Direction>" + gpsMsgInfo.getDirection() + "</Direction>\r\n")
+                .append("<Altitude>" + gpsMsgInfo.getAltitude() + "</Altitude>\r\n")
+                .append("</Notify>\r\n");
 
        sendNotify(parentPlatform, deviceStatusXml.toString(), subscribeInfo, eventResult -> {
             logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
@@ -349,21 +387,21 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 deviceAlarm.getLongitude(), deviceAlarm.getLatitude(), JSON.toJSONString(deviceAlarm));
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceStatusXml = new StringBuffer(600);
-        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        deviceStatusXml.append("<Notify>\r\n");
-        deviceStatusXml.append("<CmdType>Alarm</CmdType>\r\n");
-        deviceStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-        deviceStatusXml.append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n");
-        deviceStatusXml.append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n");
-        deviceStatusXml.append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n");
-        deviceStatusXml.append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n");
-        deviceStatusXml.append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n");
-        deviceStatusXml.append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n");
-        deviceStatusXml.append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n");
-        deviceStatusXml.append("<info>\r\n");
-        deviceStatusXml.append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n");
-        deviceStatusXml.append("</info>\r\n");
-        deviceStatusXml.append("</Notify>\r\n");
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>Alarm</CmdType>\r\n")
+                .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                .append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n")
+                .append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n")
+                .append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n")
+                .append("<AlarmTime>" + deviceAlarm.getAlarmTime() + "</AlarmTime>\r\n")
+                .append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n")
+                .append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n")
+                .append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n")
+                .append("<info>\r\n")
+                .append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n")
+                .append("</info>\r\n")
+                .append("</Notify>\r\n");
 
         CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
 
@@ -422,13 +460,13 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         StringBuffer catalogXml = new StringBuffer(600);
 
         String characterSet = parentPlatform.getCharacterSet();
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        catalogXml.append("<Notify>\r\n");
-        catalogXml.append("<CmdType>Catalog</CmdType>\r\n");
-        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        catalogXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        catalogXml.append("<SumNum>1</SumNum>\r\n");
-        catalogXml.append("<DeviceList Num=\"" + channels.size() + "\">\r\n");
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>Catalog</CmdType>\r\n")
+                .append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n")
+                .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                .append("<SumNum>1</SumNum>\r\n")
+                .append("<DeviceList Num=\"" + channels.size() + "\">\r\n");
         if (channels.size() > 0) {
             for (DeviceChannel channel : channels) {
                 if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
@@ -449,16 +487,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 catalogXml.append("<Parental>" + channel.getParental() + "</Parental>\r\n");
                 if (channel.getParental() == 0) {
                     // 通道项
-                    catalogXml.append("<Manufacturer>" + channel.getManufacture() + "</Manufacturer>\r\n");
-                    catalogXml.append("<Secrecy>" + channel.getSecrecy() + "</Secrecy>\r\n");
-                    catalogXml.append("<RegisterWay>" + channel.getRegisterWay() + "</RegisterWay>\r\n");
-                    catalogXml.append("<Status>" + (channel.getStatus() == 0 ? "OFF" : "ON") + "</Status>\r\n");
+                    catalogXml.append("<Manufacturer>" + channel.getManufacture() + "</Manufacturer>\r\n")
+                            .append("<Secrecy>" + channel.getSecrecy() + "</Secrecy>\r\n")
+                            .append("<RegisterWay>" + channel.getRegisterWay() + "</RegisterWay>\r\n")
+                            .append("<Status>" + (channel.getStatus() == 0 ? "OFF" : "ON") + "</Status>\r\n");
 
                     if (channel.getChannelType() != 2) {  // 业务分组/虚拟组织/行政区划 不设置以下属性
-                        catalogXml.append("<Model>" + channel.getModel() + "</Model>\r\n");
-                        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("<Model>" + channel.getModel() + "</Model>\r\n")
+                                .append("<Owner> " + channel.getOwner()+ "</Owner>\r\n")
+                                .append("<CivilCode>" + channel.getCivilCode() + "</CivilCode>\r\n")
+                                .append("<Address>" + channel.getAddress() + "</Address>\r\n");
                     }
                     if (!"presence".equals(subscribeInfo.getEventType())) {
                         catalogXml.append("<Event>" + type + "</Event>\r\n");
@@ -468,8 +506,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 catalogXml.append("</Item>\r\n");
             }
         }
-        catalogXml.append("</DeviceList>\r\n");
-        catalogXml.append("</Notify>\r\n");
+        catalogXml.append("</DeviceList>\r\n")
+                .append("</Notify>\r\n");
         return catalogXml.toString();
     }
 
@@ -515,26 +553,26 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer catalogXml = new StringBuffer(600);
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        catalogXml.append("<Notify>\r\n");
-        catalogXml.append("<CmdType>Catalog</CmdType>\r\n");
-        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        catalogXml.append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n");
-        catalogXml.append("<SumNum>1</SumNum>\r\n");
-        catalogXml.append("<DeviceList Num=\" " + channels.size() + " \">\r\n");
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>Catalog</CmdType>\r\n")
+                .append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n")
+                .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
+                .append("<SumNum>1</SumNum>\r\n")
+                .append("<DeviceList Num=\" " + channels.size() + " \">\r\n");
         if (channels.size() > 0) {
             for (DeviceChannel channel : channels) {
                 if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
                     channel.setParentId(parentPlatform.getDeviceGBId());
                 }
-                catalogXml.append("<Item>\r\n");
-                catalogXml.append("<DeviceID>" + channel.getChannelId() + "</DeviceID>\r\n");
-                catalogXml.append("<Event>" + type + "</Event>\r\n");
-                catalogXml.append("</Item>\r\n");
+                catalogXml.append("<Item>\r\n")
+                        .append("<DeviceID>" + channel.getChannelId() + "</DeviceID>\r\n")
+                        .append("<Event>" + type + "</Event>\r\n")
+                        .append("</Item>\r\n");
             }
         }
-        catalogXml.append("</DeviceList>\r\n");
-        catalogXml.append("</Notify>\r\n");
+        catalogXml.append("</DeviceList>\r\n")
+                .append("</Notify>\r\n");
         return catalogXml.toString();
     }
     @Override
@@ -544,12 +582,12 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         }
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer recordXml = new StringBuffer(600);
-        recordXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        recordXml.append("<Response>\r\n");
-        recordXml.append("<CmdType>RecordInfo</CmdType>\r\n");
-        recordXml.append("<SN>" +recordInfo.getSn() + "</SN>\r\n");
-        recordXml.append("<DeviceID>" + recordInfo.getDeviceId() + "</DeviceID>\r\n");
-        recordXml.append("<SumNum>" + recordInfo.getSumNum() + "</SumNum>\r\n");
+        recordXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Response>\r\n")
+                .append("<CmdType>RecordInfo</CmdType>\r\n")
+                .append("<SN>" +recordInfo.getSn() + "</SN>\r\n")
+                .append("<DeviceID>" + recordInfo.getDeviceId() + "</DeviceID>\r\n")
+                .append("<SumNum>" + recordInfo.getSumNum() + "</SumNum>\r\n");
         if (recordInfo.getRecordList() == null ) {
             recordXml.append("<RecordList Num=\"0\">\r\n");
         }else {
@@ -558,12 +596,12 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
                 for (RecordItem recordItem : recordInfo.getRecordList()) {
                     recordXml.append("<Item>\r\n");
                     if (deviceChannel != null) {
-                        recordXml.append("<DeviceID>" + recordItem.getDeviceId() + "</DeviceID>\r\n");
-                        recordXml.append("<Name>" + recordItem.getName() + "</Name>\r\n");
-                        recordXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "</StartTime>\r\n");
-                        recordXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "</EndTime>\r\n");
-                        recordXml.append("<Secrecy>" + recordItem.getSecrecy() + "</Secrecy>\r\n");
-                        recordXml.append("<Type>" + recordItem.getType() + "</Type>\r\n");
+                        recordXml.append("<DeviceID>" + recordItem.getDeviceId() + "</DeviceID>\r\n")
+                                .append("<Name>" + recordItem.getName() + "</Name>\r\n")
+                                .append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "</StartTime>\r\n")
+                                .append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "</EndTime>\r\n")
+                                .append("<Secrecy>" + recordItem.getSecrecy() + "</Secrecy>\r\n")
+                                .append("<Type>" + recordItem.getType() + "</Type>\r\n");
                         if (!ObjectUtils.isEmpty(recordItem.getFileSize())) {
                             recordXml.append("<FileSize>" + recordItem.getFileSize() + "</FileSize>\r\n");
                         }
@@ -576,8 +614,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
             }
         }
 
-        recordXml.append("</RecordList>\r\n");
-        recordXml.append("</Response>\r\n");
+        recordXml.append("</RecordList>\r\n")
+                .append("</Response>\r\n");
 
         // callid
         CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
@@ -596,13 +634,13 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
 
         String characterSet = parentPlatform.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");
+        mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
+                .append("<Notify>\r\n")
+                .append("<CmdType>MediaStatus</CmdType>\r\n")
+                .append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n")
+                .append("<DeviceID>" + sendRtpItem.getChannelId() + "</DeviceID>\r\n")
+                .append("<NotifyType>121</NotifyType>\r\n")
+                .append("</Notify>\r\n");
 
         SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(parentPlatform, mediaStatusXml.toString(),
                 sendRtpItem);

+ 3 - 62
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java

@@ -3,7 +3,6 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
-import gov.nist.javax.sip.SipProviderImpl;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.message.SIPResponse;
 import org.apache.commons.lang3.ArrayUtils;
@@ -14,19 +13,17 @@ import org.dom4j.io.SAXReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 
 import javax.sip.*;
 import javax.sip.address.Address;
-import javax.sip.address.AddressFactory;
 import javax.sip.address.SipURI;
-import javax.sip.header.*;
+import javax.sip.header.ContentTypeHeader;
+import javax.sip.header.ExpiresHeader;
+import javax.sip.header.HeaderFactory;
 import javax.sip.message.MessageFactory;
 import javax.sip.message.Request;
 import javax.sip.message.Response;
 import java.io.ByteArrayInputStream;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -44,15 +41,6 @@ public abstract class SIPRequestProcessorParent {
 	@Autowired
 	private SIPSender sipSender;
 
-	public AddressFactory getAddressFactory() {
-		try {
-			return SipFactory.getInstance().createAddressFactory();
-		} catch (PeerUnavailableException e) {
-			e.printStackTrace();
-		}
-		return null;
-	}
-
 	public HeaderFactory getHeaderFactory() {
 		try {
 			return SipFactory.getInstance().createHeaderFactory();
@@ -93,53 +81,6 @@ public abstract class SIPRequestProcessorParent {
 		return responseAck(sipRequest, statusCode, msg, null);
 	}
 
-//	public SIPResponse responseAck(ServerTransaction serverTransaction, int statusCode, String msg, ResponseAckExtraParam responseAckExtraParam) throws SipException, InvalidArgumentException, ParseException {
-//		if (serverTransaction == null) {
-//			logger.warn("[回复消息] ServerTransaction 为null");
-//			return null;
-//		}
-//		ToHeader toHeader = (ToHeader) serverTransaction.getRequest().getHeader(ToHeader.NAME);
-//		if (toHeader.getTag() == null) {
-//			toHeader.setTag(SipUtils.getNewTag());
-//		}
-//		SIPResponse response = (SIPResponse)getMessageFactory().createResponse(statusCode, serverTransaction.getRequest());
-//		if (msg != null) {
-//			response.setReasonPhrase(msg);
-//		}
-//		if (responseAckExtraParam != null) {
-//			if (responseAckExtraParam.sipURI != null && serverTransaction.getRequest().getMethod().equals(Request.INVITE)) {
-//				logger.debug("responseSdpAck SipURI: {}:{}", responseAckExtraParam.sipURI.getHost(), responseAckExtraParam.sipURI.getPort());
-//				Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(
-//						SipFactory.getInstance().createAddressFactory().createSipURI(responseAckExtraParam.sipURI.getUser(),  responseAckExtraParam.sipURI.getHost()+":"+responseAckExtraParam.sipURI.getPort()
-//						));
-//				response.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-//			}
-//			if (responseAckExtraParam.contentTypeHeader != null) {
-//				response.setContent(responseAckExtraParam.content, responseAckExtraParam.contentTypeHeader);
-//			}
-//
-//			if (serverTransaction.getRequest().getMethod().equals(Request.SUBSCRIBE)) {
-//				if (responseAckExtraParam.expires == -1) {
-//					logger.error("[参数不全] 2xx的SUBSCRIBE回复,必须设置Expires header");
-//				}else {
-//					ExpiresHeader expiresHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(responseAckExtraParam.expires);
-//					response.addHeader(expiresHeader);
-//				}
-//			}
-//		}else {
-//			if (serverTransaction.getRequest().getMethod().equals(Request.SUBSCRIBE)) {
-//				logger.error("[参数不全] 2xx的SUBSCRIBE回复,必须设置Expires header");
-//			}
-//		}
-//		serverTransaction.sendResponse(response);
-//		if (statusCode >= 200 && !"NOTIFY".equalsIgnoreCase(serverTransaction.getRequest().getMethod())) {
-//			if (serverTransaction.getDialog() != null) {
-//				serverTransaction.getDialog().delete();
-//			}
-//		}
-//		return response;
-//	}
-
 	public SIPResponse responseAck(SIPRequest sipRequest, int statusCode, String msg, ResponseAckExtraParam responseAckExtraParam) throws SipException, InvalidArgumentException, ParseException {
 		if (sipRequest.getToHeader().getTag() == null) {
 			sipRequest.getToHeader().setTag(SipUtils.getNewTag());

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

@@ -228,7 +228,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                     }
                     return;
                 } else {
-                    logger.info("通道不存在,返回404");
+                    logger.info("通道不存在,返回404: {}", channelId);
                     try {
                         // 通道不存在,发404,资源不存在
                         responseAck(request, Response.NOT_FOUND);

+ 3 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java

@@ -1,9 +1,10 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd;
 
 import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
+import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
-import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 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;
@@ -63,7 +64,6 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem
     @Override
     public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element rootElement) {
 
-        String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + parentPlatform.getServerGBId();
         FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
         try {
             // 回复200 OK

Разница между файлами не показана из-за своего большого размера
+ 631 - 619
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java


+ 36 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java

@@ -0,0 +1,36 @@
+package com.genersoft.iot.vmp.media.zlm.dto.hook;
+
+public class HookResult {
+
+    private int code;
+    private String msg;
+
+
+    public HookResult() {
+    }
+
+    public HookResult(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public static HookResult SUCCESS(){
+        return new HookResult(0, "success");
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+}

+ 44 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java

@@ -0,0 +1,44 @@
+package com.genersoft.iot.vmp.media.zlm.dto.hook;
+
+public class HookResultForOnPublish extends HookResult{
+
+    private boolean enable_audio;
+    private boolean enable_mp4;
+    private int mp4_max_second;
+
+    public HookResultForOnPublish() {
+    }
+
+    public static HookResultForOnPublish SUCCESS(){
+        return new HookResultForOnPublish(0, "success");
+    }
+
+    public HookResultForOnPublish(int code, String msg) {
+        setCode(code);
+        setMsg(msg);
+    }
+
+    public boolean isEnable_audio() {
+        return enable_audio;
+    }
+
+    public void setEnable_audio(boolean enable_audio) {
+        this.enable_audio = enable_audio;
+    }
+
+    public boolean isEnable_mp4() {
+        return enable_mp4;
+    }
+
+    public void setEnable_mp4(boolean enable_mp4) {
+        this.enable_mp4 = enable_mp4;
+    }
+
+    public int getMp4_max_second() {
+        return mp4_max_second;
+    }
+
+    public void setMp4_max_second(int mp4_max_second) {
+        this.mp4_max_second = mp4_max_second;
+    }
+}

+ 4 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java

@@ -152,6 +152,10 @@ public class GbStreamServiceImpl implements IGbStreamService {
 
     @Override
     public void sendCatalogMsg(GbStream gbStream, String type) {
+        if (gbStream == null || type == null) {
+            logger.warn("[发送目录订阅]类型:流信息或类型为NULL");
+            return;
+        }
         List<GbStream> gbStreams = new ArrayList<>();
         if (gbStream.getGbId() != null) {
             gbStreams.add(gbStream);

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

@@ -184,7 +184,9 @@ public class StreamPushServiceImpl implements IStreamPushService {
     @Override
     public boolean stop(String app, String streamId) {
         StreamPushItem streamPushItem = streamPushMapper.selectOne(app, streamId);
-        gbStreamService.sendCatalogMsg(streamPushItem, CatalogEvent.DEL);
+        if (streamPushItem != null) {
+            gbStreamService.sendCatalogMsg(streamPushItem, CatalogEvent.DEL);
+        }
 
         platformGbStreamMapper.delByAppAndStream(app, streamId);
         gbStreamMapper.del(app, streamId);

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java

@@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.storager;
 
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
-import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
 import com.genersoft.iot.vmp.storager.dao.dto.ChannelSourceInfo;
 import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
@@ -330,6 +329,8 @@ public interface IVideoManagerStorage {
 	 */
 	boolean resetChannels(String deviceId, List<DeviceChannel> deviceChannelList);
 
+	boolean updateChannels(String deviceId, List<DeviceChannel> deviceChannelList);
+
 	/**
 	 * 获取目录信息
 	 * @param platformId

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/storager/dao/UserMapper.java

@@ -50,7 +50,7 @@ public interface UserMapper {
     @ResultMap(value="roleMap")
     List<User> selectAll();
 
-    @Select("select * from (select user.*, concat(#{callId}_', pushKey) as str1 from user) as u where md5(u.str1) = #{sign}")
+    @Select("select * from (select user.*, concat(concat(#{callId}, '_'), pushKey) as str1 from user) as u where md5(u.str1) = #{sign}")
     List<User> checkPushAuthorityByCallIdAndSign(String callId, String sign);
 
     @Select("select * from user where md5(pushKey) = #{sign}")

+ 113 - 0
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java

@@ -194,6 +194,119 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 
 	}
 
+
+	@Override
+	public boolean updateChannels(String deviceId, List<DeviceChannel> deviceChannelList) {
+		if (CollectionUtils.isEmpty(deviceChannelList)) {
+			return false;
+		}
+		List<DeviceChannel> allChannels = deviceChannelMapper.queryAllChannels(deviceId);
+		Map<String,DeviceChannel> allChannelMap = new ConcurrentHashMap<>();
+		if (allChannels.size() > 0) {
+			for (DeviceChannel deviceChannel : allChannels) {
+				allChannelMap.put(deviceChannel.getChannelId(), deviceChannel);
+			}
+		}
+		List<DeviceChannel> addChannels = new ArrayList<>();
+		List<DeviceChannel> updateChannels = new ArrayList<>();
+
+
+		TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
+		// 数据去重
+		StringBuilder stringBuilder = new StringBuilder();
+		Map<String, Integer> subContMap = new HashMap<>();
+		if (deviceChannelList.size() > 0) {
+			// 数据去重
+			Set<String> gbIdSet = new HashSet<>();
+			for (DeviceChannel deviceChannel : deviceChannelList) {
+				if (!gbIdSet.contains(deviceChannel.getChannelId())) {
+					gbIdSet.add(deviceChannel.getChannelId());
+					if (allChannelMap.containsKey(deviceChannel.getChannelId())) {
+						deviceChannel.setStreamId(allChannelMap.get(deviceChannel.getChannelId()).getStreamId());
+						deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).isHasAudio());
+						updateChannels.add(deviceChannel);
+					}else {
+						addChannels.add(deviceChannel);
+					}
+					if (!ObjectUtils.isEmpty(deviceChannel.getParentId())) {
+						if (subContMap.get(deviceChannel.getParentId()) == null) {
+							subContMap.put(deviceChannel.getParentId(), 1);
+						}else {
+							Integer count = subContMap.get(deviceChannel.getParentId());
+							subContMap.put(deviceChannel.getParentId(), count++);
+						}
+					}
+				}else {
+					stringBuilder.append(deviceChannel.getChannelId()).append(",");
+				}
+			}
+			if (addChannels.size() > 0) {
+				for (DeviceChannel channel : addChannels) {
+					if (subContMap.get(channel.getChannelId()) != null){
+						channel.setSubCount(subContMap.get(channel.getChannelId()));
+					}
+				}
+			}
+			if (updateChannels.size() > 0) {
+				for (DeviceChannel channel : updateChannels) {
+					if (subContMap.get(channel.getChannelId()) != null){
+						channel.setSubCount(subContMap.get(channel.getChannelId()));
+					}
+				}
+			}
+
+		}
+		if (stringBuilder.length() > 0) {
+			logger.info("[目录查询]收到的数据存在重复: {}" , stringBuilder);
+		}
+		if(CollectionUtils.isEmpty(updateChannels) && CollectionUtils.isEmpty(addChannels) ){
+			logger.info("通道更新,数据为空={}" , deviceChannelList);
+			return false;
+		}
+		try {
+			int limitCount = 300;
+			boolean result = false;
+			if (addChannels.size() > 0) {
+				if (addChannels.size() > limitCount) {
+					for (int i = 0; i < addChannels.size(); i += limitCount) {
+						int toIndex = i + limitCount;
+						if (i + limitCount > addChannels.size()) {
+							toIndex = addChannels.size();
+						}
+						result = result || deviceChannelMapper.batchAdd(addChannels.subList(i, toIndex)) < 0;
+					}
+				}else {
+					result = result || deviceChannelMapper.batchAdd(addChannels) < 0;
+				}
+			}
+			if (updateChannels.size() > 0) {
+				if (updateChannels.size() > limitCount) {
+					for (int i = 0; i < updateChannels.size(); i += limitCount) {
+						int toIndex = i + limitCount;
+						if (i + limitCount > updateChannels.size()) {
+							toIndex = updateChannels.size();
+						}
+						result = result || deviceChannelMapper.batchUpdate(updateChannels.subList(i, toIndex)) < 0;
+					}
+				}else {
+					result = result || deviceChannelMapper.batchUpdate(updateChannels) < 0;
+				}
+			}
+			if (result) {
+				//事务回滚
+				dataSourceTransactionManager.rollback(transactionStatus);
+			}else {
+				//手动提交
+				dataSourceTransactionManager.commit(transactionStatus);
+			}
+			return true;
+		}catch (Exception e) {
+			e.printStackTrace();
+			dataSourceTransactionManager.rollback(transactionStatus);
+			return false;
+		}
+	}
+
 	@Override
 	public void deviceChannelOnline(String deviceId, String channelId) {
 		deviceChannelMapper.online(deviceId, channelId);

+ 3 - 4
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java

@@ -1,14 +1,13 @@
 package com.genersoft.iot.vmp.utils.redis;
 
-import java.util.*;
-import java.util.concurrent.TimeUnit;
-
 import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.utils.SpringBeanFactory;
-import gov.nist.javax.sip.stack.UDPMessageChannel;
 import org.springframework.data.redis.core.*;
 import org.springframework.util.CollectionUtils;
 
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
 /**    
  * Redis工具类
  * @author swwheihei

+ 1 - 5
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java

@@ -11,17 +11,13 @@ import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import com.github.pagehelper.PageInfo;
-
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.util.DigestUtils;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
 import javax.security.sasl.AuthenticationException;
@@ -90,7 +86,7 @@ public class UserController {
 
 
     @PostMapping("/add")
-    @Operation(summary = "停止视频回放")
+    @Operation(summary = "添加用户")
     @Parameter(name = "username", description = "用户名", required = true)
     @Parameter(name = "password", description = "密码(未md5加密的密码)", required = true)
     @Parameter(name = "roleId", description = "角色ID", required = true)

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

@@ -168,7 +168,7 @@ user-settings:
     # 保存移动位置历史轨迹:true:保留历史数据,false:仅保留最后的位置(默认)
     save-position-history: false
     # 点播等待超时时间,单位:毫秒
-    play-timeout: 3000
+    play-timeout: 18000
     # 上级点播等待超时时间,单位:毫秒
     platform-play-timeout: 60000
     # 是否开启接口鉴权

+ 2 - 1
web_src/src/components/GBRecordDetail.vue

@@ -181,7 +181,6 @@
       this.recordListStyle.height = this.winHeight + "px";
       this.playerStyle["height"] = this.winHeight + "px";
       this.chooseDate = moment().format('YYYY-MM-DD')
-      this.setTime(this.chooseDate + " 00:00:00", this.chooseDate + " 23:59:59");
       this.dateChange();
 		},
 		destroyed() {
@@ -192,6 +191,8 @@
         if (!this.chooseDate) {
           return;
         }
+
+        this.setTime(this.chooseDate + " 00:00:00", this.chooseDate + " 23:59:59");
         this.recordsLoading = true;
         this.detailFiles = [];
         this.$axios({