Commit 2e7c9a7341670d78f5a1ce061ac225b02d6f606f

Authored by 648540858
2 parents fc8bb378 b079039f

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

# Conflicts:
#	pom.xml
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformChannelMapper.java
#	src/main/resources/all-application.yml
#	src/main/resources/application-dev.yml
Showing 70 changed files with 2282 additions and 1000 deletions

Too many changes to show.

To preserve performance only 70 of 144 files are displayed.

libs/jdbc-x86/bcprov-jdk15on-1.70.jar 0 → 100644
No preview for this file type
libs/jdbc-x86/kingbase8-8.6.0.jar 0 → 100644
No preview for this file type
libs/jdbc-x86/kingbase8-8.6.0.jre6.jar 0 → 100644
No preview for this file type
libs/jdbc-x86/kingbase8-8.6.0.jre7.jar 0 → 100644
No preview for this file type
libs/jdbc-x86/postgresql-42.2.9.jar 0 → 100644
No preview for this file type
libs/jdbc-x86/postgresql-42.2.9.jre6.jar 0 → 100644
No preview for this file type
libs/jdbc-x86/postgresql-42.2.9.jre7.jar 0 → 100644
No preview for this file type
1 1 <?xml version="1.0"?>
2   -<project
3   - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4   - xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
5   - <modelVersion>4.0.0</modelVersion>
6   - <parent>
7   - <groupId>org.springframework.boot</groupId>
8   - <artifactId>spring-boot-starter-parent</artifactId>
9   - <version>2.7.9</version>
10   - </parent>
11   -
12   - <groupId>com.genersoft</groupId>
13   - <artifactId>wvp-pro</artifactId>
14   - <version>2.6.9</version>
15   - <name>web video platform</name>
16   - <description>国标28181视频平台</description>
17   - <packaging>${project.packaging}</packaging>
18   -
19   - <repositories>
20   - <repository>
21   - <id>nexus-aliyun</id>
22   - <name>Nexus aliyun</name>
23   - <url>https://maven.aliyun.com/repository/public</url>
24   - <layout>default</layout>
25   - <snapshots>
26   - <enabled>false</enabled>
27   - </snapshots>
28   - <releases>
29   - <enabled>true</enabled>
30   - </releases>
31   - </repository>
32   - </repositories>
33   - <pluginRepositories>
34   - <pluginRepository>
35   - <id>nexus-aliyun</id>
36   - <name>Nexus aliyun</name>
37   - <url>https://maven.aliyun.com/repository/public</url>
38   - <snapshots>
39   - <enabled>false</enabled>
40   - </snapshots>
41   - <releases>
42   - <enabled>true</enabled>
43   - </releases>
44   - </pluginRepository>
45   - </pluginRepositories>
46   -
47   - <properties>
48   - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
49   - <maven.build.timestamp.format>MMddHHmm</maven.build.timestamp.format>
50   - <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
51   -
52   - <!-- 依赖版本 -->
53   - <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
54   - <asciidoctor.input.directory>${project.basedir}/docs/asciidoc</asciidoctor.input.directory>
55   - <generated.asciidoc.directory>${project.build.directory}/asciidoc</generated.asciidoc.directory>
56   - <asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory>
57   - <asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
58   - </properties>
59   -
60   - <profiles>
61   - <profile>
62   - <id>jar</id>
63   - <activation>
64   - <activeByDefault>true</activeByDefault>
65   - </activation>
66   - <properties>
67   - <project.packaging>jar</project.packaging>
68   - </properties>
69   - </profile>
70   - <profile>
71   - <id>war</id>
72   - <properties>
73   - <project.packaging>war</project.packaging>
74   - </properties>
75   - <dependencies>
76   - <dependency>
77   - <groupId>org.springframework.boot</groupId>
78   - <artifactId>spring-boot-starter-web</artifactId>
79   - <exclusions>
80   - <exclusion>
81   - <groupId>org.springframework.boot</groupId>
82   - <artifactId>spring-boot-starter-jetty</artifactId>
83   - </exclusion>
84   - </exclusions>
85   - </dependency>
86   - <dependency>
87   - <groupId>javax.servlet</groupId>
88   - <artifactId>javax.servlet-api</artifactId>
89   - <version>3.1.0</version>
90   - <scope>provided</scope>
91   - </dependency>
92   - </dependencies>
93   - </profile>
94   - </profiles>
95   -
96   - <dependencies>
97   - <dependency>
98   - <groupId>org.springframework.boot</groupId>
99   - <artifactId>spring-boot-starter-data-redis</artifactId>
100   - </dependency>
101   - <dependency>
102   - <groupId>org.springframework.boot</groupId>
103   - <artifactId>spring-boot-starter-web</artifactId>
104   - </dependency>
105   - <dependency>
106   - <groupId>org.springframework.boot</groupId>
107   - <artifactId>spring-boot-configuration-processor</artifactId>
108   - <optional>true</optional>
109   - </dependency>
110   - <dependency>
111   - <groupId>org.mybatis.spring.boot</groupId>
112   - <artifactId>mybatis-spring-boot-starter</artifactId>
113   - <version>2.2.2</version>
114   - <exclusions>
115   - <exclusion>
116   - <groupId>com.zaxxer</groupId>
117   - <artifactId>HikariCP</artifactId>
118   - </exclusion>
119   - </exclusions>
120   - </dependency>
121   - <dependency>
122   - <groupId>org.springframework.boot</groupId>
123   - <artifactId>spring-boot-starter-security</artifactId>
124   - </dependency>
125   -
126   - <dependency>
127   - <groupId>org.springframework.boot</groupId>
128   - <artifactId>spring-boot-starter-jdbc</artifactId>
129   - </dependency>
130   -
131   - <!-- mysql数据库 -->
132   - <dependency>
133   - <groupId>mysql</groupId>
134   - <artifactId>mysql-connector-java</artifactId>
135   - <version>8.0.30</version>
136   - </dependency>
137   -
138   - <!--postgresql-->
139   - <dependency>
140   - <groupId>org.postgresql</groupId>
141   - <artifactId>postgresql</artifactId>
142   - <version>42.5.1</version>
143   - </dependency>
  2 +<project
  3 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
  4 + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  5 + <modelVersion>4.0.0</modelVersion>
  6 + <parent>
  7 + <groupId>org.springframework.boot</groupId>
  8 + <artifactId>spring-boot-starter-parent</artifactId>
  9 + <version>2.7.17</version>
  10 + </parent>
  11 +
  12 + <groupId>com.genersoft</groupId>
  13 + <artifactId>wvp-pro</artifactId>
  14 + <version>2.7.0</version>
  15 + <name>web video platform</name>
  16 + <description>国标28181视频平台</description>
  17 + <packaging>${project.packaging}</packaging>
  18 +
  19 + <repositories>
  20 + <repository>
  21 + <id>nexus-aliyun</id>
  22 + <name>Nexus aliyun</name>
  23 + <url>https://maven.aliyun.com/repository/public</url>
  24 + <layout>default</layout>
  25 + <snapshots>
  26 + <enabled>false</enabled>
  27 + </snapshots>
  28 + <releases>
  29 + <enabled>true</enabled>
  30 + </releases>
  31 + </repository>
  32 + </repositories>
  33 +
  34 + <pluginRepositories>
  35 + <pluginRepository>
  36 + <id>nexus-aliyun</id>
  37 + <name>Nexus aliyun</name>
  38 + <url>https://maven.aliyun.com/repository/public</url>
  39 + <snapshots>
  40 + <enabled>false</enabled>
  41 + </snapshots>
  42 + <releases>
  43 + <enabled>true</enabled>
  44 + </releases>
  45 + </pluginRepository>
  46 + </pluginRepositories>
  47 +
  48 + <properties>
  49 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  50 + <maven.build.timestamp.format>MMddHHmm</maven.build.timestamp.format>
  51 + <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
  52 +
  53 + <!-- 依赖版本 -->
  54 + <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
  55 + <asciidoctor.input.directory>${project.basedir}/docs/asciidoc</asciidoctor.input.directory>
  56 + <generated.asciidoc.directory>${project.build.directory}/asciidoc</generated.asciidoc.directory>
  57 + <asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory>
  58 + <asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
  59 + </properties>
  60 +
  61 + <profiles>
  62 + <profile>
  63 + <id>jar</id>
  64 + <activation>
  65 + <activeByDefault>true</activeByDefault>
  66 + </activation>
  67 + <properties>
  68 + <project.packaging>jar</project.packaging>
  69 + </properties>
  70 + </profile>
  71 + <profile>
  72 + <id>war</id>
  73 + <properties>
  74 + <project.packaging>war</project.packaging>
  75 + </properties>
  76 + <dependencies>
  77 + <dependency>
  78 + <groupId>org.springframework.boot</groupId>
  79 + <artifactId>spring-boot-starter-web</artifactId>
  80 + <exclusions>
  81 + <exclusion>
  82 + <groupId>org.springframework.boot</groupId>
  83 + <artifactId>spring-boot-starter-jetty</artifactId>
  84 + </exclusion>
  85 + </exclusions>
  86 + </dependency>
  87 + <dependency>
  88 + <groupId>javax.servlet</groupId>
  89 + <artifactId>javax.servlet-api</artifactId>
  90 + <version>3.1.0</version>
  91 + <scope>provided</scope>
  92 + </dependency>
  93 + </dependencies>
  94 + </profile>
  95 + </profiles>
  96 +
  97 + <dependencies>
  98 + <dependency>
  99 + <groupId>org.springframework.boot</groupId>
  100 + <artifactId>spring-boot-starter-data-redis</artifactId>
  101 + </dependency>
  102 + <dependency>
  103 + <groupId>org.springframework.boot</groupId>
  104 + <artifactId>spring-boot-starter-web</artifactId>
  105 + </dependency>
  106 + <dependency>
  107 + <groupId>org.springframework.boot</groupId>
  108 + <artifactId>spring-boot-configuration-processor</artifactId>
  109 + <optional>true</optional>
  110 + </dependency>
  111 + <dependency>
  112 + <groupId>org.mybatis.spring.boot</groupId>
  113 + <artifactId>mybatis-spring-boot-starter</artifactId>
  114 + <version>2.2.2</version>
  115 + <exclusions>
  116 + <exclusion>
  117 + <groupId>com.zaxxer</groupId>
  118 + <artifactId>HikariCP</artifactId>
  119 + </exclusion>
  120 + </exclusions>
  121 + </dependency>
  122 + <dependency>
  123 + <groupId>org.springframework.boot</groupId>
  124 + <artifactId>spring-boot-starter-security</artifactId>
  125 + </dependency>
  126 +
  127 + <dependency>
  128 + <groupId>org.springframework.boot</groupId>
  129 + <artifactId>spring-boot-starter-jdbc</artifactId>
  130 + </dependency>
  131 +
  132 + <!-- mysql数据库 -->
  133 + <dependency>
  134 + <groupId>com.mysql</groupId>
  135 + <artifactId>mysql-connector-j</artifactId>
  136 + <version>8.2.0</version>
  137 + </dependency>
  138 +
  139 + <!--postgresql-->
  140 + <dependency>
  141 + <groupId>org.postgresql</groupId>
  142 + <artifactId>postgresql</artifactId>
  143 + <version>42.5.1</version>
  144 + </dependency>
144 145  
145 146 <!-- kingbase人大金仓 -->
146 147 <!-- 手动下载驱动后安装 -->
... ... @@ -153,14 +154,41 @@
153 154 <scope>system</scope>
154 155 <systemPath>${basedir}/libs/jdbc-aarch/kingbase8-8.6.0.jar</systemPath>
155 156 </dependency>
  157 + <dependency>
  158 + <groupId>com.kingbase</groupId>
  159 + <artifactId>kingbase8</artifactId>
  160 + <version>8.6.0</version>
  161 + <scope>system</scope>
  162 + <systemPath>${basedir}/libs/jdbc-x86/kingbase8-8.6.0.jar</systemPath>
  163 + </dependency>
156 164  
157   - <!--Mybatis分页插件 -->
  165 + <!--Mybatis分页插件 -->
  166 + <dependency>
  167 + <groupId>com.github.pagehelper</groupId>
  168 + <artifactId>pagehelper-spring-boot-starter</artifactId>
  169 + <version>1.4.6</version>
  170 + </dependency>
  171 +
  172 + <!--在线文档 -->
  173 + <!--在线文档 -->
  174 + <dependency>
  175 + <groupId>org.springdoc</groupId>
  176 + <artifactId>springdoc-openapi-ui</artifactId>
  177 + <version>1.6.10</version>
  178 + </dependency>
  179 + <dependency>
  180 + <groupId>org.springdoc</groupId>
  181 + <artifactId>springdoc-openapi-security</artifactId>
  182 + <version>1.6.10</version>
  183 + </dependency>
  184 + <!-- https://mvnrepository.com/artifact/com.baomidou/dynamic-datasource-spring-boot-starter -->
158 185 <dependency>
159   - <groupId>com.github.pagehelper</groupId>
160   - <artifactId>pagehelper-spring-boot-starter</artifactId>
161   - <version>1.4.6</version>
  186 + <groupId>com.baomidou</groupId>
  187 + <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  188 + <version>3.6.1</version>
162 189 </dependency>
163 190  
  191 +
164 192 <!--在线文档 -->
165 193 <dependency>
166 194 <groupId>org.springdoc</groupId>
... ... @@ -168,199 +196,203 @@
168 196 <version>1.6.10</version>
169 197 </dependency>
170 198  
171   - <dependency>
172   - <groupId>com.github.xiaoymin</groupId>
173   - <artifactId>knife4j-springdoc-ui</artifactId>
174   - <version>3.0.3</version>
175   - </dependency>
176   -
177   - <!--参数校验 -->
178   - <dependency>
179   - <groupId>javax.validation</groupId>
180   - <artifactId>validation-api</artifactId>
181   - </dependency>
182   -
183   - <!-- 日志相关 -->
184   - <dependency>
185   - <groupId>org.springframework.boot</groupId>
186   - <artifactId>spring-boot-starter-aop</artifactId>
187   - </dependency>
188   -
189   - <!-- sip协议栈 -->
190   - <dependency>
191   - <groupId>javax.sip</groupId>
192   - <artifactId>jain-sip-ri</artifactId>
193   - <version>1.3.0-91</version>
194   - </dependency>
195   -
196   - <!-- 取代log4j -->
197   - <dependency>
198   - <groupId>org.slf4j</groupId>
199   - <artifactId>log4j-over-slf4j</artifactId>
200   - <version>1.7.36</version>
201   - </dependency>
202   -
203   - <!-- xml解析库 -->
204   - <dependency>
205   - <groupId>org.dom4j</groupId>
206   - <artifactId>dom4j</artifactId>
207   - <version>2.1.3</version>
208   - </dependency>
209   -
210   - <dependency>
211   - <groupId>com.google.guava</groupId>
212   - <artifactId>guava</artifactId>
213   - <version>20.0</version>
214   - </dependency>
215   -
216   - <!-- json解析库fastjson2 -->
217   - <dependency>
218   - <groupId>com.alibaba.fastjson2</groupId>
219   - <artifactId>fastjson2</artifactId>
220   - <version>2.0.17</version>
221   - </dependency>
222   - <dependency>
223   - <groupId>com.alibaba.fastjson2</groupId>
224   - <artifactId>fastjson2-extension</artifactId>
225   - <version>2.0.17</version>
226   - </dependency>
227   -
228   - <!-- okhttp -->
229   - <dependency>
230   - <groupId>com.squareup.okhttp3</groupId>
231   - <artifactId>okhttp</artifactId>
232   - <version>4.10.0</version>
233   - </dependency>
234   -
235   - <!-- okhttp 调试日志 -->
236   - <dependency>
237   - <groupId>com.squareup.okhttp3</groupId>
238   - <artifactId>logging-interceptor</artifactId>
239   - <version>4.10.0</version>
240   - </dependency>
241   -
242   - <!-- okhttp-digest -->
243   - <dependency>
244   - <groupId>io.github.rburgst</groupId>
245   - <artifactId>okhttp-digest</artifactId>
246   - <version>2.7</version>
247   - </dependency>
248   -
249   - <!-- https://mvnrepository.com/artifact/net.sf.kxml/kxml2 -->
250   -<!-- <dependency>-->
251   -<!-- <groupId>net.sf.kxml</groupId>-->
252   -<!-- <artifactId>kxml2</artifactId>-->
253   -<!-- <version>2.3.0</version>-->
254   -<!-- </dependency>-->
255   -
256   - <!-- jwt实现 -->
257   - <dependency>
258   - <groupId>org.bitbucket.b_c</groupId>
259   - <artifactId>jose4j</artifactId>
260   - <version>0.9.3</version>
261   - </dependency>
262   -
263   - <!--反向代理-->
264   - <dependency>
265   - <groupId>org.mitre.dsmiley.httpproxy</groupId>
266   - <artifactId>smiley-http-proxy-servlet</artifactId>
267   - <version>1.12.1</version>
268   - </dependency>
269   -
270   - <!--excel解析库-->
271   - <dependency>
272   - <groupId>com.alibaba</groupId>
273   - <artifactId>easyexcel</artifactId>
274   - <version>3.1.1</version>
275   - </dependency>
276   -
277   - <!-- 获取系统信息 -->
278   - <dependency>
279   - <groupId>com.github.oshi</groupId>
280   - <artifactId>oshi-core</artifactId>
281   - <version>6.2.2</version>
282   - </dependency>
283   -
284   - <dependency>
285   - <groupId>org.springframework.session</groupId>
286   - <artifactId>spring-session-core</artifactId>
287   - </dependency>
288   -
289   -<!-- &lt;!&ndash; 检测文件编码 &ndash;&gt;-->
290   -<!-- &lt;!&ndash; https://mvnrepository.com/artifact/cpdetector/cpdetector &ndash;&gt;-->
291   -<!-- <dependency>-->
292   -<!-- <groupId>cpdetector</groupId>-->
293   -<!-- <artifactId>cpdetector</artifactId>-->
294   -<!-- <version>1.0.8</version>-->
295   -<!-- </dependency>-->
296   -
297   - <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
298   - <dependency>
299   - <groupId>com.google.guava</groupId>
300   - <artifactId>guava</artifactId>
301   - <version>31.1-jre</version>
302   - </dependency>
303   -
304   -
305   - <dependency>
306   - <groupId>org.springframework.boot</groupId>
307   - <artifactId>spring-boot-starter-test</artifactId>
308   -<!-- <scope>test</scope>-->
309   - </dependency>
310   -
311   - </dependencies>
312   -
313   -
314   - <build>
315   - <finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}</finalName>
316   - <plugins>
317   - <plugin>
318   - <groupId>org.springframework.boot</groupId>
319   - <artifactId>spring-boot-maven-plugin</artifactId>
320   - <version>2.7.2</version>
321   - <configuration>
322   - <includeSystemScope>true</includeSystemScope>
323   - </configuration>
324   - </plugin>
325   - <plugin>
326   - <groupId>org.apache.maven.plugins</groupId>
327   - <artifactId>maven-compiler-plugin</artifactId>
328   - <version>3.8.1</version>
329   - <configuration>
330   - <source>1.8</source>
331   - <target>1.8</target>
332   - </configuration>
333   - </plugin>
334   -
335   - <plugin>
336   - <groupId>pl.project13.maven</groupId>
337   - <artifactId>git-commit-id-plugin</artifactId>
338   - <version>3.0.1</version>
339   - <configuration>
340   - <offline>true</offline>
341   - <failOnNoGitDirectory>false</failOnNoGitDirectory>
342   - <dateFormat>yyyyMMdd</dateFormat>
343   - </configuration>
344   - </plugin>
345   - <plugin>
346   - <groupId>org.apache.maven.plugins</groupId>
347   - <artifactId>maven-surefire-plugin</artifactId>
348   - <version>2.22.2</version>
349   - <configuration>
350   - <skipTests>true</skipTests>
351   - </configuration>
352   - </plugin>
353   - </plugins>
354   - <resources>
355   - <resource>
356   - <directory>src/main/resources</directory>
357   - </resource>
358   - <resource>
359   - <directory>src/main/java</directory>
360   - <includes>
361   - <include>**/*.xml</include>
362   - </includes>
363   - </resource>
364   - </resources>
365   - </build>
  199 + <dependency>
  200 + <groupId>com.github.xiaoymin</groupId>
  201 + <artifactId>knife4j-springdoc-ui</artifactId>
  202 + <version>3.0.3</version>
  203 + </dependency>
  204 +
  205 + <!--参数校验 -->
  206 + <dependency>
  207 + <groupId>javax.validation</groupId>
  208 + <artifactId>validation-api</artifactId>
  209 + </dependency>
  210 +
  211 + <!-- 日志相关 -->
  212 + <dependency>
  213 + <groupId>org.springframework.boot</groupId>
  214 + <artifactId>spring-boot-starter-aop</artifactId>
  215 + </dependency>
  216 +
  217 + <!-- sip协议栈 -->
  218 + <dependency>
  219 + <groupId>javax.sip</groupId>
  220 + <artifactId>jain-sip-ri</artifactId>
  221 + <version>1.3.0-91</version>
  222 + </dependency>
  223 +
  224 + <!-- 取代log4j -->
  225 + <dependency>
  226 + <groupId>org.slf4j</groupId>
  227 + <artifactId>log4j-over-slf4j</artifactId>
  228 + <version>1.7.36</version>
  229 + </dependency>
  230 +
  231 + <!-- xml解析库 -->
  232 + <dependency>
  233 + <groupId>org.dom4j</groupId>
  234 + <artifactId>dom4j</artifactId>
  235 + <version>2.1.3</version>
  236 + </dependency>
  237 +
  238 + <!-- json解析库fastjson2 -->
  239 + <dependency>
  240 + <groupId>com.alibaba.fastjson2</groupId>
  241 + <artifactId>fastjson2</artifactId>
  242 + <version>2.0.17</version>
  243 + </dependency>
  244 + <dependency>
  245 + <groupId>com.alibaba.fastjson2</groupId>
  246 + <artifactId>fastjson2-extension</artifactId>
  247 + <version>2.0.17</version>
  248 + </dependency>
  249 +
  250 + <!-- okhttp -->
  251 + <dependency>
  252 + <groupId>com.squareup.okhttp3</groupId>
  253 + <artifactId>okhttp</artifactId>
  254 + <version>4.10.0</version>
  255 + </dependency>
  256 +
  257 + <!-- okhttp 调试日志 -->
  258 + <dependency>
  259 + <groupId>com.squareup.okhttp3</groupId>
  260 + <artifactId>logging-interceptor</artifactId>
  261 + <version>4.10.0</version>
  262 + </dependency>
  263 +
  264 + <!-- okhttp-digest -->
  265 + <dependency>
  266 + <groupId>io.github.rburgst</groupId>
  267 + <artifactId>okhttp-digest</artifactId>
  268 + <version>2.7</version>
  269 + </dependency>
  270 +
  271 + <!-- https://mvnrepository.com/artifact/net.sf.kxml/kxml2 -->
  272 + <!-- <dependency>-->
  273 + <!-- <groupId>net.sf.kxml</groupId>-->
  274 + <!-- <artifactId>kxml2</artifactId>-->
  275 + <!-- <version>2.3.0</version>-->
  276 + <!-- </dependency>-->
  277 +
  278 + <!-- jwt实现 -->
  279 + <dependency>
  280 + <groupId>org.bitbucket.b_c</groupId>
  281 + <artifactId>jose4j</artifactId>
  282 + <version>0.9.3</version>
  283 + </dependency>
  284 +
  285 + <!--反向代理-->
  286 + <dependency>
  287 + <groupId>org.mitre.dsmiley.httpproxy</groupId>
  288 + <artifactId>smiley-http-proxy-servlet</artifactId>
  289 + <version>1.12.1</version>
  290 + </dependency>
  291 +
  292 + <!--excel解析库-->
  293 + <dependency>
  294 + <groupId>com.alibaba</groupId>
  295 + <artifactId>easyexcel</artifactId>
  296 + <version>3.3.2</version>
  297 + <exclusions>
  298 + <exclusion>
  299 + <groupId>org.apache.commons</groupId>
  300 + <artifactId>commons-compress</artifactId>
  301 + </exclusion>
  302 + </exclusions>
  303 + </dependency>
  304 + <dependency>
  305 + <groupId>org.apache.commons</groupId>
  306 + <artifactId>commons-compress</artifactId>
  307 + <version>1.24.0</version>
  308 + </dependency>
  309 +
  310 + <!-- 获取系统信息 -->
  311 + <dependency>
  312 + <groupId>com.github.oshi</groupId>
  313 + <artifactId>oshi-core</artifactId>
  314 + <version>6.2.2</version>
  315 + </dependency>
  316 +
  317 + <dependency>
  318 + <groupId>org.springframework.session</groupId>
  319 + <artifactId>spring-session-core</artifactId>
  320 + </dependency>
  321 +
  322 + <!-- 检测文件编码 -->
  323 + <!-- https://mvnrepository.com/artifact/cpdetector/cpdetector -->
  324 + <!--<dependency>-->
  325 + <!-- <groupId>cpdetector</groupId>-->
  326 + <!-- <artifactId>cpdetector</artifactId>-->
  327 + <!-- <version>1.0.8</version>-->
  328 + <!--</dependency>-->
  329 +
  330 + <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
  331 + <dependency>
  332 + <groupId>com.google.guava</groupId>
  333 + <artifactId>guava</artifactId>
  334 + <version>32.1.3-jre</version>
  335 + </dependency>
  336 +
  337 + <dependency>
  338 + <groupId>org.springframework.boot</groupId>
  339 + <artifactId>spring-boot-starter-test</artifactId>
  340 + <scope>test</scope>
  341 + </dependency>
  342 + </dependencies>
  343 +
  344 + <build>
  345 + <finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}</finalName>
  346 + <plugins>
  347 + <plugin>
  348 + <groupId>org.springframework.boot</groupId>
  349 + <artifactId>spring-boot-maven-plugin</artifactId>
  350 + <version>2.7.2</version>
  351 + <configuration>
  352 + <includeSystemScope>true</includeSystemScope>
  353 + </configuration>
  354 + </plugin>
  355 +
  356 + <plugin>
  357 + <groupId>org.apache.maven.plugins</groupId>
  358 + <artifactId>maven-compiler-plugin</artifactId>
  359 + <version>3.8.1</version>
  360 + <configuration>
  361 + <source>1.8</source>
  362 + <target>1.8</target>
  363 + </configuration>
  364 + </plugin>
  365 +
  366 + <plugin>
  367 + <groupId>pl.project13.maven</groupId>
  368 + <artifactId>git-commit-id-plugin</artifactId>
  369 + <version>3.0.1</version>
  370 + <configuration>
  371 + <offline>true</offline>
  372 + <failOnNoGitDirectory>false</failOnNoGitDirectory>
  373 + <dateFormat>yyyyMMdd</dateFormat>
  374 + </configuration>
  375 + </plugin>
  376 +
  377 + <plugin>
  378 + <groupId>org.apache.maven.plugins</groupId>
  379 + <artifactId>maven-surefire-plugin</artifactId>
  380 + <version>2.22.2</version>
  381 + <configuration>
  382 + <skipTests>true</skipTests>
  383 + </configuration>
  384 + </plugin>
  385 + </plugins>
  386 + <resources>
  387 + <resource>
  388 + <directory>src/main/resources</directory>
  389 + </resource>
  390 + <resource>
  391 + <directory>src/main/java</directory>
  392 + <includes>
  393 + <include>**/*.xml</include>
  394 + </includes>
  395 + </resource>
  396 + </resources>
  397 + </build>
366 398 </project>
... ...
sql/2.6.9更新.sql deleted 100644 → 0
1   -alter table wvp_device_channel
2   - change stream_id stream_id varying(255)
3   -
4   -alter table wvp_platform
5   - add auto_push_channel bool default false
6   -
7   -alter table wvp_stream_proxy
8   - add stream_key character varying(255)
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
1 1 package com.genersoft.iot.vmp.common;
2 2  
  3 +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
3 4 import io.swagger.v3.oas.annotations.media.Schema;
4 5  
5 6 import java.io.Serializable;
... ... @@ -76,6 +77,8 @@ public class StreamInfo implements Serializable, Cloneable{
76 77 private String endTime;
77 78 @Schema(description = "进度(录像下载使用)")
78 79 private double progress;
  80 + @Schema(description = "文件下载地址(录像下载使用)")
  81 + private DownloadFileInfo downLoadFilePath;
79 82  
80 83 @Schema(description = "是否暂停(录像回放使用)")
81 84 private boolean pause;
... ... @@ -605,5 +608,11 @@ public class StreamInfo implements Serializable, Cloneable{
605 608 this.subStream = subStream;
606 609 }
607 610  
  611 + public DownloadFileInfo getDownLoadFilePath() {
  612 + return downLoadFilePath;
  613 + }
608 614  
  615 + public void setDownLoadFilePath(DownloadFileInfo downLoadFilePath) {
  616 + this.downLoadFilePath = downLoadFilePath;
  617 + }
609 618 }
... ...
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
... ... @@ -53,7 +53,7 @@ public class VideoManagerConstants {
53 53  
54 54 public static final String MEDIA_TRANSACTION_USED_PREFIX = "VMP_MEDIA_TRANSACTION_";
55 55  
56   - public static final String MEDIA_STREAM_AUTHORITY = "MEDIA_STREAM_AUTHORITY_";
  56 + public static final String MEDIA_STREAM_AUTHORITY = "VMP_MEDIA_STREAM_AUTHORITY_";
57 57  
58 58 public static final String SIP_CSEQ_PREFIX = "VMP_SIP_CSEQ_";
59 59  
... ... @@ -71,6 +71,7 @@ public class VideoManagerConstants {
71 71 public static final String BROADCAST_WAITE_INVITE = "task_broadcast_waite_invite_";
72 72  
73 73 public static final String REGISTER_EXPIRE_TASK_KEY_PREFIX = "VMP_device_register_expire_";
  74 + public static final String PUSH_STREAM_LIST = "VMP_PUSH_STREAM_LIST_";
74 75  
75 76  
76 77  
... ...
src/main/java/com/genersoft/iot/vmp/conf/CloudRecordTimer.java 0 → 100644
  1 +package com.genersoft.iot.vmp.conf;
  2 +
  3 +
  4 +import com.alibaba.fastjson2.JSONObject;
  5 +import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
  6 +import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
  7 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  8 +import com.genersoft.iot.vmp.service.IMediaServerService;
  9 +import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
  10 +import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
  11 +import com.genersoft.iot.vmp.vmanager.cloudRecord.CloudRecordController;
  12 +import org.slf4j.Logger;
  13 +import org.slf4j.LoggerFactory;
  14 +import org.springframework.beans.factory.annotation.Autowired;
  15 +import org.springframework.scheduling.annotation.Scheduled;
  16 +import org.springframework.stereotype.Component;
  17 +
  18 +import java.io.File;
  19 +import java.util.ArrayList;
  20 +import java.util.Calendar;
  21 +import java.util.Date;
  22 +import java.util.List;
  23 +
  24 +/**
  25 + * 录像文件定时删除
  26 + */
  27 +@Component
  28 +public class CloudRecordTimer {
  29 +
  30 + private final static Logger logger = LoggerFactory.getLogger(CloudRecordTimer.class);
  31 +
  32 + @Autowired
  33 + private IMediaServerService mediaServerService;
  34 +
  35 + @Autowired
  36 + private CloudRecordServiceMapper cloudRecordServiceMapper;
  37 +
  38 + @Autowired
  39 + private ZLMRESTfulUtils zlmresTfulUtils;
  40 +
  41 + /**
  42 + * 定时查询待删除的录像文件
  43 + */
  44 +// @Scheduled(fixedRate = 10000) //每五秒执行一次,方便测试
  45 + @Scheduled(cron = "0 0 0 * * ?") //每天的0点执行
  46 + public void execute(){
  47 + logger.info("[录像文件定时清理] 开始清理过期录像文件");
  48 + // 获取配置了assist的流媒体节点
  49 + List<MediaServerItem> mediaServerItemList = mediaServerService.getAllOnline();
  50 + if (mediaServerItemList.isEmpty()) {
  51 + return;
  52 + }
  53 + long result = 0;
  54 + for (MediaServerItem mediaServerItem : mediaServerItemList) {
  55 +
  56 + Calendar lastCalendar = Calendar.getInstance();
  57 + if (mediaServerItem.getRecordDay() > 0) {
  58 + lastCalendar.setTime(new Date());
  59 + // 获取保存的最后截至日[期,因为每个节点都有一个日期,也就是支持每个节点设置不同的保存日期,
  60 + lastCalendar.add(Calendar.DAY_OF_MONTH, -mediaServerItem.getRecordDay());
  61 + Long lastDate = lastCalendar.getTimeInMillis();
  62 +
  63 + // 获取到截至日期之前的录像文件列表,文件列表满足未被收藏和保持的。这两个字段目前共能一致,
  64 + // 为我自己业务系统相关的代码,大家使用的时候直接使用收藏(collect)这一个类型即可
  65 + List<CloudRecordItem> cloudRecordItemList = cloudRecordServiceMapper.queryRecordListForDelete(lastDate, mediaServerItem.getId());
  66 + if (cloudRecordItemList.isEmpty()) {
  67 + continue;
  68 + }
  69 + // TODO 后续可以删除空了的过期日期文件夹
  70 + for (CloudRecordItem cloudRecordItem : cloudRecordItemList) {
  71 + String date = new File(cloudRecordItem.getFilePath()).getParentFile().getName();
  72 + JSONObject jsonObject = zlmresTfulUtils.deleteRecordDirectory(mediaServerItem, cloudRecordItem.getApp(),
  73 + cloudRecordItem.getStream(), date, cloudRecordItem.getFileName());
  74 + if (jsonObject.getInteger("code") != 0) {
  75 + logger.warn("[录像文件定时清理] 删除磁盘文件错误: {}:{}", cloudRecordItem.getFilePath(), jsonObject);
  76 + }
  77 + }
  78 + result += cloudRecordServiceMapper.deleteList(cloudRecordItemList);
  79 + }
  80 + }
  81 + logger.info("[录像文件定时清理] 共清理{}个过期录像文件", result);
  82 + }
  83 +}
... ...
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
... ... @@ -81,6 +81,12 @@ public class MediaConfig{
81 81 @Value("${media.record-assist-port:0}")
82 82 private Integer recordAssistPort = 0;
83 83  
  84 + @Value("${media.record-day:7}")
  85 + private Integer recordDay;
  86 +
  87 + @Value("${media.record-path:}")
  88 + private String recordPath;
  89 +
84 90 public String getId() {
85 91 return id;
86 92 }
... ... @@ -212,13 +218,32 @@ public class MediaConfig{
212 218 mediaServerItem.setSendRtpPortRange(rtpSendPortRange);
213 219 mediaServerItem.setRecordAssistPort(recordAssistPort);
214 220 mediaServerItem.setHookAliveInterval(30.00f);
215   -
  221 + mediaServerItem.setRecordDay(recordDay);
  222 + if (recordPath != null) {
  223 + mediaServerItem.setRecordPath(recordPath);
  224 + }
216 225 mediaServerItem.setCreateTime(DateUtil.getNow());
217 226 mediaServerItem.setUpdateTime(DateUtil.getNow());
218 227  
219 228 return mediaServerItem;
220 229 }
221 230  
  231 + public Integer getRecordDay() {
  232 + return recordDay;
  233 + }
  234 +
  235 + public void setRecordDay(Integer recordDay) {
  236 + this.recordDay = recordDay;
  237 + }
  238 +
  239 + public String getRecordPath() {
  240 + return recordPath;
  241 + }
  242 +
  243 + public void setRecordPath(String recordPath) {
  244 + this.recordPath = recordPath;
  245 + }
  246 +
222 247 public String getRtpSendPortRange() {
223 248 return rtpSendPortRange;
224 249 }
... ...
src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java
1 1 package com.genersoft.iot.vmp.conf;
2 2  
  3 +import com.genersoft.iot.vmp.conf.security.JwtUtils;
  4 +import io.swagger.v3.oas.models.Components;
3 5 import io.swagger.v3.oas.models.OpenAPI;
4 6 import io.swagger.v3.oas.models.info.Contact;
5 7 import io.swagger.v3.oas.models.info.Info;
6 8 import io.swagger.v3.oas.models.info.License;
  9 +import io.swagger.v3.oas.models.security.SecurityScheme;
7 10 import org.springframework.core.annotation.Order;
8 11 import org.springdoc.core.GroupedOpenApi;
9 12 import org.springframework.beans.factory.annotation.Value;
... ... @@ -26,10 +29,14 @@ public class SpringDocConfig {
26 29 contact.setName("pan");
27 30 contact.setEmail("648540858@qq.com");
28 31 return new OpenAPI()
  32 + .components(new Components()
  33 + .addSecuritySchemes(JwtUtils.HEADER, new SecurityScheme()
  34 + .type(SecurityScheme.Type.HTTP)
  35 + .bearerFormat("JWT")))
29 36 .info(new Info().title("WVP-PRO 接口文档")
30 37 .contact(contact)
31 38 .description("开箱即用的28181协议视频平台")
32   - .version("v2.0")
  39 + .version("v3.1.0")
33 40 .license(new License().name("Apache 2.0").url("http://springdoc.org")));
34 41 }
35 42  
... ...
src/main/java/com/genersoft/iot/vmp/conf/SystemInfoTimerTask.java
... ... @@ -39,4 +39,6 @@ public class SystemInfoTimerTask {
39 39 }
40 40  
41 41 }
  42 +
  43 +
42 44 }
... ...
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
... ... @@ -23,7 +23,7 @@ public class UserSetting {
23 23  
24 24 private Integer playTimeout = 18000;
25 25  
26   - private int platformPlayTimeout = 60000;
  26 + private int platformPlayTimeout = 20000;
27 27  
28 28 private Boolean interfaceAuthentication = Boolean.TRUE;
29 29  
... ... @@ -51,13 +51,11 @@ public class UserSetting {
51 51  
52 52 private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE;
53 53  
54   - private Boolean deviceStatusNotify = Boolean.FALSE;
  54 + private Boolean deviceStatusNotify = Boolean.TRUE;
55 55 private Boolean useCustomSsrcForParentInvite = Boolean.TRUE;
56 56  
57 57 private String serverId = "000000";
58 58  
59   - private String recordPath = null;
60   -
61 59 private String thirdPartyGBIdReg = "[\\s\\S]*";
62 60  
63 61 private String broadcastForPlatform = "UDP";
... ... @@ -262,14 +260,6 @@ public class UserSetting {
262 260 this.refuseChannelStatusChannelFormNotify = refuseChannelStatusChannelFormNotify;
263 261 }
264 262  
265   - public String getRecordPath() {
266   - return recordPath;
267   - }
268   -
269   - public void setRecordPath(String recordPath) {
270   - this.recordPath = recordPath;
271   - }
272   -
273 263 public int getMaxNotifyCountQueue() {
274 264 return maxNotifyCountQueue;
275 265 }
... ...
src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java
... ... @@ -78,6 +78,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
78 78  
79 79 // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录
80 80 User user = new User();
  81 + user.setId(jwtUser.getUserId());
81 82 user.setUsername(jwtUser.getUserName());
82 83 user.setPassword(jwtUser.getPassword());
83 84 Role role = new Role();
... ...
src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java
... ... @@ -28,7 +28,7 @@ public class JwtUtils implements InitializingBean {
28 28  
29 29 private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
30 30  
31   - private static final String HEADER = "access-token";
  31 + public static final String HEADER = "access-token";
32 32  
33 33 private static final String AUDIENCE = "Audience";
34 34  
... ... @@ -144,6 +144,7 @@ public class JwtUtils implements InitializingBean {
144 144 jwtUser.setUserName(username);
145 145 jwtUser.setPassword(user.getPassword());
146 146 jwtUser.setRoleId(user.getRole().getId());
  147 + jwtUser.setUserId(user.getId());
147 148  
148 149 return jwtUser;
149 150 } catch (InvalidJwtException e) {
... ...
src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java
1 1 package com.genersoft.iot.vmp.conf.security;
2 2  
3 3 import com.genersoft.iot.vmp.conf.UserSetting;
4   -import org.springframework.core.annotation.Order;
5 4 import org.slf4j.Logger;
6 5 import org.slf4j.LoggerFactory;
7 6 import org.springframework.beans.factory.annotation.Autowired;
8 7 import org.springframework.context.annotation.Bean;
9 8 import org.springframework.context.annotation.Configuration;
  9 +import org.springframework.core.annotation.Order;
10 10 import org.springframework.security.authentication.AuthenticationManager;
11 11 import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
12 12 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
... ... @@ -25,9 +25,11 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
25 25  
26 26 import java.util.ArrayList;
27 27 import java.util.Arrays;
  28 +import java.util.Collections;
28 29  
29 30 /**
30 31 * 配置Spring Security
  32 + *
31 33 * @author lin
32 34 */
33 35 @Configuration
... ... @@ -67,6 +69,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
67 69 matchers.add("/");
68 70 matchers.add("/#/**");
69 71 matchers.add("/static/**");
  72 + matchers.add("/swagger-ui.html");
  73 + matchers.add("/swagger-ui/");
70 74 matchers.add("/index.html");
71 75 matchers.add("/doc.html");
72 76 matchers.add("/webjars/**");
... ... @@ -75,7 +79,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
75 79 matchers.add("/js/**");
76 80 matchers.add("/api/device/query/snap/**");
77 81 matchers.add("/record_proxy/*/**");
78   - matchers.addAll(userSetting.getInterfaceAuthenticationExcludes());
  82 + matchers.add("/api/emit");
  83 + matchers.add("/favicon.ico");
79 84 // 可以直接访问的静态数据
80 85 web.ignoring().antMatchers(matchers.toArray(new String[0]));
81 86 }
... ... @@ -83,6 +88,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
83 88  
84 89 /**
85 90 * 配置认证方式
  91 + *
86 92 * @param auth
87 93 * @throws Exception
88 94 */
... ... @@ -111,7 +117,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
111 117 .authorizeRequests()
112 118 .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
113 119 .antMatchers(userSetting.getInterfaceAuthenticationExcludes().toArray(new String[0])).permitAll()
114   - .antMatchers("/api/user/login","/index/hook/**","/zlm_Proxy/FhTuMYqB2HeCuNOb/record/t/1/2023-03-25/16:35:07-16:35:16-9353.mp4").permitAll()
  120 + .antMatchers("/api/user/login", "/index/hook/**", "/swagger-ui/**", "/doc.html").permitAll()
115 121 .anyRequest().authenticated()
116 122 // 异常处理器
117 123 .and()
... ... @@ -124,18 +130,24 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
124 130  
125 131 }
126 132  
127   - CorsConfigurationSource configurationSource(){
  133 + CorsConfigurationSource configurationSource() {
128 134 // 配置跨域
129 135 CorsConfiguration corsConfiguration = new CorsConfiguration();
130 136 corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
131 137 corsConfiguration.setAllowedMethods(Arrays.asList("*"));
132 138 corsConfiguration.setMaxAge(3600L);
133   - corsConfiguration.setAllowCredentials(true);
134   - corsConfiguration.setAllowedOrigins(userSetting.getAllowedOrigins());
  139 + if (userSetting.getAllowedOrigins() != null && !userSetting.getAllowedOrigins().isEmpty()) {
  140 + corsConfiguration.setAllowCredentials(true);
  141 + corsConfiguration.setAllowedOrigins(userSetting.getAllowedOrigins());
  142 + }else {
  143 + corsConfiguration.setAllowCredentials(false);
  144 + corsConfiguration.setAllowedOrigins(Collections.singletonList(CorsConfiguration.ALL));
  145 + }
  146 +
135 147 corsConfiguration.setExposedHeaders(Arrays.asList(JwtUtils.getHeader()));
136 148  
137 149 UrlBasedCorsConfigurationSource url = new UrlBasedCorsConfigurationSource();
138   - url.registerCorsConfiguration("/**",corsConfiguration);
  150 + url.registerCorsConfiguration("/**", corsConfiguration);
139 151 return url;
140 152 }
141 153  
... ...
src/main/java/com/genersoft/iot/vmp/conf/security/dto/JwtUser.java
... ... @@ -21,6 +21,7 @@ public class JwtUser {
21 21 EXCEPTION
22 22 }
23 23  
  24 + private int userId;
24 25 private String userName;
25 26  
26 27 private String password;
... ... @@ -29,6 +30,14 @@ public class JwtUser {
29 30  
30 31 private TokenStatus status;
31 32  
  33 + public int getUserId() {
  34 + return userId;
  35 + }
  36 +
  37 + public void setUserId(int userId) {
  38 + this.userId = userId;
  39 + }
  40 +
32 41 public String getUserName() {
33 42 return userName;
34 43 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/conf/StackLoggerImpl.java
1 1 package com.genersoft.iot.vmp.gb28181.conf;
2 2  
3 3 import gov.nist.core.StackLogger;
4   -import org.slf4j.Logger;
5 4 import org.slf4j.LoggerFactory;
  5 +import org.slf4j.spi.LocationAwareLogger;
6 6 import org.springframework.stereotype.Component;
7 7  
8 8 import java.util.Properties;
... ... @@ -10,100 +10,132 @@ import java.util.Properties;
10 10 @Component
11 11 public class StackLoggerImpl implements StackLogger {
12 12  
13   - private final static Logger logger = LoggerFactory.getLogger(StackLoggerImpl.class);
  13 + /**
  14 + * 完全限定类名(Fully Qualified Class Name),用于定位日志位置
  15 + */
  16 + private static final String FQCN = StackLoggerImpl.class.getName();
  17 +
  18 + /**
  19 + * 获取栈中类信息(以便底层日志记录系统能够提取正确的位置信息(方法名、行号))
  20 + * @return LocationAwareLogger
  21 + */
  22 + private static LocationAwareLogger getLocationAwareLogger() {
  23 + return (LocationAwareLogger) LoggerFactory.getLogger(new Throwable().getStackTrace()[4].getClassName());
  24 + }
  25 +
  26 +
  27 + /**
  28 + * 封装打印日志的位置信息
  29 + * @param level 日志级别
  30 + * @param message 日志事件的消息
  31 + */
  32 + private static void log(int level, String message) {
  33 + LocationAwareLogger locationAwareLogger = getLocationAwareLogger();
  34 + locationAwareLogger.log(null, FQCN, level, message, null, null);
  35 + }
  36 +
  37 + /**
  38 + * 封装打印日志的位置信息
  39 + * @param level 日志级别
  40 + * @param message 日志事件的消息
  41 + */
  42 + private static void log(int level, String message, Throwable throwable) {
  43 + LocationAwareLogger locationAwareLogger = getLocationAwareLogger();
  44 + locationAwareLogger.log(null, FQCN, level, message, null, throwable);
  45 + }
  46 +
  47 + @Override
  48 + public void logStackTrace() {
  49 +
  50 + }
  51 +
  52 + @Override
  53 + public void logStackTrace(int traceLevel) {
  54 + System.out.println("traceLevel: " + traceLevel);
  55 + }
  56 +
  57 + @Override
  58 + public int getLineCount() {
  59 + return 0;
  60 + }
  61 +
  62 + @Override
  63 + public void logException(Throwable ex) {
  64 +
  65 + }
  66 +
  67 + @Override
  68 + public void logDebug(String message) {
  69 + log(LocationAwareLogger.INFO_INT, message);
  70 + }
  71 +
  72 + @Override
  73 + public void logDebug(String message, Exception ex) {
  74 + log(LocationAwareLogger.INFO_INT, message, ex);
  75 + }
  76 +
  77 + @Override
  78 + public void logTrace(String message) {
  79 + log(LocationAwareLogger.INFO_INT, message);
  80 + }
  81 +
  82 + @Override
  83 + public void logFatalError(String message) {
  84 + log(LocationAwareLogger.INFO_INT, message);
  85 + }
  86 +
  87 + @Override
  88 + public void logError(String message) {
  89 + log(LocationAwareLogger.INFO_INT, message);
  90 + }
  91 +
  92 + @Override
  93 + public boolean isLoggingEnabled() {
  94 + return true;
  95 + }
  96 +
  97 + @Override
  98 + public boolean isLoggingEnabled(int logLevel) {
  99 + return true;
  100 + }
  101 +
  102 + @Override
  103 + public void logError(String message, Exception ex) {
  104 + log(LocationAwareLogger.INFO_INT, message, ex);
  105 + }
  106 +
  107 + @Override
  108 + public void logWarning(String message) {
  109 + log(LocationAwareLogger.INFO_INT, message);
  110 + }
  111 +
  112 + @Override
  113 + public void logInfo(String message) {
  114 + log(LocationAwareLogger.INFO_INT, message);
  115 + }
  116 +
  117 + @Override
  118 + public void disableLogging() {
  119 +
  120 + }
  121 +
  122 + @Override
  123 + public void enableLogging() {
  124 +
  125 + }
  126 +
  127 + @Override
  128 + public void setBuildTimeStamp(String buildTimeStamp) {
  129 +
  130 + }
  131 +
  132 + @Override
  133 + public void setStackProperties(Properties stackProperties) {
14 134  
15   - @Override
16   - public void logStackTrace() {
  135 + }
17 136  
18   - }
19   -
20   - @Override
21   - public void logStackTrace(int traceLevel) {
22   - System.out.println("traceLevel: " + traceLevel);
23   - }
24   -
25   - @Override
26   - public int getLineCount() {
27   - return 0;
28   - }
29   -
30   - @Override
31   - public void logException(Throwable ex) {
32   -
33   - }
34   -
35   - @Override
36   - public void logDebug(String message) {
37   -// logger.debug(message);
38   - }
39   -
40   - @Override
41   - public void logDebug(String message, Exception ex) {
42   -// logger.debug(message);
43   - }
44   -
45   - @Override
46   - public void logTrace(String message) {
47   - logger.trace(message);
48   - }
49   -
50   - @Override
51   - public void logFatalError(String message) {
52   -// logger.error(message);
53   - }
54   -
55   - @Override
56   - public void logError(String message) {
57   -// logger.error(message);
58   - }
59   -
60   - @Override
61   - public boolean isLoggingEnabled() {
62   - return true;
63   - }
64   -
65   - @Override
66   - public boolean isLoggingEnabled(int logLevel) {
67   - return true;
68   - }
69   -
70   - @Override
71   - public void logError(String message, Exception ex) {
72   -// logger.error(message);
73   - }
74   -
75   - @Override
76   - public void logWarning(String message) {
77   - logger.warn(message);
78   - }
79   -
80   - @Override
81   - public void logInfo(String message) {
82   - logger.info(message);
83   - }
84   -
85   - @Override
86   - public void disableLogging() {
87   -
88   - }
89   -
90   - @Override
91   - public void enableLogging() {
92   -
93   - }
94   -
95   - @Override
96   - public void setBuildTimeStamp(String buildTimeStamp) {
97   -
98   - }
99   -
100   - @Override
101   - public void setStackProperties(Properties stackProperties) {
102   -
103   - }
104   -
105   - @Override
106   - public String getLoggerName() {
107   - return null;
108   - }
  137 + @Override
  138 + public String getLoggerName() {
  139 + return null;
  140 + }
109 141 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEventListener.java
1 1 package com.genersoft.iot.vmp.gb28181.event.alarm;
2 2  
  3 +import org.jetbrains.annotations.NotNull;
  4 +import org.slf4j.Logger;
  5 +import org.slf4j.LoggerFactory;
3 6 import org.springframework.context.ApplicationListener;
4 7 import org.springframework.stereotype.Component;
5   -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
6   -import java.io.IOException;
7   -import java.util.Hashtable;
  8 +
  9 +import java.io.PrintWriter;
8 10 import java.util.Iterator;
9 11 import java.util.Map;
10   -
11   -import org.slf4j.Logger;
12   -import org.slf4j.LoggerFactory;
  12 +import java.util.concurrent.ConcurrentHashMap;
13 13  
14 14 /**
15   - * @description: 报警事件监听
16   - * @author: lawrencehj
17   - * @data: 2021-01-20
  15 + * 报警事件监听器.
  16 + *
  17 + * @author lawrencehj
  18 + * @author <a href="mailto:xiaoQQya@126.com">xiaoQQya</a>
  19 + * @since 2021/01/20
18 20 */
19   -
20 21 @Component
21 22 public class AlarmEventListener implements ApplicationListener<AlarmEvent> {
22 23  
23   - private final static Logger logger = LoggerFactory.getLogger(AlarmEventListener.class);
  24 + private static final Logger logger = LoggerFactory.getLogger(AlarmEventListener.class);
24 25  
25   - private static Map<String, SseEmitter> sseEmitters = new Hashtable<>();
  26 + private static final Map<String, PrintWriter> SSE_CACHE = new ConcurrentHashMap<>();
26 27  
27   - public void addSseEmitters(String browserId, SseEmitter sseEmitter) {
28   - sseEmitters.put(browserId, sseEmitter);
  28 + public void addSseEmitter(String browserId, PrintWriter writer) {
  29 + SSE_CACHE.put(browserId, writer);
  30 + logger.info("SSE 在线数量: {}", SSE_CACHE.size());
  31 + }
  32 +
  33 + public void removeSseEmitter(String browserId, PrintWriter writer) {
  34 + SSE_CACHE.remove(browserId, writer);
  35 + logger.info("SSE 在线数量: {}", SSE_CACHE.size());
29 36 }
30 37  
31 38 @Override
32   - public void onApplicationEvent(AlarmEvent event) {
  39 + public void onApplicationEvent(@NotNull AlarmEvent event) {
33 40 if (logger.isDebugEnabled()) {
34   - logger.debug("设备报警事件触发,deviceId:" + event.getAlarmInfo().getDeviceId() + ", "
35   - + event.getAlarmInfo().getAlarmDescription());
  41 + logger.debug("设备报警事件触发, deviceId: {}, {}", event.getAlarmInfo().getDeviceId(), event.getAlarmInfo().getAlarmDescription());
36 42 }
37   - String msg = "<strong>设备编码:</strong> <i>" + event.getAlarmInfo().getDeviceId() + "</i>"
38   - + "<br><strong>报警描述:</strong> <i>" + event.getAlarmInfo().getAlarmDescription() + "</i>"
39   - + "<br><strong>报警时间:</strong> <i>" + event.getAlarmInfo().getAlarmTime() + "</i>"
40   - + "<br><strong>报警位置:</strong> <i>" + event.getAlarmInfo().getLongitude() + "</i>"
41   - + ", <i>" + event.getAlarmInfo().getLatitude() + "</i>";
42   -
43   - for (Iterator<Map.Entry<String, SseEmitter>> it = sseEmitters.entrySet().iterator(); it.hasNext();) {
44   - Map.Entry<String, SseEmitter> emitter = it.next();
45   - logger.info("推送到SSE连接,浏览器ID: " + emitter.getKey());
  43 +
  44 + String msg = "<strong>设备编号:</strong> <i>" + event.getAlarmInfo().getDeviceId() + "</i>"
  45 + + "<br><strong>通道编号:</strong> <i>" + event.getAlarmInfo().getChannelId() + "</i>"
  46 + + "<br><strong>报警描述:</strong> <i>" + event.getAlarmInfo().getAlarmDescription() + "</i>"
  47 + + "<br><strong>报警时间:</strong> <i>" + event.getAlarmInfo().getAlarmTime() + "</i>";
  48 +
  49 + for (Iterator<Map.Entry<String, PrintWriter>> it = SSE_CACHE.entrySet().iterator(); it.hasNext(); ) {
  50 + Map.Entry<String, PrintWriter> response = it.next();
  51 + logger.info("推送到 SSE 连接, 浏览器 ID: {}", response.getKey());
46 52 try {
47   - emitter.getValue().send(msg);
48   - } catch (IOException | IllegalStateException e) {
49   - if (logger.isDebugEnabled()) {
50   - logger.debug("SSE连接已关闭");
  53 + PrintWriter writer = response.getValue();
  54 +
  55 + if (writer.checkError()) {
  56 + it.remove();
  57 + continue;
51 58 }
52   - // 移除已关闭的连接
  59 +
  60 + String sseMsg = "event:message\n" +
  61 + "data:" + msg + "\n" +
  62 + "\n";
  63 + writer.write(sseMsg);
  64 + writer.flush();
  65 + } catch (Exception e) {
53 66 it.remove();
54 67 }
55 68 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java
... ... @@ -35,7 +35,7 @@ public class RecordEndEventListener implements ApplicationListener&lt;RecordEndEven
35 35 int sumNum = event.getRecordInfo().getSumNum();
36 36 logger.info("录像查询完成事件触发,deviceId:{}, channelId: {}, 录像数量{}/{}条", event.getRecordInfo().getDeviceId(),
37 37 event.getRecordInfo().getChannelId(), count,sumNum);
38   - if (handlerMap.size() > 0) {
  38 + if (!handlerMap.isEmpty()) {
39 39 RecordEndEventHandler handler = handlerMap.get(deviceId + channelId);
40 40 if (handler !=null){
41 41 handler.handler(event.getRecordInfo());
... ... @@ -43,6 +43,9 @@ public class RecordEndEventListener implements ApplicationListener&lt;RecordEndEven
43 43 handlerMap.remove(deviceId + channelId);
44 44 }
45 45 }
  46 + }else {
  47 + logger.info("录像查询完成事件触发, 但是订阅为空,取消发送,deviceId:{}, channelId: {}",
  48 + event.getRecordInfo().getDeviceId(), event.getRecordInfo().getChannelId());
46 49 }
47 50 }
48 51  
... ... @@ -53,6 +56,7 @@ public class RecordEndEventListener implements ApplicationListener&lt;RecordEndEven
53 56 * @param recordEndEventHandler
54 57 */
55 58 public void addEndEventHandler(String device, String channelId, RecordEndEventHandler recordEndEventHandler) {
  59 + logger.info("录像查询事件添加监听,deviceId:{}, channelId: {}", device, channelId);
56 60 handlerMap.put(device + channelId, recordEndEventHandler);
57 61 }
58 62 /**
... ... @@ -61,6 +65,7 @@ public class RecordEndEventListener implements ApplicationListener&lt;RecordEndEven
61 65 * @param channelId
62 66 */
63 67 public void delEndEventHandler(String device, String channelId) {
  68 + logger.info("录像查询事件移除监听,deviceId:{}, channelId: {}", device, channelId);
64 69 handlerMap.remove(device + channelId);
65 70 }
66 71  
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java
... ... @@ -148,13 +148,13 @@ public class CatalogEventLister implements ApplicationListener&lt;CatalogEvent&gt; {
148 148 if (event.getDeviceChannels() != null) {
149 149 deviceChannelList.addAll(event.getDeviceChannels());
150 150 }
151   - if (event.getGbStreams() != null && event.getGbStreams().size() > 0){
  151 + if (event.getGbStreams() != null && !event.getGbStreams().isEmpty()){
152 152 for (GbStream gbStream : event.getGbStreams()) {
153 153 deviceChannelList.add(
154 154 gbStreamService.getDeviceChannelListByStreamWithStatus(gbStream, gbStream.getCatalogId(), parentPlatform));
155 155 }
156 156 }
157   - if (deviceChannelList.size() > 0) {
  157 + if (!deviceChannelList.isEmpty()) {
158 158 logger.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), event.getPlatformId(), deviceChannelList.size());
159 159 try {
160 160 sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getType(), parentPlatform, deviceChannelList, subscribe, null);
... ... @@ -163,10 +163,10 @@ public class CatalogEventLister implements ApplicationListener&lt;CatalogEvent&gt; {
163 163 logger.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage());
164 164 }
165 165 }
166   - }else if (parentPlatformMap.keySet().size() > 0) {
  166 + }else if (!parentPlatformMap.keySet().isEmpty()) {
167 167 for (String gbId : parentPlatformMap.keySet()) {
168 168 List<ParentPlatform> parentPlatforms = parentPlatformMap.get(gbId);
169   - if (parentPlatforms != null && parentPlatforms.size() > 0) {
  169 + if (parentPlatforms != null && !parentPlatforms.isEmpty()) {
170 170 for (ParentPlatform platform : parentPlatforms) {
171 171 SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId());
172 172 if (subscribeInfo == null) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java
... ... @@ -38,7 +38,8 @@ public class SSRCFactory {
38 38  
39 39  
40 40 public void initMediaServerSSRC(String mediaServerId, Set<String> usedSet) {
41   - String ssrcPrefix = sipConfig.getDomain().substring(3, 8);
  41 + String sipDomain = sipConfig.getDomain();
  42 + String ssrcPrefix = sipDomain.length() >= 8 ? sipDomain.substring(3, 8) : sipDomain;
42 43 String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId;
43 44 List<String> ssrcList = new ArrayList<>();
44 45 for (int i = 1; i < MAX_STREAM_COUNT; i++) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
... ... @@ -75,6 +75,33 @@ public class VideoStreamSessionManager {
75 75 return (SsrcTransaction)redisTemplate.opsForValue().get(scanResult.get(0));
76 76 }
77 77  
  78 + public SsrcTransaction getSsrcTransactionByCallId(String callId){
  79 +
  80 + if (ObjectUtils.isEmpty(callId)) {
  81 + return null;
  82 + }
  83 + String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_*_*_" + callId+ "_*";
  84 + List<Object> scanResult = RedisUtil.scan(redisTemplate, key);
  85 + if (!scanResult.isEmpty()) {
  86 + return (SsrcTransaction)redisTemplate.opsForValue().get(scanResult.get(0));
  87 + }else {
  88 + key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_*_*_play_*";
  89 + scanResult = RedisUtil.scan(redisTemplate, key);
  90 + if (scanResult.isEmpty()) {
  91 + return null;
  92 + }
  93 + for (Object keyObj : scanResult) {
  94 + SsrcTransaction ssrcTransaction = (SsrcTransaction)redisTemplate.opsForValue().get(keyObj);
  95 + if (ssrcTransaction.getSipTransactionInfo() != null &&
  96 + ssrcTransaction.getSipTransactionInfo().getCallId().equals(callId)) {
  97 + return ssrcTransaction;
  98 + }
  99 + }
  100 + return null;
  101 + }
  102 +
  103 + }
  104 +
78 105 public List<SsrcTransaction> getSsrcTransactionForAll(String deviceId, String channelId, String callId, String stream){
79 106 if (ObjectUtils.isEmpty(deviceId)) {
80 107 deviceId ="*";
... ... @@ -117,8 +144,19 @@ public class VideoStreamSessionManager {
117 144 }
118 145  
119 146 public void remove(String deviceId, String channelId, String stream) {
120   - SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
121   - if (ssrcTransaction == null) {
  147 + List<SsrcTransaction> ssrcTransactionList = getSsrcTransactionForAll(deviceId, channelId, null, stream);
  148 + if (ssrcTransactionList == null || ssrcTransactionList.isEmpty()) {
  149 + return;
  150 + }
  151 + for (SsrcTransaction ssrcTransaction : ssrcTransactionList) {
  152 + redisTemplate.delete(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_"
  153 + + deviceId + "_" + channelId + "_" + ssrcTransaction.getCallId() + "_" + ssrcTransaction.getStream());
  154 + }
  155 + }
  156 +
  157 + public void removeByCallId(String deviceId, String channelId, String callId) {
  158 + SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, null);
  159 + if (ssrcTransaction == null ) {
122 160 return;
123 161 }
124 162 redisTemplate.delete(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_"
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java
... ... @@ -129,4 +129,6 @@ public class SipRunner implements CommandLineRunner {
129 129 }
130 130 }
131 131 }
  132 +
  133 +
132 134 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java
... ... @@ -66,17 +66,17 @@ public class SIPSender {
66 66 // 添加错误订阅
67 67 if (errorEvent != null) {
68 68 sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
69   - errorEvent.response(eventResult);
70 69 sipSubscribe.removeErrorSubscribe(eventResult.callId);
71 70 sipSubscribe.removeOkSubscribe(eventResult.callId);
  71 + errorEvent.response(eventResult);
72 72 }));
73 73 }
74 74 // 添加订阅
75 75 if (okEvent != null) {
76 76 sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
77   - okEvent.response(eventResult);
78 77 sipSubscribe.removeOkSubscribe(eventResult.callId);
79 78 sipSubscribe.removeErrorSubscribe(eventResult.callId);
  79 + okEvent.response(eventResult);
80 80 });
81 81 }
82 82 if ("TCP".equals(transport)) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java
... ... @@ -164,6 +164,7 @@ public class SIPRequestHeaderProvider {
164 164 Request request = null;
165 165 //请求行
166 166 SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
  167 +// SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
167 168 // via
168 169 ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
169 170 ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
... ... @@ -174,6 +175,7 @@ public class SIPRequestHeaderProvider {
174 175 FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
175 176 //to
176 177 SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
  178 +// SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(),device.getHostAddress());
177 179 Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
178 180 ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, transactionInfo.getToTag());
179 181  
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
... ... @@ -40,6 +40,7 @@ import javax.sip.SipFactory;
40 40 import javax.sip.header.CallIdHeader;
41 41 import javax.sip.message.Request;
42 42 import java.text.ParseException;
  43 +import java.util.List;
43 44  
44 45 /**
45 46 * @description:设备能力接口,用于定义设备的控制、查询能力
... ... @@ -374,7 +375,8 @@ public class SIPCommander implements ISIPCommander {
374 375 }), e -> {
375 376 ResponseEvent responseEvent = (ResponseEvent) e.event;
376 377 SIPResponse response = (SIPResponse) responseEvent.getResponse();
377   - streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response,
  378 + String callId = response.getCallIdHeader().getCallId();
  379 + streamSession.put(device.getDeviceId(), channelId, callId, stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response,
378 380 InviteSessionType.PLAY);
379 381 okEvent.response(e);
380 382 });
... ... @@ -676,22 +678,26 @@ public class SIPCommander implements ISIPCommander {
676 678 */
677 679 @Override
678 680 public void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
679   - SsrcTransaction ssrcTransaction;
680   - if (callId != null) {
681   - ssrcTransaction = streamSession.getSsrcTransaction(null, null, callId, null);
682   - }else {
683   - ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, null, stream);
  681 + if (device == null) {
  682 + logger.warn("[发送BYE] device为null");
  683 + return;
684 684 }
685   - if (ssrcTransaction == null) {
  685 + List<SsrcTransaction> ssrcTransactionList = streamSession.getSsrcTransactionForAll(device.getDeviceId(), channelId, callId, stream);
  686 + if (ssrcTransactionList == null || ssrcTransactionList.isEmpty()) {
  687 + logger.info("[发送BYE] 未找到事务信息,设备: device: {}, channel: {}", device.getDeviceId(), channelId);
686 688 throw new SsrcTransactionNotFoundException(device.getDeviceId(), channelId, callId, stream);
687 689 }
688 690  
689   - mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
690   - mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
691   - streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
  691 + for (SsrcTransaction ssrcTransaction : ssrcTransactionList) {
  692 + logger.info("[发送BYE] 设备: device: {}, channel: {}, callId: {}", device.getDeviceId(), channelId, ssrcTransaction.getCallId());
  693 + mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
692 694  
693   - Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
694   - sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
  695 + mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
  696 + streamSession.removeByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getCallId());
  697 + Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
  698 + sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
  699 +
  700 + }
695 701 }
696 702  
697 703 @Override
... ... @@ -1000,7 +1006,7 @@ public class SIPCommander implements ISIPCommander {
1000 1006 catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
1001 1007 catalogXml.append("</Query>\r\n");
1002 1008  
1003   -
  1009 +
1004 1010  
1005 1011 Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
1006 1012  
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
... ... @@ -579,7 +579,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
579 579  
580 580 @Override
581 581 public void sendNotifyForCatalogAddOrUpdate(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException {
582   - if (parentPlatform == null || deviceChannels == null || deviceChannels.size() == 0 || subscribeInfo == null) {
  582 + if (parentPlatform == null || deviceChannels == null || deviceChannels.isEmpty() || subscribeInfo == null) {
583 583 return;
584 584 }
585 585 if (index == null) {
... ... @@ -597,6 +597,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
597 597 Integer finalIndex = index;
598 598 String catalogXmlContent = getCatalogXmlContentForCatalogAddOrUpdate(parentPlatform, channels,
599 599 deviceChannels.size(), type, subscribeInfo);
  600 + logger.info("[发送NOTIFY通知]类型: {},发送数量: {}", type, channels.size());
600 601 sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> {
601 602 logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg);
602 603 }, (eventResult -> {
... ... @@ -620,7 +621,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
620 621  
621 622 SIPRequest notifyRequest = headerProviderPlatformProvider.createNotifyRequest(parentPlatform, catalogXmlContent, subscribeInfo);
622 623  
623   - sipSender.transmitRequest(parentPlatform.getDeviceIp(), notifyRequest);
  624 + sipSender.transmitRequest(parentPlatform.getDeviceIp(), notifyRequest, errorEvent, okEvent);
624 625 }
625 626  
626 627 private String getCatalogXmlContentForCatalogAddOrUpdate(ParentPlatform parentPlatform, List<DeviceChannel> channels, int sumNum, String type, SubscribeInfo subscribeInfo) {
... ... @@ -632,9 +633,9 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
632 633 .append("<CmdType>Catalog</CmdType>\r\n")
633 634 .append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n")
634 635 .append("<DeviceID>" + parentPlatform.getDeviceGBId() + "</DeviceID>\r\n")
635   - .append("<SumNum>1</SumNum>\r\n")
  636 + .append("<SumNum>"+ sumNum +"</SumNum>\r\n")
636 637 .append("<DeviceList Num=\"" + channels.size() + "\">\r\n");
637   - if (channels.size() > 0) {
  638 + if (!channels.isEmpty()) {
638 639 for (DeviceChannel channel : channels) {
639 640 if (parentPlatform.getServerGBId().equals(channel.getParentId())) {
640 641 channel.setParentId(parentPlatform.getDeviceGBId());
... ... @@ -701,6 +702,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
701 702 }else {
702 703 channels = deviceChannels.subList(index, deviceChannels.size());
703 704 }
  705 + logger.info("[发送NOTIFY通知]类型: {},发送数量: {}", type, channels.size());
704 706 Integer finalIndex = index;
705 707 String catalogXmlContent = getCatalogXmlContentForCatalogOther(parentPlatform, channels, type);
706 708 sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> {
... ... @@ -747,13 +749,14 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
747 749 if ( parentPlatform ==null) {
748 750 return ;
749 751 }
  752 + logger.info("[国标级联] 发送录像数据通道: {}", recordInfo.getChannelId());
750 753 String characterSet = parentPlatform.getCharacterSet();
751 754 StringBuffer recordXml = new StringBuffer(600);
752 755 recordXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
753 756 .append("<Response>\r\n")
754 757 .append("<CmdType>RecordInfo</CmdType>\r\n")
755 758 .append("<SN>" +recordInfo.getSn() + "</SN>\r\n")
756   - .append("<DeviceID>" + recordInfo.getChannelId() + "</DeviceID>\r\n")
  759 + .append("<DeviceID>" + deviceChannel.getChannelId() + "</DeviceID>\r\n")
757 760 .append("<SumNum>" + recordInfo.getSumNum() + "</SumNum>\r\n");
758 761 if (recordInfo.getRecordList() == null ) {
759 762 recordXml.append("<RecordList Num=\"0\">\r\n");
... ... @@ -763,7 +766,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
763 766 for (RecordItem recordItem : recordInfo.getRecordList()) {
764 767 recordXml.append("<Item>\r\n");
765 768 if (deviceChannel != null) {
766   - recordXml.append("<DeviceID>" + recordItem.getDeviceId() + "</DeviceID>\r\n")
  769 + recordXml.append("<DeviceID>" + deviceChannel.getChannelId() + "</DeviceID>\r\n")
767 770 .append("<Name>" + recordItem.getName() + "</Name>\r\n")
768 771 .append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "</StartTime>\r\n")
769 772 .append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "</EndTime>\r\n")
... ... @@ -783,12 +786,14 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
783 786  
784 787 recordXml.append("</RecordList>\r\n")
785 788 .append("</Response>\r\n");
786   -
  789 + logger.info("[国标级联] 发送录像数据通道:{}, 内容: {}", recordInfo.getChannelId(), recordXml);
787 790 // callid
788 791 CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());
789 792  
790 793 Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, recordXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader);
791   - sipSender.transmitRequest(parentPlatform.getDeviceIp(), request);
  794 + sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, null, eventResult -> {
  795 + logger.info("[国标级联] 发送录像数据通道:{}, 发送成功", recordInfo.getChannelId());
  796 + });
792 797  
793 798 }
794 799  
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
... ... @@ -128,6 +128,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
128 128 redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(),
129 129 callIdHeader.getCallId(), null);
130 130 zlmServerFactory.stopSendRtpStream(mediaInfo, param);
  131 + if (userSetting.getUseCustomSsrcForParentInvite()) {
  132 + mediaServerService.releaseSsrc(mediaInfo.getId(), sendRtpItem.getSsrc());
  133 + }
131 134 if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
132 135 ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
133 136 if (platform != null) {
... ... @@ -167,14 +170,12 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
167 170 }
168 171 }
169 172  
170   - // 发流端发送的停止
171   - SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
172   - if (ssrcTransaction == null ) {
173   - logger.info("[收到bye] 但是无法获取推流信息和发流信息,忽略此请求");
174   - logger.info(request.toString());
175   - return;
176   - }
177   -
  173 + // 可能是设备发送的停止
  174 + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransactionByCallId(callIdHeader.getCallId());
  175 + if (ssrcTransaction == null) {
  176 + return;
  177 + }
  178 + logger.info("[收到bye] 来自设备:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
178 179  
179 180 ParentPlatform platform = platformService.queryPlatformByServerGBId(ssrcTransaction.getDeviceId());
180 181 if (platform != null ) {
... ... @@ -216,7 +217,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
216 217 if (mediaServerItem != null) {
217 218 mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
218 219 }
219   - streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getStream());
  220 + streamSession.removeByCallId(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getCallId());
220 221 if (ssrcTransaction.getType() == InviteSessionType.BROADCAST) {
221 222 // 查找来源的对讲设备,发送停止
222 223 Device sourceDevice = storager.queryVideoDeviceByPlatformIdAndChannelId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
... ... @@ -152,7 +152,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
152 152 String requesterId = SipUtils.getUserIdFromFromHeader(request);
153 153 CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
154 154 if (requesterId == null || channelId == null) {
155   - logger.info("无法从FromHeader的Address中获取到平台id,返回400");
  155 + logger.info("无法从请求中获取到平台id,返回400");
156 156 // 参数不全, 发400,请求错误
157 157 try {
158 158 responseAck(request, Response.BAD_REQUEST);
... ... @@ -162,6 +162,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
162 162 return;
163 163 }
164 164  
  165 + logger.info("[INVITE] requesterId: {}, callId: {}, 来自:{}:{}",
  166 + requesterId, callIdHeader.getCallId(), request.getRemoteAddress(), request.getRemotePort());
165 167  
166 168 // 查询请求是否来自上级平台\设备
167 169 ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
... ... @@ -409,7 +411,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
409 411 // 非严格模式端口不统一, 增加兼容性,修改为一个不为0的端口
410 412 localPort = new Random().nextInt(65535) + 1;
411 413 }
412   - content.append("m=video " + localPort + " RTP/AVP 96\r\n");
  414 + if (sendRtpItem.isTcp()) {
  415 + content.append("m=video " + localPort + " TCP/RTP/AVP 96\r\n");
  416 + if (!sendRtpItem.isTcpActive()) {
  417 + content.append("a=setup:active\r\n");
  418 + } else {
  419 + content.append("a=setup:passive\r\n");
  420 + }
  421 + }else {
  422 + content.append("m=video " + localPort + " RTP/AVP 96\r\n");
  423 + }
413 424 content.append("a=sendonly\r\n");
414 425 content.append("a=rtpmap:96 PS/90000\r\n");
415 426 content.append("y=" + sendRtpItem.getSsrc() + "\r\n");
... ... @@ -524,7 +535,10 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
524 535 }
525 536 });
526 537 } else {
527   -
  538 + sendRtpItem.setPlayType(InviteStreamType.PLAY);
  539 + String streamId = String.format("%s_%s", device.getDeviceId(), channelId);
  540 + sendRtpItem.setStreamId(streamId);
  541 + redisCatchStorage.updateSendRTPSever(sendRtpItem);
528 542 SSRCInfo ssrcInfo = playService.play(mediaServerItem, device.getDeviceId(), channelId, ssrc, ((code, msg, data) -> {
529 543 if (code == InviteErrorCode.SUCCESS.getCode()) {
530 544 hookEvent.run(code, msg, data);
... ... @@ -536,9 +550,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
536 550 errorEvent.run(code, msg, data);
537 551 }
538 552 }));
539   - sendRtpItem.setPlayType(InviteStreamType.PLAY);
540   - String streamId = String.format("%s_%s", device.getDeviceId(), channelId);
541   - sendRtpItem.setStream(streamId);
542 553 sendRtpItem.setSsrc(ssrcInfo.getSsrc());
543 554 redisCatchStorage.updateSendRTPSever(sendRtpItem);
544 555  
... ... @@ -721,8 +732,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
721 732 zlmHttpHookSubscribe.removeSubscribe(hookSubscribe);
722 733 dynamicTask.stop(callIdHeader.getCallId());
723 734 }
724   -
725   -
726 735 } else if ("push".equals(gbStream.getStreamType())) {
727 736 if (!platform.isStartOfflinePush()) {
728 737 // 平台设置中关闭了拉起离线的推流则直接回复
... ... @@ -745,13 +754,10 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
745 754 dynamicTask.startDelay(callIdHeader.getCallId(), () -> {
746 755 logger.info("[ app={}, stream={} ] 等待设备开始推流超时", gbStream.getApp(), gbStream.getStream());
747 756 try {
  757 + redisPushStreamResponseListener.removeEvent(gbStream.getApp(), gbStream.getStream());
748 758 mediaListManager.removedChannelOnlineEventLister(gbStream.getApp(), gbStream.getStream());
749 759 responseAck(request, Response.REQUEST_TIMEOUT); // 超时
750   - } catch (SipException e) {
751   - logger.error("未处理的异常 ", e);
752   - } catch (InvalidArgumentException e) {
753   - logger.error("未处理的异常 ", e);
754   - } catch (ParseException e) {
  760 + } catch (SipException | InvalidArgumentException | ParseException e) {
755 761 logger.error("未处理的异常 ", e);
756 762 }
757 763 }, userSetting.getPlatformPlayTimeout());
... ... @@ -762,6 +768,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
762 768 // 添加在本机上线的通知
763 769 mediaListManager.addChannelOnlineEventLister(gbStream.getApp(), gbStream.getStream(), (app, stream, serverId) -> {
764 770 dynamicTask.stop(callIdHeader.getCallId());
  771 + redisPushStreamResponseListener.removeEvent(gbStream.getApp(), gbStream.getStream());
765 772 if (serverId.equals(userSetting.getServerId())) {
766 773 SendRtpItem sendRtpItem = zlmServerFactory.createSendRtpItem(mediaServerItem, addressStr, finalPort, ssrc, requesterId,
767 774 app, stream, channelId, mediaTransmissionTCP, platform.isRtcp());
... ... @@ -826,7 +833,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
826 833 // 发送redis消息
827 834 redisGbPlayMsgListener.sendMsg(streamPushItem.getServerId(), streamPushItem.getMediaServerId(),
828 835 streamPushItem.getApp(), streamPushItem.getStream(), addressStr, port, ssrc, requesterId,
829   - channelId, mediaTransmissionTCP, platform.isRtcp(), null, responseSendItemMsg -> {
  836 + channelId, mediaTransmissionTCP, platform.isRtcp(),platform.getName(), responseSendItemMsg -> {
830 837 SendRtpItem sendRtpItem = responseSendItemMsg.getSendRtpItem();
831 838 if (sendRtpItem == null || responseSendItemMsg.getMediaServerItem() == null) {
832 839 logger.warn("服务器端口资源不足");
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java
... ... @@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
13 13 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
14 14 import com.genersoft.iot.vmp.service.IDeviceChannelService;
15 15 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  16 +import com.genersoft.iot.vmp.utils.DateUtil;
16 17 import org.dom4j.DocumentException;
17 18 import org.dom4j.Element;
18 19 import org.slf4j.Logger;
... ... @@ -185,6 +186,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
185 186 // 判断此通道是否存在
186 187 DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId, channel.getChannelId());
187 188 if (deviceChannel != null) {
  189 + logger.info("[增加通道] 已存在,不发送通知只更新,设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
188 190 channel.setId(deviceChannel.getId());
189 191 updateChannelMap.put(channel.getChannelId(), channel);
190 192 if (updateChannelMap.keySet().size() > 300) {
... ... @@ -222,6 +224,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
222 224 DeviceChannel deviceChannelForUpdate = deviceChannelService.getOne(deviceId, channel.getChannelId());
223 225 if (deviceChannelForUpdate != null) {
224 226 channel.setId(deviceChannelForUpdate.getId());
  227 + channel.setUpdateTime(DateUtil.getNow());
225 228 updateChannelMap.put(channel.getChannelId(), channel);
226 229 if (updateChannelMap.keySet().size() > 300) {
227 230 executeSaveForUpdate();
... ... @@ -244,11 +247,11 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
244 247 // 转发变化信息
245 248 eventPublisher.catalogEventPublish(null, channel, event);
246 249  
247   - if (updateChannelMap.keySet().size() > 0
248   - || addChannelMap.keySet().size() > 0
249   - || updateChannelOnlineList.size() > 0
250   - || updateChannelOfflineList.size() > 0
251   - || deleteChannelList.size() > 0) {
  250 + if (!updateChannelMap.keySet().isEmpty()
  251 + || !addChannelMap.keySet().isEmpty()
  252 + || !updateChannelOnlineList.isEmpty()
  253 + || !updateChannelOfflineList.isEmpty()
  254 + || !deleteChannelList.isEmpty()) {
252 255  
253 256 if (!dynamicTask.contains(talkKey)) {
254 257 dynamicTask.startDelay(talkKey, this::executeSave, 1000);
... ... @@ -262,16 +265,36 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
262 265 }
263 266  
264 267 private void executeSave(){
265   - executeSaveForAdd();
266   - executeSaveForUpdate();
267   - executeSaveForDelete();
268   - executeSaveForOnline();
269   - executeSaveForOffline();
  268 + try {
  269 + executeSaveForAdd();
  270 + } catch (Exception e) {
  271 + logger.error("[存储收到的增加通道] 异常: ", e );
  272 + }
  273 + try {
  274 + executeSaveForUpdate();
  275 + } catch (Exception e) {
  276 + logger.error("[存储收到的更新通道] 异常: ", e );
  277 + }
  278 + try {
  279 + executeSaveForDelete();
  280 + } catch (Exception e) {
  281 + logger.error("[存储收到的删除通道] 异常: ", e );
  282 + }
  283 + try {
  284 + executeSaveForOnline();
  285 + } catch (Exception e) {
  286 + logger.error("[存储收到的通道上线] 异常: ", e );
  287 + }
  288 + try {
  289 + executeSaveForOffline();
  290 + } catch (Exception e) {
  291 + logger.error("[存储收到的通道离线] 异常: ", e );
  292 + }
270 293 dynamicTask.stop(talkKey);
271 294 }
272 295  
273 296 private void executeSaveForUpdate(){
274   - if (updateChannelMap.values().size() > 0) {
  297 + if (!updateChannelMap.values().isEmpty()) {
275 298 ArrayList<DeviceChannel> deviceChannels = new ArrayList<>(updateChannelMap.values());
276 299 updateChannelMap.clear();
277 300 deviceChannelService.batchUpdateChannel(deviceChannels);
... ... @@ -280,7 +303,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
280 303 }
281 304  
282 305 private void executeSaveForAdd(){
283   - if (addChannelMap.values().size() > 0) {
  306 + if (!addChannelMap.values().isEmpty()) {
284 307 ArrayList<DeviceChannel> deviceChannels = new ArrayList<>(addChannelMap.values());
285 308 addChannelMap.clear();
286 309 deviceChannelService.batchAddChannel(deviceChannels);
... ... @@ -288,21 +311,21 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
288 311 }
289 312  
290 313 private void executeSaveForDelete(){
291   - if (deleteChannelList.size() > 0) {
  314 + if (!deleteChannelList.isEmpty()) {
292 315 deviceChannelService.deleteChannels(deleteChannelList);
293 316 deleteChannelList.clear();
294 317 }
295 318 }
296 319  
297 320 private void executeSaveForOnline(){
298   - if (updateChannelOnlineList.size() > 0) {
  321 + if (!updateChannelOnlineList.isEmpty()) {
299 322 deviceChannelService.channelsOnline(updateChannelOnlineList);
300 323 updateChannelOnlineList.clear();
301 324 }
302 325 }
303 326  
304 327 private void executeSaveForOffline(){
305   - if (updateChannelOfflineList.size() > 0) {
  328 + if (!updateChannelOfflineList.isEmpty()) {
306 329 deviceChannelService.channelsOffline(updateChannelOfflineList);
307 330 updateChannelOfflineList.clear();
308 331 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java
... ... @@ -106,23 +106,27 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
106 106 String title = registerFlag ? "[注册请求]": "[注销请求]";
107 107 logger.info(title + "设备:{}, 开始处理: {}", deviceId, requestAddress);
108 108 if (device != null &&
109   - device.getSipTransactionInfo() != null &&
110   - request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) {
  109 + device.getSipTransactionInfo() != null &&
  110 + request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) {
111 111 logger.info(title + "设备:{}, 注册续订: {}",device.getDeviceId(), device.getDeviceId());
112   - device.setExpires(request.getExpires().getExpires());
113   - device.setIp(remoteAddressInfo.getIp());
114   - device.setPort(remoteAddressInfo.getPort());
115   - device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
116   - device.setLocalIp(request.getLocalAddress().getHostAddress());
117   - Response registerOkResponse = getRegisterOkResponse(request);
118   - // 判断TCP还是UDP
119   - ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
120   - String transport = reqViaHeader.getTransport();
121   - device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
122   - sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), registerOkResponse);
123   - device.setRegisterTime(DateUtil.getNow());
124   - SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse)registerOkResponse);
125   - deviceService.online(device, sipTransactionInfo);
  112 + if (registerFlag) {
  113 + device.setExpires(request.getExpires().getExpires());
  114 + device.setIp(remoteAddressInfo.getIp());
  115 + device.setPort(remoteAddressInfo.getPort());
  116 + device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
  117 + device.setLocalIp(request.getLocalAddress().getHostAddress());
  118 + Response registerOkResponse = getRegisterOkResponse(request);
  119 + // 判断TCP还是UDP
  120 + ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
  121 + String transport = reqViaHeader.getTransport();
  122 + device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP");
  123 + sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), registerOkResponse);
  124 + device.setRegisterTime(DateUtil.getNow());
  125 + SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse)registerOkResponse);
  126 + deviceService.online(device, sipTransactionInfo);
  127 + }else {
  128 + deviceService.offline(deviceId, "主动注销");
  129 + }
126 130 return;
127 131 }
128 132 String password = (device != null && !ObjectUtils.isEmpty(device.getPassword()))? device.getPassword() : sipConfig.getPassword();
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java
... ... @@ -61,7 +61,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
61 61 return;
62 62 }
63 63 SIPRequest request = (SIPRequest) evt.getRequest();
64   - logger.info("[收到心跳] device: {}, callId: {}", device.getDeviceId(), request.getCallIdHeader().getCallId());
  64 + logger.info("[收到心跳] device: {}, callId: {}", device.getDeviceId(), request.getCallIdHeader().getCallId());
65 65  
66 66 // 回复200 OK
67 67 try {
... ... @@ -76,10 +76,15 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
76 76  
77 77 RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress());
78 78 if (!device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) {
79   - logger.info("[心跳] 设备{}地址变化, 远程地址为: {}:{}", device.getDeviceId(), remoteAddressInfo.getIp(), remoteAddressInfo.getPort());
  79 + logger.info("[收到心跳] 设备{}地址变化, 远程地址为: {}:{}", device.getDeviceId(), remoteAddressInfo.getIp(), remoteAddressInfo.getPort());
80 80 device.setPort(remoteAddressInfo.getPort());
81 81 device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort())));
82 82 device.setIp(remoteAddressInfo.getIp());
  83 + // 设备地址变化会引起目录订阅任务失效,需要重新添加
  84 + if (device.getSubscribeCycleForCatalog() > 0) {
  85 + deviceService.removeCatalogSubscribe(device);
  86 + deviceService.addCatalogSubscribe(device);
  87 + }
83 88 }
84 89 if (device.getKeepaliveTime() == null) {
85 90 device.setKeepaliveIntervalTime(60);
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java
... ... @@ -102,6 +102,7 @@ public class RecordInfoQueryMessageHandler extends SIPRequestProcessorParent imp
102 102 // 接收录像数据
103 103 recordEndEventListener.addEndEventHandler(deviceChannel.getDeviceId(), channelId, (recordInfo)->{
104 104 try {
  105 + logger.info("[国标级联] 录像查询收到数据, 通道: {},准备转发===", channelId);
105 106 cmderFroPlatform.recordInfo(deviceChannel, parentPlatform, request.getFromTag(), recordInfo);
106 107 } catch (SipException | InvalidArgumentException | ParseException e) {
107 108 logger.error("[命令发送失败] 国标级联 回复录像数据: {}", e.getMessage());
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
... ... @@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.gb28181.bean.Device;
8 8 import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
9 9 import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
10 10 import com.genersoft.iot.vmp.utils.DateUtil;
  11 +import org.apache.commons.lang3.StringUtils;
11 12 import org.apache.commons.lang3.math.NumberUtils;
12 13 import org.dom4j.Attribute;
13 14 import org.dom4j.Document;
... ... @@ -214,8 +215,11 @@ public class XmlUtil {
214 215 return deviceChannel;
215 216 }
216 217 Element nameElement = itemDevice.element("Name");
217   - if (nameElement != null) {
  218 + // 当通道名称为空时,设置通道名称为通道编码,避免级联时因通道名称为空导致上级接收通道失败
  219 + if (nameElement != null && StringUtils.isNotBlank(nameElement.getText())) {
218 220 deviceChannel.setName(nameElement.getText());
  221 + } else {
  222 + deviceChannel.setName(channelId);
219 223 }
220 224 if(channelId.length() <= 8) {
221 225 deviceChannel.setHasAudio(false);
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java
... ... @@ -2,40 +2,69 @@ package com.genersoft.iot.vmp.media.zlm;
2 2  
3 3 import com.alibaba.fastjson2.JSON;
4 4 import com.alibaba.fastjson2.JSONObject;
  5 +import com.genersoft.iot.vmp.conf.exception.ControllerException;
5 6 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  7 +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
6 8 import okhttp3.*;
7 9 import okhttp3.logging.HttpLoggingInterceptor;
8 10 import org.jetbrains.annotations.NotNull;
9 11 import org.slf4j.Logger;
10 12 import org.slf4j.LoggerFactory;
11 13 import org.springframework.stereotype.Component;
  14 +import org.springframework.util.ObjectUtils;
12 15  
13 16 import java.io.IOException;
14 17 import java.net.ConnectException;
  18 +import java.net.MalformedURLException;
  19 +import java.net.SocketTimeoutException;
  20 +import java.net.URL;
15 21 import java.util.HashMap;
  22 +import java.util.List;
16 23 import java.util.Map;
17 24 import java.util.Objects;
  25 +import java.util.concurrent.TimeUnit;
18 26  
19 27 @Component
20 28 public class AssistRESTfulUtils {
21 29  
22 30 private final static Logger logger = LoggerFactory.getLogger(AssistRESTfulUtils.class);
23 31  
  32 +
  33 + private OkHttpClient client;
  34 +
  35 +
24 36 public interface RequestCallback{
25 37 void run(JSONObject response);
26 38 }
27 39  
28 40 private OkHttpClient getClient(){
29   - OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
30   - if (logger.isDebugEnabled()) {
31   - HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> {
32   - logger.debug("http请求参数:" + message);
33   - });
34   - logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
35   - // OkHttp進行添加攔截器loggingInterceptor
36   - httpClientBuilder.addInterceptor(logging);
  41 + return getClient(null);
  42 + }
  43 +
  44 + private OkHttpClient getClient(Integer readTimeOut){
  45 + if (client == null) {
  46 + if (readTimeOut == null) {
  47 + readTimeOut = 10;
  48 + }
  49 + OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
  50 + // 设置连接超时时间
  51 + httpClientBuilder.connectTimeout(8, TimeUnit.SECONDS);
  52 + // 设置读取超时时间
  53 + httpClientBuilder.readTimeout(readTimeOut,TimeUnit.SECONDS);
  54 + // 设置连接池
  55 + httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES));
  56 + if (logger.isDebugEnabled()) {
  57 + HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> {
  58 + logger.debug("http请求参数:" + message);
  59 + });
  60 + logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
  61 + // OkHttp進行添加攔截器loggingInterceptor
  62 + httpClientBuilder.addInterceptor(logging);
  63 + }
  64 + client = httpClientBuilder.build();
37 65 }
38   - return httpClientBuilder.build();
  66 + return client;
  67 +
39 68 }
40 69  
41 70  
... ... @@ -49,11 +78,11 @@ public class AssistRESTfulUtils {
49 78 logger.warn("未启用Assist服务");
50 79 return null;
51 80 }
52   - StringBuffer stringBuffer = new StringBuffer();
53   - stringBuffer.append(String.format("http://%s:%s/%s", mediaServerItem.getIp(), mediaServerItem.getRecordAssistPort(), api));
  81 + StringBuilder stringBuffer = new StringBuilder();
  82 + stringBuffer.append(api);
54 83 JSONObject responseJSON = null;
55 84  
56   - if (param != null && param.keySet().size() > 0) {
  85 + if (param != null && !param.keySet().isEmpty()) {
57 86 stringBuffer.append("?");
58 87 int index = 1;
59 88 for (String key : param.keySet()){
... ... @@ -68,6 +97,7 @@ public class AssistRESTfulUtils {
68 97 }
69 98  
70 99 String url = stringBuffer.toString();
  100 + logger.info("[访问assist]: {}", url);
71 101 Request request = new Request.Builder()
72 102 .get()
73 103 .url(url)
... ... @@ -123,13 +153,93 @@ public class AssistRESTfulUtils {
123 153 return responseJSON;
124 154 }
125 155  
  156 + public JSONObject sendPost(MediaServerItem mediaServerItem, String url,
  157 + JSONObject param, ZLMRESTfulUtils.RequestCallback callback,
  158 + Integer readTimeOut) {
  159 + OkHttpClient client = getClient(readTimeOut);
126 160  
127   - public JSONObject fileDuration(MediaServerItem mediaServerItem, String app, String stream, RequestCallback callback){
128   - Map<String, Object> param = new HashMap<>();
129   - param.put("app",app);
130   - param.put("stream",stream);
131   - param.put("recordIng",true);
132   - return sendGet(mediaServerItem, "api/record/file/duration",param, callback);
  161 + if (mediaServerItem == null) {
  162 + return null;
  163 + }
  164 + logger.info("[访问assist]: {}, 参数: {}", url, param);
  165 + JSONObject responseJSON = new JSONObject();
  166 + //-2自定义流媒体 调用错误码
  167 + responseJSON.put("code",-2);
  168 + responseJSON.put("msg","ASSIST调用失败");
  169 +
  170 + RequestBody requestBodyJson = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), param.toString());
  171 +
  172 + Request request = new Request.Builder()
  173 + .post(requestBodyJson)
  174 + .url(url)
  175 + .addHeader("Content-Type", "application/json")
  176 + .build();
  177 + if (callback == null) {
  178 + try {
  179 + Response response = client.newCall(request).execute();
  180 + if (response.isSuccessful()) {
  181 + ResponseBody responseBody = response.body();
  182 + if (responseBody != null) {
  183 + String responseStr = responseBody.string();
  184 + responseJSON = JSON.parseObject(responseStr);
  185 + }
  186 + }else {
  187 + response.close();
  188 + Objects.requireNonNull(response.body()).close();
  189 + }
  190 + }catch (IOException e) {
  191 + logger.error(String.format("[ %s ]ASSIST请求失败: %s", url, e.getMessage()));
  192 +
  193 + if(e instanceof SocketTimeoutException){
  194 + //读取超时超时异常
  195 + logger.error(String.format("读取ASSIST数据失败: %s, %s", url, e.getMessage()));
  196 + }
  197 + if(e instanceof ConnectException){
  198 + //判断连接异常,我这里是报Failed to connect to 10.7.5.144
  199 + logger.error(String.format("连接ASSIST失败: %s, %s", url, e.getMessage()));
  200 + }
  201 +
  202 + }catch (Exception e){
  203 + logger.error(String.format("访问ASSIST失败: %s, %s", url, e.getMessage()));
  204 + }
  205 + }else {
  206 + client.newCall(request).enqueue(new Callback(){
  207 +
  208 + @Override
  209 + public void onResponse(@NotNull Call call, @NotNull Response response){
  210 + if (response.isSuccessful()) {
  211 + try {
  212 + String responseStr = Objects.requireNonNull(response.body()).string();
  213 + callback.run(JSON.parseObject(responseStr));
  214 + } catch (IOException e) {
  215 + logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
  216 + }
  217 +
  218 + }else {
  219 + response.close();
  220 + Objects.requireNonNull(response.body()).close();
  221 + }
  222 + }
  223 +
  224 + @Override
  225 + public void onFailure(@NotNull Call call, @NotNull IOException e) {
  226 + logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage()));
  227 +
  228 + if(e instanceof SocketTimeoutException){
  229 + //读取超时超时异常
  230 + logger.error(String.format("读取ZLM数据失败: %s, %s", call.request().toString(), e.getMessage()));
  231 + }
  232 + if(e instanceof ConnectException){
  233 + //判断连接异常,我这里是报Failed to connect to 10.7.5.144
  234 + logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage()));
  235 + }
  236 + }
  237 + });
  238 + }
  239 +
  240 +
  241 +
  242 + return responseJSON;
133 243 }
134 244  
135 245 public JSONObject getInfo(MediaServerItem mediaServerItem, RequestCallback callback){
... ... @@ -137,33 +247,43 @@ public class AssistRESTfulUtils {
137 247 return sendGet(mediaServerItem, "api/record/info",param, callback);
138 248 }
139 249  
140   - public JSONObject addStreamCallInfo(MediaServerItem mediaServerItem, String app, String stream, String callId, RequestCallback callback){
141   - Map<String, Object> param = new HashMap<>();
142   - param.put("app",app);
143   - param.put("stream",stream);
144   - param.put("callId",callId);
145   - return sendGet(mediaServerItem, "api/record/addStreamCallInfo",param, callback);
146   - }
  250 + public JSONObject addTask(MediaServerItem mediaServerItem, String app, String stream, String startTime,
  251 + String endTime, String callId, List<String> filePathList, String remoteHost) {
147 252  
148   - public JSONObject getDateList(MediaServerItem mediaServerItem, String app, String stream, int year, int month) {
149   - Map<String, Object> param = new HashMap<>();
150   - param.put("app", app);
151   - param.put("stream", stream);
152   - param.put("year", year);
153   - param.put("month", month);
154   - return sendGet(mediaServerItem, "api/record/date/list", param, null);
  253 + JSONObject videoTaskInfoJSON = new JSONObject();
  254 + videoTaskInfoJSON.put("app", app);
  255 + videoTaskInfoJSON.put("stream", stream);
  256 + videoTaskInfoJSON.put("startTime", startTime);
  257 + videoTaskInfoJSON.put("endTime", endTime);
  258 + videoTaskInfoJSON.put("callId", callId);
  259 + videoTaskInfoJSON.put("filePathList", filePathList);
  260 + if (!ObjectUtils.isEmpty(remoteHost)) {
  261 + videoTaskInfoJSON.put("remoteHost", remoteHost);
  262 + }
  263 + String urlStr = String.format("%s/api/record/file/download/task/add", remoteHost);;
  264 + return sendPost(mediaServerItem, urlStr, videoTaskInfoJSON, null, 30);
155 265 }
156 266  
157   - public JSONObject getFileList(MediaServerItem mediaServerItem, int page, int count, String app, String stream,
158   - String startTime, String endTime) {
  267 + public JSONObject queryTaskList(MediaServerItem mediaServerItem, String app, String stream, String callId,
  268 + String taskId, Boolean isEnd, String scheme) {
159 269 Map<String, Object> param = new HashMap<>();
160   - param.put("app", app);
161   - param.put("stream", stream);
162   - param.put("page", page);
163   - param.put("count", count);
164   - param.put("startTime", startTime);
165   - param.put("endTime", endTime);
166   - return sendGet(mediaServerItem, "api/record/file/listWithDate", param, null);
  270 + if (!ObjectUtils.isEmpty(app)) {
  271 + param.put("app", app);
  272 + }
  273 + if (!ObjectUtils.isEmpty(stream)) {
  274 + param.put("stream", stream);
  275 + }
  276 + if (!ObjectUtils.isEmpty(callId)) {
  277 + param.put("callId", callId);
  278 + }
  279 + if (!ObjectUtils.isEmpty(taskId)) {
  280 + param.put("taskId", taskId);
  281 + }
  282 + if (!ObjectUtils.isEmpty(isEnd)) {
  283 + param.put("isEnd", isEnd);
  284 + }
  285 + String urlStr = String.format("%s://%s:%s/api/record/file/download/task/list",
  286 + scheme, mediaServerItem.getIp(), mediaServerItem.getRecordAssistPort());;
  287 + return sendGet(mediaServerItem, urlStr, param, null);
167 288 }
168   -
169 289 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
... ... @@ -118,6 +118,9 @@ public class ZLMHttpHookListener {
118 118 private IUserService userService;
119 119  
120 120 @Autowired
  121 + private ICloudRecordService cloudRecordService;
  122 +
  123 + @Autowired
121 124 private VideoStreamSessionManager sessionManager;
122 125  
123 126 @Autowired
... ... @@ -238,12 +241,6 @@ public class ZLMHttpHookListener {
238 241 streamAuthorityInfo.setSign(sign);
239 242 // 鉴权通过
240 243 redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
241   - // 通知assist新的callId
242   - if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) {
243   - taskExecutor.execute(() -> {
244   - assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null);
245   - });
246   - }
247 244 }
248 245 } else {
249 246 zlmMediaListManager.sendStreamEvent(param.getApp(), param.getStream(), param.getMediaServerId());
... ... @@ -269,51 +266,57 @@ public class ZLMHttpHookListener {
269 266 } else {
270 267 result.setEnable_mp4(userSetting.isRecordPushLive());
271 268 }
272   - // 替换流地址
273   - if ("rtp".equals(param.getApp()) && !mediaInfo.isRtpEnable()) {
274   - String ssrc = String.format("%010d", Long.parseLong(param.getStream(), 16));;
275   - InviteInfo inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc);
276   - if (inviteInfo != null) {
277   - result.setStream_replace(inviteInfo.getStream());
278   - logger.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", param.getStream(), inviteInfo.getStream());
279   - }
280   - }
281   - List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream());
282   - if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
283   - String deviceId = ssrcTransactionForAll.get(0).getDeviceId();
284   - String channelId = ssrcTransactionForAll.get(0).getChannelId();
285   - DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
286   - if (deviceChannel != null) {
  269 + // 国标流
  270 + if ("rtp".equals(param.getApp()) ) {
287 271  
288   - result.setEnable_audio(deviceChannel.isHasAudio());
289   - }
290   - // 如果是录像下载就设置视频间隔十秒
291   - if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) {
292   - result.setMp4_max_second(10);
293   - result.setEnable_mp4(true);
294   - }
295   - // 如果是talk对讲,则默认获取声音
296   - if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.TALK) {
297   - result.setEnable_audio(true);
  272 + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
  273 +
  274 + // 单端口模式下修改流 ID
  275 + if (!mediaInfo.isRtpEnable() && inviteInfo == null) {
  276 + String ssrc = String.format("%010d", Long.parseLong(param.getStream(), 16));
  277 + inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc);
  278 + if (inviteInfo != null) {
  279 + result.setStream_replace(inviteInfo.getStream());
  280 + logger.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", param.getStream(), inviteInfo.getStream());
  281 + }
298 282 }
299 283  
300   - }
301   - if (mediaInfo.getRecordAssistPort() > 0 && userSetting.getRecordPath() == null) {
302   - logger.info("推流时发现尚未设置录像路径,从assist服务中读取");
303   - JSONObject info = assistRESTfulUtils.getInfo(mediaInfo, null);
304   - if (info != null && info.getInteger("code") != null && info.getInteger("code") == 0) {
305   - JSONObject dataJson = info.getJSONObject("data");
306   - if (dataJson != null) {
307   - String recordPath = dataJson.getString("record");
308   - userSetting.setRecordPath(recordPath);
309   - result.setMp4_save_path(recordPath);
310   - // 修改zlm中的录像路径
311   - if (mediaInfo.isAutoConfig()) {
312   - taskExecutor.execute(() -> {
313   - mediaServerService.setZLMConfig(mediaInfo, false);
314   - });
  284 + // 设置音频信息及录制信息
  285 + List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream());
  286 + if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
  287 +
  288 + // 为录制国标模拟一个鉴权信息, 方便后续写入录像文件时使用
  289 + StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
  290 + streamAuthorityInfo.setApp(param.getApp());
  291 + streamAuthorityInfo.setStream(ssrcTransactionForAll.get(0).getStream());
  292 + streamAuthorityInfo.setCallId(ssrcTransactionForAll.get(0).getSipTransactionInfo().getCallId());
  293 +
  294 + redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), ssrcTransactionForAll.get(0).getStream(), streamAuthorityInfo);
  295 +
  296 + String deviceId = ssrcTransactionForAll.get(0).getDeviceId();
  297 + String channelId = ssrcTransactionForAll.get(0).getChannelId();
  298 + DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
  299 + if (deviceChannel != null) {
  300 + result.setEnable_audio(deviceChannel.isHasAudio());
  301 + }
  302 + // 如果是录像下载就设置视频间隔十秒
  303 + if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) {
  304 + // 获取录像的总时长,然后设置为这个视频的时长
  305 + InviteInfo inviteInfoForDownload = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, param.getStream());
  306 + if (inviteInfoForDownload != null && inviteInfoForDownload.getStreamInfo() != null) {
  307 + String startTime = inviteInfoForDownload.getStreamInfo().getStartTime();
  308 + String endTime = inviteInfoForDownload.getStreamInfo().getEndTime();
  309 + long difference = DateUtil.getDifference(startTime, endTime) / 1000;
  310 + result.setMp4_max_second((int) difference);
  311 + result.setEnable_mp4(true);
  312 + // 设置为2保证得到的mp4的时长是正常的
  313 + result.setModify_stamp(2);
315 314 }
316 315 }
  316 + // 如果是talk对讲,则默认获取声音
  317 + if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.TALK) {
  318 + result.setEnable_audio(true);
  319 + }
317 320 }
318 321 }
319 322 if (param.getApp().equalsIgnoreCase("rtp")) {
... ... @@ -361,13 +364,11 @@ public class ZLMHttpHookListener {
361 364  
362 365 List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
363 366 // TODO 重构此处逻辑
364   - boolean isPush = false;
365 367 if (param.isRegist()) {
366   - // 处理流注册的鉴权信息
  368 + // 处理流注册的鉴权信息, 流注销这里不再删除鉴权信息,下次来了新的鉴权信息会对就的进行覆盖
367 369 if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
368 370 || param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
369 371 || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
370   - isPush = true;
371 372 StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
372 373 if (streamAuthorityInfo == null) {
373 374 streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
... ... @@ -377,8 +378,6 @@ public class ZLMHttpHookListener {
377 378 }
378 379 redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
379 380 }
380   - } else {
381   - redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream());
382 381 }
383 382  
384 383 if ("rtsp".equals(param.getSchema())) {
... ... @@ -460,35 +459,40 @@ public class ZLMHttpHookListener {
460 459 } else {
461 460 if (!"rtp".equals(param.getApp())) {
462 461 String type = OriginType.values()[param.getOriginType()].getType();
463   - MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
464   -
465   - if (mediaServerItem != null) {
466   - if (param.isRegist()) {
467   - StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
468   - String callId = null;
469   - if (streamAuthorityInfo != null) {
470   - callId = streamAuthorityInfo.getCallId();
471   - }
472   - StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaServerItem,
473   - param.getApp(), param.getStream(), param.getTracks(), callId);
474   - param.setStreamInfo(new StreamContent(streamInfoByAppAndStream));
475   - redisCatchStorage.addStream(mediaServerItem, type, param.getApp(), param.getStream(), param);
476   - if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
477   - || param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
478   - || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
479   - param.setSeverId(userSetting.getServerId());
480   - zlmMediaListManager.addPush(param);
481   - }
482   - } else {
483   - // 兼容流注销时类型从redis记录获取
484   - OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo(
485   - param.getApp(), param.getStream(), param.getMediaServerId());
486   - if (onStreamChangedHookParam != null) {
487   - type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType();
488   - redisCatchStorage.removeStream(mediaServerItem.getId(), type, param.getApp(), param.getStream());
  462 + if (param.isRegist()) {
  463 + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(
  464 + param.getApp(), param.getStream());
  465 + String callId = null;
  466 + if (streamAuthorityInfo != null) {
  467 + callId = streamAuthorityInfo.getCallId();
  468 + }
  469 + StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaInfo,
  470 + param.getApp(), param.getStream(), tracks, callId);
  471 + param.setStreamInfo(new StreamContent(streamInfoByAppAndStream));
  472 + redisCatchStorage.addStream(mediaInfo, type, param.getApp(), param.getStream(), param);
  473 + if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
  474 + || param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
  475 + || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
  476 + param.setSeverId(userSetting.getServerId());
  477 + zlmMediaListManager.addPush(param);
  478 +
  479 + // 冗余数据,自己系统中自用
  480 + redisCatchStorage.addPushListItem(param.getApp(), param.getStream(), param);
  481 + }
  482 + } else {
  483 + // 兼容流注销时类型从redis记录获取
  484 + OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo(
  485 + param.getApp(), param.getStream(), param.getMediaServerId());
  486 + if (onStreamChangedHookParam != null) {
  487 + type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType();
  488 + redisCatchStorage.removeStream(mediaInfo.getId(), type, param.getApp(), param.getStream());
  489 + if ("PUSH".equalsIgnoreCase(type)) {
  490 + // 冗余数据,自己系统中自用
  491 + redisCatchStorage.removePushListItem(param.getApp(), param.getStream(), param.getMediaServerId());
489 492 }
490   - GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream());
491   - if (gbStream != null) {
  493 + }
  494 + GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream());
  495 + if (gbStream != null) {
492 496 // eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF);
493 497 }
494 498 zlmMediaListManager.removeMedia(param.getApp(), param.getStream());
... ... @@ -513,7 +517,7 @@ public class ZLMHttpHookListener {
513 517 }
514 518 if (!param.isRegist()) {
515 519 List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
516   - if (sendRtpItems.size() > 0) {
  520 + if (!sendRtpItems.isEmpty()) {
517 521 for (SendRtpItem sendRtpItem : sendRtpItems) {
518 522 if (sendRtpItem != null && sendRtpItem.getApp().equals(param.getApp())) {
519 523 String platformId = sendRtpItem.getPlatformId();
... ... @@ -608,11 +612,15 @@ public class ZLMHttpHookListener {
608 612 if (info != null) {
609 613 cmder.streamByeCmd(device, inviteInfo.getChannelId(),
610 614 inviteInfo.getStream(), null);
  615 + }else {
  616 + logger.info("[无人观看] 未找到设备的点播信息: {}, 流:{}", inviteInfo.getDeviceId(), param.getStream());
611 617 }
612 618 } catch (InvalidArgumentException | ParseException | SipException |
613 619 SsrcTransactionNotFoundException e) {
614 620 logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage());
615 621 }
  622 + }else {
  623 + logger.info("[无人观看] 未找到设备: {},流:{}", inviteInfo.getDeviceId(), param.getStream());
616 624 }
617 625  
618 626 inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(),
... ... @@ -684,7 +692,7 @@ public class ZLMHttpHookListener {
684 692 String deviceId = s[0];
685 693 String channelId = s[1];
686 694 Device device = redisCatchStorage.getDevice(deviceId);
687   - if (device == null) {
  695 + if (device == null || !device.isOnLine()) {
688 696 defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
689 697 return defaultResult;
690 698 }
... ... @@ -848,11 +856,33 @@ public class ZLMHttpHookListener {
848 856 taskExecutor.execute(() -> {
849 857 JSONObject json = (JSONObject) JSON.toJSON(param);
850 858 List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout);
851   - if (subscribes != null && subscribes.size() > 0) {
  859 + if (subscribes != null && !subscribes.isEmpty()) {
  860 + for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
  861 + subscribe.response(null, param);
  862 + }
  863 + }
  864 + });
  865 +
  866 + return HookResult.SUCCESS();
  867 + }
  868 +
  869 + /**
  870 + * 录像完成事件
  871 + */
  872 + @ResponseBody
  873 + @PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8")
  874 + public HookResult onRecordMp4(HttpServletRequest request, @RequestBody OnRecordMp4HookParam param) {
  875 + logger.info("[ZLM HOOK] 录像完成事件:{}->{}", param.getMediaServerId(), param.getFile_path());
  876 +
  877 + taskExecutor.execute(() -> {
  878 + List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_record_mp4);
  879 + if (subscribes != null && !subscribes.isEmpty()) {
852 880 for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
853 881 subscribe.response(null, param);
854 882 }
855 883 }
  884 + cloudRecordService.addRecord(param);
  885 +
856 886 });
857 887  
858 888 return HookResult.SUCCESS();
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
... ... @@ -25,8 +25,6 @@ public class ZLMRESTfulUtils {
25 25  
26 26 private OkHttpClient client;
27 27  
28   -
29   -
30 28 public interface RequestCallback{
31 29 void run(JSONObject response);
32 30 }
... ... @@ -405,4 +403,14 @@ public class ZLMRESTfulUtils {
405 403 param.put("stream_id", streamId);
406 404 return sendPost(mediaServerItem, "updateRtpServerSSRC",param, null);
407 405 }
  406 +
  407 + public JSONObject deleteRecordDirectory(MediaServerItem mediaServerItem, String app, String stream, String date, String fileName) {
  408 + Map<String, Object> param = new HashMap<>(1);
  409 + param.put("vhost", "__defaultVhost__");
  410 + param.put("app", app);
  411 + param.put("stream", stream);
  412 + param.put("period", date);
  413 + param.put("name", fileName);
  414 + return sendPost(mediaServerItem, "deleteRecordDirectory",param, null);
  415 + }
408 416 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java
... ... @@ -57,4 +57,15 @@ public class HookSubscribeFactory {
57 57 return hookSubscribe;
58 58 }
59 59  
  60 + public static HookSubscribeForRecordMp4 on_record_mp4(String mediaServerId, String app, String stream) {
  61 + HookSubscribeForRecordMp4 hookSubscribe = new HookSubscribeForRecordMp4();
  62 + JSONObject subscribeKey = new com.alibaba.fastjson2.JSONObject();
  63 + subscribeKey.put("app", app);
  64 + subscribeKey.put("stream", stream);
  65 + subscribeKey.put("mediaServerId", mediaServerId);
  66 + hookSubscribe.setContent(subscribeKey);
  67 +
  68 + return hookSubscribe;
  69 + }
  70 +
60 71 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForRecordMp4.java 0 → 100755
  1 +package com.genersoft.iot.vmp.media.zlm.dto;
  2 +
  3 +import com.alibaba.fastjson2.JSONObject;
  4 +import com.alibaba.fastjson2.annotation.JSONField;
  5 +
  6 +import java.time.Instant;
  7 +
  8 +/**
  9 + * hook订阅-录像完成
  10 + * @author lin
  11 + */
  12 +public class HookSubscribeForRecordMp4 implements IHookSubscribe{
  13 +
  14 + private HookType hookType = HookType.on_record_mp4;
  15 +
  16 + private JSONObject content;
  17 +
  18 + @JSONField(format="yyyy-MM-dd HH:mm:ss")
  19 + private Instant expires;
  20 +
  21 + @Override
  22 + public HookType getHookType() {
  23 + return hookType;
  24 + }
  25 +
  26 + @Override
  27 + public JSONObject getContent() {
  28 + return content;
  29 + }
  30 +
  31 + public void setContent(JSONObject content) {
  32 + this.content = content;
  33 + }
  34 +
  35 + @Override
  36 + public Instant getExpires() {
  37 + return expires;
  38 + }
  39 +
  40 + @Override
  41 + public void setExpires(Instant expires) {
  42 + this.expires = expires;
  43 + }
  44 +}
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java
... ... @@ -80,9 +80,11 @@ public class MediaServerItem{
80 80 @Schema(description = "是否是默认ZLM")
81 81 private boolean defaultServer;
82 82  
83   - @Schema(description = "当前使用到的端口")
84   - private int currentPort;
  83 + @Schema(description = "录像存储时长")
  84 + private int recordDay;
85 85  
  86 + @Schema(description = "录像存储路径")
  87 + private String recordPath;
86 88  
87 89 public MediaServerItem() {
88 90 }
... ... @@ -269,14 +271,6 @@ public class MediaServerItem{
269 271 this.updateTime = updateTime;
270 272 }
271 273  
272   - public int getCurrentPort() {
273   - return currentPort;
274   - }
275   -
276   - public void setCurrentPort(int currentPort) {
277   - this.currentPort = currentPort;
278   - }
279   -
280 274 public boolean isStatus() {
281 275 return status;
282 276 }
... ... @@ -308,4 +302,20 @@ public class MediaServerItem{
308 302 public void setSendRtpPortRange(String sendRtpPortRange) {
309 303 this.sendRtpPortRange = sendRtpPortRange;
310 304 }
  305 +
  306 + public int getRecordDay() {
  307 + return recordDay;
  308 + }
  309 +
  310 + public void setRecordDay(int recordDay) {
  311 + this.recordDay = recordDay;
  312 + }
  313 +
  314 + public String getRecordPath() {
  315 + return recordPath;
  316 + }
  317 +
  318 + public void setRecordPath(String recordPath) {
  319 + this.recordPath = recordPath;
  320 + }
311 321 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java
... ... @@ -7,6 +7,7 @@ public class HookResultForOnPublish extends HookResult{
7 7 private int mp4_max_second;
8 8 private String mp4_save_path;
9 9 private String stream_replace;
  10 + private Integer modify_stamp;
10 11  
11 12 public HookResultForOnPublish() {
12 13 }
... ... @@ -60,14 +61,23 @@ public class HookResultForOnPublish extends HookResult{
60 61 this.stream_replace = stream_replace;
61 62 }
62 63  
  64 + public Integer getModify_stamp() {
  65 + return modify_stamp;
  66 + }
  67 +
  68 + public void setModify_stamp(Integer modify_stamp) {
  69 + this.modify_stamp = modify_stamp;
  70 + }
  71 +
63 72 @Override
64 73 public String toString() {
65 74 return "HookResultForOnPublish{" +
66 75 "enable_audio=" + enable_audio +
67 76 ", enable_mp4=" + enable_mp4 +
68 77 ", mp4_max_second=" + mp4_max_second +
69   - ", stream_replace=" + stream_replace +
70 78 ", mp4_save_path='" + mp4_save_path + '\'' +
  79 + ", stream_replace='" + stream_replace + '\'' +
  80 + ", modify_stamp='" + modify_stamp + '\'' +
71 81 '}';
72 82 }
73 83 }
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnRecordMp4HookParam.java 0 → 100755
  1 +package com.genersoft.iot.vmp.media.zlm.dto.hook;
  2 +
  3 +/**
  4 + * zlm hook事件中的on_rtp_server_timeout事件的参数
  5 + * @author lin
  6 + */
  7 +public class OnRecordMp4HookParam extends HookParam{
  8 + private String app;
  9 + private String stream;
  10 + private String file_name;
  11 + private String file_path;
  12 + private long file_size;
  13 + private String folder;
  14 + private String url;
  15 + private String vhost;
  16 + private long start_time;
  17 + private double time_len;
  18 +
  19 + public String getApp() {
  20 + return app;
  21 + }
  22 +
  23 + public void setApp(String app) {
  24 + this.app = app;
  25 + }
  26 +
  27 + public String getStream() {
  28 + return stream;
  29 + }
  30 +
  31 + public void setStream(String stream) {
  32 + this.stream = stream;
  33 + }
  34 +
  35 + public String getFile_name() {
  36 + return file_name;
  37 + }
  38 +
  39 + public void setFile_name(String file_name) {
  40 + this.file_name = file_name;
  41 + }
  42 +
  43 + public String getFile_path() {
  44 + return file_path;
  45 + }
  46 +
  47 + public void setFile_path(String file_path) {
  48 + this.file_path = file_path;
  49 + }
  50 +
  51 + public long getFile_size() {
  52 + return file_size;
  53 + }
  54 +
  55 + public void setFile_size(long file_size) {
  56 + this.file_size = file_size;
  57 + }
  58 +
  59 + public String getFolder() {
  60 + return folder;
  61 + }
  62 +
  63 + public void setFolder(String folder) {
  64 + this.folder = folder;
  65 + }
  66 +
  67 + public String getUrl() {
  68 + return url;
  69 + }
  70 +
  71 + public void setUrl(String url) {
  72 + this.url = url;
  73 + }
  74 +
  75 + public String getVhost() {
  76 + return vhost;
  77 + }
  78 +
  79 + public void setVhost(String vhost) {
  80 + this.vhost = vhost;
  81 + }
  82 +
  83 + public long getStart_time() {
  84 + return start_time;
  85 + }
  86 +
  87 + public void setStart_time(long start_time) {
  88 + this.start_time = start_time;
  89 + }
  90 +
  91 + public double getTime_len() {
  92 + return time_len;
  93 + }
  94 +
  95 + public void setTime_len(double time_len) {
  96 + this.time_len = time_len;
  97 + }
  98 +
  99 + @Override
  100 + public String toString() {
  101 + return "OnRecordMp4HookParam{" +
  102 + "app='" + app + '\'' +
  103 + ", stream='" + stream + '\'' +
  104 + ", file_name='" + file_name + '\'' +
  105 + ", file_path='" + file_path + '\'' +
  106 + ", file_size='" + file_size + '\'' +
  107 + ", folder='" + folder + '\'' +
  108 + ", url='" + url + '\'' +
  109 + ", vhost='" + vhost + '\'' +
  110 + ", start_time=" + start_time +
  111 + ", time_len=" + time_len +
  112 + '}';
  113 + }
  114 +}
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamChangedHookParam.java
... ... @@ -120,17 +120,17 @@ public class OnStreamChangedHookParam extends HookParam{
120 120 /**
121 121 * H264 = 0, H265 = 1, AAC = 2, G711A = 3, G711U = 4
122 122 */
123   - private int codecId;
  123 + private int codec_id;
124 124  
125 125 /**
126 126 * 编码类型名称 CodecAAC CodecH264
127 127 */
128   - private String codecIdName;
  128 + private String codec_id_name;
129 129  
130 130 /**
131 131 * Video = 0, Audio = 1
132 132 */
133   - private int codecType;
  133 + private int codec_type;
134 134  
135 135 /**
136 136 * 轨道是否准备就绪
... ... @@ -140,17 +140,17 @@ public class OnStreamChangedHookParam extends HookParam{
140 140 /**
141 141 * 音频采样位数
142 142 */
143   - private int sampleBit;
  143 + private int sample_bit;
144 144  
145 145 /**
146 146 * 音频采样率
147 147 */
148   - private int sampleRate;
  148 + private int sample_rate;
149 149  
150 150 /**
151 151 * 视频fps
152 152 */
153   - private int fps;
  153 + private float fps;
154 154  
155 155 /**
156 156 * 视频高
... ... @@ -162,6 +162,31 @@ public class OnStreamChangedHookParam extends HookParam{
162 162 */
163 163 private int width;
164 164  
  165 + /**
  166 + * 帧数
  167 + */
  168 + private int frames;
  169 +
  170 + /**
  171 + * 关键帧数
  172 + */
  173 + private int key_frames;
  174 +
  175 + /**
  176 + * GOP大小
  177 + */
  178 + private int gop_size;
  179 +
  180 + /**
  181 + * GOP间隔时长(ms)
  182 + */
  183 + private int gop_interval_ms;
  184 +
  185 + /**
  186 + * 丢帧率
  187 + */
  188 + private float loss;
  189 +
165 190 public int getChannels() {
166 191 return channels;
167 192 }
... ... @@ -170,28 +195,28 @@ public class OnStreamChangedHookParam extends HookParam{
170 195 this.channels = channels;
171 196 }
172 197  
173   - public int getCodecId() {
174   - return codecId;
  198 + public int getCodec_id() {
  199 + return codec_id;
175 200 }
176 201  
177   - public void setCodecId(int codecId) {
178   - this.codecId = codecId;
  202 + public void setCodec_id(int codec_id) {
  203 + this.codec_id = codec_id;
179 204 }
180 205  
181   - public String getCodecIdName() {
182   - return codecIdName;
  206 + public String getCodec_id_name() {
  207 + return codec_id_name;
183 208 }
184 209  
185   - public void setCodecIdName(String codecIdName) {
186   - this.codecIdName = codecIdName;
  210 + public void setCodec_id_name(String codec_id_name) {
  211 + this.codec_id_name = codec_id_name;
187 212 }
188 213  
189   - public int getCodecType() {
190   - return codecType;
  214 + public int getCodec_type() {
  215 + return codec_type;
191 216 }
192 217  
193   - public void setCodecType(int codecType) {
194   - this.codecType = codecType;
  218 + public void setCodec_type(int codec_type) {
  219 + this.codec_type = codec_type;
195 220 }
196 221  
197 222 public boolean isReady() {
... ... @@ -202,27 +227,27 @@ public class OnStreamChangedHookParam extends HookParam{
202 227 this.ready = ready;
203 228 }
204 229  
205   - public int getSampleBit() {
206   - return sampleBit;
  230 + public int getSample_bit() {
  231 + return sample_bit;
207 232 }
208 233  
209   - public void setSampleBit(int sampleBit) {
210   - this.sampleBit = sampleBit;
  234 + public void setSample_bit(int sample_bit) {
  235 + this.sample_bit = sample_bit;
211 236 }
212 237  
213   - public int getSampleRate() {
214   - return sampleRate;
  238 + public int getSample_rate() {
  239 + return sample_rate;
215 240 }
216 241  
217   - public void setSampleRate(int sampleRate) {
218   - this.sampleRate = sampleRate;
  242 + public void setSample_rate(int sample_rate) {
  243 + this.sample_rate = sample_rate;
219 244 }
220 245  
221   - public int getFps() {
  246 + public float getFps() {
222 247 return fps;
223 248 }
224 249  
225   - public void setFps(int fps) {
  250 + public void setFps(float fps) {
226 251 this.fps = fps;
227 252 }
228 253  
... ... @@ -241,6 +266,46 @@ public class OnStreamChangedHookParam extends HookParam{
241 266 public void setWidth(int width) {
242 267 this.width = width;
243 268 }
  269 +
  270 + public int getFrames() {
  271 + return frames;
  272 + }
  273 +
  274 + public void setFrames(int frames) {
  275 + this.frames = frames;
  276 + }
  277 +
  278 + public int getKey_frames() {
  279 + return key_frames;
  280 + }
  281 +
  282 + public void setKey_frames(int key_frames) {
  283 + this.key_frames = key_frames;
  284 + }
  285 +
  286 + public int getGop_size() {
  287 + return gop_size;
  288 + }
  289 +
  290 + public void setGop_size(int gop_size) {
  291 + this.gop_size = gop_size;
  292 + }
  293 +
  294 + public int getGop_interval_ms() {
  295 + return gop_interval_ms;
  296 + }
  297 +
  298 + public void setGop_interval_ms(int gop_interval_ms) {
  299 + this.gop_interval_ms = gop_interval_ms;
  300 + }
  301 +
  302 + public float getLoss() {
  303 + return loss;
  304 + }
  305 +
  306 + public void setLoss(float loss) {
  307 + this.loss = loss;
  308 + }
244 309 }
245 310  
246 311 public static class OriginSock{
... ...
src/main/java/com/genersoft/iot/vmp/service/ICloudRecordService.java 0 → 100755
  1 +package com.genersoft.iot.vmp.service;
  2 +
  3 +import com.alibaba.fastjson2.JSONArray;
  4 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  5 +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
  6 +import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
  7 +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
  8 +import com.github.pagehelper.PageInfo;
  9 +
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * 云端录像管理
  14 + * @author lin
  15 + */
  16 +public interface ICloudRecordService {
  17 +
  18 + /**
  19 + * 分页回去云端录像列表
  20 + */
  21 + PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServerItem> mediaServerItems);
  22 +
  23 + /**
  24 + * 根据hook消息增加一条记录
  25 + */
  26 + void addRecord(OnRecordMp4HookParam param);
  27 +
  28 + /**
  29 + * 获取所有的日期
  30 + */
  31 + List<String> getDateList(String app, String stream, int year, int month, List<MediaServerItem> mediaServerItems);
  32 +
  33 + /**
  34 + * 添加合并任务
  35 + */
  36 + String addTask(String app, String stream, MediaServerItem mediaServerItem, String startTime,
  37 + String endTime, String callId, String remoteHost, boolean filterMediaServer);
  38 +
  39 +
  40 + /**
  41 + * 查询合并任务列表
  42 + */
  43 + JSONArray queryTask(String app, String stream, String callId, String taskId, String mediaServerId, Boolean isEnd, String scheme);
  44 +
  45 + /**
  46 + * 收藏视频,收藏的视频过期不会删除
  47 + */
  48 + int changeCollect(boolean result, String app, String stream, String mediaServerId, String startTime, String endTime, String callId);
  49 +
  50 + /**
  51 + * 添加指定录像收藏
  52 + */
  53 + int changeCollectById(Integer recordId, boolean result);
  54 +
  55 + /**
  56 + * 获取播放地址
  57 + */
  58 + DownloadFileInfo getPlayUrlPath(Integer recordId);
  59 +}
... ...
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
... ... @@ -89,21 +89,12 @@ public interface IMediaServerService {
89 89  
90 90 void updateMediaServerKeepalive(String mediaServerId, ServerKeepaliveData data);
91 91  
92   - boolean checkRtpServer(MediaServerItem mediaServerItem, String rtp, String stream);
93   -
94 92 /**
95 93 * 获取负载信息
96 94 * @return
97 95 */
98 96 MediaServerLoad getLoad(MediaServerItem mediaServerItem);
99 97  
100   - /**
101   - * 按时间查找录像文件
102   - */
103   - List<RecordFile> getRecords(String app, String stream, String startTime, String endTime, List<MediaServerItem> mediaServerItems);
  98 + List<MediaServerItem> getAllWithAssistPort();
104 99  
105   - /**
106   - * 查找存在录像文件的时间
107   - */
108   - List<String> getRecordDates(String app, String stream, int year, int month, List<MediaServerItem> mediaServerItems);
109 100 }
... ...
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
... ... @@ -33,11 +33,6 @@ public interface IPlayService {
33 33  
34 34 MediaServerItem getNewMediaServerItem(Device device);
35 35  
36   - /**
37   - * 获取包含assist服务的节点
38   - */
39   - MediaServerItem getNewMediaServerItemHasAssist(Device device);
40   -
41 36 void playBack(String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback);
42 37 void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback);
43 38 void zlmServerOffline(String mediaServerId);
... ... @@ -72,5 +67,4 @@ public interface IPlayService {
72 67  
73 68 void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback);
74 69  
75   -
76 70 }
... ...
src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java
... ... @@ -114,4 +114,5 @@ public interface IStreamPushService {
114 114 * @return
115 115 */
116 116 ResourceBaseInfo getOverview();
  117 +
117 118 }
... ...
src/main/java/com/genersoft/iot/vmp/service/bean/CloudRecordItem.java 0 → 100644
  1 +package com.genersoft.iot.vmp.service.bean;
  2 +
  3 +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
  4 +
  5 +/**
  6 + * 云端录像数据
  7 + */
  8 +public class CloudRecordItem {
  9 + /**
  10 + * 主键
  11 + */
  12 + private int id;
  13 +
  14 + /**
  15 + * 应用名
  16 + */
  17 + private String app;
  18 +
  19 + /**
  20 + * 流
  21 + */
  22 + private String stream;
  23 +
  24 + /**
  25 + * 健全ID
  26 + */
  27 + private String callId;
  28 +
  29 + /**
  30 + * 开始时间
  31 + */
  32 + private long startTime;
  33 +
  34 + /**
  35 + * 结束时间
  36 + */
  37 + private long endTime;
  38 +
  39 + /**
  40 + * ZLM Id
  41 + */
  42 + private String mediaServerId;
  43 +
  44 + /**
  45 + * 文件名称
  46 + */
  47 + private String fileName;
  48 +
  49 + /**
  50 + * 文件路径
  51 + */
  52 + private String filePath;
  53 +
  54 + /**
  55 + * 文件夹
  56 + */
  57 + private String folder;
  58 +
  59 + /**
  60 + * 收藏,收藏的文件不移除
  61 + */
  62 + private Boolean collect;
  63 +
  64 + /**
  65 + * 保留,收藏的文件不移除
  66 + */
  67 + private Boolean reserve;
  68 +
  69 + /**
  70 + * 文件大小
  71 + */
  72 + private long fileSize;
  73 +
  74 + /**
  75 + * 文件时长
  76 + */
  77 + private long timeLen;
  78 +
  79 + public static CloudRecordItem getInstance(OnRecordMp4HookParam param) {
  80 + CloudRecordItem cloudRecordItem = new CloudRecordItem();
  81 + cloudRecordItem.setApp(param.getApp());
  82 + cloudRecordItem.setStream(param.getStream());
  83 + cloudRecordItem.setStartTime(param.getStart_time()*1000);
  84 + cloudRecordItem.setFileName(param.getFile_name());
  85 + cloudRecordItem.setFolder(param.getFolder());
  86 + cloudRecordItem.setFileSize(param.getFile_size());
  87 + cloudRecordItem.setFilePath(param.getFile_path());
  88 + cloudRecordItem.setMediaServerId(param.getMediaServerId());
  89 + cloudRecordItem.setTimeLen((long) param.getTime_len() * 1000);
  90 + cloudRecordItem.setEndTime((param.getStart_time() + (long)param.getTime_len()) * 1000);
  91 + return cloudRecordItem;
  92 + }
  93 +
  94 + public int getId() {
  95 + return id;
  96 + }
  97 +
  98 + public void setId(int id) {
  99 + this.id = id;
  100 + }
  101 +
  102 + public String getApp() {
  103 + return app;
  104 + }
  105 +
  106 + public void setApp(String app) {
  107 + this.app = app;
  108 + }
  109 +
  110 + public String getStream() {
  111 + return stream;
  112 + }
  113 +
  114 + public void setStream(String stream) {
  115 + this.stream = stream;
  116 + }
  117 +
  118 + public String getCallId() {
  119 + return callId;
  120 + }
  121 +
  122 + public void setCallId(String callId) {
  123 + this.callId = callId;
  124 + }
  125 +
  126 + public long getStartTime() {
  127 + return startTime;
  128 + }
  129 +
  130 + public void setStartTime(long startTime) {
  131 + this.startTime = startTime;
  132 + }
  133 +
  134 + public long getEndTime() {
  135 + return endTime;
  136 + }
  137 +
  138 + public void setEndTime(long endTime) {
  139 + this.endTime = endTime;
  140 + }
  141 +
  142 + public String getMediaServerId() {
  143 + return mediaServerId;
  144 + }
  145 +
  146 + public void setMediaServerId(String mediaServerId) {
  147 + this.mediaServerId = mediaServerId;
  148 + }
  149 +
  150 + public String getFileName() {
  151 + return fileName;
  152 + }
  153 +
  154 + public void setFileName(String fileName) {
  155 + this.fileName = fileName;
  156 + }
  157 +
  158 + public String getFilePath() {
  159 + return filePath;
  160 + }
  161 +
  162 + public void setFilePath(String filePath) {
  163 + this.filePath = filePath;
  164 + }
  165 +
  166 + public String getFolder() {
  167 + return folder;
  168 + }
  169 +
  170 + public void setFolder(String folder) {
  171 + this.folder = folder;
  172 + }
  173 +
  174 + public long getFileSize() {
  175 + return fileSize;
  176 + }
  177 +
  178 + public void setFileSize(long fileSize) {
  179 + this.fileSize = fileSize;
  180 + }
  181 +
  182 + public long getTimeLen() {
  183 + return timeLen;
  184 + }
  185 +
  186 + public void setTimeLen(long timeLen) {
  187 + this.timeLen = timeLen;
  188 + }
  189 +
  190 + public Boolean getCollect() {
  191 + return collect;
  192 + }
  193 +
  194 + public void setCollect(Boolean collect) {
  195 + this.collect = collect;
  196 + }
  197 +
  198 + public Boolean getReserve() {
  199 + return reserve;
  200 + }
  201 +
  202 + public void setReserve(Boolean reserve) {
  203 + this.reserve = reserve;
  204 + }
  205 +}
... ...
src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java 0 → 100644
  1 +package com.genersoft.iot.vmp.service.bean;
  2 +
  3 +public class DownloadFileInfo {
  4 +
  5 + private String httpPath;
  6 + private String httpsPath;
  7 + private String httpDomainPath;
  8 + private String httpsDomainPath;
  9 +
  10 + public String getHttpPath() {
  11 + return httpPath;
  12 + }
  13 +
  14 + public void setHttpPath(String httpPath) {
  15 + this.httpPath = httpPath;
  16 + }
  17 +
  18 + public String getHttpsPath() {
  19 + return httpsPath;
  20 + }
  21 +
  22 + public void setHttpsPath(String httpsPath) {
  23 + this.httpsPath = httpsPath;
  24 + }
  25 +
  26 + public String getHttpDomainPath() {
  27 + return httpDomainPath;
  28 + }
  29 +
  30 + public void setHttpDomainPath(String httpDomainPath) {
  31 + this.httpDomainPath = httpDomainPath;
  32 + }
  33 +
  34 + public String getHttpsDomainPath() {
  35 + return httpsDomainPath;
  36 + }
  37 +
  38 + public void setHttpsDomainPath(String httpsDomainPath) {
  39 + this.httpsDomainPath = httpsDomainPath;
  40 + }
  41 +}
... ...
src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java
... ... @@ -29,12 +29,12 @@ public class WvpRedisMsg {
29 29 * 消息的ID
30 30 */
31 31 private String serial;
32   - private Object content;
  32 + private String content;
33 33  
34 34 private final static String requestTag = "req";
35 35 private final static String responseTag = "res";
36 36  
37   - public static WvpRedisMsg getRequestInstance(String fromId, String toId, String cmd, String serial, Object content) {
  37 + public static WvpRedisMsg getRequestInstance(String fromId, String toId, String cmd, String serial, String content) {
38 38 WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
39 39 wvpRedisMsg.setType(requestTag);
40 40 wvpRedisMsg.setFromId(fromId);
... ... @@ -51,7 +51,7 @@ public class WvpRedisMsg {
51 51 return wvpRedisMsg;
52 52 }
53 53  
54   - public static WvpRedisMsg getResponseInstance(String fromId, String toId, String cmd, String serial, Object content) {
  54 + public static WvpRedisMsg getResponseInstance(String fromId, String toId, String cmd, String serial, String content) {
55 55 WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
56 56 wvpRedisMsg.setType(responseTag);
57 57 wvpRedisMsg.setFromId(fromId);
... ... @@ -106,11 +106,11 @@ public class WvpRedisMsg {
106 106 this.cmd = cmd;
107 107 }
108 108  
109   - public Object getContent() {
  109 + public String getContent() {
110 110 return content;
111 111 }
112 112  
113   - public void setContent(Object content) {
  113 + public void setContent(String content) {
114 114 this.content = content;
115 115 }
116 116 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/CloudRecordServiceImpl.java 0 → 100644
  1 +package com.genersoft.iot.vmp.service.impl;
  2 +
  3 +import com.alibaba.fastjson2.JSONArray;
  4 +import com.alibaba.fastjson2.JSONObject;
  5 +import com.baomidou.dynamic.datasource.annotation.DS;
  6 +import com.genersoft.iot.vmp.conf.exception.ControllerException;
  7 +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
  8 +import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
  9 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  10 +import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
  11 +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
  12 +import com.genersoft.iot.vmp.service.ICloudRecordService;
  13 +import com.genersoft.iot.vmp.service.IMediaServerService;
  14 +import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
  15 +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
  16 +import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  17 +import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
  18 +import com.genersoft.iot.vmp.utils.CloudRecordUtils;
  19 +import com.genersoft.iot.vmp.utils.DateUtil;
  20 +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
  21 +import com.github.pagehelper.PageHelper;
  22 +import com.github.pagehelper.PageInfo;
  23 +import org.apache.commons.lang3.ObjectUtils;
  24 +import org.slf4j.Logger;
  25 +import org.slf4j.LoggerFactory;
  26 +import org.springframework.beans.factory.annotation.Autowired;
  27 +import org.springframework.stereotype.Service;
  28 +
  29 +import java.time.*;
  30 +import java.util.*;
  31 +
  32 +@Service
  33 +@DS("share")
  34 +public class CloudRecordServiceImpl implements ICloudRecordService {
  35 +
  36 + private final static Logger logger = LoggerFactory.getLogger(CloudRecordServiceImpl.class);
  37 +
  38 + @Autowired
  39 + private CloudRecordServiceMapper cloudRecordServiceMapper;
  40 +
  41 + @Autowired
  42 + private IMediaServerService mediaServerService;
  43 +
  44 + @Autowired
  45 + private IRedisCatchStorage redisCatchStorage;
  46 +
  47 + @Autowired
  48 + private AssistRESTfulUtils assistRESTfulUtils;
  49 +
  50 + @Autowired
  51 + private VideoStreamSessionManager streamSession;
  52 +
  53 + @Override
  54 + public PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServerItem> mediaServerItems) {
  55 + // 开始时间和结束时间在数据库中都是以秒为单位的
  56 + Long startTimeStamp = null;
  57 + Long endTimeStamp = null;
  58 + if (startTime != null ) {
  59 + if (!DateUtil.verification(startTime, DateUtil.formatter)) {
  60 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "开始时间格式错误,正确格式为: " + DateUtil.formatter);
  61 + }
  62 + startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
  63 +
  64 + }
  65 + if (endTime != null ) {
  66 + if (!DateUtil.verification(endTime, DateUtil.formatter)) {
  67 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "结束时间格式错误,正确格式为: " + DateUtil.formatter);
  68 + }
  69 + endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
  70 +
  71 + }
  72 + PageHelper.startPage(page, count);
  73 + List<CloudRecordItem> all = cloudRecordServiceMapper.getList(query, app, stream, startTimeStamp, endTimeStamp,
  74 + null, mediaServerItems);
  75 + return new PageInfo<>(all);
  76 + }
  77 +
  78 + @Override
  79 + public List<String> getDateList(String app, String stream, int year, int month, List<MediaServerItem> mediaServerItems) {
  80 + LocalDate startDate = LocalDate.of(year, month, 1);
  81 + LocalDate endDate;
  82 + if (month == 12) {
  83 + endDate = LocalDate.of(year + 1, 1, 1);
  84 + }else {
  85 + endDate = LocalDate.of(year, month + 1, 1);
  86 + }
  87 + long startTimeStamp = startDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).getEpochSecond();
  88 + long endTimeStamp = endDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).getEpochSecond();
  89 + List<CloudRecordItem> cloudRecordItemList = cloudRecordServiceMapper.getList(null, app, stream, startTimeStamp,
  90 + endTimeStamp, null, mediaServerItems);
  91 + if (cloudRecordItemList.isEmpty()) {
  92 + return new ArrayList<>();
  93 + }
  94 + Set<String> resultSet = new HashSet<>();
  95 + cloudRecordItemList.stream().forEach(cloudRecordItem -> {
  96 + String date = DateUtil.timestampTo_yyyy_MM_dd(cloudRecordItem.getStartTime());
  97 + resultSet.add(date);
  98 + });
  99 + return new ArrayList<>(resultSet);
  100 + }
  101 +
  102 + @Override
  103 + public void addRecord(OnRecordMp4HookParam param) {
  104 + CloudRecordItem cloudRecordItem = CloudRecordItem.getInstance(param);
  105 + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
  106 + if (streamAuthorityInfo != null) {
  107 + cloudRecordItem.setCallId(streamAuthorityInfo.getCallId());
  108 + }
  109 + logger.info("[添加录像记录] {}/{} 文件大小:{}, 时长: {}秒", param.getApp(), param.getStream(), param.getFile_size(),param.getTime_len());
  110 + cloudRecordServiceMapper.add(cloudRecordItem);
  111 + }
  112 +
  113 + @Override
  114 + public String addTask(String app, String stream, MediaServerItem mediaServerItem, String startTime, String endTime,
  115 + String callId, String remoteHost, boolean filterMediaServer) {
  116 + // 参数校验
  117 + assert app != null;
  118 + assert stream != null;
  119 + if (mediaServerItem.getRecordAssistPort() == 0) {
  120 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "为配置Assist服务");
  121 + }
  122 + Long startTimeStamp = null;
  123 + Long endTimeStamp = null;
  124 + if (startTime != null) {
  125 + startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
  126 + }
  127 + if (endTime != null) {
  128 + endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
  129 + }
  130 +
  131 + List<MediaServerItem> mediaServers = new ArrayList<>();
  132 + mediaServers.add(mediaServerItem);
  133 + // 检索相关的录像文件
  134 + List<String> filePathList = cloudRecordServiceMapper.queryRecordFilePathList(app, stream, startTimeStamp,
  135 + endTimeStamp, callId, filterMediaServer ? mediaServers : null);
  136 + if (filePathList == null || filePathList.isEmpty()) {
  137 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未检索到视频文件");
  138 + }
  139 + JSONObject result = assistRESTfulUtils.addTask(mediaServerItem, app, stream, startTime, endTime, callId, filePathList, remoteHost);
  140 + if (result.getInteger("code") != 0) {
  141 + throw new ControllerException(result.getInteger("code"), result.getString("msg"));
  142 + }
  143 + return result.getString("data");
  144 + }
  145 +
  146 + @Override
  147 + public JSONArray queryTask(String app, String stream, String callId, String taskId, String mediaServerId,
  148 + Boolean isEnd, String scheme) {
  149 + MediaServerItem mediaServerItem = null;
  150 + if (mediaServerId == null) {
  151 + mediaServerItem = mediaServerService.getDefaultMediaServer();
  152 + }else {
  153 + mediaServerItem = mediaServerService.getOne(mediaServerId);
  154 + }
  155 + if (mediaServerItem == null) {
  156 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的流媒体");
  157 + }
  158 +
  159 + JSONObject result = assistRESTfulUtils.queryTaskList(mediaServerItem, app, stream, callId, taskId, isEnd, scheme);
  160 + if (result == null || result.getInteger("code") != 0) {
  161 + throw new ControllerException(ErrorCode.ERROR100.getCode(), result == null ? "查询任务列表失败" : result.getString("msg"));
  162 + }
  163 + return result.getJSONArray("data");
  164 + }
  165 +
  166 + @Override
  167 + public int changeCollect(boolean result, String app, String stream, String mediaServerId, String startTime, String endTime, String callId) {
  168 + // 开始时间和结束时间在数据库中都是以秒为单位的
  169 + Long startTimeStamp = null;
  170 + Long endTimeStamp = null;
  171 + if (startTime != null ) {
  172 + if (!DateUtil.verification(startTime, DateUtil.formatter)) {
  173 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "开始时间格式错误,正确格式为: " + DateUtil.formatter);
  174 + }
  175 + startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
  176 +
  177 + }
  178 + if (endTime != null ) {
  179 + if (!DateUtil.verification(endTime, DateUtil.formatter)) {
  180 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "结束时间格式错误,正确格式为: " + DateUtil.formatter);
  181 + }
  182 + endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
  183 +
  184 + }
  185 +
  186 + List<MediaServerItem> mediaServerItems;
  187 + if (!ObjectUtils.isEmpty(mediaServerId)) {
  188 + mediaServerItems = new ArrayList<>();
  189 + MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
  190 + if (mediaServerItem == null) {
  191 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId);
  192 + }
  193 + mediaServerItems.add(mediaServerItem);
  194 + } else {
  195 + mediaServerItems = null;
  196 + }
  197 +
  198 + List<CloudRecordItem> all = cloudRecordServiceMapper.getList(null, app, stream, startTimeStamp, endTimeStamp,
  199 + callId, mediaServerItems);
  200 + if (all.isEmpty()) {
  201 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到待收藏的视频");
  202 + }
  203 + int limitCount = 50;
  204 + int resultCount = 0;
  205 + if (all.size() > limitCount) {
  206 + for (int i = 0; i < all.size(); i += limitCount) {
  207 + int toIndex = i + limitCount;
  208 + if (i + limitCount > all.size()) {
  209 + toIndex = all.size();
  210 + }
  211 + resultCount += cloudRecordServiceMapper.updateCollectList(result, all.subList(i, toIndex));
  212 +
  213 + }
  214 + }else {
  215 + resultCount = cloudRecordServiceMapper.updateCollectList(result, all);
  216 + }
  217 + return resultCount;
  218 + }
  219 +
  220 + @Override
  221 + public int changeCollectById(Integer recordId, boolean result) {
  222 + return cloudRecordServiceMapper.changeCollectById(result, recordId);
  223 + }
  224 +
  225 + @Override
  226 + public DownloadFileInfo getPlayUrlPath(Integer recordId) {
  227 + CloudRecordItem recordItem = cloudRecordServiceMapper.queryOne(recordId);
  228 + if (recordItem == null) {
  229 + throw new ControllerException(ErrorCode.ERROR400.getCode(), "资源不存在");
  230 + }
  231 + String filePath = recordItem.getFilePath();
  232 + MediaServerItem mediaServerItem = mediaServerService.getOne(recordItem.getMediaServerId());
  233 + return CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath);
  234 + }
  235 +}
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.baomidou.dynamic.datasource.annotation.DS;
3 4 import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
4 5 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
5 6 import com.genersoft.iot.vmp.service.IDeviceAlarmService;
... ... @@ -12,6 +13,7 @@ import org.springframework.stereotype.Service;
12 13 import java.util.List;
13 14  
14 15 @Service
  16 +@DS("master")
15 17 public class DeviceAlarmServiceImpl implements IDeviceAlarmService {
16 18  
17 19 @Autowired
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceChannelServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.baomidou.dynamic.datasource.annotation.DS;
3 4 import com.genersoft.iot.vmp.common.InviteInfo;
4 5 import com.genersoft.iot.vmp.common.InviteSessionType;
5 6 import com.genersoft.iot.vmp.gb28181.bean.Device;
... ... @@ -27,6 +28,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
27 28 * @author lin
28 29 */
29 30 @Service
  31 +@DS("master")
30 32 public class DeviceChannelServiceImpl implements IDeviceChannelService {
31 33  
32 34 private final static Logger logger = LoggerFactory.getLogger(DeviceChannelServiceImpl.class);
... ... @@ -243,6 +245,10 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
243 245  
244 246 @Override
245 247 public void batchUpdateChannel(List<DeviceChannel> channels) {
  248 + String now = DateUtil.getNow();
  249 + for (DeviceChannel channel : channels) {
  250 + channel.setUpdateTime(now);
  251 + }
246 252 channelMapper.batchUpdate(channels);
247 253 for (DeviceChannel channel : channels) {
248 254 if (channel.getParentId() != null) {
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.baomidou.dynamic.datasource.annotation.DS;
3 4 import com.genersoft.iot.vmp.common.VideoManagerConstants;
4 5 import com.genersoft.iot.vmp.conf.DynamicTask;
5 6 import com.genersoft.iot.vmp.conf.UserSetting;
... ... @@ -46,6 +47,7 @@ import java.util.concurrent.TimeUnit;
46 47 * 设备业务(目录订阅)
47 48 */
48 49 @Service
  50 +@DS("master")
49 51 public class DeviceServiceImpl implements IDeviceService {
50 52  
51 53 private final static Logger logger = LoggerFactory.getLogger(DeviceServiceImpl.class);
... ... @@ -162,6 +164,19 @@ public class DeviceServiceImpl implements IDeviceService {
162 164 sync(device);
163 165 // TODO 如果设备下的通道级联到了其他平台,那么需要发送事件或者notify给上级平台
164 166 }
  167 + // 上线添加订阅
  168 + if (device.getSubscribeCycleForCatalog() > 0) {
  169 + // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
  170 + addCatalogSubscribe(device);
  171 + }
  172 + if (device.getSubscribeCycleForMobilePosition() > 0) {
  173 + addMobilePositionSubscribe(device);
  174 + }
  175 + if (userSetting.getDeviceStatusNotify()) {
  176 + // 发送redis消息
  177 + redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, true);
  178 + }
  179 +
165 180 }else {
166 181 if (deviceChannelMapper.queryAllChannels(device.getDeviceId()).size() == 0) {
167 182 logger.info("[设备上线]: {},通道数为0,查询通道信息", device.getDeviceId());
... ... @@ -174,22 +189,10 @@ public class DeviceServiceImpl implements IDeviceService {
174 189  
175 190 }
176 191  
177   - // 上线添加订阅
178   - if (device.getSubscribeCycleForCatalog() > 0) {
179   - // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
180   - addCatalogSubscribe(device);
181   - }
182   - if (device.getSubscribeCycleForMobilePosition() > 0) {
183   - addMobilePositionSubscribe(device);
184   - }
185 192 // 刷新过期任务
186 193 String registerExpireTaskKey = VideoManagerConstants.REGISTER_EXPIRE_TASK_KEY_PREFIX + device.getDeviceId();
187 194 // 如果第一次注册那么必须在60 * 3时间内收到一个心跳,否则设备离线
188 195 dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId(), "首次注册后未能收到心跳"), device.getKeepaliveIntervalTime() * 1000 * 3);
189   - if (userSetting.getDeviceStatusNotify()) {
190   - // 发送redis消息
191   - redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, true);
192   - }
193 196  
194 197 //
195 198 // try {
... ... @@ -213,6 +216,13 @@ public class DeviceServiceImpl implements IDeviceService {
213 216 }
214 217 String registerExpireTaskKey = VideoManagerConstants.REGISTER_EXPIRE_TASK_KEY_PREFIX + deviceId;
215 218 dynamicTask.stop(registerExpireTaskKey);
  219 + if (device.isOnLine()) {
  220 + if (userSetting.getDeviceStatusNotify()) {
  221 + // 发送redis消息
  222 + redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, false);
  223 + }
  224 + }
  225 +
216 226 device.setOnLine(false);
217 227 redisCatchStorage.updateDevice(device);
218 228 deviceMapper.update(device);
... ... @@ -224,7 +234,7 @@ public class DeviceServiceImpl implements IDeviceService {
224 234 for (SsrcTransaction ssrcTransaction : ssrcTransactions) {
225 235 mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
226 236 mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
227   - streamSession.remove(deviceId, ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
  237 + streamSession.removeByCallId(deviceId, ssrcTransaction.getChannelId(), ssrcTransaction.getCallId());
228 238 }
229 239 }
230 240 // 移除订阅
... ... @@ -299,7 +309,7 @@ public class DeviceServiceImpl implements IDeviceService {
299 309 // 设置最小值为30
300 310 int subscribeCycleForCatalog = Math.max(device.getSubscribeCycleForMobilePosition(),30);
301 311 // 刷新订阅
302   - dynamicTask.startCron(device.getDeviceId() + "mobile_position" , mobilePositionSubscribeTask, (subscribeCycleForCatalog) * 1000);
  312 + dynamicTask.startCron(device.getDeviceId() + "mobile_position" , mobilePositionSubscribeTask, subscribeCycleForCatalog * 1000);
303 313 return true;
304 314 }
305 315  
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.baomidou.dynamic.datasource.annotation.DS;
3 4 import com.genersoft.iot.vmp.gb28181.bean.*;
4 5 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
5 6 import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
... ... @@ -25,6 +26,7 @@ import java.util.ArrayList;
25 26 import java.util.List;
26 27  
27 28 @Service
  29 +@DS("master")
28 30 public class GbStreamServiceImpl implements IGbStreamService {
29 31  
30 32 private final static Logger logger = LoggerFactory.getLogger(GbStreamServiceImpl.class);
... ... @@ -77,8 +79,6 @@ public class GbStreamServiceImpl implements IGbStreamService {
77 79 }
78 80 try {
79 81 List<DeviceChannel> deviceChannelList = new ArrayList<>();
80   -
81   -
82 82 for (int i = 0; i < gbStreams.size(); i++) {
83 83 GbStream gbStream = gbStreams.get(i);
84 84 gbStream.setCatalogId(catalogId);
... ... @@ -251,18 +251,17 @@ public class GbStreamServiceImpl implements IGbStreamService {
251 251 return ;
252 252 }
253 253 if (ObjectUtils.isEmpty(catalogId)) {
254   - catalogId = platform.getDeviceGBId();
  254 + catalogId = null;
255 255 }
256   - if (platformGbStreamMapper.delByPlatformAndCatalogId(platformId, catalogId) > 0) {
257   - List<GbStream> gbStreams = platformGbStreamMapper.queryChannelInParentPlatformAndCatalog(platformId, catalogId);
258   - List<DeviceChannel> deviceChannelList = new ArrayList<>();
259   - for (GbStream gbStream : gbStreams) {
260   - DeviceChannel deviceChannel = new DeviceChannel();
261   - deviceChannel.setChannelId(gbStream.getGbId());
262   - deviceChannelList.add(deviceChannel);
263   - }
264   - eventPublisher.catalogEventPublish(platformId, deviceChannelList, CatalogEvent.DEL);
  256 + List<GbStream> gbStreams = platformGbStreamMapper.queryChannelInParentPlatformAndCatalog(platformId, catalogId);
  257 + List<DeviceChannel> deviceChannelList = new ArrayList<>();
  258 + for (GbStream gbStream : gbStreams) {
  259 + DeviceChannel deviceChannel = new DeviceChannel();
  260 + deviceChannel.setChannelId(gbStream.getGbId());
  261 + deviceChannelList.add(deviceChannel);
265 262 }
  263 + eventPublisher.catalogEventPublish(platformId, deviceChannelList, CatalogEvent.DEL);
  264 + platformGbStreamMapper.delByPlatformAndCatalogId(platformId, catalogId);
266 265 }
267 266  
268 267 @Override
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
3 3 import com.alibaba.fastjson2.JSON;
  4 +import com.baomidou.dynamic.datasource.annotation.DS;
4 5 import com.genersoft.iot.vmp.common.InviteInfo;
5 6 import com.genersoft.iot.vmp.common.InviteSessionStatus;
6 7 import com.genersoft.iot.vmp.common.InviteSessionType;
... ... @@ -20,6 +21,7 @@ import java.util.concurrent.ConcurrentHashMap;
20 21 import java.util.concurrent.CopyOnWriteArrayList;
21 22  
22 23 @Service
  24 +@DS("master")
23 25 public class InviteStreamServiceImpl implements IInviteStreamService {
24 26  
25 27 private final Logger logger = LoggerFactory.getLogger(InviteStreamServiceImpl.class);
... ... @@ -116,9 +118,12 @@ public class InviteStreamServiceImpl implements IInviteStreamService {
116 118 ":" + (stream != null ? stream : "*")
117 119 + ":*";
118 120 List<Object> scanResult = RedisUtil.scan(redisTemplate, key);
119   - if (scanResult.size() != 1) {
  121 + if (scanResult.isEmpty()) {
120 122 return null;
121 123 }
  124 + if (scanResult.size() != 1) {
  125 + logger.warn("[获取InviteInfo] 发现 key: {}存在多条", key);
  126 + }
122 127  
123 128 return (InviteInfo) redisTemplate.opsForValue().get(scanResult.get(0));
124 129 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/LogServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.baomidou.dynamic.datasource.annotation.DS;
3 4 import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
4 5 import com.genersoft.iot.vmp.service.ILogService;
5 6 import com.genersoft.iot.vmp.storager.dao.LogMapper;
... ... @@ -12,6 +13,7 @@ import org.springframework.stereotype.Service;
12 13 import java.util.List;
13 14  
14 15 @Service
  16 +@DS("master")
15 17 public class LogServiceImpl implements ILogService {
16 18  
17 19 @Autowired
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
... ... @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.service.impl;
3 3 import com.alibaba.fastjson2.JSON;
4 4 import com.alibaba.fastjson2.JSONArray;
5 5 import com.alibaba.fastjson2.JSONObject;
  6 +import com.baomidou.dynamic.datasource.annotation.DS;
6 7 import com.genersoft.iot.vmp.common.CommonCallback;
7 8 import com.genersoft.iot.vmp.common.VideoManagerConstants;
8 9 import com.genersoft.iot.vmp.conf.DynamicTask;
... ... @@ -53,6 +54,7 @@ import java.util.concurrent.ExecutionException;
53 54 * 媒体服务器节点管理
54 55 */
55 56 @Service
  57 +@DS("master")
56 58 public class MediaServerServiceImpl implements IMediaServerService {
57 59  
58 60 private final static Logger logger = LoggerFactory.getLogger(MediaServerServiceImpl.class);
... ... @@ -165,14 +167,13 @@ public class MediaServerServiceImpl implements IMediaServerService {
165 167 if (streamId == null) {
166 168 streamId = String.format("%08x", Long.parseLong(ssrc)).toUpperCase();
167 169 }
168   - int ssrcCheckParam = 0;
169   - if (ssrcCheck && tcpMode > 1) {
  170 + if (ssrcCheck && tcpMode > 0) {
170 171 // 目前zlm不支持 tcp模式更新ssrc,暂时关闭ssrc校验
171   - logger.warn("[openRTPServer] TCP被动/TCP主动收流时,默认关闭ssrc检验");
  172 + logger.warn("[openRTPServer] 平台对接时下级可能自定义ssrc,但是tcp模式zlm收流目前无法更新ssrc,可能收流超时,此时请使用udp收流或者关闭ssrc校验");
172 173 }
173 174 int rtpServerPort;
174 175 if (mediaServerItem.isRtpEnable()) {
175   - rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, (ssrcCheck && tcpMode == 0) ? Long.parseLong(ssrc) : 0, port, onlyAuto, reUsePort, tcpMode);
  176 + rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck ? Long.parseLong(ssrc) : 0, port, onlyAuto, reUsePort, tcpMode);
176 177 } else {
177 178 rtpServerPort = mediaServerItem.getRtpProxyPort();
178 179 }
... ... @@ -205,7 +206,10 @@ public class MediaServerServiceImpl implements IMediaServerService {
205 206 @Override
206 207 public void closeRTPServer(String mediaServerId, String streamId) {
207 208 MediaServerItem mediaServerItem = this.getOne(mediaServerId);
208   - closeRTPServer(mediaServerItem, streamId);
  209 + if (mediaServerItem.isRtpEnable()) {
  210 + closeRTPServer(mediaServerItem, streamId);
  211 + }
  212 + zlmresTfulUtils.closeStreams(mediaServerItem, "rtp", streamId);
209 213 }
210 214  
211 215 @Override
... ... @@ -313,7 +317,6 @@ public class MediaServerServiceImpl implements IMediaServerService {
313 317  
314 318 @Override
315 319 public MediaServerItem getDefaultMediaServer() {
316   -
317 320 return mediaServerMapper.queryDefault();
318 321 }
319 322  
... ... @@ -428,17 +431,6 @@ public class MediaServerServiceImpl implements IMediaServerService {
428 431  
429 432  
430 433 if (serverItem.isAutoConfig()) {
431   - // 查看assist服务的录像路径配置
432   - if (serverItem.getRecordAssistPort() > 0 && userSetting.getRecordPath() == null) {
433   - JSONObject info = assistRESTfulUtils.getInfo(serverItem, null);
434   - if (info != null && info.getInteger("code") != null && info.getInteger("code") == 0 ) {
435   - JSONObject dataJson = info.getJSONObject("data");
436   - if (dataJson != null) {
437   - String recordPath = dataJson.getString("record");
438   - userSetting.setRecordPath(recordPath);
439   - }
440   - }
441   - }
442 434 setZLMConfig(serverItem, "0".equals(zlmServerConfig.getHookEnable()));
443 435 }
444 436 final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + serverItem.getId();
... ... @@ -573,34 +565,30 @@ public class MediaServerServiceImpl implements IMediaServerService {
573 565 logger.info("[ZLM] 正在设置 :{} -> {}:{}",
574 566 mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
575 567 String protocol = sslEnabled ? "https" : "http";
576   - String hookPrex = String.format("%s://%s:%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort);
  568 + String hookPrefix = String.format("%s://%s:%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort);
577 569  
578 570 Map<String, Object> param = new HashMap<>();
579 571 param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline
580 572 if (mediaServerItem.getRtspPort() != 0) {
581   - param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -t 0.001 %s");
  573 + param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -frames:v 1 %s");
582 574 }
583 575 param.put("hook.enable","1");
584 576 param.put("hook.on_flow_report","");
585   - param.put("hook.on_play",String.format("%s/on_play", hookPrex));
  577 + param.put("hook.on_play",String.format("%s/on_play", hookPrefix));
586 578 param.put("hook.on_http_access","");
587   - param.put("hook.on_publish", String.format("%s/on_publish", hookPrex));
  579 + param.put("hook.on_publish", String.format("%s/on_publish", hookPrefix));
588 580 param.put("hook.on_record_ts","");
589 581 param.put("hook.on_rtsp_auth","");
590 582 param.put("hook.on_rtsp_realm","");
591   - param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex));
  583 + param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrefix));
592 584 param.put("hook.on_shell_login","");
593   - param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex));
594   - param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex));
595   - param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex));
596   - param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrex));
597   - param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrex));
598   - param.put("hook.on_rtp_server_timeout",String.format("%s/on_rtp_server_timeout", hookPrex));
599   - if (mediaServerItem.getRecordAssistPort() > 0) {
600   - param.put("hook.on_record_mp4",String.format("http://127.0.0.1:%s/api/record/on_record_mp4", mediaServerItem.getRecordAssistPort()));
601   - }else {
602   - param.put("hook.on_record_mp4","");
603   - }
  585 + param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrefix));
  586 + param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrefix));
  587 + param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrefix));
  588 + param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrefix));
  589 + param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrefix));
  590 + param.put("hook.on_rtp_server_timeout",String.format("%s/on_rtp_server_timeout", hookPrefix));
  591 + param.put("hook.on_record_mp4",String.format("%s/on_record_mp4", hookPrefix));
604 592 param.put("hook.timeoutSec","20");
605 593 // 推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
606 594 // 置0关闭此特性(推流断开会导致立即断开播放器)
... ... @@ -609,15 +597,14 @@ public class MediaServerServiceImpl implements IMediaServerService {
609 597 param.put("protocol.continue_push_ms", "3000" );
610 598 // 最多等待未初始化的Track时间,单位毫秒,超时之后会忽略未初始化的Track, 设置此选项优化那些音频错误的不规范流,
611 599 // 等zlm支持给每个rtpServer设置关闭音频的时候可以不设置此选项
612   -// param.put("general.wait_track_ready_ms", "3000" );
613 600 if (mediaServerItem.isRtpEnable() && !ObjectUtils.isEmpty(mediaServerItem.getRtpPortRange())) {
614 601 param.put("rtp_proxy.port_range", mediaServerItem.getRtpPortRange().replace(",", "-"));
615 602 }
616 603  
617   - if (userSetting.getRecordPath() != null) {
618   - File recordPathFile = new File(userSetting.getRecordPath());
619   - File mp4SavePathFile = recordPathFile.getParentFile().getAbsoluteFile();
620   - param.put("protocol.mp4_save_path", mp4SavePathFile.getAbsoluteFile());
  604 + if (!ObjectUtils.isEmpty(mediaServerItem.getRecordPath())) {
  605 + File recordPathFile = new File(mediaServerItem.getRecordPath());
  606 + param.put("protocol.mp4_save_path", recordPathFile.getParentFile().getPath());
  607 + param.put("protocol.downloadRoot", recordPathFile.getParentFile().getPath());
621 608 param.put("record.appName", recordPathFile.getName());
622 609 }
623 610  
... ... @@ -722,6 +709,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
722 709 ssrcFactory.initMediaServerSSRC(mediaServerItem.getId(), null);
723 710 String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItem.getId();
724 711 redisTemplate.opsForValue().set(key, mediaServerItem);
  712 + resetOnlineServerItem(mediaServerItem);
725 713 clearRTPServer(mediaServerItem);
726 714 }
727 715 final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + mediaServerItem.getId();
... ... @@ -750,15 +738,6 @@ public class MediaServerServiceImpl implements IMediaServerService {
750 738 }
751 739  
752 740 @Override
753   - public boolean checkRtpServer(MediaServerItem mediaServerItem, String app, String stream) {
754   - JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, stream);
755   - if(rtpInfo.getInteger("code") == 0){
756   - return rtpInfo.getBoolean("exist");
757   - }
758   - return false;
759   - }
760   -
761   - @Override
762 741 public MediaServerLoad getLoad(MediaServerItem mediaServerItem) {
763 742 MediaServerLoad result = new MediaServerLoad();
764 743 result.setId(mediaServerItem.getId());
... ... @@ -771,88 +750,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
771 750 }
772 751  
773 752 @Override
774   - public List<RecordFile> getRecords(String app, String stream, String startTime, String endTime, List<MediaServerItem> mediaServerItems) {
775   - Assert.notNull(app, "app不存在");
776   - Assert.notNull(stream, "stream不存在");
777   - Assert.notNull(startTime, "startTime不存在");
778   - Assert.notNull(endTime, "endTime不存在");
779   - Assert.notEmpty(mediaServerItems, "流媒体列表为空");
780   -
781   - CompletableFuture[] completableFutures = new CompletableFuture[mediaServerItems.size()];
782   - for (int i = 0; i < mediaServerItems.size(); i++) {
783   - completableFutures[i] = getRecordFilesForOne(app, stream, startTime, endTime, mediaServerItems.get(i));
784   - }
785   - List<RecordFile> result = new ArrayList<>();
786   - for (int i = 0; i < completableFutures.length; i++) {
787   - try {
788   - List<RecordFile> list = (List<RecordFile>) completableFutures[i].get();
789   - if (!list.isEmpty()) {
790   - for (int g = 0; g < list.size(); g++) {
791   - list.get(g).setMediaServerId(mediaServerItems.get(i).getId());
792   - }
793   - result.addAll(list);
794   - }
795   - } catch (InterruptedException e) {
796   - throw new RuntimeException(e);
797   - } catch (ExecutionException e) {
798   - throw new RuntimeException(e);
799   - }
800   - }
801   - Comparator<RecordFile> comparator = Comparator.comparing(RecordFile::getFileName);
802   - result.sort(comparator);
803   - return result;
804   - }
805   -
806   - @Override
807   - public List<String> getRecordDates(String app, String stream, int year, int month, List<MediaServerItem> mediaServerItems) {
808   - Assert.notNull(app, "app不存在");
809   - Assert.notNull(stream, "stream不存在");
810   - Assert.notEmpty(mediaServerItems, "流媒体列表为空");
811   - CompletableFuture[] completableFutures = new CompletableFuture[mediaServerItems.size()];
812   -
813   - for (int i = 0; i < mediaServerItems.size(); i++) {
814   - completableFutures[i] = getRecordDatesForOne(app, stream, year, month, mediaServerItems.get(i));
815   - }
816   - List<String> result = new ArrayList<>();
817   - CompletableFuture.allOf(completableFutures).join();
818   - for (CompletableFuture completableFuture : completableFutures) {
819   - try {
820   - List<String> list = (List<String>) completableFuture.get();
821   - result.addAll(list);
822   - } catch (InterruptedException e) {
823   - throw new RuntimeException(e);
824   - } catch (ExecutionException e) {
825   - throw new RuntimeException(e);
826   - }
827   - }
828   - Collections.sort(result);
829   - return result;
830   - }
831   -
832   - @Async
833   - public CompletableFuture<List<String>> getRecordDatesForOne(String app, String stream, int year, int month, MediaServerItem mediaServerItem) {
834   - JSONObject fileListJson = assistRESTfulUtils.getDateList(mediaServerItem, app, stream, year, month);
835   - if (fileListJson != null && !fileListJson.isEmpty()) {
836   - if (fileListJson.getString("code") != null && fileListJson.getInteger("code") == 0) {
837   - JSONArray data = fileListJson.getJSONArray("data");
838   - return CompletableFuture.completedFuture(data.toJavaList(String.class));
839   - }
840   - }
841   - return CompletableFuture.completedFuture(new ArrayList<>());
842   - }
843   -
844   - @Async
845   - public CompletableFuture<List<RecordFile>> getRecordFilesForOne(String app, String stream, String startTime, String endTime, MediaServerItem mediaServerItem) {
846   - JSONObject fileListJson = assistRESTfulUtils.getFileList(mediaServerItem, 1, 100000000, app, stream, startTime, endTime);
847   - if (fileListJson != null && !fileListJson.isEmpty()) {
848   - if (fileListJson.getString("code") != null && fileListJson.getInteger("code") == 0) {
849   - JSONObject data = fileListJson.getJSONObject("data");
850   - JSONArray list = data.getJSONArray("list");
851   - if (list != null) {
852   - return CompletableFuture.completedFuture(list.toJavaList(RecordFile.class));
853   - }
854   - }
855   - }
856   - return CompletableFuture.completedFuture(new ArrayList<>());
  753 + public List<MediaServerItem> getAllWithAssistPort() {
  754 + return mediaServerMapper.queryAllWithAssistPort();
857 755 }
858 756 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
... ... @@ -64,7 +64,7 @@ public class MediaServiceImpl implements IMediaService {
64 64 if (data == null) {
65 65 return null;
66 66 }
67   - JSONObject mediaJSON = JSON.parseObject(JSON.toJSONString(data.get(0)), JSONObject.class);
  67 + JSONObject mediaJSON = data.getJSONObject(0);
68 68 JSONArray tracks = mediaJSON.getJSONArray("tracks");
69 69 if (authority) {
70 70 streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld, true);
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.baomidou.dynamic.datasource.annotation.DS;
3 4 import com.genersoft.iot.vmp.gb28181.bean.*;
4 5 import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
5 6 import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
... ... @@ -27,6 +28,7 @@ import java.util.Map;
27 28 * @author lin
28 29 */
29 30 @Service
  31 +@DS("master")
30 32 public class PlatformChannelServiceImpl implements IPlatformChannelService {
31 33  
32 34 private final static Logger logger = LoggerFactory.getLogger(PlatformChannelServiceImpl.class);
... ... @@ -165,10 +167,9 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService {
165 167 catalogId = null;
166 168 }
167 169  
168   - if ((result = platformChannelMapper.delChannelForGBByCatalogId(platformId, catalogId)) > 0) {
169   - List<DeviceChannel> deviceChannels = platformChannelMapper.queryAllChannelInCatalog(platformId, catalogId);
170   - eventPublisher.catalogEventPublish(platformId, deviceChannels, CatalogEvent.DEL);
171   - }
172   - return result;
  170 + List<DeviceChannel> deviceChannels = platformChannelMapper.queryAllChannelInCatalog(platformId, catalogId);
  171 + eventPublisher.catalogEventPublish(platformId, deviceChannels, CatalogEvent.DEL);
  172 +
  173 + return platformChannelMapper.delChannelForGBByCatalogId(platformId, catalogId);
173 174 }
174 175 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
... ... @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.service.impl;
2 2  
3 3 import com.genersoft.iot.vmp.common.InviteInfo;
4 4 import com.genersoft.iot.vmp.common.InviteSessionType;
  5 +import com.baomidou.dynamic.datasource.annotation.DS;
5 6 import com.genersoft.iot.vmp.conf.DynamicTask;
6 7 import com.genersoft.iot.vmp.conf.UserSetting;
7 8 import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
... ... @@ -55,6 +56,7 @@ import java.util.*;
55 56 * @author lin
56 57 */
57 58 @Service
  59 +@DS("master")
58 60 public class PlatformServiceImpl implements IPlatformService {
59 61  
60 62 private final static String REGISTER_KEY_PREFIX = "platform_register_";
... ... @@ -169,7 +171,7 @@ public class PlatformServiceImpl implements IPlatformService {
169 171 dynamicTask.stop(registerTaskKey);
170 172 // 注销旧的
171 173 try {
172   - if (parentPlatformOld.isStatus()) {
  174 + if (parentPlatformOld.isStatus() && parentPlatformCatchOld != null) {
173 175 logger.info("保存平台{}时发现旧平台在线,发送注销命令", parentPlatformOld.getServerGBId());
174 176 commanderForPlatform.unregister(parentPlatformOld, parentPlatformCatchOld.getSipTransactionInfo(), null, eventResult -> {
175 177 logger.info("[国标级联] 注销成功, 平台:{}", parentPlatformOld.getServerGBId());
... ... @@ -286,6 +288,7 @@ public class PlatformServiceImpl implements IPlatformService {
286 288 }
287 289 if (parentPlatform.isAutoPushChannel()) {
288 290 if (subscribeHolder.getCatalogSubscribe(parentPlatform.getServerGBId()) == null) {
  291 + logger.info("[国标级联]:{}, 添加自动通道推送模拟订阅信息", parentPlatform.getServerGBId());
289 292 addSimulatedSubscribeInfo(parentPlatform);
290 293 }
291 294 }else {
... ... @@ -363,9 +366,16 @@ public class PlatformServiceImpl implements IPlatformService {
363 366 // 清除心跳任务
364 367 dynamicTask.stop(keepaliveTaskKey);
365 368 }
366   - // 停止目录订阅回复
367   - logger.info("[平台离线] {}, 停止订阅回复", parentPlatform.getServerGBId());
368   - subscribeHolder.removeAllSubscribe(parentPlatform.getServerGBId());
  369 + // 停止订阅回复
  370 + SubscribeInfo catalogSubscribe = subscribeHolder.getCatalogSubscribe(parentPlatform.getServerGBId());
  371 + if (catalogSubscribe != null) {
  372 + if (catalogSubscribe.getExpires() > 0) {
  373 + logger.info("[平台离线] {}, 停止目录订阅回复", parentPlatform.getServerGBId());
  374 + subscribeHolder.removeCatalogSubscribe(parentPlatform.getServerGBId());
  375 + }
  376 + }
  377 + logger.info("[平台离线] {}, 停止移动位置订阅回复", parentPlatform.getServerGBId());
  378 + subscribeHolder.removeMobilePositionSubscribe(parentPlatform.getServerGBId());
369 379 // 发起定时自动重新注册
370 380 if (!stopRegister) {
371 381 // 设置为60秒自动尝试重新注册
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.alibaba.fastjson2.JSONArray;
3 4 import com.alibaba.fastjson2.JSONObject;
  5 +import com.baomidou.dynamic.datasource.annotation.DS;
4 6 import com.genersoft.iot.vmp.common.InviteInfo;
5 7 import com.genersoft.iot.vmp.common.InviteSessionStatus;
6 8 import com.genersoft.iot.vmp.common.InviteSessionType;
... ... @@ -23,7 +25,12 @@ import com.genersoft.iot.vmp.media.zlm.*;
23 25 import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
24 26 import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
25 27 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  28 +import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
  29 +import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
  30 +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
  31 +import com.genersoft.iot.vmp.media.zlm.dto.*;
26 32 import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
  33 +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
27 34 import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
28 35 import com.genersoft.iot.vmp.service.*;
29 36 import com.genersoft.iot.vmp.service.bean.ErrorCallback;
... ... @@ -31,8 +38,11 @@ import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
31 38 import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
32 39 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
33 40 import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
  41 +import com.genersoft.iot.vmp.service.bean.*;
34 42 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
35 43 import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
  44 +import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
  45 +import com.genersoft.iot.vmp.utils.CloudRecordUtils;
36 46 import com.genersoft.iot.vmp.utils.DateUtil;
37 47 import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
38 48 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
... ... @@ -61,6 +71,7 @@ import java.util.*;
61 71  
62 72 @SuppressWarnings(value = {"rawtypes", "unchecked"})
63 73 @Service
  74 +@DS("master")
64 75 public class PlayServiceImpl implements IPlayService {
65 76  
66 77 private final static Logger logger = LoggerFactory.getLogger(PlayServiceImpl.class);
... ... @@ -93,12 +104,18 @@ public class PlayServiceImpl implements IPlayService {
93 104 private SendRtpPortManager sendRtpPortManager;
94 105  
95 106 @Autowired
  107 + private ZlmHttpHookSubscribe subscribe;
  108 +
  109 + @Autowired
96 110 private ZLMRESTfulUtils zlmresTfulUtils;
97 111  
98 112 @Autowired
99 113 private AssistRESTfulUtils assistRESTfulUtils;
100 114  
101 115 @Autowired
  116 + private ZLMServerFactory zlmServerFactory;
  117 +
  118 + @Autowired
102 119 private IMediaService mediaService;
103 120  
104 121 @Autowired
... ... @@ -117,7 +134,7 @@ public class PlayServiceImpl implements IPlayService {
117 134 private DynamicTask dynamicTask;
118 135  
119 136 @Autowired
120   - private ZlmHttpHookSubscribe subscribe;
  137 + private CloudRecordServiceMapper cloudRecordServiceMapper;
121 138  
122 139 @Autowired
123 140 private ISIPCommanderForPlatform commanderForPlatform;
... ... @@ -407,6 +424,15 @@ public class PlayServiceImpl implements IPlayService {
407 424 HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
408 425 subscribe.removeSubscribe(hookSubscribe);
409 426 }
  427 + }else {
  428 + logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},码流类型:{},端口:{}, SSRC: {}",
  429 + device.getDeviceId(), channelId, device.isSwitchPrimarySubStream() ? "辅码流" : "主码流",
  430 + ssrcInfo.getPort(), ssrcInfo.getSsrc());
  431 +
  432 + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  433 +
  434 + mediaServerService.closeRTPServer(mediaServerItem.getId(), ssrcInfo.getStream());
  435 + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
410 436 }
411 437 }, userSetting.getPlayTimeout());
412 438  
... ... @@ -437,6 +463,7 @@ public class PlayServiceImpl implements IPlayService {
437 463 InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId,
438 464 timeOutTaskKey, callback, inviteInfo, InviteSessionType.PLAY);
439 465 }, (event) -> {
  466 + logger.info("[点播失败] deviceId: {}, channelId:{}, {}: {}", device.getDeviceId(), channelId, event.statusCode, event.msg);
440 467 dynamicTask.stop(timeOutTaskKey);
441 468 mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
442 469 // 释放ssrc
... ... @@ -478,7 +505,13 @@ public class PlayServiceImpl implements IPlayService {
478 505 if (!device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
479 506 return;
480 507 }
481   - String substring = contentString.substring(0, contentString.indexOf("y="));
  508 +
  509 + String substring;
  510 + if (contentString.indexOf("y=") > 0) {
  511 + substring = contentString.substring(0, contentString.indexOf("y="));
  512 + }else {
  513 + substring = contentString;
  514 + }
482 515 try {
483 516 SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
484 517 int port = -1;
... ... @@ -568,7 +601,7 @@ public class PlayServiceImpl implements IPlayService {
568 601 deviceChannel.setStreamId(streamInfo.getStream());
569 602 storager.startPlay(deviceId, channelId, streamInfo.getStream());
570 603 }
571   - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAYBACK, deviceId, channelId);
  604 + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, ((OnStreamChangedHookParam) param).getStream());
572 605 if (inviteInfo != null) {
573 606 inviteInfo.setStatus(InviteSessionStatus.ok);
574 607  
... ... @@ -598,23 +631,6 @@ public class PlayServiceImpl implements IPlayService {
598 631 }
599 632  
600 633 @Override
601   - public MediaServerItem getNewMediaServerItemHasAssist(Device device) {
602   - if (device == null) {
603   - return null;
604   - }
605   - MediaServerItem mediaServerItem;
606   - if (ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) {
607   - mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(true);
608   - } else {
609   - mediaServerItem = mediaServerService.getOne(device.getMediaServerId());
610   - }
611   - if (mediaServerItem == null) {
612   - logger.warn("[获取可用的ZLM节点]未找到可使用的ZLM...");
613   - }
614   - return mediaServerItem;
615   - }
616   -
617   - @Override
618 634 public void playBack(String deviceId, String channelId, String startTime,
619 635 String endTime, ErrorCallback<Object> callback) {
620 636 Device device = storager.queryVideoDevice(deviceId);
... ... @@ -711,7 +727,6 @@ public class PlayServiceImpl implements IPlayService {
711 727 // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
712 728 InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId,
713 729 playBackTimeOutTaskKey, callback, inviteInfo, InviteSessionType.PLAYBACK);
714   -
715 730 }, errorEvent);
716 731 } catch (InvalidArgumentException | SipException | ParseException e) {
717 732 logger.error("[命令发送失败] 录像回放: {}", e.getMessage());
... ... @@ -732,6 +747,10 @@ public class PlayServiceImpl implements IPlayService {
732 747 ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
733 748 String contentString = new String(responseEvent.getResponse().getRawContent());
734 749 String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString);
  750 + // 兼容回复的消息中缺少ssrc(y字段)的情况
  751 + if (ssrcInResponse == null) {
  752 + ssrcInResponse = ssrcInfo.getSsrc();
  753 + }
735 754 if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
736 755 // ssrc 一致
737 756 if (mediaServerItem.isRtpEnable()) {
... ... @@ -809,13 +828,15 @@ public class PlayServiceImpl implements IPlayService {
809 828 }
810 829  
811 830  
  831 +
  832 +
812 833 @Override
813 834 public void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback) {
814 835 Device device = storager.queryVideoDevice(deviceId);
815 836 if (device == null) {
816 837 return;
817 838 }
818   - MediaServerItem newMediaServerItem = getNewMediaServerItemHasAssist(device);
  839 + MediaServerItem newMediaServerItem = this.getNewMediaServerItem(device);
819 840 if (newMediaServerItem == null) {
820 841 callback.run(InviteErrorCode.ERROR_FOR_ASSIST_NOT_READY.getCode(),
821 842 InviteErrorCode.ERROR_FOR_ASSIST_NOT_READY.getMsg(),
... ... @@ -894,6 +915,28 @@ public class PlayServiceImpl implements IPlayService {
894 915 // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
895 916 InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId,
896 917 downLoadTimeOutTaskKey, callback, inviteInfo, InviteSessionType.DOWNLOAD);
  918 +
  919 + // 注册录像回调事件,录像下载结束后写入下载地址
  920 + ZlmHttpHookSubscribe.Event hookEventForRecord = (mediaServerItemInuse, hookParam) -> {
  921 + logger.info("[录像下载] 收到录像写入磁盘消息: , {}/{}-{}",
  922 + inviteInfo.getDeviceId(), inviteInfo.getChannelId(), ssrcInfo.getStream());
  923 + logger.info("[录像下载] 收到录像写入磁盘消息内容: " + hookParam);
  924 + OnRecordMp4HookParam recordMp4HookParam = (OnRecordMp4HookParam)hookParam;
  925 + String filePath = recordMp4HookParam.getFile_path();
  926 + DownloadFileInfo downloadFileInfo = CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath);
  927 + InviteInfo inviteInfoForNew = inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId()
  928 + , inviteInfo.getChannelId(), inviteInfo.getStream());
  929 + inviteInfoForNew.getStreamInfo().setDownLoadFilePath(downloadFileInfo);
  930 + inviteStreamService.updateInviteInfo(inviteInfoForNew);
  931 + };
  932 + HookSubscribeForRecordMp4 hookSubscribe = HookSubscribeFactory.on_record_mp4(
  933 + mediaServerItem.getId(), "rtp", ssrcInfo.getStream());
  934 +
  935 + // 设置过期时间,下载失败时自动处理订阅数据
  936 +// long difference = DateUtil.getDifference(startTime, endTime)/1000;
  937 +// Instant expiresInstant = Instant.now().plusSeconds(TimeUnit.MINUTES.toSeconds(difference * 2));
  938 +// hookSubscribe.setExpires(expiresInstant);
  939 + subscribe.addSubscribe(hookSubscribe, hookEventForRecord);
897 940 });
898 941 } catch (InvalidArgumentException | SipException | ParseException e) {
899 942 logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
... ... @@ -909,47 +952,71 @@ public class PlayServiceImpl implements IPlayService {
909 952 @Override
910 953 public StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream) {
911 954 InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, stream);
  955 + if (inviteInfo == null || inviteInfo.getStreamInfo() == null) {
  956 + logger.warn("[获取下载进度] 未查询到录像下载的信息");
  957 + return null;
  958 + }
912 959  
913   - if (inviteInfo != null && inviteInfo.getStreamInfo() != null) {
914   - if (inviteInfo.getStreamInfo().getProgress() == 1) {
915   - return inviteInfo.getStreamInfo();
916   - }
  960 + if (inviteInfo.getStreamInfo().getProgress() == 1) {
  961 + return inviteInfo.getStreamInfo();
  962 + }
917 963  
918   - // 获取当前已下载时长
919   - String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId();
920   - MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
921   - if (mediaServerItem == null) {
922   - logger.warn("查询录像信息时发现节点已离线");
923   - return null;
924   - }
925   - if (mediaServerItem.getRecordAssistPort() > 0) {
926   - JSONObject jsonObject = assistRESTfulUtils.fileDuration(mediaServerItem, inviteInfo.getStreamInfo().getApp(), inviteInfo.getStreamInfo().getStream(), null);
927   - if (jsonObject == null) {
928   - throw new ControllerException(ErrorCode.ERROR100.getCode(), "连接Assist服务失败");
929   - }
930   - if (jsonObject.getInteger("code") == 0) {
931   - long duration = jsonObject.getLong("data");
  964 + // 获取当前已下载时长
  965 + String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId();
  966 + MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
  967 + if (mediaServerItem == null) {
  968 + logger.warn("[获取下载进度] 查询录像信息时发现节点不存在");
  969 + return null;
  970 + }
  971 + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, null, stream);
932 972  
933   - if (duration == 0) {
934   - inviteInfo.getStreamInfo().setProgress(0);
935   - } else {
936   - String startTime = inviteInfo.getStreamInfo().getStartTime();
937   - String endTime = inviteInfo.getStreamInfo().getEndTime();
938   - long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
939   - long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
940   -
941   - BigDecimal currentCount = new BigDecimal(duration / 1000);
942   - BigDecimal totalCount = new BigDecimal(end - start);
943   - BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP);
944   - double process = divide.doubleValue();
945   - inviteInfo.getStreamInfo().setProgress(process);
946   - }
947   - inviteStreamService.updateInviteInfo(inviteInfo);
948   - }
  973 + if (ssrcTransaction == null) {
  974 + logger.warn("[获取下载进度] 下载已结束");
  975 + return null;
  976 + }
  977 +
  978 + JSONObject mediaListJson= zlmresTfulUtils.getMediaList(mediaServerItem, "rtp", stream);
  979 + if (mediaListJson == null) {
  980 + logger.warn("[获取下载进度] 从zlm查询进度失败");
  981 + return null;
  982 + }
  983 + if (mediaListJson.getInteger("code") != 0) {
  984 + logger.warn("[获取下载进度] 从zlm查询进度出现错误: {}", mediaListJson.getString("msg"));
  985 + return null;
  986 + }
  987 + JSONArray data = mediaListJson.getJSONArray("data");
  988 + if (data == null) {
  989 + logger.warn("[获取下载进度] 从zlm查询进度时未返回数据");
  990 + return null;
  991 + }
  992 + JSONObject mediaJSON = data.getJSONObject(0);
  993 + JSONArray tracks = mediaJSON.getJSONArray("tracks");
  994 + if (tracks.isEmpty()) {
  995 + logger.warn("[获取下载进度] 从zlm查询进度时未返回数据");
  996 + return null;
  997 + }
  998 + JSONObject jsonObject = tracks.getJSONObject(0);
  999 + long duration = jsonObject.getLongValue("duration");
  1000 + if (duration == 0) {
  1001 + inviteInfo.getStreamInfo().setProgress(0);
  1002 + } else {
  1003 + String startTime = inviteInfo.getStreamInfo().getStartTime();
  1004 + String endTime = inviteInfo.getStreamInfo().getEndTime();
  1005 + // 此时start和end单位是秒
  1006 + long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
  1007 + long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
  1008 +
  1009 + BigDecimal currentCount = new BigDecimal(duration);
  1010 + BigDecimal totalCount = new BigDecimal((end - start) * 1000);
  1011 + BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP);
  1012 + double process = divide.doubleValue();
  1013 + if (process > 0.999) {
  1014 + process = 1.0;
949 1015 }
950   - return inviteInfo.getStreamInfo();
  1016 + inviteInfo.getStreamInfo().setProgress(process);
951 1017 }
952   - return null;
  1018 + inviteStreamService.updateInviteInfo(inviteInfo);
  1019 + return inviteInfo.getStreamInfo();
953 1020 }
954 1021  
955 1022 private StreamInfo onPublishHandlerForDownload(MediaServerItem mediaServerItemInuse, HookParam hookParam, String deviceId, String channelId, String startTime, String endTime) {
... ... @@ -1219,7 +1286,12 @@ public class PlayServiceImpl implements IPlayService {
1219 1286 throw new ServiceException("mediaServer不存在");
1220 1287 }
1221 1288 // zlm 暂停RTP超时检查
1222   - JSONObject jsonObject = zlmresTfulUtils.pauseRtpCheck(mediaServerItem, streamId);
  1289 + // 使用zlm中的流ID
  1290 + String streamKey = inviteInfo.getStream();
  1291 + if (!mediaServerItem.isRtpEnable()) {
  1292 + streamKey = Long.toHexString(Long.parseLong(inviteInfo.getSsrcInfo().getSsrc())).toUpperCase();
  1293 + }
  1294 + JSONObject jsonObject = zlmresTfulUtils.pauseRtpCheck(mediaServerItem, streamKey);
1223 1295 if (jsonObject == null || jsonObject.getInteger("code") != 0) {
1224 1296 throw new ServiceException("暂停RTP接收失败");
1225 1297 }
... ... @@ -1242,7 +1314,12 @@ public class PlayServiceImpl implements IPlayService {
1242 1314 throw new ServiceException("mediaServer不存在");
1243 1315 }
1244 1316 // zlm 暂停RTP超时检查
1245   - JSONObject jsonObject = zlmresTfulUtils.resumeRtpCheck(mediaServerItem, streamId);
  1317 + // 使用zlm中的流ID
  1318 + String streamKey = inviteInfo.getStream();
  1319 + if (!mediaServerItem.isRtpEnable()) {
  1320 + streamKey = Long.toHexString(Long.parseLong(inviteInfo.getSsrcInfo().getSsrc())).toUpperCase();
  1321 + }
  1322 + JSONObject jsonObject = zlmresTfulUtils.resumeRtpCheck(mediaServerItem, streamKey);
1246 1323 if (jsonObject == null || jsonObject.getInteger("code") != 0) {
1247 1324 throw new ServiceException("继续RTP接收失败");
1248 1325 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/RoleServerImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.baomidou.dynamic.datasource.annotation.DS;
3 4 import com.genersoft.iot.vmp.service.IRoleService;
4 5 import com.genersoft.iot.vmp.storager.dao.RoleMapper;
5 6 import com.genersoft.iot.vmp.storager.dao.dto.Role;
... ... @@ -9,6 +10,7 @@ import org.springframework.stereotype.Service;
9 10 import java.util.List;
10 11  
11 12 @Service
  13 +@DS("master")
12 14 public class RoleServerImpl implements IRoleService {
13 15  
14 16 @Autowired
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java
... ... @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.service.impl;
2 2  
3 3 import com.alibaba.fastjson2.JSONArray;
4 4 import com.alibaba.fastjson2.JSONObject;
  5 +import com.baomidou.dynamic.datasource.annotation.DS;
5 6 import com.genersoft.iot.vmp.common.GeneralCallback;
6 7 import com.genersoft.iot.vmp.common.StreamInfo;
7 8 import com.genersoft.iot.vmp.conf.DynamicTask;
... ... @@ -53,6 +54,7 @@ import java.util.stream.Collectors;
53 54 * 视频代理业务
54 55 */
55 56 @Service
  57 +@DS("master")
56 58 public class StreamProxyServiceImpl implements IStreamProxyService {
57 59  
58 60 private final static Logger logger = LoggerFactory.getLogger(StreamProxyServiceImpl.class);
... ... @@ -126,7 +128,13 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
126 128 }
127 129 JSONArray dataArray = jsonObject.getJSONArray("data");
128 130 JSONObject mediaServerConfig = dataArray.getJSONObject(0);
  131 + if (ObjectUtils.isEmpty(param.getFfmpegCmdKey())) {
  132 + param.setFfmpegCmdKey("ffmpeg.cmd");
  133 + }
129 134 String ffmpegCmd = mediaServerConfig.getString(param.getFfmpegCmdKey());
  135 + if (ffmpegCmd == null) {
  136 + throw new ControllerException(ErrorCode.ERROR100.getCode(), "ffmpeg拉流代理无法获取ffmpeg cmd");
  137 + }
130 138 String schema = getSchemaFromFFmpegCmd(ffmpegCmd);
131 139 if (schema == null) {
132 140 throw new ControllerException(ErrorCode.ERROR100.getCode(), "ffmpeg拉流代理无法从ffmpeg cmd中获取到输出格式");
... ... @@ -401,6 +409,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
401 409 logger.info("启用代理失败: {}/{}->{}({})", app, stream, jsonObject.getString("msg"),
402 410 streamProxy.getSrcUrl() == null? streamProxy.getUrl():streamProxy.getSrcUrl());
403 411 }
  412 + } else if (streamProxy != null && streamProxy.isEnable()) {
  413 + return true ;
404 414 }
405 415 return result;
406 416 }
... ... @@ -452,7 +462,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
452 462 streamProxyMapper.deleteAutoRemoveItemByMediaServerId(mediaServerId);
453 463  
454 464 // 移除拉流代理生成的流信息
455   -// syncPullStream(mediaServerId);
  465 + syncPullStream(mediaServerId);
456 466  
457 467 // 恢复流代理, 只查找这个这个流媒体
458 468 List<StreamProxyItem> streamProxyListForEnable = storager.getStreamProxyListForEnableInMediaServer(
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
... ... @@ -4,6 +4,7 @@ import com.alibaba.fastjson2.JSON;
4 4 import com.alibaba.fastjson2.JSONArray;
5 5 import com.alibaba.fastjson2.JSONObject;
6 6 import com.alibaba.fastjson2.TypeReference;
  7 +import com.baomidou.dynamic.datasource.annotation.DS;
7 8 import com.genersoft.iot.vmp.conf.MediaConfig;
8 9 import com.genersoft.iot.vmp.conf.UserSetting;
9 10 import com.genersoft.iot.vmp.gb28181.bean.*;
... ... @@ -36,6 +37,7 @@ import java.util.*;
36 37 import java.util.stream.Collectors;
37 38  
38 39 @Service
  40 +@DS("master")
39 41 public class StreamPushServiceImpl implements IStreamPushService {
40 42  
41 43 private final static Logger logger = LoggerFactory.getLogger(StreamPushServiceImpl.class);
... ... @@ -282,6 +284,8 @@ public class StreamPushServiceImpl implements IStreamPushService {
282 284 redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
283 285 // 移除redis内流的信息
284 286 redisCatchStorage.removeStream(mediaServerItem.getId(), "PUSH", offlineOnStreamChangedHookParam.getApp(), offlineOnStreamChangedHookParam.getStream());
  287 + // 冗余数据,自己系统中自用
  288 + redisCatchStorage.removePushListItem(offlineOnStreamChangedHookParam.getApp(), offlineOnStreamChangedHookParam.getStream(), mediaServerItem.getId());
285 289 }
286 290 }
287 291  
... ... @@ -319,6 +323,9 @@ public class StreamPushServiceImpl implements IStreamPushService {
319 323 jsonObject.put("register", false);
320 324 jsonObject.put("mediaServerId", mediaServerId);
321 325 redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
  326 +
  327 + // 冗余数据,自己系统中自用
  328 + redisCatchStorage.removePushListItem(onStreamChangedHookParam.getApp(), onStreamChangedHookParam.getStream(), mediaServerId);
322 329 }
323 330 }
324 331 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.baomidou.dynamic.datasource.annotation.DS;
3 4 import com.genersoft.iot.vmp.service.IUserService;
4 5 import com.genersoft.iot.vmp.storager.dao.UserMapper;
5 6 import com.genersoft.iot.vmp.storager.dao.dto.User;
... ... @@ -12,6 +13,7 @@ import org.springframework.util.DigestUtils;
12 13 import java.util.List;
13 14  
14 15 @Service
  16 +@DS("master")
15 17 public class UserServiceImpl implements IUserService {
16 18  
17 19 @Autowired
... ...