Browse Source

Merge branch 'master' into wvp-28181-2.0

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
#	src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
#	src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
#	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/request/impl/MessageRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
#	src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
#	src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
#	src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
#	src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java
#	src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
#	src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
#	src/main/java/com/genersoft/iot/vmp/vmanager/service/IPlayService.java
#	src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java
#	src/main/resources/application-dev.yml
pull/76/head
wangshaopeng@sunnybs.com 4 years ago
parent
commit
19e8325ede
  1. 2
      .gitignore
  2. 2
      README.md
  3. 90
      pom.xml
  4. 2
      src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
  5. 119
      src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
  6. 2
      src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
  7. 75
      src/main/java/com/genersoft/iot/vmp/conf/ApplicationCheckRunner.java
  8. 53
      src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
  9. 535
      src/main/java/com/genersoft/iot/vmp/conf/MediaServerConfig.java
  10. 96
      src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java
  11. 29
      src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java
  12. 51
      src/main/java/com/genersoft/iot/vmp/conf/SsrcConfig.java
  13. 386
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  14. 4
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  15. 6
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
  16. 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java
  17. 2
      src/main/java/com/genersoft/iot/vmp/gb28181/event/DeviceOffLineDetector.java
  18. 23
      src/main/java/com/genersoft/iot/vmp/gb28181/session/PlayTypeEnum.java
  19. 93
      src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcUtil.java
  20. 291
      src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
  21. 9
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
  22. 7
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  23. 1235
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  24. 1080
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
  25. 82
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java
  26. 43
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java
  27. 312
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  28. 127
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
  29. 51
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
  30. 137
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
  31. 41
      src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
  32. 336
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
  33. 2
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
  34. 213
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  35. 8
      src/main/java/com/genersoft/iot/vmp/utils/ConfigConst.java
  36. 97
      src/main/java/com/genersoft/iot/vmp/utils/redis/JedisUtil.java
  37. 154
      src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
  38. 120
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceConfig.java
  39. 119
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceConfigController.java
  40. 238
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceControl.java
  41. 237
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceControlController.java
  42. 261
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java
  43. 255
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceQuery.java
  44. 330
      src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
  45. 169
      src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java
  46. 7
      src/main/java/com/genersoft/iot/vmp/vmanager/service/IPlayService.java
  47. 64
      src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java
  48. 126
      src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java
  49. 7
      src/main/resources/application-dev.yml
  50. 2
      web_src/src/components/channelList.vue
  51. 10
      web_src/src/components/gb28181/devicePlayer.vue

2
.gitignore

@ -10,6 +10,8 @@
# Mobile Tools for Java (J2ME) # Mobile Tools for Java (J2ME)
.mtj.tmp/ .mtj.tmp/
src/main/resources/application-*.yml src/main/resources/application-*.yml
src/main/resources/static/
web_src/package-lock.json
# Package Files # # Package Files #
#*.jar #*.jar
*.war *.war

2
README.md

@ -53,7 +53,7 @@ https://gitee.com/18010473990/wvp-GB28181.git
8. 支持udp/tcp国标流传输模式; 8. 支持udp/tcp国标流传输模式;
9. 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址 9. 支持直接输出RTSP、RTMP、HTTP-FLV、Websocket-FLV、HLS多种协议流地址
10. 支持国标网络校时 10. 支持国标网络校时
11. 支持公网部署, 支持wvp与zlm分开部署 11. 支持公网部署, 支持wvp与zlm分开部署,支持配置多台zlm
12. 支持播放h265, g.711格式的流 12. 支持播放h265, g.711格式的流
13. 支持固定流地址和自动点播,同时支持未点播时直接播放流地址,代码自动发起点播. ( [查看WIKI](https://github.com/648540858/wvp-GB28181-pro/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E5%9B%BA%E5%AE%9A%E6%92%AD%E6%94%BE%E5%9C%B0%E5%9D%80%E4%B8%8E%E8%87%AA%E5%8A%A8%E7%82%B9%E6%92%AD)) 13. 支持固定流地址和自动点播,同时支持未点播时直接播放流地址,代码自动发起点播. ( [查看WIKI](https://github.com/648540858/wvp-GB28181-pro/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E5%9B%BA%E5%AE%9A%E6%92%AD%E6%94%BE%E5%9C%B0%E5%9D%80%E4%B8%8E%E8%87%AA%E5%8A%A8%E7%82%B9%E6%92%AD))
14. 报警信息处理,支持向前端推送报警信息 14. 报警信息处理,支持向前端推送报警信息

90
pom.xml

@ -13,6 +13,34 @@
<artifactId>wvp</artifactId> <artifactId>wvp</artifactId>
<name>web video platform</name> <name>web video platform</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 依赖版本 -->
<mybatis-spring-boot-starter-version>2.1.4</mybatis-spring-boot-starter-version>
<druid-version>1.2.3</druid-version>
<mysql-connector-java-version>8.0.22</mysql-connector-java-version>
<sqlite-jdbc-version>3.32.3.2</sqlite-jdbc-version>
<jedis-version>3.1.0</jedis-version>
<pagehelper-spring-boot-starter-version>1.2.10</pagehelper-spring-boot-starter-version>
<springfox-swagger2-version>2.9.2</springfox-swagger2-version>
<springfox-swagger-ui-version>2.6.1</springfox-swagger-ui-version>
<jain-sip-ri-version>1.3.0-91</jain-sip-ri-version>
<log4j-version>1.2.17</log4j-version>
<dom4j-version>2.1.3</dom4j-version>
<fastjson-version>1.2.73</fastjson-version>
<guava-version>30.0-jre</guava-version>
<lombok-version>1.18.12</lombok-version>
<commons-lang3-version>3.7</commons-lang3-version>
<commons-io-version>2.6</commons-io-version>
<okhttp-version>4.9.0</okhttp-version>
<snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
<asciidoctor.input.directory>${project.basedir}/docs/asciidoc</asciidoctor.input.directory>
<generated.asciidoc.directory>${project.build.directory}/asciidoc</generated.asciidoc.directory>
<asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory>
<asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
</properties>
<repositories> <repositories>
<repository> <repository>
<id>nexus-aliyun</id> <id>nexus-aliyun</id>
@ -41,17 +69,6 @@
</pluginRepository> </pluginRepository>
</pluginRepositories> </pluginRepositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 依赖版本 -->
<pagehelper.version>5.2.0</pagehelper.version>
<snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory>
<asciidoctor.input.directory>${project.basedir}/docs/asciidoc</asciidoctor.input.directory>
<generated.asciidoc.directory>${project.build.directory}/asciidoc</generated.asciidoc.directory>
<asciidoctor.html.output.directory>${project.build.directory}/asciidoc/html</asciidoctor.html.output.directory>
<asciidoctor.pdf.output.directory>${project.build.directory}/asciidoc/pdf</asciidoctor.pdf.output.directory>
</properties>
<dependencies> <dependencies>
<dependency> <dependency>
@ -65,54 +82,54 @@
<dependency> <dependency>
<groupId>org.mybatis.spring.boot</groupId> <groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId> <artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version> <version>${mybatis-spring-boot-starter-version}</version>
</dependency> </dependency>
<!-- druid数据库连接池 --> <!-- druid数据库连接池 -->
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid</artifactId> <artifactId>druid</artifactId>
<version>1.2.3</version> <version>${druid-version}</version>
</dependency> </dependency>
<!-- mysql数据库 --> <!-- mysql数据库 -->
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version> <version>${mysql-connector-java-version}</version>
</dependency> </dependency>
<!-- 添加sqlite-jdbc数据库驱动 --> <!-- 添加sqlite-jdbc数据库驱动 -->
<dependency> <dependency>
<groupId>org.xerial</groupId> <groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId> <artifactId>sqlite-jdbc</artifactId>
<version>3.32.3.2</version> <version>${sqlite-jdbc-version}</version>
</dependency> </dependency>
<!--Mybatis分页插件 --> <!--Mybatis分页插件 -->
<dependency> <dependency>
<groupId>com.github.pagehelper</groupId> <groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId> <artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.10</version> <version>${pagehelper-spring-boot-starter-version}</version>
</dependency> </dependency>
<!-- <dependency>--> <dependency>
<!-- <groupId>org.apache.commons</groupId>--> <groupId>redis.clients</groupId>
<!-- <artifactId>commons-lang3</artifactId>--> <artifactId>jedis</artifactId>
<!-- <version>3.11</version>--> <version>${jedis-version}</version>
<!-- </dependency>--> </dependency>
<!--Swagger2 --> <!--Swagger2 -->
<!--在线文档 --> <!--在线文档 -->
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId> <artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version> <version>${springfox-swagger2-version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.springfox</groupId> <groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId> <artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version> <version>${springfox-swagger-ui-version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.validation</groupId> <groupId>javax.validation</groupId>
@ -129,41 +146,56 @@
<dependency> <dependency>
<groupId>javax.sip</groupId> <groupId>javax.sip</groupId>
<artifactId>jain-sip-ri</artifactId> <artifactId>jain-sip-ri</artifactId>
<version>1.3.0-91</version> <version>${jain-sip-ri-version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>log4j</groupId> <groupId>log4j</groupId>
<artifactId>log4j</artifactId> <artifactId>log4j</artifactId>
<version>1.2.17</version> <version>${log4j-version}</version>
</dependency> </dependency>
<!-- xml解析库 --> <!-- xml解析库 -->
<dependency> <dependency>
<groupId>org.dom4j</groupId> <groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId> <artifactId>dom4j</artifactId>
<version>2.1.3</version> <version>${dom4j-version}</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId> <artifactId>fastjson</artifactId>
<version>1.2.73</version> <version>${fastjson-version}</version>
</dependency> </dependency>
<!--Guava是一种基于开源的Java库--> <!--Guava是一种基于开源的Java库-->
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<version>30.0-jre</version> <version>${guava-version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3-version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io-version}</version>
</dependency> </dependency>
<!-- okhttp --> <!-- okhttp -->
<dependency> <dependency>
<groupId>com.squareup.okhttp3</groupId> <groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId> <artifactId>okhttp</artifactId>
<version>4.9.0</version> <version>${okhttp-version}</version>
</dependency> </dependency>
</dependencies> </dependencies>

2
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java

@ -1,7 +1,5 @@
package com.genersoft.iot.vmp; package com.genersoft.iot.vmp;
import java.util.logging.LogManager;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;

119
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java

@ -1,9 +1,16 @@
package com.genersoft.iot.vmp.common; package com.genersoft.iot.vmp.common;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import lombok.Data;
@Data
public class StreamInfo { public class StreamInfo {
/**
* zlm流媒体服务器IP
*/
private String mediaServerIp;
private String ssrc;
private String streamId; private String streamId;
private String deviceID; private String deviceID;
private String channelId; private String channelId;
@ -18,116 +25,4 @@ public class StreamInfo {
private String rtmp; private String rtmp;
private String rtsp; private String rtsp;
private JSONArray tracks; private JSONArray tracks;
public String getDeviceID() {
return deviceID;
}
public void setDeviceID(String deviceID) {
this.deviceID = deviceID;
}
public String getChannelId() {
return channelId;
}
public void setChannelId(String channelId) {
this.channelId = channelId;
}
public String getFlv() {
return flv;
}
public void setFlv(String flv) {
this.flv = flv;
}
public String getWs_flv() {
return ws_flv;
}
public void setWs_flv(String ws_flv) {
this.ws_flv = ws_flv;
}
public String getRtmp() {
return rtmp;
}
public void setRtmp(String rtmp) {
this.rtmp = rtmp;
}
public String getHls() {
return hls;
}
public void setHls(String hls) {
this.hls = hls;
}
public String getRtsp() {
return rtsp;
}
public void setRtsp(String rtsp) {
this.rtsp = rtsp;
}
public JSONArray getTracks() {
return tracks;
}
public void setTracks(JSONArray tracks) {
this.tracks = tracks;
}
public String getFmp4() {
return fmp4;
}
public void setFmp4(String fmp4) {
this.fmp4 = fmp4;
}
public String getWs_fmp4() {
return ws_fmp4;
}
public void setWs_fmp4(String ws_fmp4) {
this.ws_fmp4 = ws_fmp4;
}
public String getWs_hls() {
return ws_hls;
}
public void setWs_hls(String ws_hls) {
this.ws_hls = ws_hls;
}
public String getTs() {
return ts;
}
public void setTs(String ts) {
this.ts = ts;
}
public String getWs_ts() {
return ws_ts;
}
public void setWs_ts(String ws_ts) {
this.ws_ts = ws_ts;
}
public String getStreamId() {
return streamId;
}
public void setStreamId(String streamId) {
this.streamId = streamId;
}
} }

2
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java

@ -43,4 +43,6 @@ public class VideoManagerConstants {
public static final String EVENT_OUTLINE_UNREGISTER = "1"; public static final String EVENT_OUTLINE_UNREGISTER = "1";
public static final String EVENT_OUTLINE_TIMEOUT = "2"; public static final String EVENT_OUTLINE_TIMEOUT = "2";
public static final String MEDIA_SSRC_USED_PREFIX = "VMP_media_used_ssrc_";
} }

75
src/main/java/com/genersoft/iot/vmp/conf/ApplicationCheckRunner.java

@ -1,63 +1,58 @@
package com.genersoft.iot.vmp.conf; package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.utils.redis.JedisUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner; import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Set;
/** /**
* 对配置文件进行校验 * 对配置文件进行校验
*/ */
@Component @Component
@Order(value=2) @Order(value = 0)
public class ApplicationCheckRunner implements CommandLineRunner { public class ApplicationCheckRunner implements CommandLineRunner {
private Logger logger = LoggerFactory.getLogger("ApplicationCheckRunner"); private Logger logger = LoggerFactory.getLogger("ApplicationCheckRunner");
@Autowired
@Value("${sip.ip}") SipConfig sipConfig;
private String sipIp; @Autowired
MediaConfig mediaConfig;
@Value("${media.ip}") @Autowired
private String mediaIp; JedisUtil jedisUtil;
@Value("${media.wanIp}")
private String mediaWanIp;
@Value("${media.hookIp}")
private String mediaHookIp;
@Value("${media.port}")
private int mediaPort;
@Value("${media.secret}")
private String mediaSecret;
@Value("${media.streamNoneReaderDelayMS}")
private String streamNoneReaderDelayMS;
@Value("${sip.ip}")
private String sipIP;
@Value("${server.port}")
private String serverPort;
@Value("${media.autoConfig}")
private boolean autoConfig;
@Override @Override
public void run(String... args) throws Exception { public void run(String... args) {
if (sipIP.equals("localhost") || sipIP.equals("127.0.0.1")) { String sipIp = sipConfig.getSipIp();
logger.error("sip.ip不能使用 {} ,请使用类似192.168.1.44这样的来自网卡的IP!!!", sipIP ); if (sipIp.equals("localhost") || sipIp.equals("127.0.0.1")) {
logger.error("sip.ip不能使用 {} ,请使用类似192.168.1.44这样的来自网卡的IP!!!", sipIp);
System.exit(1); System.exit(1);
} }
String mediaIp = mediaConfig.getMediaIp();
if (mediaIp.equals("localhost") || mediaIp.equals("127.0.0.1")) { String[] mediaIpArr = mediaIp.split(",");
logger.warn("mediaIp.ip使用 {} ,将无法收到网络内其他设备的推流!!!", mediaIp ); mediaConfig.setMediaIpArr(mediaIpArr);
for (String mId : mediaIpArr) {
if (mId.equals("localhost") || mId.equals("127.0.0.1")) {
logger.warn("mediaIp.ip使用localhost或127.0.0.1,将无法收到网络内其他设备的推流!!!");
}
} }
HashMap mediaServerSsrcMap = new HashMap<>(mediaIpArr.length);
for (int i = 0; i < mediaIpArr.length; i++) {
String mIp = mediaIpArr[i];
SsrcConfig ssrcConfig = new SsrcConfig();
Set<String> usedSet = jedisUtil.smembers(VideoManagerConstants.MEDIA_SSRC_USED_PREFIX + mIp);
ssrcConfig.init(mIp, usedSet);
mediaServerSsrcMap.put(mIp, ssrcConfig);
}
mediaConfig.setMediaServerSsrcMap(mediaServerSsrcMap);
} }
} }

53
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java

@ -0,0 +1,53 @@
package com.genersoft.iot.vmp.conf;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
/**
* 对配置文件进行校验
*/
@Configuration("mediaConfig")
@Data
public class MediaConfig {
@Value("${media.ip}")
private String mediaIp;
private String[] mediaIpArr;
@Value("${media.hookIp}")
private String mediaHookIp;
@Value("${media.port}")
private Integer mediaPort;
@Value("${media.autoConfig}")
private Boolean autoConfig;
@Value("${media.secret}")
private String mediaSecret;
@Value("${media.streamNoneReaderDelayMS}")
private String streamNoneReaderDelayMS;
@Value("${media.autoApplyPlay}")
private Boolean autoApplyPlay;
@Value("${media.seniorSdp}")
private Boolean seniorSdp;
@Value("${media.rtp.enable}")
private Boolean rtpEnable;
@Value("${media.rtp.udpPortRange}")
private String udpPortRange;
/**
* 每一台ZLM都有一套独立的SSRC列表
* 在ApplicationCheckRunner里对mediaServerSsrcMap进行初始化
*/
private HashMap<String, SsrcConfig> mediaServerSsrcMap;
}

535
src/main/java/com/genersoft/iot/vmp/conf/MediaServerConfig.java

@ -1,7 +1,9 @@
package com.genersoft.iot.vmp.conf; package com.genersoft.iot.vmp.conf;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
@Data
public class MediaServerConfig { public class MediaServerConfig {
@JSONField(name = "api.apiDebug") @JSONField(name = "api.apiDebug")
@ -31,10 +33,6 @@ public class MediaServerConfig {
@JSONField(name = "general.streamNoneReaderDelayMS") @JSONField(name = "general.streamNoneReaderDelayMS")
private String generalStreamNoneReaderDelayMS; private String generalStreamNoneReaderDelayMS;
private String localIP;
private String wanIp;
@JSONField(name = "hls.fileBufSize") @JSONField(name = "hls.fileBufSize")
private String hlsFileBufSize; private String hlsFileBufSize;
@ -199,533 +197,4 @@ public class MediaServerConfig {
@JSONField(name = "shell.shell") @JSONField(name = "shell.shell")
private String shellPhell; private String shellPhell;
public String getApiDebug() {
return apiDebug;
}
public void setApiDebug(String apiDebug) {
this.apiDebug = apiDebug;
}
public String getApiSecret() {
return apiSecret;
}
public void setApiSecret(String apiSecret) {
this.apiSecret = apiSecret;
}
public String getFfmpegBin() {
return ffmpegBin;
}
public void setFfmpegBin(String ffmpegBin) {
this.ffmpegBin = ffmpegBin;
}
public String getFfmpegCmd() {
return ffmpegCmd;
}
public void setFfmpegCmd(String ffmpegCmd) {
this.ffmpegCmd = ffmpegCmd;
}
public String getFfmpegLog() {
return ffmpegLog;
}
public void setFfmpegLog(String ffmpegLog) {
this.ffmpegLog = ffmpegLog;
}
public String getGeneralEnableVhost() {
return generalEnableVhost;
}
public void setGeneralEnableVhost(String generalEnableVhost) {
this.generalEnableVhost = generalEnableVhost;
}
public String getGeneralFlowThreshold() {
return generalFlowThreshold;
}
public void setGeneralFlowThreshold(String generalFlowThreshold) {
this.generalFlowThreshold = generalFlowThreshold;
}
public String getGeneralMaxStreamWaitMS() {
return generalMaxStreamWaitMS;
}
public void setGeneralMaxStreamWaitMS(String generalMaxStreamWaitMS) {
this.generalMaxStreamWaitMS = generalMaxStreamWaitMS;
}
public String getGeneralStreamNoneReaderDelayMS() {
return generalStreamNoneReaderDelayMS;
}
public void setGeneralStreamNoneReaderDelayMS(String generalStreamNoneReaderDelayMS) {
this.generalStreamNoneReaderDelayMS = generalStreamNoneReaderDelayMS;
}
public String getLocalIP() {
return localIP;
}
public void setLocalIP(String localIP) {
this.localIP = localIP;
}
public String getHlsFileBufSize() {
return hlsFileBufSize;
}
public void setHlsFileBufSize(String hlsFileBufSize) {
this.hlsFileBufSize = hlsFileBufSize;
}
public String getHlsFilePath() {
return hlsFilePath;
}
public void setHlsFilePath(String hlsFilePath) {
this.hlsFilePath = hlsFilePath;
}
public String getHlsSegDur() {
return hlsSegDur;
}
public void setHlsSegDur(String hlsSegDur) {
this.hlsSegDur = hlsSegDur;
}
public String getHlsSegNum() {
return hlsSegNum;
}
public void setHlsSegNum(String hlsSegNum) {
this.hlsSegNum = hlsSegNum;
}
public String getHookAccessFileExceptHLS() {
return hookAccessFileExceptHLS;
}
public void setHookAccessFileExceptHLS(String hookAccessFileExceptHLS) {
this.hookAccessFileExceptHLS = hookAccessFileExceptHLS;
}
public String getHookAdminParams() {
return hookAdminParams;
}
public void setHookAdminParams(String hookAdminParams) {
this.hookAdminParams = hookAdminParams;
}
public String getHookEnable() {
return hookEnable;
}
public void setHookEnable(String hookEnable) {
this.hookEnable = hookEnable;
}
public String getHookOnFlowReport() {
return hookOnFlowReport;
}
public void setHookOnFlowReport(String hookOnFlowReport) {
this.hookOnFlowReport = hookOnFlowReport;
}
public String getHookOnHttpAccess() {
return hookOnHttpAccess;
}
public void setHookOnHttpAccess(String hookOnHttpAccess) {
this.hookOnHttpAccess = hookOnHttpAccess;
}
public String getHookOnPlay() {
return hookOnPlay;
}
public void setHookOnPlay(String hookOnPlay) {
this.hookOnPlay = hookOnPlay;
}
public String getHookOnPublish() {
return hookOnPublish;
}
public void setHookOnPublish(String hookOnPublish) {
this.hookOnPublish = hookOnPublish;
}
public String getHookOnRecordMp4() {
return hookOnRecordMp4;
}
public void setHookOnRecordMp4(String hookOnRecordMp4) {
this.hookOnRecordMp4 = hookOnRecordMp4;
}
public String getHookOnRtspAuth() {
return hookOnRtspAuth;
}
public void setHookOnRtspAuth(String hookOnRtspAuth) {
this.hookOnRtspAuth = hookOnRtspAuth;
}
public String getHookOnRtspRealm() {
return hookOnRtspRealm;
}
public void setHookOnRtspRealm(String hookOnRtspRealm) {
this.hookOnRtspRealm = hookOnRtspRealm;
}
public String getHookOnShellLogin() {
return hookOnShellLogin;
}
public void setHookOnShellLogin(String hookOnShellLogin) {
this.hookOnShellLogin = hookOnShellLogin;
}
public String getHookOnStreamChanged() {
return hookOnStreamChanged;
}
public void setHookOnStreamChanged(String hookOnStreamChanged) {
this.hookOnStreamChanged = hookOnStreamChanged;
}
public String getHookOnStreamNoneReader() {
return hookOnStreamNoneReader;
}
public void setHookOnStreamNoneReader(String hookOnStreamNoneReader) {
this.hookOnStreamNoneReader = hookOnStreamNoneReader;
}
public String getHookOnStreamNotFound() {
return hookOnStreamNotFound;
}
public void setHookOnStreamNotFound(String hookOnStreamNotFound) {
this.hookOnStreamNotFound = hookOnStreamNotFound;
}
public String getHookTimeoutSec() {
return hookTimeoutSec;
}
public void setHookTimeoutSec(String hookTimeoutSec) {
this.hookTimeoutSec = hookTimeoutSec;
}
public String getHttpCharSet() {
return httpCharSet;
}
public void setHttpCharSet(String httpCharSet) {
this.httpCharSet = httpCharSet;
}
public String getHttpKeepAliveSecond() {
return httpKeepAliveSecond;
}
public void setHttpKeepAliveSecond(String httpKeepAliveSecond) {
this.httpKeepAliveSecond = httpKeepAliveSecond;
}
public String getHttpMaxReqCount() {
return httpMaxReqCount;
}
public void setHttpMaxReqCount(String httpMaxReqCount) {
this.httpMaxReqCount = httpMaxReqCount;
}
public String getHttpMaxReqSize() {
return httpMaxReqSize;
}
public void setHttpMaxReqSize(String httpMaxReqSize) {
this.httpMaxReqSize = httpMaxReqSize;
}
public String getHttpNotFound() {
return httpNotFound;
}
public void setHttpNotFound(String httpNotFound) {
this.httpNotFound = httpNotFound;
}
public String getHttpPort() {
return httpPort;
}
public void setHttpPort(String httpPort) {
this.httpPort = httpPort;
}
public String getHttpRootPath() {
return httpRootPath;
}
public void setHttpRootPath(String httpRootPath) {
this.httpRootPath = httpRootPath;
}
public String getHttpSendBufSize() {
return httpSendBufSize;
}
public void setHttpSendBufSize(String httpSendBufSize) {
this.httpSendBufSize = httpSendBufSize;
}
public String getHttpSSLport() {
return httpSSLport;
}
public void setHttpSSLport(String httpSSLport) {
this.httpSSLport = httpSSLport;
}
public String getMulticastAddrMax() {
return multicastAddrMax;
}
public void setMulticastAddrMax(String multicastAddrMax) {
this.multicastAddrMax = multicastAddrMax;
}
public String getMulticastAddrMin() {
return multicastAddrMin;
}
public void setMulticastAddrMin(String multicastAddrMin) {
this.multicastAddrMin = multicastAddrMin;
}
public String getMulticastUdpTTL() {
return multicastUdpTTL;
}
public void setMulticastUdpTTL(String multicastUdpTTL) {
this.multicastUdpTTL = multicastUdpTTL;
}
public String getRecordAppName() {
return recordAppName;
}
public void setRecordAppName(String recordAppName) {
this.recordAppName = recordAppName;
}
public String getRecordFilePath() {
return recordFilePath;
}
public void setRecordFilePath(String recordFilePath) {
this.recordFilePath = recordFilePath;
}
public String getRecordFileSecond() {
return recordFileSecond;
}
public void setRecordFileSecond(String recordFileSecond) {
this.recordFileSecond = recordFileSecond;
}
public String getRecordFileSampleMS() {
return recordFileSampleMS;
}
public void setRecordFileSampleMS(String recordFileSampleMS) {
this.recordFileSampleMS = recordFileSampleMS;
}
public String getRtmpHandshakeSecond() {
return rtmpHandshakeSecond;
}
public void setRtmpHandshakeSecond(String rtmpHandshakeSecond) {
this.rtmpHandshakeSecond = rtmpHandshakeSecond;
}
public String getRtmpKeepAliveSecond() {
return rtmpKeepAliveSecond;
}
public void setRtmpKeepAliveSecond(String rtmpKeepAliveSecond) {
this.rtmpKeepAliveSecond = rtmpKeepAliveSecond;
}
public String getRtmpModifyStamp() {
return rtmpModifyStamp;
}
public void setRtmpModifyStamp(String rtmpModifyStamp) {
this.rtmpModifyStamp = rtmpModifyStamp;
}
public String getRtmpPort() {
return rtmpPort;
}
public void setRtmpPort(String rtmpPort) {
this.rtmpPort = rtmpPort;
}
public String getRtpAudioMtuSize() {
return rtpAudioMtuSize;
}
public void setRtpAudioMtuSize(String rtpAudioMtuSize) {
this.rtpAudioMtuSize = rtpAudioMtuSize;
}
public String getRtpClearCount() {
return rtpClearCount;
}
public void setRtpClearCount(String rtpClearCount) {
this.rtpClearCount = rtpClearCount;
}
public String getRtpCycleMS() {
return rtpCycleMS;
}
public void setRtpCycleMS(String rtpCycleMS) {
this.rtpCycleMS = rtpCycleMS;
}
public String getRtpMaxRtpCount() {
return rtpMaxRtpCount;
}
public void setRtpMaxRtpCount(String rtpMaxRtpCount) {
this.rtpMaxRtpCount = rtpMaxRtpCount;
}
public String getRtpVideoMtuSize() {
return rtpVideoMtuSize;
}
public void setRtpVideoMtuSize(String rtpVideoMtuSize) {
this.rtpVideoMtuSize = rtpVideoMtuSize;
}
public String getRtpProxyCheckSource() {
return rtpProxyCheckSource;
}
public void setRtpProxyCheckSource(String rtpProxyCheckSource) {
this.rtpProxyCheckSource = rtpProxyCheckSource;
}
public String getRtpProxyDumpDir() {
return rtpProxyDumpDir;
}
public void setRtpProxyDumpDir(String rtpProxyDumpDir) {
this.rtpProxyDumpDir = rtpProxyDumpDir;
}
public String getRtpProxyPort() {
return rtpProxyPort;
}
public void setRtpProxyPort(String rtpProxyPort) {
this.rtpProxyPort = rtpProxyPort;
}
public String getRtpProxyTimeoutSec() {
return rtpProxyTimeoutSec;
}
public void setRtpProxyTimeoutSec(String rtpProxyTimeoutSec) {
this.rtpProxyTimeoutSec = rtpProxyTimeoutSec;
}
public String getRtspAuthBasic() {
return rtspAuthBasic;
}
public void setRtspAuthBasic(String rtspAuthBasic) {
this.rtspAuthBasic = rtspAuthBasic;
}
public String getRtspHandshakeSecond() {
return rtspHandshakeSecond;
}
public void setRtspHandshakeSecond(String rtspHandshakeSecond) {
this.rtspHandshakeSecond = rtspHandshakeSecond;
}
public String getRtspKeepAliveSecond() {
return rtspKeepAliveSecond;
}
public void setRtspKeepAliveSecond(String rtspKeepAliveSecond) {
this.rtspKeepAliveSecond = rtspKeepAliveSecond;
}
public String getRtspPort() {
return rtspPort;
}
public void setRtspPort(String rtspPort) {
this.rtspPort = rtspPort;
}
public String getRtspSSlport() {
return rtspSSlport;
}
public void setRtspSSlport(String rtspSSlport) {
this.rtspSSlport = rtspSSlport;
}
public String getShellMaxReqSize() {
return shellMaxReqSize;
}
public void setShellMaxReqSize(String shellMaxReqSize) {
this.shellMaxReqSize = shellMaxReqSize;
}
public String getShellPhell() {
return shellPhell;
}
public void setShellPhell(String shellPhell) {
this.shellPhell = shellPhell;
}
public String getWanIp() {
return wanIp;
}
public void setWanIp(String wanIp) {
this.wanIp = wanIp;
}
} }

96
src/main/java/com/genersoft/iot/vmp/conf/RedisConfig.java

@ -1,5 +1,9 @@
package com.genersoft.iot.vmp.conf; package com.genersoft.iot.vmp.conf;
import com.alibaba.fastjson.parser.ParserConfig;
import com.genersoft.iot.vmp.utils.redis.FastJsonRedisSerializer;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -7,47 +11,75 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPool;
import com.alibaba.fastjson.parser.ParserConfig; import redis.clients.jedis.JedisPoolConfig;
import com.genersoft.iot.vmp.utils.redis.FastJsonRedisSerializer;
/** /**
* @Description:Redis中间件配置类使用spring-data-redis集成自动从application.yml中加载redis配置 * @Description:Redis中间件配置类使用spring-data-redis集成自动从application.yml中加载redis配置
* @author: swwheihei * @author: swwheihei
* @date: 2019年5月30日 上午10:58:25 * @date: 2019年5月30日 上午10:58:25
*
*/ */
@Configuration @Configuration
public class RedisConfig extends CachingConfigurerSupport { public class RedisConfig extends CachingConfigurerSupport {
@Bean("redisTemplate") @Value("${spring.redis.host}")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { private String host;
RedisTemplate<Object, Object> template = new RedisTemplate<>(); @Value("${spring.redis.port}")
template.setConnectionFactory(redisConnectionFactory); private int port;
// 使用fastjson进行序列化处理,提高解析效率 @Value("${spring.redis.database}")
FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<Object>(Object.class); private int database;
// value值的序列化采用fastJsonRedisSerializer @Value("${spring.redis.password}")
template.setValueSerializer(serializer); private String password;
template.setHashValueSerializer(serializer); @Value("${spring.redis.timeout}")
// key的序列化采用StringRedisSerializer private int timeout;
template.setKeySerializer(new StringRedisSerializer()); @Value("${spring.redis.poolMaxTotal}")
template.setHashKeySerializer(new StringRedisSerializer()); private int poolMaxTotal;
template.setConnectionFactory(redisConnectionFactory); @Value("${spring.redis.poolMaxIdle}")
// 使用fastjson时需设置此项,否则会报异常not support type private int poolMaxIdle;
ParserConfig.getGlobalInstance().setAutoTypeSupport(true); @Value("${spring.redis.poolMaxWait}")
return template; private int poolMaxWait;
}
@Bean
/** public JedisPool jedisPool() {
* redis消息监听器容器 可以添加多个监听不同话题的redis监听器只需要把消息监听器和相应的消息订阅处理器绑定该消息监听器 if (StringUtils.isBlank(password)) {
* 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理 password = null;
* }
* @param connectionFactory JedisPoolConfig poolConfig = new JedisPoolConfig();
* @param listenerAdapter poolConfig.setMaxIdle(poolMaxIdle);
* @return poolConfig.setMaxTotal(poolMaxTotal);
*/ // 秒转毫秒
@Bean poolConfig.setMaxWaitMillis(poolMaxWait * 1000);
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) { JedisPool jp = new JedisPool(poolConfig, host, port, timeout * 1000, password, database);
return jp;
}
@Bean("redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 使用fastjson进行序列化处理,提高解析效率
FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<Object>(Object.class);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
// 使用fastjson时需设置此项,否则会报异常not support type
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
return template;
}
/**
* redis消息监听器容器 可以添加多个监听不同话题的redis监听器只需要把消息监听器和相应的消息订阅处理器绑定该消息监听器
* 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
*
* @param connectionFactory
* @return
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer(); RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory); container.setConnectionFactory(connectionFactory);

29
src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java

@ -8,15 +8,15 @@ import org.springframework.context.annotation.Configuration;
public class SipConfig { public class SipConfig {
@Value("${sip.ip}") @Value("${sip.ip}")
private String sipIp; String sipIp;
@Value("${sip.port}") @Value("${sip.port}")
private Integer sipPort; Integer sipPort;
@Value("${sip.domain}") @Value("${sip.domain}")
private String sipDomain; String sipDomain;
@Value("${sip.id}") @Value("${sip.id}")
private String sipId; String sipId;
@Value("${sip.password}") @Value("${sip.password}")
private String sipPassword; String sipPassword;
@Value("${sip.ptz.speed:50}") @Value("${sip.ptz.speed:50}")
Integer speed; Integer speed;
@ -25,28 +25,47 @@ public class SipConfig {
return sipIp; return sipIp;
} }
public void setSipIp(String sipIp) {
this.sipIp = sipIp;
}
public Integer getSipPort() { public Integer getSipPort() {
return sipPort; return sipPort;
} }
public void setSipPort(Integer sipPort) {
this.sipPort = sipPort;
}
public String getSipDomain() { public String getSipDomain() {
return sipDomain; return sipDomain;
} }
public void setSipDomain(String sipDomain) {
this.sipDomain = sipDomain;
}
public String getSipId() { public String getSipId() {
return sipId; return sipId;
} }
public void setSipId(String sipId) {
this.sipId = sipId;
}
public String getSipPassword() { public String getSipPassword() {
return sipPassword; return sipPassword;
} }
public void setSipPassword(String sipPassword) {
this.sipPassword = sipPassword;
}
public Integer getSpeed() { public Integer getSpeed() {
return speed; return speed;
} }
public void setSpeed(Integer speed) {
this.speed = speed;
}
} }

51
src/main/java/com/genersoft/iot/vmp/conf/SsrcConfig.java

@ -0,0 +1,51 @@
package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.utils.ConfigConst;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* 每一个zlm流媒体服务器都设置MAX_STRTEAM_COUNT个可用同步信源(SSRC)
*/
@Data
public class SsrcConfig {
/**
* zlm流媒体服务器IP
*/
String mediaServerIp;
/**
* zlm流媒体服务器已用会话句柄
*/
private List<String> isUsed;
/**
* zlm流媒体服务器可用会话句柄
*/
private List<String> notUsed;
public void init(String mediaServerIp, Set<String> usedSet) {
this.mediaServerIp = mediaServerIp;
this.isUsed = new ArrayList<>();
this.notUsed = new ArrayList<>();
for (int i = 1; i < ConfigConst.MAX_STRTEAM_COUNT; i++) {
String ssrc;
if (i < 10) {
ssrc = "000" + i;
} else if (i < 100) {
ssrc = "00" + i;
} else if (i < 1000) {
ssrc = "0" + i;
} else {
ssrc = String.valueOf(i);
}
if (null == usedSet || !usedSet.contains(ssrc)) {
this.notUsed.add(ssrc);
} else {
this.isUsed.add(ssrc);
}
}
}
}

386
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java

@ -1,132 +1,157 @@
package com.genersoft.iot.vmp.gb28181; package com.genersoft.iot.vmp.gb28181;
import java.text.ParseException; import com.genersoft.iot.vmp.conf.SipConfig;
import java.util.Properties;
import java.util.TooManyListenersException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.sip.*;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Response;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorFactory;
import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
import gov.nist.javax.sip.SipStackImpl;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
// import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.SipConfig; import javax.sip.*;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorFactory; import javax.sip.header.CallIdHeader;
import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor; import javax.sip.message.Response;
import java.text.ParseException;
import gov.nist.javax.sip.SipStackImpl; import java.util.Properties;
import java.util.TooManyListenersException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Component @Component
public class SipLayer implements SipListener { public class SipLayer implements SipListener {
private final static Logger logger = LoggerFactory.getLogger(SipLayer.class); private final static Logger logger = LoggerFactory.getLogger(SipLayer.class);
@Autowired @Autowired
private SipConfig sipConfig; private SipConfig sipConfig;
@Autowired @Autowired
private SIPProcessorFactory processorFactory; private SIPProcessorFactory processorFactory;
@Autowired @Autowired
private SipSubscribe sipSubscribe; private SipSubscribe sipSubscribe;
private SipStack sipStack; private SipStack sipStack;
private SipFactory sipFactory; private SipFactory sipFactory;
/** /**
* 消息处理器线程池 * 消息处理器线程池
*/ */
private ThreadPoolExecutor processThreadPool; private ThreadPoolExecutor processThreadPool;
@Bean("initSipServer") @Bean("initSipServer")
private ThreadPoolExecutor initSipServer() { private ThreadPoolExecutor initSipServer() {
int processThreadNum = Runtime.getRuntime().availableProcessors() * 10; int processThreadNum = Runtime.getRuntime().availableProcessors() * 10;
LinkedBlockingQueue<Runnable> processQueue = new LinkedBlockingQueue<Runnable>(10000); LinkedBlockingQueue<Runnable> processQueue = new LinkedBlockingQueue<>(10000);
processThreadPool = new ThreadPoolExecutor(processThreadNum,processThreadNum, processThreadPool = new ThreadPoolExecutor(processThreadNum, processThreadNum,
0L,TimeUnit.MILLISECONDS,processQueue, 0L, TimeUnit.MILLISECONDS, processQueue,
new ThreadPoolExecutor.CallerRunsPolicy()); new ThreadPoolExecutor.CallerRunsPolicy());
return processThreadPool; return processThreadPool;
} }
@Bean("sipFactory") @Bean("sipFactory")
@DependsOn("initSipServer") @DependsOn("initSipServer")
private SipFactory createSipFactory() { private SipFactory createSipFactory() {
sipFactory = SipFactory.getInstance(); sipFactory = SipFactory.getInstance();
sipFactory.setPathName("gov.nist"); sipFactory.setPathName("gov.nist");
return sipFactory; return sipFactory;
} }
@Bean("sipStack") @Bean("sipStack")
@DependsOn({"initSipServer", "sipFactory"}) @DependsOn({"initSipServer", "sipFactory"})
private SipStack createSipStack() throws PeerUnavailableException { private SipStack createSipStack() throws PeerUnavailableException {
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP"); properties.setProperty("javax.sip.STACK_NAME", "GB28181_SIP");
properties.setProperty("javax.sip.IP_ADDRESS", sipConfig.getSipIp()); properties.setProperty("javax.sip.IP_ADDRESS", sipConfig.getSipIp());
properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false"); properties.setProperty("gov.nist.javax.sip.LOG_MESSAGE_CONTENT", "false");
/** /**
* sip_server_log.log sip_debug_log.log public static final int TRACE_NONE = * sip_server_log.log sip_debug_log.log public static final int TRACE_NONE =
* 0; public static final int TRACE_MESSAGES = 16; public static final int * 0; public static final int TRACE_MESSAGES = 16; public static final int
* TRACE_EXCEPTION = 17; public static final int TRACE_DEBUG = 32; * TRACE_EXCEPTION = 17; public static final int TRACE_DEBUG = 32;
*/ */
properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "0"); properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "0");
properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "sip_server_log"); properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "sip_server_log");
properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "sip_debug_log"); properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "sip_debug_log");
sipStack = (SipStackImpl) sipFactory.createSipStack(properties); sipStack = (SipStackImpl) sipFactory.createSipStack(properties);
return sipStack; return sipStack;
} }
@Bean("tcpSipProvider") @Bean("tcpSipProvider")
@DependsOn("sipStack") @DependsOn("sipStack")
private SipProvider startTcpListener() { private SipProvider startTcpListener() {
ListeningPoint tcpListeningPoint = null; ListeningPoint tcpListeningPoint = null;
SipProvider tcpSipProvider = null; SipProvider tcpSipProvider = null;
try { try {
tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "TCP"); tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "TCP");
tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint); } catch (TransportNotSupportedException e) {
tcpSipProvider.addSipListener(this); logger.error("创建SIP服务失败!", e);
logger.info("Sip Server TCP 启动成功 port {" + sipConfig.getSipPort() + "}"); System.exit(1);
} catch (TransportNotSupportedException | InvalidArgumentException | TooManyListenersException | ObjectInUseException e) { } catch (InvalidArgumentException e) {
logger.error(String.format("创建SIP服务失败: %s", e.getMessage())); if ("Cannot assign requested address: JVM_Bind".equals(e.getMessage())) {
} logger.error("创建SIP服务失败,请检查sip.ip必须是本地网卡上的IP!", e);
return tcpSipProvider; } else {
} logger.error("创建SIP服务失败!", e);
}
@Bean("udpSipProvider") System.exit(1);
@DependsOn("sipStack") }
private SipProvider startUdpListener() throws Exception { try {
ListeningPoint udpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "UDP"); tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint);
SipProvider udpSipProvider = sipStack.createSipProvider(udpListeningPoint); } catch (ObjectInUseException e) {
udpSipProvider.addSipListener(this); logger.error("创建SIP服务失败!", e);
logger.info("Sip Server UDP 启动成功 port {" + sipConfig.getSipPort() + "}"); System.exit(1);
return udpSipProvider; }
} try {
tcpSipProvider.addSipListener(this);
/** } catch (TooManyListenersException e) {
* SIP服务端接收消息的方法 Content 里面是GBK编码 This method is called by the SIP stack when a logger.error("创建SIP服务失败!", e);
* new request arrives. System.exit(1);
*/ }
@Override logger.info("Sip Server TCP 启动成功 port {" + sipConfig.getSipPort() + "}");
public void processRequest(RequestEvent evt) { return tcpSipProvider;
logger.debug(evt.getRequest().toString());
// 由于jainsip是单线程程序,为提高性能并发处理
processThreadPool.execute(() -> { // try {
if (processorFactory != null) { // tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "TCP");
processorFactory.createRequestProcessor(evt).process(); // tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint);
} // tcpSipProvider.addSipListener(this);
}); // logger.info("Sip Server TCP 启动成功 port {" + sipConfig.getSipPort() + "}");
} // } catch (TransportNotSupportedException | InvalidArgumentException | TooManyListenersException | ObjectInUseException e) {
// logger.error(String.format("创建SIP服务失败: %s", e.getMessage()));
// }
// return tcpSipProvider;
}
@Bean("udpSipProvider")
@DependsOn("sipStack")
private SipProvider startUdpListener() throws Exception {
ListeningPoint udpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "UDP");
SipProvider udpSipProvider = sipStack.createSipProvider(udpListeningPoint);
udpSipProvider.addSipListener(this);
logger.info("Sip Server UDP 启动成功 port {" + sipConfig.getSipPort() + "}");
return udpSipProvider;
}
/**
* SIP服务端接收消息的方法 Content 里面是GBK编码 This method is called by the SIP stack when a
* new request arrives.
*/
@Override
public void processRequest(RequestEvent evt) {
logger.debug(evt.getRequest().toString());
// 由于jainsip是单线程程序,为提高性能并发处理
processThreadPool.execute(() -> {
if (processorFactory != null) {
processorFactory.createRequestProcessor(evt).process();
}
});
}
@Override @Override
public void processResponse(ResponseEvent evt) { public void processResponse(ResponseEvent evt) {
@ -166,71 +191,70 @@ public class SipLayer implements SipListener {
} }
}
}
/**
/** * <p>
* <p> * Title: processTimeout
* Title: processTimeout * </p>
* </p> * <p>
* <p> * Description:
* Description: * </p>
* </p> *
* * @param timeoutEvent
* @param timeoutEvent */
*/ @Override
@Override public void processTimeout(TimeoutEvent timeoutEvent) {
public void processTimeout(TimeoutEvent timeoutEvent) { // TODO Auto-generated method stub
// TODO Auto-generated method stub
}
}
/**
/** * <p>
* <p> * Title: processIOException
* Title: processIOException * </p>
* </p> * <p>
* <p> * Description:
* Description: * </p>
* </p> *
* * @param exceptionEvent
* @param exceptionEvent */
*/ @Override
@Override public void processIOException(IOExceptionEvent exceptionEvent) {
public void processIOException(IOExceptionEvent exceptionEvent) { // TODO Auto-generated method stub
// TODO Auto-generated method stub
}
}
/**
/** * <p>
* <p> * Title: processTransactionTerminated
* Title: processTransactionTerminated * </p>
* </p> * <p>
* <p> * Description:
* Description: * </p>
* </p> *
* * @param transactionTerminatedEvent
* @param transactionTerminatedEvent */
*/ @Override
@Override public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) { // TODO Auto-generated method stub
// TODO Auto-generated method stub
}
}
/**
/** * <p>
* <p> * Title: processDialogTerminated
* Title: processDialogTerminated * </p>
* </p> * <p>
* <p> * Description:
* Description: * </p>
* </p> *
* * @param dialogTerminatedEvent
* @param dialogTerminatedEvent */
*/ @Override
@Override public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) { // TODO Auto-generated method stub
// TODO Auto-generated method stub
}
}
} }

4
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java

@ -45,7 +45,7 @@ public class Device {
/** /**
* wan地址_ip * wan地址_ip
*/ */
private String ip; private String ip;
/** /**
* wan地址_port * wan地址_port
@ -55,7 +55,7 @@ public class Device {
/** /**
* wan地址 * wan地址
*/ */
private String hostAddress; private String hostAddress;
/** /**
* 在线 * 在线

6
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java

@ -153,7 +153,7 @@ public class DeviceChannel {
/** /**
* 是否含有音频 * 是否含有音频
*/ */
private boolean hasAudio; private Boolean hasAudio;
public String getDeviceId() { public String getDeviceId() {
return deviceId; return deviceId;
@ -388,11 +388,11 @@ public class DeviceChannel {
this.subCount = subCount; this.subCount = subCount;
} }
public boolean isHasAudio() { public Boolean isHasAudio() {
return hasAudio; return hasAudio;
} }
public void setHasAudio(boolean hasAudio) { public void setHasAudio(Boolean hasAudio) {
this.hasAudio = hasAudio; this.hasAudio = hasAudio;
} }

2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java

@ -1,8 +1,6 @@
package com.genersoft.iot.vmp.gb28181.bean; package com.genersoft.iot.vmp.gb28181.bean;
//import gov.nist.javax.sip.header.SIPDate;
import java.util.List; import java.util.List;
/** /**

2
src/main/java/com/genersoft/iot/vmp/gb28181/event/DeviceOffLineDetector.java

@ -17,7 +17,7 @@ public class DeviceOffLineDetector {
@Autowired @Autowired
private RedisUtil redis; private RedisUtil redis;
public boolean isOnline(String deviceId) { public Boolean isOnline(String deviceId) {
String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + deviceId; String key = VideoManagerConstants.KEEPLIVEKEY_PREFIX + deviceId;
return redis.hasKey(key); return redis.hasKey(key);
} }

23
src/main/java/com/genersoft/iot/vmp/gb28181/session/PlayTypeEnum.java

@ -0,0 +1,23 @@
package com.genersoft.iot.vmp.gb28181.session;
public enum PlayTypeEnum {
PLAY("0", "直播"),
PLAY_BACK("1", "回放");
private String value;
private String name;
PlayTypeEnum(String value, String name) {
this.value = value;
this.name = name;
}
public String getValue() {
return value;
}
public String getName() {
return name;
}
}

93
src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcUtil.java

@ -1,93 +0,0 @@
package com.genersoft.iot.vmp.gb28181.session;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.utils.SpringBeanFactory;
/**
* @Description:SIP信令中的SSRC工具类SSRC值由10位十进制整数组成的字符串第一位为0代表实况为1则代表回放第二位至第六位由监控域ID的第4位到第8位组成最后4位为不重复的4个整数
* @author: swwheihei
* @date: 2020年5月10日 上午11:57:57
*/
public class SsrcUtil {
private static String ssrcPrefix;
private static List<String> isUsed;
private static List<String> notUsed;
private static void init() {
SipConfig sipConfig = (SipConfig) SpringBeanFactory.getBean("sipConfig");
ssrcPrefix = sipConfig.getSipDomain().substring(3, 8);
isUsed = new ArrayList<String>();
notUsed = new ArrayList<String>();
for (int i = 1; i < 10000; i++) {
if (i < 10) {
notUsed.add("000" + i);
} else if (i < 100) {
notUsed.add("00" + i);
} else if (i < 1000) {
notUsed.add("0" + i);
} else {
notUsed.add(String.valueOf(i));
}
}
}
/**
* 获取视频预览的SSRC值,第一位固定为0
*
*/
public static String getPlaySsrc() {
return "0" + getSsrcPrefix() + getSN();
}
/**
* 获取录像回放的SSRC值,第一位固定为1
*
*/
public static String getPlayBackSsrc() {
return "1" + getSsrcPrefix() + getSN();
}
/**
* 释放ssrc主要用完的ssrc一定要释放否则会耗尽
*
*/
public static void releaseSsrc(String ssrc) {
String sn = ssrc.substring(6);
isUsed.remove(sn);
notUsed.add(sn);
}
/**
* 获取后四位数SN,随机数
*
*/
private static String getSN() {
String sn = null;
int index = 0;
if (notUsed.size() == 0) {
throw new RuntimeException("ssrc已经用完");
} else if (notUsed.size() == 1) {
sn = notUsed.get(0);
} else {
index = new Random().nextInt(notUsed.size() - 1);
sn = notUsed.get(index);
}
notUsed.remove(index);
isUsed.add(sn);
return sn;
}
private static String getSsrcPrefix() {
if (ssrcPrefix == null) {
init();
}
return ssrcPrefix;
}
}

291
src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java

@ -1,42 +1,285 @@
package com.genersoft.iot.vmp.gb28181.session; package com.genersoft.iot.vmp.gb28181.session;
import java.util.concurrent.ConcurrentHashMap; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.SsrcConfig;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.redis.JedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.sip.ClientTransaction; import javax.sip.ClientTransaction;
import java.util.List;
import org.springframework.stereotype.Component; import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* @Description:视频流session管理器管理视频预览预览回放的通信句柄 * @Description:视频流session管理器管理视频预览预览回放的通信句柄
* @author: swwheihei * @author: swwheihei
* @date: 2020年5月13日 下午4:03:02 * @date: 2020年5月13日 下午4:03:02
*/ */
@Slf4j
@Component @Component
public class VideoStreamSessionManager { public class VideoStreamSessionManager {
/**
* key: ssrc 播流会话句柄(streamId)和同步信源(SSRC)的对应关系
* value: 流媒体服务器
*/
private ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>();
private String ssrcPrefix;
@Autowired
private SipConfig sipConfig;
@Autowired
private MediaConfig mediaConfig;
@Autowired
private JedisUtil jedisUtil;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@PostConstruct
public void init() {
this.ssrcPrefix = sipConfig.getSipDomain().substring(3, 8);
}
/**
* 获取视频预览的会话信息
*/
public StreamInfo createPlayStreamInfo(Device device, String channelId) {
// SSRC值,第一位固定为0
StreamInfo streamInfo = createStreamInfo(device, channelId, PlayTypeEnum.PLAY);
// 会话句柄和ZLM服务器的对应关系,存到redis,防止服务器宕机数据丢失。
redisCatchStorage.startPlay(streamInfo);
return streamInfo;
}
/**
* 获取录像回放的会话信息
* 会话句柄和ZLM服务器的对应关系存到redis防止服务器宕机数据丢失
*/
public StreamInfo createPlayBackStreamInfo(Device device, String channelId) {
// SSRC值,第一位固定为1
StreamInfo streamInfo = createStreamInfo(device, channelId, PlayTypeEnum.PLAY_BACK);
// 会话句柄和ZLM服务器的对应关系,存到redis,防止服务器宕机数据丢失。
redisCatchStorage.startPlayback(streamInfo);
return streamInfo;
}
/**
* 1选举ZLM服务器
* 2分配SSRC
* 3生成streamId和播流RUL如果此时未连接ZLM会抛出运行时异常
* 4已分配SSRC存储到redis防止服务器宕机后数据丢失
*/
private StreamInfo createStreamInfo(Device device, String channelId, PlayTypeEnum playType) {
// 1、选举ZLM服务器
SsrcConfig ssrcConfig = elect();
List<String> isUsed = ssrcConfig.getIsUsed();
List<String> notUsed = ssrcConfig.getNotUsed();
// 2、分配SSRC
String sn;
int index = 0;
if (notUsed.size() == 0) {
throw new RuntimeException("ssrc已经用完");
} else if (notUsed.size() == 1) {
sn = notUsed.get(0);
} else {
index = new Random().nextInt(notUsed.size() - 1);
sn = notUsed.get(index);
}
String ssrc = playType.getValue() + ssrcPrefix + sn;
String mediaServerIp = ssrcConfig.getMediaServerIp();
// 3、生成streamId和播流RUL,如果此时未连接ZLM,会抛出运行时异常
StreamInfo streamInfo = initStreamInfo(device, channelId, ssrc, mediaServerIp);
// 4、已分配SSRC存储到redis,防止服务器宕机后数据丢失。
jedisUtil.sadd(VideoManagerConstants.MEDIA_SSRC_USED_PREFIX + mediaServerIp, sn);
notUsed.remove(index);
isUsed.add(sn);
return streamInfo;
}
/**
* 流媒体服务器选举算法
*
* @return
*/
private SsrcConfig elect() {
Set<Map.Entry<String, SsrcConfig>> entries = mediaConfig.getMediaServerSsrcMap().entrySet();
SsrcConfig min = null;
for (Map.Entry<String, SsrcConfig> e : entries) {
SsrcConfig vo = e.getValue();
if (null == min) {
min = vo;
continue;
}
if (vo.getNotUsed().size() > min.getNotUsed().size()) {
min = vo;
}
}
return min;
}
/**
* 生成streamId和播流RUL如果此时未连接ZLM会抛出运行时异常
*
* @param device
* @param channelId
* @param ssrc
* @param mediaServerIp
* @return
*/
private StreamInfo initStreamInfo(Device device, String channelId, String ssrc, String mediaServerIp) {
String streamId;
if (ssrc.startsWith(PlayTypeEnum.PLAY.getValue()) && mediaConfig.getRtpEnable()) {
streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
} else {
streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
}
StreamInfo streamInfo = new StreamInfo();
streamInfo.setMediaServerIp(mediaServerIp);
streamInfo.setSsrc(ssrc);
streamInfo.setStreamId(streamId);
streamInfo.setDeviceID(device.getDeviceId());
streamInfo.setChannelId(channelId);
MediaServerConfig mediaServerConfig = redisCatchStorage.getMediaInfo();
if (null == mediaServerConfig) {
throw new RuntimeException("点播时发现ZLM尚未连接...");
}
streamInfo.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaServerIp, mediaServerConfig.getHttpPort(), streamId));
streamInfo.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaServerIp, mediaServerConfig.getHttpPort(), streamId));
streamInfo.setFmp4(String.format("http://%s:%s/rtp/%s.live.mp4", mediaServerIp, mediaServerConfig.getHttpPort(), streamId));
streamInfo.setWs_fmp4(String.format("ws://%s:%s/rtp/%s.live.mp4", mediaServerIp, mediaServerConfig.getHttpPort(), streamId));
streamInfo.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaServerIp, mediaServerConfig.getHttpPort(), streamId));
streamInfo.setWs_hls(String.format("ws://%s:%s/rtp/%s/hls.m3u8", mediaServerIp, mediaServerConfig.getHttpPort(), streamId));
streamInfo.setTs(String.format("http://%s:%s/rtp/%s.live.ts", mediaServerIp, mediaServerConfig.getHttpPort(), streamId));
streamInfo.setWs_ts(String.format("ws://%s:%s/rtp/%s.live.ts", mediaServerIp, mediaServerConfig.getHttpPort(), streamId));
streamInfo.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaServerIp, mediaServerConfig.getRtmpPort(), streamId));
streamInfo.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaServerIp, mediaServerConfig.getRtspPort(), streamId));
return streamInfo;
}
/**
* 查找IPC通道播流使用流媒体服务器的IP
*
* @param channelId
* @param streamId
* @return
*/
public String getMediaServerIp(String channelId, String streamId) {
StreamInfo streamInfo = this.getStreamInfo(channelId, streamId);
return null == streamInfo ? null : streamInfo.getMediaServerIp();
}
public StreamInfo getPlayStreamInfo(String channelId) {
if (StringUtils.isBlank(channelId)) {
log.error("getPlayStreamInfo channelId can not be null!!!");
return null;
}
return redisCatchStorage.queryPlayByChannel(channelId);
}
public StreamInfo getPlayBackStreamInfo(String channelId) {
if (StringUtils.isBlank(channelId)) {
log.error("getPlayBackStreamInfo channelId can not be null!!!");
return null;
}
return redisCatchStorage.queryPlaybackByChannel(channelId);
}
public StreamInfo getStreamInfo(String channelId, String streamId) {
if (StringUtils.isBlank(channelId) || StringUtils.isBlank(streamId)) {
log.error("getStreamInfo channelId and streamId can not be null!!!");
return null;
}
StreamInfo streamInfo = getStreamInfo(channelId, streamId, PlayTypeEnum.PLAY);
if (null == streamInfo) {
streamInfo = getStreamInfo(channelId, streamId, PlayTypeEnum.PLAY_BACK);
}
return streamInfo;
}
private StreamInfo getStreamInfo(String channelId, String streamId, PlayTypeEnum playType) {
if (StringUtils.isBlank(channelId) || StringUtils.isBlank(streamId)) {
log.error("getStreamInfo channelId and streamId can not be null!!!");
return null;
}
// TODO channelId
if (null == playType || PlayTypeEnum.PLAY.equals(playType)) {
return redisCatchStorage.queryPlayByStreamId(channelId, streamId);
} else {
return redisCatchStorage.queryPlaybackByStreamId(channelId, streamId);
}
}
/**
* 存储会话
*
* @param channelId
* @param streamId
* @param transaction
*/
public void putClientTransaction(String channelId, String streamId, ClientTransaction transaction) {
String streamKey = getStreamKey(channelId, streamId);
sessionMap.put(streamKey, transaction);
}
private ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>(); public ClientTransaction getClientTransaction(String channelId, String streamId) {
private ConcurrentHashMap<String, String> ssrcMap = new ConcurrentHashMap<>(); String streamKey = getStreamKey(channelId, streamId);
return sessionMap.get(streamKey);
}
public String createPlaySsrc(){ public void remove(String channelId, String streamId) {
return SsrcUtil.getPlaySsrc(); StreamInfo streamInfo = this.getStreamInfo(channelId, streamId);
} if (null == streamId) {
return;
}
this.remove(streamInfo);
}
public String createPlayBackSsrc(){ /**
return SsrcUtil.getPlayBackSsrc(); * 移除会话并释放ssrc主要用完的ssrc一定要释放否则会耗尽
} */
public void remove(StreamInfo streamInfo) {
String streamKey = getStreamKey(streamInfo.getChannelId(), streamInfo.getStreamId());
// 移除会话
sessionMap.remove(streamKey);
public void put(String streamId,String ssrc,ClientTransaction transaction){ String ssrc = streamInfo.getSsrc();
sessionMap.put(streamId, transaction); String sn = ssrc.substring(6);
ssrcMap.put(streamId, ssrc); String mediaServerIp = streamInfo.getMediaServerIp();
} // 释放ssrc,并从redis移除
jedisUtil.srem(VideoManagerConstants.MEDIA_SSRC_USED_PREFIX + mediaServerIp, sn);
SsrcConfig ssrcConfig = mediaConfig.getMediaServerSsrcMap().get(mediaServerIp);
ssrcConfig.getIsUsed().remove(sn);
ssrcConfig.getNotUsed().add(sn);
public ClientTransaction get(String streamId){ // 会话句柄和ZLM服务器的对应关系,从redis移除
return sessionMap.get(streamId); if (ssrc.startsWith(PlayTypeEnum.PLAY.getValue())) {
} redisCatchStorage.stopPlay(streamInfo);
} else {
redisCatchStorage.stopPlayback(streamInfo);
}
}
public void remove(String streamId) { private static String getStreamKey(String channelId, String streamId) {
sessionMap.remove(streamId); return channelId + "_" + streamId;
SsrcUtil.releaseSsrc(ssrcMap.get(streamId)); }
ssrcMap.remove(streamId);
}
} }

9
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java

@ -13,7 +13,6 @@ import org.springframework.web.context.request.async.DeferredResult;
* @author: swwheihei * @author: swwheihei
* @date: 2020年5月8日 下午7:59:05 * @date: 2020年5月8日 下午7:59:05
*/ */
@SuppressWarnings(value = {"rawtypes", "unchecked"})
@Component @Component
public class DeferredResultHolder { public class DeferredResultHolder {
@ -49,12 +48,10 @@ public class DeferredResultHolder {
map.put(key, result); map.put(key, result);
} }
public DeferredResult get(String key) {
return map.get(key);
}
public void invokeResult(RequestMessage msg) { public void invokeResult(RequestMessage msg) {
DeferredResult result = map.get(msg.getId()); // DeferredResult result = map.get(msg.getId());
// 获取并移除
DeferredResult result = map.remove(msg.getId());
if (result == null) { if (result == null) {
return; return;
} }

7
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.gb28181.transmit.cmd; package com.genersoft.iot.vmp.gb28181.transmit.cmd;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
@ -106,10 +107,10 @@ public interface ISIPCommander {
/** /**
* 视频流停止 * 视频流停止
* *
* @param ssrc ssrc * @param streamInfo streamInfo
* @param okEvent okEvent
*/ */
void streamByeCmd(String ssrc, SipSubscribe.Event okEvent); void stopStreamByeCmd(StreamInfo streamInfo, SipSubscribe.Event okEvent);
void streamByeCmd(String ssrc);
/** /**
* 语音广播 * 语音广播

1235
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java

File diff suppressed because it is too large

1080
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java

File diff suppressed because it is too large

82
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java

@ -1,6 +1,9 @@
package com.genersoft.iot.vmp.gb28181.transmit.response.impl; package com.genersoft.iot.vmp.gb28181.transmit.response.impl;
import java.text.ParseException; import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
import org.springframework.stereotype.Component;
import javax.sip.Dialog; import javax.sip.Dialog;
import javax.sip.InvalidArgumentException; import javax.sip.InvalidArgumentException;
@ -11,14 +14,7 @@ import javax.sip.header.CSeqHeader;
import javax.sip.header.ViaHeader; import javax.sip.header.ViaHeader;
import javax.sip.message.Request; import javax.sip.message.Request;
import javax.sip.message.Response; import javax.sip.message.Response;
import java.text.ParseException;
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.SipLayer;
import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
/** /**
@ -29,40 +25,38 @@ import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor;
@Component @Component
public class InviteResponseProcessor implements ISIPResponseProcessor { public class InviteResponseProcessor implements ISIPResponseProcessor {
// private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class); /**
* 处理invite响应
/** *
* 处理invite响应 * @param evt 响应消息
* * @throws ParseException
* @param evt 响应消息 */
* @throws ParseException @Override
*/ public void process(ResponseEvent evt, SipLayer layer, SipConfig config) throws ParseException {
@Override try {
public void process(ResponseEvent evt, SipLayer layer, SipConfig config) throws ParseException { Response response = evt.getResponse();
try { int statusCode = response.getStatusCode();
Response response = evt.getResponse(); // trying不会回复
int statusCode = response.getStatusCode(); if (statusCode == Response.TRYING) {
// trying不会回复 }
if (statusCode == Response.TRYING) { // 成功响应
} // 下发ack
// 成功响应 if (statusCode == Response.OK) {
// 下发ack Dialog dialog = evt.getDialog();
if (statusCode == Response.OK) { CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
Dialog dialog = evt.getDialog(); Request reqAck = dialog.createAck(cseq.getSeqNumber());
CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
Request reqAck = dialog.createAck(cseq.getSeqNumber()); SipURI requestURI = (SipURI) reqAck.getRequestURI();
ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME);
SipURI requestURI = (SipURI) reqAck.getRequestURI(); requestURI.setHost(viaHeader.getHost());
ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME); requestURI.setPort(viaHeader.getPort());
requestURI.setHost(viaHeader.getHost()); reqAck.setRequestURI(requestURI);
requestURI.setPort(viaHeader.getPort());
reqAck.setRequestURI(requestURI); dialog.sendAck(reqAck);
}
dialog.sendAck(reqAck); } catch (InvalidArgumentException | SipException e) {
} e.printStackTrace();
} catch (InvalidArgumentException | SipException e) { }
e.printStackTrace(); }
}
}
} }

43
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java

@ -1,13 +1,13 @@
package com.genersoft.iot.vmp.media.zlm; package com.genersoft.iot.vmp.media.zlm;
import com.genersoft.iot.vmp.conf.MediaServerConfig; import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
// import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import org.apache.commons.lang3.StringUtils;
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@ -18,30 +18,34 @@ import javax.servlet.http.HttpServletResponse;
@RequestMapping("/zlm") @RequestMapping("/zlm")
public class ZLMHTTPProxyController { public class ZLMHTTPProxyController {
// private final static Logger logger = LoggerFactory.getLogger(ZLMHTTPProxyController.class);
// @Autowired
// private IVideoManagerStorager storager;
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private IRedisCatchStorage redisCatchStorage;
@Value("${media.port}") @Autowired
private int mediaHttpPort; private VideoStreamSessionManager streamSession;
@ResponseBody @ResponseBody
@RequestMapping(value = "/**/**/**", produces = "application/json;charset=UTF-8") @RequestMapping(value = "/**/**/**", produces = "application/json;charset=UTF-8")
public Object proxy(HttpServletRequest request, HttpServletResponse response){ public Object proxy(HttpServletRequest request, HttpServletResponse response) {
if (redisCatchStorage.getMediaInfo() == null) { if (redisCatchStorage.getMediaInfo() == null) {
return "未接入流媒体"; return "未接入流媒体";
} }
String mediaServerIp = request.getParameter("mediaServerIp");
if (StringUtils.isBlank(mediaServerIp)) {
String channelId = request.getParameter("channelId");
String streamId = request.getParameter("stream");
mediaServerIp = streamSession.getMediaServerIp(channelId, streamId);
}
if (StringUtils.isBlank(mediaServerIp)) {
// TODO 前端要提供zlm选择列表,传递回来
mediaServerIp = "127.0.0.1";
}
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
String requestURI = String.format("http://%s:%s%s?%s&%s", String requestURI = String.format("http://%s:%s%s?%s&%s",
mediaInfo.getLocalIP(), mediaServerIp,
mediaHttpPort, mediaInfo.getHttpPort(),
request.getRequestURI().replace("/zlm",""), request.getRequestURI().replace("/zlm", ""),
mediaInfo.getHookAdminParams(), mediaInfo.getHookAdminParams(),
request.getQueryString() request.getQueryString()
); );
@ -50,9 +54,8 @@ public class ZLMHTTPProxyController {
//将指定的url返回的参数自动封装到自定义好的对应类对象中 //将指定的url返回的参数自动封装到自定义好的对应类对象中
Object result = null; Object result = null;
try { try {
result = restTemplate.getForObject(requestURI,Object.class); result = restTemplate.getForObject(requestURI, Object.class);
} catch (HttpClientErrorException httpClientErrorException) {
}catch (HttpClientErrorException httpClientErrorException) {
response.setStatus(httpClientErrorException.getStatusCode().value()); response.setStatus(httpClientErrorException.getStatusCode().value());
} }
return result; return result;

312
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@ -1,73 +1,63 @@
package com.genersoft.iot.vmp.media.zlm; package com.genersoft.iot.vmp.media.zlm;
import java.util.UUID;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.conf.MediaServerConfig; import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.service.IPlayService; import com.genersoft.iot.vmp.vmanager.service.IPlayService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
/** /**
* @Description:针对 ZLMediaServer的hook事件监听 * @Description:针对 ZLMediaServer的hook事件监听
* @author: swwheihei * @author: swwheihei
* @date: 2020年5月8日 上午10:46:48 * @date: 2020年5月8日 上午10:46:48
*/ */
@RestController @RestController
@RequestMapping("/index/hook") @RequestMapping("/index/hook")
public class ZLMHttpHookListener { public class ZLMHttpHookListener {
private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class); private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class);
@Autowired @Autowired
private SIPCommander cmder; private SIPCommander cmder;
@Autowired @Autowired
private IPlayService playService; private IPlayService playService;
@Autowired @Autowired
private IVideoManagerStorager storager; private IVideoManagerStorager storager;
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private IRedisCatchStorage redisCatchStorage;
@Autowired @Autowired
private ZLMMediaListManager zlmMediaListManager; private ZLMMediaListManager zlmMediaListManager;
@Autowired @Autowired
private ZLMHttpHookSubscribe subscribe; private ZLMHttpHookSubscribe subscribe;
@Autowired
private ZLMHttpHookSubscribe subscribe;
@Value("${media.autoApplyPlay}") @Autowired
private boolean autoApplyPlay; MediaConfig mediaConfig;
@Value("${media.ip}")
private String mediaIp;
@Value("${media.wanIp}")
private String mediaWanIp;
@Value("${media.port}") @Autowired
private int mediaPort; private VideoStreamSessionManager streamSession;
/** /**
* 流量统计事件播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件阈值通过配置文件general.flowThreshold配置此事件对回复不敏感 * 流量统计事件播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件阈值通过配置文件general.flowThreshold配置此事件对回复不敏感
@ -134,8 +124,10 @@ public class ZLMHttpHookListener {
logger.debug("ZLM HOOK on_publish API调用,参数:" + json.toString()); logger.debug("ZLM HOOK on_publish API调用,参数:" + json.toString());
} }
ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, json); ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, json);
if (subscribe != null) subscribe.response(json); if (subscribe != null) {
subscribe.response(json);
}
JSONObject ret = new JSONObject(); JSONObject ret = new JSONObject();
ret.put("code", 0); ret.put("code", 0);
@ -223,117 +215,126 @@ public class ZLMHttpHookListener {
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK); return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
} }
/** /**
* rtsp/rtmp流注册或注销时触发此事件此事件对回复不敏感 * rtsp/rtmp流注册或注销时触发此事件此事件对回复不敏感
* */
*/ @ResponseBody
@ResponseBody @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
@PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") public ResponseEntity<String> onStreamChanged(@RequestBody JSONObject json) {
public ResponseEntity<String> onStreamChanged(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
if (logger.isDebugEnabled()) { logger.debug("ZLM HOOK on_stream_changed API调用,参数:" + json.toString());
logger.debug("ZLM HOOK on_stream_changed API调用,参数:" + json.toString()); }
}
// 流消失移除redis play JSONObject ret = new JSONObject();
String app = json.getString("app"); ret.put("code", 0);
String streamId = json.getString("stream"); ret.put("msg", "success");
String schema = json.getString("schema");
boolean regist = json.getBoolean("regist"); // 流消失移除redis play
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); String app = json.getString("app");
if ("rtp".equals(app) && !regist ) { String streamId = json.getString("stream");
if (streamInfo!=null){ boolean regist = json.getBoolean("regist");
redisCatchStorage.stopPlay(streamInfo); if (!"rtp".equals(app) || regist) {
storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); if (!"rtp".equals(app) && "rtsp".equals(schema)){
}else{ zlmMediaListManager.updateMediaList();
streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId); }
redisCatchStorage.stopPlayback(streamInfo); return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
} }
}else {
if (!"rtp".equals(app) && "rtsp".equals(schema)){ String[] s = streamId.split("_");
zlmMediaListManager.updateMediaList(); if (s.length != 4) {
} return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
} }
JSONObject ret = new JSONObject(); String channelId = s[3];
ret.put("code", 0); // TODO channelId
ret.put("msg", "success"); StreamInfo streamInfo = streamSession.getStreamInfo(channelId, streamId);
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK); if (null != streamInfo) {
} cmder.stopStreamByeCmd(streamInfo, null);
}
/** return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
* 流无人观看时事件用户可以通过此事件选择是否关闭无人看的流 }
*
*/ /**
@ResponseBody * 流无人观看时事件用户可以通过此事件选择是否关闭无人看的流
@PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8") */
public ResponseEntity<String> onStreamNoneReader(@RequestBody JSONObject json){ @ResponseBody
@PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
if (logger.isDebugEnabled()) { public ResponseEntity<String> onStreamNoneReader(@RequestBody JSONObject json) {
logger.debug("ZLM HOOK on_stream_none_reader API调用,参数:" + json.toString());
} if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_stream_none_reader API调用,参数:" + json.toString());
String streamId = json.getString("stream"); }
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
JSONObject ret = new JSONObject();
JSONObject ret = new JSONObject(); ret.put("code", 0);
ret.put("code", 0); ret.put("close", true);
ret.put("close", true);
String app = json.getString("app");
if (streamInfo != null) { String streamId = json.getString("stream");
if (redisCatchStorage.isChannelSendingRTP(streamInfo.getChannelId())) { if (!"rtp".equals(app) || streamId.indexOf("gb_play") < 0) {
ret.put("close", false); return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
} else { }
cmder.streamByeCmd(streamId); String[] s = streamId.split("_");
redisCatchStorage.stopPlay(streamInfo); if (s.length != 4) {
storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
} }
}else{ String channelId = s[3];
cmder.streamByeCmd(streamId); // TODO channelId
streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId); StreamInfo streamInfo = streamSession.getStreamInfo(channelId, streamId);
redisCatchStorage.stopPlayback(streamInfo); if (null != streamInfo) {
} if (redisCatchStorage.isChannelSendingRTP(streamInfo.getChannelId())) {
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK); ret.put("close", false);
} } else {
cmder.stopStreamByeCmd(streamInfo, null);
/** }
* 流未找到事件用户可以在此事件触发时立即去拉流这样可以实现按需拉流此事件对回复不敏感 }
* return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
*/ }
@ResponseBody
@PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8") /**
public ResponseEntity<String> onStreamNotFound(@RequestBody JSONObject json){ * 流未找到事件用户可以在此事件触发时立即去拉流这样可以实现按需拉流此事件对回复不敏感
*/
if (logger.isDebugEnabled()) { @ResponseBody
logger.debug("ZLM HOOK on_stream_not_found API调用,参数:" + json.toString()); @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8")
} public ResponseEntity<String> onStreamNotFound(@RequestBody JSONObject json) {
if (autoApplyPlay) {
String app = json.getString("app"); if (logger.isDebugEnabled()) {
String streamId = json.getString("stream"); logger.debug("ZLM HOOK on_stream_not_found API调用,参数:" + json.toString());
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); }
if ("rtp".equals(app) && streamId.indexOf("gb_play") > -1 && streamInfo == null) {
String[] s = streamId.split("_"); JSONObject ret = new JSONObject();
if (s.length == 4) { ret.put("code", 0);
String deviceId = s[2]; ret.put("msg", "success");
String channelId = s[3]; if (!mediaConfig.getAutoApplyPlay()) {
Device device = storager.queryVideoDevice(deviceId); return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
if (device != null) { }
UUID uuid = UUID.randomUUID(); String app = json.getString("app");
cmder.playStreamCmd(device, channelId, (JSONObject response) -> { String streamId = json.getString("stream");
logger.info("收到订阅消息: " + response.toJSONString());
playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString()); if (!"rtp".equals(app) || streamId.indexOf("gb_play") < 0) {
}, null); return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
} }
String[] s = streamId.split("_");
} if (s.length != 4) {
return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
} }
String deviceId = s[2];
} String channelId = s[3];
StreamInfo streamInfo = streamSession.getStreamInfo(channelId, streamId);
JSONObject ret = new JSONObject();
ret.put("code", 0); if (streamInfo != null) {
ret.put("msg", "success"); return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK); }
} Device device = storager.queryVideoDevice(deviceId);
if (device != null) {
RequestMessage msg = playService.createCallbackPlayMsg();
cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
logger.info("收到订阅消息: " + response.toJSONString());
playService.onPublishHandlerForPlay(response, deviceId, channelId, msg);
}, null);
}
return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
}
/** /**
* 服务器启动事件可以用于监听服务器崩溃重启此事件对回复不敏感 * 服务器启动事件可以用于监听服务器崩溃重启此事件对回复不敏感
@ -347,16 +348,13 @@ public class ZLMHttpHookListener {
logger.debug("ZLM HOOK on_server_started API调用,参数:" + json.toString()); logger.debug("ZLM HOOK on_server_started API调用,参数:" + json.toString());
} }
// String data = json.getString("data"); MediaServerConfig mediaServerConfig = JSON.toJavaObject(json, MediaServerConfig.class);
// List<MediaServerConfig> mediaServerConfigs = JSON.parseArray(JSON.toJSONString(json), MediaServerConfig.class); redisCatchStorage.updateMediaInfo(mediaServerConfig);
// MediaServerConfig mediaServerConfig = mediaServerConfigs.get(0); // TODO Auto-generated method stub
MediaServerConfig mediaServerConfig = JSON.toJavaObject(json, MediaServerConfig.class);
mediaServerConfig.setWanIp(StringUtils.isEmpty(mediaWanIp)? mediaIp: mediaWanIp); JSONObject ret = new JSONObject();
mediaServerConfig.setLocalIP(mediaIp); ret.put("code", 0);
redisCatchStorage.updateMediaInfo(mediaServerConfig); ret.put("msg", "success");
JSONObject ret = new JSONObject(); return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
ret.put("code", 0); }
ret.put("msg", "success");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
} }

127
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java

@ -2,10 +2,14 @@ package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import okhttp3.*; import com.genersoft.iot.vmp.conf.MediaConfig;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.IOException; import java.io.IOException;
@ -18,25 +22,19 @@ public class ZLMRESTfulUtils {
private final static Logger logger = LoggerFactory.getLogger(ZLMRESTfulUtils.class); private final static Logger logger = LoggerFactory.getLogger(ZLMRESTfulUtils.class);
@Value("${media.ip}") @Autowired
private String mediaIp; MediaConfig mediaConfig;
@Value("${media.port}") public JSONObject sendPost(String mediaServerIp, String api, Map<String, Object> param) {
private int mediaPort;
@Value("${media.secret}")
private String mediaSecret;
public JSONObject sendPost(String api, Map<String, Object> param) {
OkHttpClient client = new OkHttpClient(); OkHttpClient client = new OkHttpClient();
String url = String.format("http://%s:%s/index/api/%s", mediaIp, mediaPort, api); String url = String.format("http://%s:%s/index/api/%s", mediaServerIp, mediaConfig.getMediaPort(), api);
JSONObject responseJSON = null; JSONObject responseJSON = null;
logger.debug(url); logger.debug(url);
FormBody.Builder builder = new FormBody.Builder(); FormBody.Builder builder = new FormBody.Builder();
builder.add("secret",mediaSecret); builder.add("secret", mediaConfig.getMediaSecret());
if (param != null) { if (param != null) {
for (String key : param.keySet()){ for (String key : param.keySet()) {
builder.add(key, param.get(key).toString()); builder.add(key, param.get(key).toString());
} }
} }
@ -58,20 +56,29 @@ public class ZLMRESTfulUtils {
} catch (ConnectException e) { } catch (ConnectException e) {
logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage())); logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
logger.info("请检查media配置并确认ZLM已启动..."); logger.info("请检查media配置并确认ZLM已启动...");
}catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
return responseJSON; return responseJSON;
} }
public JSONObject getMediaList(String app, String schema){ // public JSONObject getMediaList(String app, String schema) {
Map<String, Object> param = new HashMap<>(); // Map<String, Object> param = new HashMap<>();
param.put("app",app); // param.put("app", app);
param.put("schema",schema); // param.put("schema", schema);
param.put("vhost","__defaultVhost__"); // param.put("vhost", "__defaultVhost__");
return sendPost("getMediaList",param); // return sendPost("getMediaList", param);
} // }
//
// public JSONObject getMediaInfo(String app, String schema, String stream) {
// Map<String, Object> param = new HashMap<>();
// param.put("app", app);
// param.put("schema", schema);
// param.put("stream", stream);
// param.put("vhost", "__defaultVhost__");
// return sendPost("getMediaInfo", param);
// }
public JSONObject getMediaList(){ public JSONObject getMediaList(){
return sendPost("getMediaList",null); return sendPost("getMediaList",null);
@ -86,42 +93,88 @@ public class ZLMRESTfulUtils {
return sendPost("getMediaInfo",param); return sendPost("getMediaInfo",param);
} }
public JSONObject getRtpInfo(String stream_id){ public JSONObject getRtpInfo(String mediaServerIp, String stream_id) {
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("stream_id",stream_id); param.put("stream_id", stream_id);
return sendPost("getRtpInfo",param); return sendPost(mediaServerIp, "getRtpInfo", param);
} }
public JSONObject addFFmpegSource(String src_url, String dst_url, String timeout_ms){ public JSONObject addFFmpegSource(String mediaServerIp, String src_url, String dst_url, String timeout_ms) {
System.out.println(src_url); System.out.println(src_url);
System.out.println(dst_url); System.out.println(dst_url);
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("src_url", src_url); param.put("src_url", src_url);
param.put("dst_url", dst_url); param.put("dst_url", dst_url);
param.put("timeout_ms", timeout_ms); param.put("timeout_ms", timeout_ms);
return sendPost("addFFmpegSource",param); return sendPost(mediaServerIp, "addFFmpegSource", param);
} }
public JSONObject delFFmpegSource(String key){ public JSONObject delFFmpegSource(String mediaServerIp, String key) {
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("key", key); param.put("key", key);
return sendPost("delFFmpegSource",param); return sendPost(mediaServerIp, "delFFmpegSource", param);
}
public JSONObject getMediaServerConfig(String mediaServerIp) {
return sendPost(mediaServerIp, "getServerConfig", null);
}
public JSONObject setServerConfig(String mediaServerIp, Map<String, Object> param) {
return sendPost(mediaServerIp, "setServerConfig", param);
}
public JSONObject openRtpServer(String mediaServerIp, Map<String, Object> param) {
return sendPost(mediaServerIp, "openRtpServer", param);
} }
public JSONObject getMediaServerConfig(){ public JSONObject closeRtpServer(String mediaServerIp, Map<String, Object> param) {
return sendPost("getServerConfig",null); return sendPost(mediaServerIp, "closeRtpServer", param);
} }
public JSONObject setServerConfig(Map<String, Object> param){ /**
return sendPost("setServerConfig",param); * 截图
*
* @param mediaServerIp
* @param url
* @return
*/
public JSONObject getSnap(String mediaServerIp, String url) {
Map<String, Object> param = new HashMap<>();
param.put("url", url);
param.put("timeout_sec", 10);
param.put("expire_sec", 1);
param.put("url", url);
return sendPost(mediaServerIp, "getSnap", param);
} }
public JSONObject openRtpServer(Map<String, Object> param){ /**
return sendPost("openRtpServer",param); * 开始录制
*
* @param mediaServerIp
* @param stream
* @return
*/
public JSONObject startRecord(String mediaServerIp, String stream) {
Map<String, Object> param = new HashMap<>();
param.put("type", 1);
param.put("app", "rtp");
param.put("stream", stream);
return sendPost(mediaServerIp, "startRecord", param);
} }
public JSONObject closeRtpServer(Map<String, Object> param) { /**
return sendPost("closeRtpServer",param); * 结束录制
*
* @param mediaServerIp
* @param stream
* @return
*/
public JSONObject stopRecord(String mediaServerIp, String stream) {
Map<String, Object> param = new HashMap<>();
param.put("type", 1);
param.put("app", "rtp");
param.put("stream", stream);
return sendPost(mediaServerIp, "stopRecord", param);
} }
public JSONObject startSendRtp(Map<String, Object> param) { public JSONObject startSendRtp(Map<String, Object> param) {

51
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java

@ -3,89 +3,102 @@ package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.session.SsrcUtil; import com.genersoft.iot.vmp.gb28181.session.SsrcUtil;
import com.genersoft.iot.vmp.conf.MediaConfig;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component @Component
public class ZLMRTPServerFactory { public class ZLMRTPServerFactory {
private Logger logger = LoggerFactory.getLogger("ZLMRTPServerFactory"); private Logger logger = LoggerFactory.getLogger("ZLMRTPServerFactory");
@Value("${media.rtp.udpPortRange}") @Autowired
private String udpPortRange; MediaConfig mediaConfig;
@Autowired @Autowired
private ZLMRESTfulUtils zlmresTfulUtils; private ZLMRESTfulUtils zlmresTfulUtils;
private int[] udpPortRangeArray = new int[2]; private int[] udpPortRangeArray = new int[2];
private int currentPort = 0; private ConcurrentHashMap<String, Integer> currentPortMap = new ConcurrentHashMap<>();
public int createRTPServer(String streamId) { public int createRTPServer(String mediaServerIp, String streamId) {
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
int result = -1; int result = -1;
int newPort = getPortFromUdpPortRange(); int newPort = getPortFromUdpPortRange(mediaServerIp);
param.put("port", newPort); param.put("port", newPort);
param.put("enable_tcp", 1); param.put("enable_tcp", 1);
param.put("stream_id", streamId); param.put("stream_id", streamId);
JSONObject jsonObject = zlmresTfulUtils.openRtpServer(param); JSONObject jsonObject = zlmresTfulUtils.openRtpServer(mediaServerIp, param);
System.out.println(jsonObject); System.out.println(jsonObject);
if (jsonObject != null) { if (jsonObject != null) {
switch (jsonObject.getInteger("code")){ switch (jsonObject.getInteger("code")) {
case 0: case 0:
result= newPort; result = newPort;
break; break;
case -300: // id已经存在 case -300: // id已经存在
result = newPort; result = newPort;
break; break;
case -400: // 端口占用 case -400: // 端口占用
result= createRTPServer(streamId); result = createRTPServer(mediaServerIp, streamId);
break; break;
default: default:
logger.error("创建RTP Server 失败: " + jsonObject.getString("msg")); logger.error("创建RTP Server 失败: " + jsonObject.getString("msg"));
break; break;
} }
}else { } else {
// 检查ZLM状态 // 检查ZLM状态
logger.error("创建RTP Server 失败: 请检查ZLM服务"); logger.error("创建RTP Server 失败: 请检查ZLM服务");
} }
return result; return result;
} }
public boolean closeRTPServer(String streamId) { public boolean closeRTPServer(String mediaServerIp, String streamId) {
boolean result = false; boolean result = false;
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("stream_id", streamId); param.put("stream_id", streamId);
JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(param);
if (jsonObject != null ) { JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerIp, param);
if (jsonObject != null) {
if (jsonObject.getInteger("code") == 0) { if (jsonObject.getInteger("code") == 0) {
result = jsonObject.getInteger("hit") == 1; result = jsonObject.getInteger("hit") == 1;
}else { } else {
logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg")); logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
} }
}else { } else {
// 检查ZLM状态 // 检查ZLM状态
logger.error("关闭RTP Server 失败: 请检查ZLM服务"); logger.error("关闭RTP Server 失败: 请检查ZLM服务");
} }
return result; return result;
} }
private int getPortFromUdpPortRange() { // private int getPortFromUdpPortRange() {
// currentPort = getPortFromUdpPortRange(currentPort);
// return currentPort;
// }
private int getPortFromUdpPortRange(String mediaServerIp) {
Integer currentPort = currentPortMap.get(mediaServerIp);
currentPort = getPortFromUdpPortRange(null == currentPort ? 0 : currentPort);
currentPortMap.put(mediaServerIp, currentPort);
return currentPort;
}
private int getPortFromUdpPortRange(Integer currentPort) {
if (currentPort == 0) { if (currentPort == 0) {
String[] udpPortRangeStrArray = udpPortRange.split(","); String[] udpPortRangeStrArray = mediaConfig.getUdpPortRange().split(",");
udpPortRangeArray[0] = Integer.parseInt(udpPortRangeStrArray[0]); udpPortRangeArray[0] = Integer.parseInt(udpPortRangeStrArray[0]);
udpPortRangeArray[1] = Integer.parseInt(udpPortRangeStrArray[1]); udpPortRangeArray[1] = Integer.parseInt(udpPortRangeStrArray[1]);
} }
if (currentPort == 0 || currentPort++ > udpPortRangeArray[1]) { if (currentPort == 0 || currentPort++ > udpPortRangeArray[1]) {
currentPort = udpPortRangeArray[0];
return udpPortRangeArray[0]; return udpPortRangeArray[0];
} else { } else {
if (currentPort % 2 == 1) { if (currentPort % 2 == 1) {

137
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java

@ -3,11 +3,11 @@ package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.conf.MediaServerConfig; import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
//import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner; import org.springframework.boot.CommandLineRunner;
@ -18,44 +18,20 @@ import org.springframework.util.StringUtils;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@Slf4j
@Component @Component
@Order(value=1) @Order(value = 1)
public class ZLMRunner implements CommandLineRunner { public class ZLMRunner implements CommandLineRunner {
private final static Logger logger = LoggerFactory.getLogger(ZLMRunner.class);
// @Autowired
// private IVideoManagerStorager storager;
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private IRedisCatchStorage redisCatchStorage;
@Value("${media.ip}")
private String mediaIp;
@Value("${media.wanIp}")
private String mediaWanIp;
@Value("${media.hookIp}")
private String mediaHookIp;
@Value("${media.port}")
private int mediaPort;
@Value("${media.secret}")
private String mediaSecret;
@Value("${media.streamNoneReaderDelayMS}")
private String streamNoneReaderDelayMS;
@Value("${sip.ip}")
private String sipIP;
@Value("${server.port}") @Value("${server.port}")
private String serverPort; private String serverPort;
@Autowired
@Value("${media.autoConfig}") MediaConfig mediaConfig;
private boolean autoConfig; @Autowired
SipConfig sipConfig;
@Autowired @Autowired
private ZLMRESTfulUtils zlmresTfulUtils; private ZLMRESTfulUtils zlmresTfulUtils;
@ -64,76 +40,81 @@ public class ZLMRunner implements CommandLineRunner {
private ZLMMediaListManager zlmMediaListManager; private ZLMMediaListManager zlmMediaListManager;
@Override @Override
public void run(String... strings) throws Exception { public void run(String... strings) {
// 获取zlm信息 String[] mediaIpArr = mediaConfig.getMediaIpArr();
logger.info("等待zlm接入..."); for (String mediaIp : mediaIpArr) {
MediaServerConfig mediaServerConfig = getMediaServerConfig(); // 获取zlm信息
if (mediaServerConfig != null) { log.info("等待zlm {} 接入...", mediaIp);
logger.info("zlm接入成功..."); MediaServerConfig mediaServerConfig = getMediaServerConfig(mediaIp);
if (autoConfig) saveZLMConfig(); if (mediaServerConfig != null) {
mediaServerConfig = getMediaServerConfig(); log.info("zlm {} 接入成功...", mediaIp);
redisCatchStorage.updateMediaInfo(mediaServerConfig); if (mediaConfig.getAutoConfig()) {
// 更新流列表 // 自动配置zlm
zlmMediaListManager.updateMediaList(); saveZLMConfig(mediaIp);
// 配置后,从zlm重新获取流媒体服务器信息
mediaServerConfig = getMediaServerConfig(mediaIp);
}
// TODO 这里需要把多台zlm服务器的配置,分别存储到redis,不能用同一个key,否则会覆盖
redisCatchStorage.updateMediaInfo(mediaServerConfig);
// 更新流列表
zlmMediaListManager.updateMediaList();
}
} }
} }
public MediaServerConfig getMediaServerConfig() { public MediaServerConfig getMediaServerConfig(String mediaIp) {
JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(); JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaIp);
MediaServerConfig mediaServerConfig = null; MediaServerConfig mediaServerConfig = null;
if (responseJSON != null) { if (responseJSON != null) {
JSONArray data = responseJSON.getJSONArray("data"); JSONArray data = responseJSON.getJSONArray("data");
if (data != null && data.size() > 0) { if (data != null && data.size() > 0) {
mediaServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), MediaServerConfig.class); mediaServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), MediaServerConfig.class);
mediaServerConfig.setLocalIP(mediaIp);
mediaServerConfig.setWanIp(StringUtils.isEmpty(mediaWanIp)? mediaIp: mediaWanIp);
} }
} else { } else {
logger.error("getMediaServerConfig失败, 1s后重试"); log.error("getMediaServerConfig失败, 1s后重试");
try { try {
Thread.sleep(1000); Thread.sleep(1000);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
mediaServerConfig = getMediaServerConfig(); mediaServerConfig = getMediaServerConfig(mediaIp);
} }
return mediaServerConfig; return mediaServerConfig;
} }
private void saveZLMConfig() { private void saveZLMConfig(String mediaIp) {
logger.info("设置zlm..."); log.info("设置zlm {} ...", mediaIp);
String mediaHookIp = mediaConfig.getMediaHookIp();
if (StringUtils.isEmpty(mediaHookIp)) { if (StringUtils.isEmpty(mediaHookIp)) {
mediaHookIp = sipIP; mediaHookIp = sipConfig.getSipIp();
} }
String hookPrex = String.format("http://%s:%s/index/hook", mediaHookIp, serverPort); String hookPrex = String.format("http://%s:%s/index/hook", mediaHookIp, serverPort);
Map<String, Object> param = new HashMap<>(); Map<String, Object> param = new HashMap<>();
param.put("api.secret",mediaSecret); // -profile:v Baseline param.put("api.secret", mediaConfig.getMediaSecret()); // -profile:v Baseline
param.put("ffmpeg.cmd","%s -fflags nobuffer -rtsp_transport tcp -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s"); param.put("ffmpeg.cmd", "%s -fflags nobuffer -rtsp_transport tcp -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s");
param.put("hook.enable","1"); param.put("hook.enable", "1");
param.put("hook.on_flow_report",""); param.put("hook.on_flow_report", "");
param.put("hook.on_play",""); param.put("hook.on_play", "");
param.put("hook.on_http_access",""); param.put("hook.on_http_access", "");
param.put("hook.on_publish",String.format("%s/on_publish", hookPrex)); param.put("hook.on_publish", String.format("%s/on_publish", hookPrex));
param.put("hook.on_record_mp4",""); param.put("hook.on_record_mp4", "");
param.put("hook.on_record_ts",""); param.put("hook.on_record_ts", "");
param.put("hook.on_rtsp_auth",""); param.put("hook.on_rtsp_auth", "");
param.put("hook.on_rtsp_realm",""); param.put("hook.on_rtsp_realm", "");
param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex)); param.put("hook.on_server_started", String.format("%s/on_server_started", hookPrex));
param.put("hook.on_shell_login",String.format("%s/on_shell_login", hookPrex)); param.put("hook.on_shell_login", String.format("%s/on_shell_login", hookPrex));
param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex)); param.put("hook.on_stream_changed", String.format("%s/on_stream_changed", hookPrex));
param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex)); param.put("hook.on_stream_none_reader", String.format("%s/on_stream_none_reader", hookPrex));
param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex)); param.put("hook.on_stream_not_found", String.format("%s/on_stream_not_found", hookPrex));
param.put("hook.timeoutSec","20"); param.put("hook.timeoutSec", "20");
param.put("general.streamNoneReaderDelayMS",streamNoneReaderDelayMS); param.put("general.streamNoneReaderDelayMS", mediaConfig.getStreamNoneReaderDelayMS());
JSONObject responseJSON = zlmresTfulUtils.setServerConfig(param); JSONObject responseJSON = zlmresTfulUtils.setServerConfig(mediaIp, param);
if (responseJSON != null && responseJSON.getInteger("code") == 0) { if (responseJSON != null && responseJSON.getInteger("code") == 0) {
logger.info("设置zlm成功"); log.info("设置zlm {} 成功", mediaIp);
}else { } else {
logger.info("设置zlm失败: " + responseJSON.getString("msg")); log.info("设置zlm {} 失败: {}", mediaIp, responseJSON);
} }
} }
} }

41
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java

@ -13,52 +13,49 @@ import java.util.Map;
public interface IRedisCatchStorage { public interface IRedisCatchStorage {
/** /**
* 开始播放时将流存入 * 更新流媒体信息
* *
* @param stream 流信息 * @param mediaServerConfig
* @return * @return
*/ */
boolean startPlay(StreamInfo stream); boolean updateMediaInfo(MediaServerConfig mediaServerConfig);
/** /**
* 停止播放时删除 * 获取流媒体信息
* *
* @return * @return
*/ */
boolean stopPlay(StreamInfo streamInfo); MediaServerConfig getMediaInfo();
/** /**
* 查询播放列表 * 开始播放时将流存入
*
* @param stream 流信息
* @return * @return
*/ */
StreamInfo queryPlay(StreamInfo streamInfo); boolean startPlay(StreamInfo stream);
StreamInfo queryPlayByStreamId(String steamId);
StreamInfo queryPlaybackByStreamId(String steamId);
StreamInfo queryPlayByDevice(String deviceId, String code);
/** /**
* 更新流媒体信息 * 停止播放时删除
* @param mediaServerConfig *
* @return * @return
*/ */
boolean updateMediaInfo(MediaServerConfig mediaServerConfig); boolean stopPlay(StreamInfo streamInfo);
/** StreamInfo queryPlayByStreamId(String channelId, String steamId);
* 获取流媒体信息
* @return
*/
MediaServerConfig getMediaInfo();
Map<String, StreamInfo> queryPlayByDeviceId(String deviceId); StreamInfo queryPlayByChannel(String channelId);
boolean startPlayback(StreamInfo stream); boolean startPlayback(StreamInfo stream);
boolean stopPlayback(StreamInfo streamInfo); boolean stopPlayback(StreamInfo streamInfo);
StreamInfo queryPlaybackByStreamId(String channelId, String steamId);
StreamInfo queryPlaybackByChannel(String channelId);
List<StreamInfo> queryPlayBackByDeviceId(String deviceId);
StreamInfo queryPlaybackByDevice(String deviceId, String code); StreamInfo queryPlaybackByDevice(String deviceId, String code);
void updatePlatformCatchInfo(ParentPlatformCatch parentPlatformCatch); void updatePlatformCatchInfo(ParentPlatformCatch parentPlatformCatch);

336
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java

@ -1,7 +1,5 @@
package com.genersoft.iot.vmp.storager; package com.genersoft.iot.vmp.storager;
import java.util.List;
import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
@ -9,152 +7,165 @@ import com.genersoft.iot.vmp.vmanager.platform.bean.ChannelReduce;
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
import java.util.List;
/** /**
* @Description:视频设备数据存储接口 * @Description:视频设备数据存储接口
* @author: swwheihei * @author: swwheihei
* @date: 2020年5月6日 下午2:14:31 * @date: 2020年5月6日 下午2:14:31
*/ */
@SuppressWarnings("rawtypes")
public interface IVideoManagerStorager { public interface IVideoManagerStorager {
/** /**
* 根据设备ID判断设备是否存在 * 根据设备ID判断设备是否存在
* *
* @param deviceId 设备ID * @param deviceId 设备ID
* @return true:存在 false不存在 * @return true:存在 false不存在
*/ */
public boolean exists(String deviceId); public boolean exists(String deviceId);
/** /**
* 视频设备创建 * 视频设备创建
* *
* @param device 设备对象 * @param device 设备对象
* @return true创建成功 false创建失败 * @return true创建成功 false创建失败
*/ */
public boolean create(Device device); public boolean create(Device device);
/** /**
* 视频设备更新 * 视频设备更新
* *
* @param device 设备对象 * @param device 设备对象
* @return true创建成功 false创建失败 * @return true创建成功 false创建失败
*/ */
public boolean updateDevice(Device device); public boolean updateDevice(Device device);
/** /**
* 添加设备通道 * 添加设备通道
* *
* @param deviceId 设备id * @param deviceId 设备id
* @param channel 通道 * @param channel 通道
*/ */
public void updateChannel(String deviceId, DeviceChannel channel); public void updateChannel(String deviceId, DeviceChannel channel);
/** /**
* 开始播放 * 开始播放
* @param deviceId 设备id *
* @param channelId 通道ID * @param deviceId 设备id
* @param streamId 流地址 * @param channelId 通道ID
*/ * @param streamId 流地址
public void startPlay(String deviceId, String channelId, String streamId); */
public void startPlay(String deviceId, String channelId, String streamId);
/**
* 停止播放 /**
* @param deviceId 设备id * 停止播放
* @param channelId 通道ID *
*/ * @param deviceId 设备id
public void stopPlay(String deviceId, String channelId); * @param channelId 通道ID
*/
/** public void stopPlay(String deviceId, String channelId);
* 获取设备
* /**
* @param deviceId 设备ID * 获取设备
* @return DShadow 设备对象 *
*/ * @param deviceId 设备ID
public Device queryVideoDevice(String deviceId); * @return DShadow 设备对象
*/
/** public Device queryVideoDevice(String deviceId);
* 获取某个设备的通道列表
* /**
* @param deviceId 设备ID * 获取某个设备的通道列表
* @param page 分页 当前页 *
* @param count 每页数量 * @param deviceId 设备ID
* @return * @param page 分页 当前页
*/ * @param count 每页数量
public PageInfo queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, int page, int count); * @return
*/
/** public PageInfo queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, int page, int count);
* 获取某个设备的通道列表
* /**
* @param deviceId 设备ID * 获取某个设备的通道列表
* @return *
*/ * @param deviceId 设备ID
public List<DeviceChannel> queryChannelsByDeviceId(String deviceId); * @return
*/
/** public List<DeviceChannel> queryChannelsByDeviceId(String deviceId);
* 获取某个设备的通道
* @param deviceId 设备ID /**
* @param channelId 通道ID * 获取某个设备的通道
*/ *
public DeviceChannel queryChannel(String deviceId, String channelId); * @param deviceId 设备ID
* @param channelId 通道ID
/** */
* 获取多个设备 public DeviceChannel queryChannel(String deviceId, String channelId);
* @param page 当前页数
* @param count 每页数量 /**
* @return List<Device> 设备对象数组 * 获取多个设备
*/ *
public PageInfo<Device> queryVideoDeviceList(int page, int count); * @param page 当前页数
* @param count 每页数量
/** * @return List<Device> 设备对象数组
* 获取多个设备 */
* public PageInfo<Device> queryVideoDeviceList(int page, int count);
* @return List<Device> 设备对象数组
*/ /**
public List<Device> queryVideoDeviceList(); * 获取多个设备
*
/** * @return List<Device> 设备对象数组
* 删除设备 */
* public List<Device> queryVideoDeviceList();
* @param deviceId 设备ID
* @return true删除成功 false删除失败 /**
*/ * 删除设备
public boolean delete(String deviceId); *
* @param deviceId 设备ID
/** * @return true删除成功 false删除失败
* 更新设备在线 */
* public boolean delete(String deviceId);
* @param deviceId 设备ID
* @return true更新成功 false更新失败 /**
*/ * 更新设备在线
public boolean online(String deviceId); *
* @param deviceId 设备ID
/** * @return true更新成功 false更新失败
* 更新设备离线 */
* public boolean online(String deviceId);
* @param deviceId 设备ID
* @return true更新成功 false更新失败 /**
*/ * 更新设备离线
public boolean outline(String deviceId); *
* @param deviceId 设备ID
* @return true更新成功 false更新失败
/** */
* 查询子设备 public boolean outline(String deviceId);
*
* @param deviceId
* @param channelId /**
* @param page * 查询子设备
* @param count *
* @return * @param deviceId
*/ * @param channelId
PageInfo querySubChannels(String deviceId, String channelId, String query, Boolean hasSubChannel, String online, int page, int count); * @param page
* @param count
* @return
/** */
* 清空通道 PageInfo querySubChannels(String deviceId, String channelId, String query, Boolean hasSubChannel, String online, int page, int count);
* @param deviceId
*/
void cleanChannelsForDevice(String deviceId); /**
* 清空通道
*
* @param deviceId
*/
void cleanChannelsForDevice(String deviceId);
/**
* 添加Mobile Position设备移动位置
*
* @param MobilePosition
* @return
*/
public boolean insertMobilePosition(MobilePosition mobilePosition);
/** /**
* 更新上级平台 * 更新上级平台
@ -242,23 +253,26 @@ public interface IVideoManagerStorager {
*/ */
public boolean insertMobilePosition(MobilePosition mobilePosition); public boolean insertMobilePosition(MobilePosition mobilePosition);
/** /**
* 查询移动位置轨迹 * 查询移动位置轨迹
* @param deviceId *
* @param startTime * @param deviceId
* @param endTime * @param startTime
*/ * @param endTime
public List<MobilePosition> queryMobilePositions(String deviceId, String startTime, String endTime); */
public List<MobilePosition> queryMobilePositions(String deviceId, String startTime, String endTime);
/**
* 查询最新移动位置 /**
* @param deviceId * 查询最新移动位置
*/ *
public MobilePosition queryLatestPosition(String deviceId); * @param deviceId
*/
/** public MobilePosition queryLatestPosition(String deviceId);
* 删除指定设备的所有移动位置
* @param deviceId /**
*/ * 删除指定设备的所有移动位置
public int clearMobilePositionsByDeviceId(String deviceId); *
* @param deviceId
*/
public int clearMobilePositionsByDeviceId(String deviceId);
} }

2
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java

@ -46,7 +46,7 @@ public interface DeviceChannelMapper {
"<if test=\"PTZType != null\">, PTZType=${PTZType}</if>" + "<if test=\"PTZType != null\">, PTZType=${PTZType}</if>" +
"<if test=\"status != null\">, status='${status}'</if>" + "<if test=\"status != null\">, status='${status}'</if>" +
"<if test=\"streamId != null\">, streamId='${streamId}'</if>" + "<if test=\"streamId != null\">, streamId='${streamId}'</if>" +
"<if test=\"hasAudio != null\">, hasAudio='${hasAudio}'</if>" + "<if test=\"hasAudio != null\">, hasAudio='#{hasAudio}'</if>" +
"WHERE deviceId='${deviceId}' AND channelId='${channelId}'"+ "WHERE deviceId='${deviceId}' AND channelId='${channelId}'"+
" </script>"}) " </script>"})
int update(DeviceChannel channel); int update(DeviceChannel channel);

213
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@ -8,153 +8,187 @@ import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.utils.redis.RedisUtil; import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.*; import java.util.*;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Component @Component
public class RedisCatchStorageImpl implements IRedisCatchStorage { public class RedisCatchStorageImpl implements IRedisCatchStorage {
@Autowired @Autowired
private RedisUtil redis; private RedisUtil redis;
@Autowired @Autowired
private DeviceChannelMapper deviceChannelMapper; private DeviceChannelMapper deviceChannelMapper;
/** /**
* 开始播放时将流存入redis * 更新流媒体信息
* *
* @param mediaServerConfig
* @return * @return
*/ */
@Override @Override
public boolean startPlay(StreamInfo stream) { public boolean updateMediaInfo(MediaServerConfig mediaServerConfig) {
return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, stream.getStreamId(),stream.getDeviceID(), stream.getChannelId()), return redis.set(VideoManagerConstants.MEDIA_SERVER_PREFIX, mediaServerConfig);
stream);
} }
/** /**
* 停止播放时从redis删除 * 获取流媒体信息
* *
* @return * @return
*/ */
@Override @Override
public boolean stopPlay(StreamInfo streamInfo) { public MediaServerConfig getMediaInfo() {
if (streamInfo == null) return false; return (MediaServerConfig) redis.get(VideoManagerConstants.MEDIA_SERVER_PREFIX);
return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
streamInfo.getStreamId(),
streamInfo.getDeviceID(),
streamInfo.getChannelId()));
} }
/** /**
* 查询播放列表 * 开始播放时将流存入redis
*
* @return * @return
*/ */
@Override @Override
public StreamInfo queryPlay(StreamInfo streamInfo) { public boolean startPlay(StreamInfo stream) {
return (StreamInfo)redis.get(String.format("%S_%s_%s_%s", String key = getKey(VideoManagerConstants.PLAYER_PREFIX,
VideoManagerConstants.PLAYER_PREFIX, stream.getStreamId(),
streamInfo.getStreamId(), stream.getChannelId(),
streamInfo.getDeviceID(), stream.getDeviceID()
streamInfo.getChannelId())); );
return redis.set(key, stream);
} }
/**
* 停止播放时从redis删除
*
* @return
*/
@Override @Override
public StreamInfo queryPlayByStreamId(String steamId) { public boolean stopPlay(StreamInfo streamInfo) {
List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, steamId)); if (streamInfo == null) {
if (playLeys == null || playLeys.size() == 0) return null; return false;
return (StreamInfo)redis.get(playLeys.get(0).toString()); }
String key = getKey(VideoManagerConstants.PLAYER_PREFIX,
streamInfo.getStreamId(),
streamInfo.getChannelId(),
streamInfo.getDeviceID()
);
return redis.del(key);
} }
@Override @Override
public StreamInfo queryPlaybackByStreamId(String steamId) { public StreamInfo queryPlayByStreamId(String channelId, String steamId) {
List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, steamId)); String key = getKey(VideoManagerConstants.PLAYER_PREFIX,
if (playLeys == null || playLeys.size() == 0) return null; steamId,
return (StreamInfo)redis.get(playLeys.get(0).toString()); channelId,
null
);
return scanOne(key);
} }
@Override @Override
public StreamInfo queryPlayByDevice(String deviceId, String code) { public StreamInfo queryPlayByChannel(String channelId) {
// List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, String key = getKey(VideoManagerConstants.PLAYER_PREFIX,
List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, null,
deviceId, channelId,
code)); null
if (playLeys == null || playLeys.size() == 0) return null; );
return (StreamInfo)redis.get(playLeys.get(0).toString()); return scanOne(key);
} }
/** /**
* 更新流媒体信息 * zlm流媒体服务器播流成功后回调播放状态缓存到redis
* @param mediaServerConfig *
* @param stream
* @return * @return
*/ */
@Override @Override
public boolean updateMediaInfo(MediaServerConfig mediaServerConfig) { public boolean startPlayback(StreamInfo stream) {
return redis.set(VideoManagerConstants.MEDIA_SERVER_PREFIX,mediaServerConfig); String key = getKey(VideoManagerConstants.PLAY_BLACK_PREFIX,
stream.getStreamId(),
stream.getChannelId(),
stream.getDeviceID()
);
return redis.set(key, stream);
} }
/** /**
* 获取流媒体信息 * zlm流媒体服务器成功停止拉流后回调从redis缓存移除播放状态
*
* @param streamInfo
* @return * @return
*/ */
@Override @Override
public MediaServerConfig getMediaInfo() { public boolean stopPlayback(StreamInfo streamInfo) {
return (MediaServerConfig)redis.get(VideoManagerConstants.MEDIA_SERVER_PREFIX); if (streamInfo == null) {
return false;
}
DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(streamInfo.getDeviceID(), streamInfo.getChannelId());
if (deviceChannel != null) {
deviceChannel.setStreamId(null);
deviceChannel.setDeviceId(streamInfo.getDeviceID());
deviceChannelMapper.update(deviceChannel);
}
String key = getKey(VideoManagerConstants.PLAY_BLACK_PREFIX,
streamInfo.getStreamId(),
streamInfo.getChannelId(),
streamInfo.getDeviceID()
);
return redis.del(key);
} }
@Override @Override
public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) { public StreamInfo queryPlaybackByStreamId(String channelId, String steamId) {
Map<String, StreamInfo> streamInfos = new HashMap<>(); String key = getKey(VideoManagerConstants.PLAY_BLACK_PREFIX,
// List<Object> playLeys = redis.keys(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId)); steamId,
List<Object> players = redis.scan(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId)); channelId,
if (players.size() == 0) return streamInfos; null
for (int i = 0; i < players.size(); i++) { );
String key = (String) players.get(i); return scanOne(key);
StreamInfo streamInfo = (StreamInfo)redis.get(key);
streamInfos.put(streamInfo.getDeviceID() + "_" + streamInfo.getChannelId(), streamInfo);
}
return streamInfos;
} }
@Override @Override
public boolean startPlayback(StreamInfo stream) { public StreamInfo queryPlaybackByChannel(String channelId) {
return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, stream.getStreamId(),stream.getDeviceID(), stream.getChannelId()),
stream); String key = getKey(VideoManagerConstants.PLAY_BLACK_PREFIX,
null,
channelId,
null
);
return scanOne(key);
} }
@Override @Override
public boolean stopPlayback(StreamInfo streamInfo) { public List<StreamInfo> queryPlayBackByDeviceId(String deviceId) {
if (streamInfo == null) return false; String key = getKey(VideoManagerConstants.PLAY_BLACK_PREFIX,
DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(streamInfo.getDeviceID(), streamInfo.getChannelId()); null,
if (deviceChannel != null) { null,
deviceChannel.setStreamId(null); deviceId
deviceChannel.setDeviceId(streamInfo.getDeviceID()); );
deviceChannelMapper.update(deviceChannel); List<Object> players = redis.scan(key);
if (players.size() == 0) {
return new ArrayList<>();
} }
return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, List<StreamInfo> streamInfos = new ArrayList<>(players.size());
streamInfo.getStreamId(), for (int i = 0; i < players.size(); i++) {
streamInfo.getDeviceID(), String redisKey = (String) players.get(i);
streamInfo.getChannelId())); StreamInfo streamInfo = (StreamInfo) redis.get(redisKey);
streamInfos.add(streamInfo);
}
return streamInfos;
} }
@Override private StreamInfo scanOne(String key) {
public StreamInfo queryPlaybackByDevice(String deviceId, String code) { List<Object> playLeys = redis.scan(key);
// String format = String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
// deviceId,
// code);
List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
deviceId,
code));
if (playLeys == null || playLeys.size() == 0) { if (playLeys == null || playLeys.size() == 0) {
playLeys = redis.scan(String.format("%S_*_*_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, return null;
deviceId));
} }
if (playLeys == null || playLeys.size() == 0) return null; return (StreamInfo) redis.get(playLeys.get(0).toString());
return (StreamInfo)redis.get(playLeys.get(0).toString());
} }
@Override @Override
@ -285,4 +319,21 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
Set<Object> realVideos = redis.ZRange(key, start, end); Set<Object> realVideos = redis.ZRange(key, start, end);
return new ArrayList(realVideos); return new ArrayList(realVideos);
} }
public static String getKey(String prefix, String streamId, String channelId, String deviceId) {
if (StringUtils.isBlank(streamId)) {
streamId = "*";
}
if (StringUtils.isBlank(channelId)) {
channelId = "*";
}
if (StringUtils.isBlank(deviceId)) {
deviceId = "*";
}
return String.format("%S%s_%s_%s",
prefix,
streamId,
channelId,
deviceId);
}
} }

8
src/main/java/com/genersoft/iot/vmp/utils/ConfigConst.java

@ -0,0 +1,8 @@
package com.genersoft.iot.vmp.utils;
public class ConfigConst {
/**
* 播流最大并发个数
*/
public static final Integer MAX_STRTEAM_COUNT = 10000;
}

97
src/main/java/com/genersoft/iot/vmp/utils/redis/JedisUtil.java

@ -0,0 +1,97 @@
package com.genersoft.iot.vmp.utils.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.Set;
/**
* @Description:Jedis工具类
* @author: wangshaopeng@sunnybs.com
* @date: 2021年03月22日 下午8:27:29
*/
@Component
public class JedisUtil {
@Autowired
private JedisPool jedisPool;
// ============================== Key ==============================
/**
* 检查给定 key 是否存在
*
* @param key
* @return
*/
public Boolean exists(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
Boolean exists = jedis.exists(key);
return exists;
} finally {
returnToPool(jedis);
}
}
// ============================== Set ==============================
/**
* SADD key member [member ...]
* 将一个或多个 member 元素加入到集合 key 当中已经存在于集合的 member 元素将被忽略
* 假如 key 不存在则创建一个只包含 member 元素作成员的集合
* key 不是集合类型时返回一个错误
*/
public Long sadd(String key, String... members) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
Long smove = jedis.sadd(key, members);
return smove;
} finally {
returnToPool(jedis);
}
}
/**
* SMEMBERS key
* 返回集合 key 中的所有成员
* 不存在的 key 被视为空集合
*/
public Set<String> smembers(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
Set<String> smembers = jedis.smembers(key);
return smembers;
} finally {
returnToPool(jedis);
}
}
/**
* SREM key member1 [member2]
* 移除集合中一个或多个成员
*/
public Long srem(String key, String... member) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
Long srem = jedis.srem(key, member);
return srem;
} finally {
returnToPool(jedis);
}
}
private void returnToPool(Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
}

154
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java

@ -1,28 +1,28 @@
package com.genersoft.iot.vmp.utils.redis; package com.genersoft.iot.vmp.utils.redis;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*; import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
/** /**
* @Description:Redis工具类 * @Description:Redis工具类
* @author: swwheihei * @author: swwheihei
* @date: 2020年5月6日 下午8:27:29 * @date: 2020年5月6日 下午8:27:29
*/ */
@Component @Component
@SuppressWarnings(value = {"rawtypes", "unchecked"})
public class RedisUtil { public class RedisUtil {
@Autowired @Autowired
private RedisTemplate redisTemplate; private RedisTemplate redisTemplate;
/** /**
* 指定缓存失效时间 * 指定缓存失效时间
* @param key *
* @param key
* @param time 时间 * @param time 时间
* @return true / false * @return true / false
*/ */
@ -40,6 +40,7 @@ public class RedisUtil {
/** /**
* 根据 key 获取过期时间 * 根据 key 获取过期时间
*
* @param key * @param key
* @return * @return
*/ */
@ -49,6 +50,7 @@ public class RedisUtil {
/** /**
* 判断 key 是否存在 * 判断 key 是否存在
*
* @param key * @param key
* @return true / false * @return true / false
*/ */
@ -63,12 +65,13 @@ public class RedisUtil {
/** /**
* 删除缓存 * 删除缓存
* @SuppressWarnings("unchecked") 忽略类型转换警告 *
* @param key 一个或者多个 * @param key 一个或者多个
* @SuppressWarnings("unchecked") 忽略类型转换警告
*/ */
public boolean del(String... key) { public boolean del(String... key) {
try { try {
if (key != null && key.length > 0) { if (key != null && key.length > 0) {
if (key.length == 1) { if (key.length == 1) {
redisTemplate.delete(key[0]); redisTemplate.delete(key[0]);
} else { } else {
@ -87,6 +90,7 @@ public class RedisUtil {
/** /**
* 普通缓存获取 * 普通缓存获取
*
* @param key * @param key
* @return * @return
*/ */
@ -96,7 +100,8 @@ public class RedisUtil {
/** /**
* 普通缓存放入 * 普通缓存放入
* @param key *
* @param key
* @param value * @param value
* @return true / false * @return true / false
*/ */
@ -112,9 +117,10 @@ public class RedisUtil {
/** /**
* 普通缓存放入并设置时间 * 普通缓存放入并设置时间
* @param key *
* @param key
* @param value * @param value
* @param time 时间如果 time < 0 则设置无限时间 * @param time 时间如果 time < 0 则设置无限时间
* @return true / false * @return true / false
*/ */
public boolean set(String key, Object value, long time) { public boolean set(String key, Object value, long time) {
@ -133,7 +139,8 @@ public class RedisUtil {
/** /**
* 递增 * 递增
* @param key *
* @param key
* @param delta 递增大小 * @param delta 递增大小
* @return * @return
*/ */
@ -146,7 +153,8 @@ public class RedisUtil {
/** /**
* 递减 * 递减
* @param key *
* @param key
* @param delta 递减大小 * @param delta 递减大小
* @return * @return
*/ */
@ -161,7 +169,8 @@ public class RedisUtil {
/** /**
* HashGet * HashGet
* @param key no null *
* @param key no null
* @param item no null * @param item no null
* @return * @return
*/ */
@ -171,6 +180,7 @@ public class RedisUtil {
/** /**
* 获取 key 对应的 map * 获取 key 对应的 map
*
* @param key no null * @param key no null
* @return 对应的多个键值 * @return 对应的多个键值
*/ */
@ -180,6 +190,7 @@ public class RedisUtil {
/** /**
* HashSet * HashSet
*
* @param key * @param key
* @param map * @param map
* @return true / false * @return true / false
@ -196,8 +207,9 @@ public class RedisUtil {
/** /**
* HashSet 并设置时间 * HashSet 并设置时间
* @param key *
* @param map * @param key
* @param map
* @param time 时间 * @param time 时间
* @return true / false * @return true / false
*/ */
@ -216,8 +228,9 @@ public class RedisUtil {
/** /**
* 向一张 Hash表 中放入数据如不存在则创建 * 向一张 Hash表 中放入数据如不存在则创建
* @param key *
* @param item * @param key
* @param item
* @param value * @param value
* @return true / false * @return true / false
*/ */
@ -233,10 +246,11 @@ public class RedisUtil {
/** /**
* 向一张 Hash表 中放入数据并设置时间如不存在则创建 * 向一张 Hash表 中放入数据并设置时间如不存在则创建
* @param key *
* @param item * @param key
* @param item
* @param value * @param value
* @param time 时间如果原来的 Hash表 设置了时间这里会覆盖 * @param time 时间如果原来的 Hash表 设置了时间这里会覆盖
* @return true / false * @return true / false
*/ */
public boolean hset(String key, String item, Object value, long time) { public boolean hset(String key, String item, Object value, long time) {
@ -254,7 +268,8 @@ public class RedisUtil {
/** /**
* 删除 Hash表 中的值 * 删除 Hash表 中的值
* @param key *
* @param key
* @param item 可以多个no null * @param item 可以多个no null
*/ */
public void hdel(String key, Object... item) { public void hdel(String key, Object... item) {
@ -263,7 +278,8 @@ public class RedisUtil {
/** /**
* 判断 Hash表 中是否有该键的值 * 判断 Hash表 中是否有该键的值
* @param key no null *
* @param key no null
* @param item no null * @param item no null
* @return true / false * @return true / false
*/ */
@ -273,9 +289,10 @@ public class RedisUtil {
/** /**
* Hash递增如果不存在则创建一个并把新增的值返回 * Hash递增如果不存在则创建一个并把新增的值返回
* @param key *
* @param key
* @param item * @param item
* @param by 递增大小 > 0 * @param by 递增大小 > 0
* @return * @return
*/ */
public Double hincr(String key, String item, Double by) { public Double hincr(String key, String item, Double by) {
@ -284,9 +301,10 @@ public class RedisUtil {
/** /**
* Hash递减 * Hash递减
* @param key *
* @param key
* @param item * @param item
* @param by 递减大小 * @param by 递减大小
* @return * @return
*/ */
public Double hdecr(String key, String item, Double by) { public Double hdecr(String key, String item, Double by) {
@ -297,6 +315,7 @@ public class RedisUtil {
/** /**
* 根据 key 获取 set 中的所有值 * 根据 key 获取 set 中的所有值
*
* @param key * @param key
* @return * @return
*/ */
@ -311,7 +330,8 @@ public class RedisUtil {
/** /**
* 从键为 key set 根据 value 查询是否存在 * 从键为 key set 根据 value 查询是否存在
* @param key *
* @param key
* @param value * @param value
* @return true / false * @return true / false
*/ */
@ -326,7 +346,8 @@ public class RedisUtil {
/** /**
* 将数据放入 set缓存 * 将数据放入 set缓存
* @param key 键值 *
* @param key 键值
* @param values 可以多个 * @param values 可以多个
* @return 成功个数 * @return 成功个数
*/ */
@ -341,8 +362,9 @@ public class RedisUtil {
/** /**
* 将数据放入 set缓存并设置时间 * 将数据放入 set缓存并设置时间
* @param key *
* @param time 时间 * @param key
* @param time 时间
* @param values 可以多个 * @param values 可以多个
* @return 成功放入个数 * @return 成功放入个数
*/ */
@ -361,6 +383,7 @@ public class RedisUtil {
/** /**
* 获取 set缓存的长度 * 获取 set缓存的长度
*
* @param key * @param key
* @return 长度 * @return 长度
*/ */
@ -375,7 +398,8 @@ public class RedisUtil {
/** /**
* 移除 set缓存中值为 value * 移除 set缓存中值为 value
* @param key *
* @param key
* @param values * @param values
* @return 成功移除个数 * @return 成功移除个数
*/ */
@ -396,7 +420,7 @@ public class RedisUtil {
* @param value * @param value
* @param score * @param score
*/ */
public void zAdd(Object key, Object value, double score) { public void zAdd(String key, String value, double score) {
redisTemplate.opsForZSet().add(key, value, score); redisTemplate.opsForZSet().add(key, value, score);
} }
@ -406,7 +430,7 @@ public class RedisUtil {
* @param key * @param key
* @param value * @param value
*/ */
public void zRemove(Object key, Object value) { public void zRemove(String key, String value) {
redisTemplate.opsForZSet().remove(key, value); redisTemplate.opsForZSet().remove(key, value);
} }
@ -417,7 +441,7 @@ public class RedisUtil {
* @param value * @param value
* @param score * @param score
*/ */
public Double zIncrScore(Object key, Object value, double score) { public Double zIncrScore(String key, String value, double score) {
return redisTemplate.opsForZSet().incrementScore(key, value, score); return redisTemplate.opsForZSet().incrementScore(key, value, score);
} }
@ -428,7 +452,7 @@ public class RedisUtil {
* @param value * @param value
* @return * @return
*/ */
public Double zScore(Object key, Object value) { public Double zScore(String key, String value) {
return redisTemplate.opsForZSet().score(key, value); return redisTemplate.opsForZSet().score(key, value);
} }
@ -439,7 +463,7 @@ public class RedisUtil {
* @param value * @param value
* @return * @return
*/ */
public Long zRank(Object key, Object value) { public Long zRank(String key, String value) {
return redisTemplate.opsForZSet().rank(key, value); return redisTemplate.opsForZSet().rank(key, value);
} }
@ -449,13 +473,13 @@ public class RedisUtil {
* @param key * @param key
* @return * @return
*/ */
public Long zSize(Object key) { public Long zSize(String key) {
return redisTemplate.opsForZSet().zCard(key); return redisTemplate.opsForZSet().zCard(key);
} }
/** /**
* 查询集合中指定顺序的值 0 -1 表示获取全部的集合内容 zrange * 查询集合中指定顺序的值 0 -1 表示获取全部的集合内容 zrange
* * <p>
* 返回有序的集合score小的在前面 * 返回有序的集合score小的在前面
* *
* @param key * @param key
@ -463,9 +487,10 @@ public class RedisUtil {
* @param end * @param end
* @return * @return
*/ */
public Set<Object> ZRange(Object key, int start, int end) { public Set<String> ZRange(String key, int start, int end) {
return redisTemplate.opsForZSet().range(key, start, end); return redisTemplate.opsForZSet().range(key, start, end);
} }
/** /**
* 查询集合中指定顺序的值和score0, -1 表示获取全部的集合内容 * 查询集合中指定顺序的值和score0, -1 表示获取全部的集合内容
* *
@ -474,12 +499,13 @@ public class RedisUtil {
* @param end * @param end
* @return * @return
*/ */
public Set<ZSetOperations.TypedTuple<String>> zRangeWithScore(Object key, int start, int end) { public Set<ZSetOperations.TypedTuple<String>> zRangeWithScore(String key, int start, int end) {
return redisTemplate.opsForZSet().rangeWithScores(key, start, end); return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
} }
/** /**
* 查询集合中指定顺序的值 zrevrange * 查询集合中指定顺序的值 zrevrange
* * <p>
* 返回有序的集合中score大的在前面 * 返回有序的集合中score大的在前面
* *
* @param key * @param key
@ -487,9 +513,10 @@ public class RedisUtil {
* @param end * @param end
* @return * @return
*/ */
public Set<String> zRevRange(Object key, int start, int end) { public Set<String> zRevRange(String key, int start, int end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end); return redisTemplate.opsForZSet().reverseRange(key, start, end);
} }
/** /**
* 根据score的值来获取满足条件的集合 zrangebyscore * 根据score的值来获取满足条件的集合 zrangebyscore
* *
@ -498,7 +525,7 @@ public class RedisUtil {
* @param max * @param max
* @return * @return
*/ */
public Set<String> zSortRange(Object key, int min, int max) { public Set<String> zSortRange(String key, int min, int max) {
return redisTemplate.opsForZSet().rangeByScore(key, min, max); return redisTemplate.opsForZSet().rangeByScore(key, min, max);
} }
@ -507,9 +534,10 @@ public class RedisUtil {
/** /**
* 获取 list缓存的内容 * 获取 list缓存的内容
* @param key *
* @param key
* @param start 开始 * @param start 开始
* @param end 结束0 -1 代表所有值 * @param end 结束0 -1 代表所有值
* @return * @return
*/ */
public List<Object> lGet(String key, long start, long end) { public List<Object> lGet(String key, long start, long end) {
@ -523,6 +551,7 @@ public class RedisUtil {
/** /**
* 获取 list缓存的长度 * 获取 list缓存的长度
*
* @param key * @param key
* @return 长度 * @return 长度
*/ */
@ -537,7 +566,8 @@ public class RedisUtil {
/** /**
* 根据索引 index 获取键为 key list 中的元素 * 根据索引 index 获取键为 key list 中的元素
* @param key *
* @param key
* @param index 索引 * @param index 索引
* index >= 0 {0:表头, 1:第二个元素} * index >= 0 {0:表头, 1:第二个元素}
* index < 0 {-1:表尾, -2:倒数第二个元素} * index < 0 {-1:表尾, -2:倒数第二个元素}
@ -554,7 +584,8 @@ public class RedisUtil {
/** /**
* 将值 value 插入键为 key list 如果 list 不存在则创建空 list * 将值 value 插入键为 key list 如果 list 不存在则创建空 list
* @param key *
* @param key
* @param value * @param value
* @return true / false * @return true / false
*/ */
@ -570,9 +601,10 @@ public class RedisUtil {
/** /**
* 将值 value 插入键为 key list 并设置时间 * 将值 value 插入键为 key list 并设置时间
* @param key *
* @param key
* @param value * @param value
* @param time 时间 * @param time 时间
* @return true / false * @return true / false
*/ */
public boolean lSet(String key, Object value, long time) { public boolean lSet(String key, Object value, long time) {
@ -590,7 +622,8 @@ public class RedisUtil {
/** /**
* values 插入键为 key list * values 插入键为 key list
* @param key *
* @param key
* @param values * @param values
* @return true / false * @return true / false
*/ */
@ -606,9 +639,10 @@ public class RedisUtil {
/** /**
* values 插入键为 key list 并设置时间 * values 插入键为 key list 并设置时间
* @param key *
* @param key
* @param values * @param values
* @param time 时间 * @param time 时间
* @return true / false * @return true / false
*/ */
public boolean lSetList(String key, List<Object> values, long time) { public boolean lSetList(String key, List<Object> values, long time) {
@ -626,7 +660,8 @@ public class RedisUtil {
/** /**
* 根据索引 index 修改键为 key 的值 * 根据索引 index 修改键为 key 的值
* @param key *
* @param key
* @param index 索引 * @param index 索引
* @param value * @param value
* @return true / false * @return true / false
@ -643,7 +678,8 @@ public class RedisUtil {
/** /**
* 在键为 key list 中删除值为 value 的元素 * 在键为 key list 中删除值为 value 的元素
* @param key *
* @param key
* @param count 如果 count == 0 则删除 list 中所有值为 value 的元素 * @param count 如果 count == 0 则删除 list 中所有值为 value 的元素
* 如果 count > 0 则删除 list 中最左边那个值为 value 的元素 * 如果 count > 0 则删除 list 中最左边那个值为 value 的元素
* 如果 count < 0 则删除 list 中最右边那个值为 value 的元素 * 如果 count < 0 则删除 list 中最右边那个值为 value 的元素
@ -661,6 +697,7 @@ public class RedisUtil {
/** /**
* 模糊查询 * 模糊查询
*
* @param key * @param key
* @return true / false * @return true / false
*/ */
@ -701,6 +738,7 @@ public class RedisUtil {
/** /**
* 模糊查询 * 模糊查询
*
* @param query 查询参数 * @param query 查询参数
* @return * @return
*/ */

120
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceConfig.java

@ -1,120 +0,0 @@
/**
* 设备设置命令API接口
*
* @author lawrencehj
* @date 2021年2月2日
*/
package com.genersoft.iot.vmp.vmanager.device;
import javax.sip.message.Response;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class DeviceConfig {
private final static Logger logger = LoggerFactory.getLogger(DeviceQuery.class);
@Autowired
private IVideoManagerStorager storager;
@Autowired
private SIPCommander cmder;
@Autowired
private DeferredResultHolder resultHolder;
/**
* 看守位控制命令API接口
*
* @param deviceId
* @param enabled 看守位使能1:开启,0:关闭
* @param resetTime 自动归位时间间隔可选
* @param presetIndex 调用预置位编号可选
* @param channelId 通道编码可选
*/
@GetMapping("/config/{deviceId}/basicParam")
public DeferredResult<ResponseEntity<String>> homePositionApi(@PathVariable String deviceId,
@RequestParam(required = false) String channelId,
@RequestParam(required = false) String name,
@RequestParam(required = false) String expiration,
@RequestParam(required = false) String heartBeatInterval,
@RequestParam(required = false) String heartBeatCount) {
if (logger.isDebugEnabled()) {
logger.debug("报警复位API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.deviceBasicConfigCmd(device, channelId, name, expiration, heartBeatInterval, heartBeatCount, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData(String.format("设备配置操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("设备配置操作超时, 设备未返回应答指令"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
JSONObject json = new JSONObject();
json.put("DeviceID", deviceId);
json.put("Status", "Timeout");
json.put("Description", "设备配置操作超时, 设备未返回应答指令");
msg.setData(json); //("看守位控制操作超时, 设备未返回应答指令");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
return result;
}
/**
* 设备配置查询请求API接口
*
* @param deviceId
*/
@GetMapping("/config/{deviceId}/query/{configType}")
public DeferredResult<ResponseEntity<String>> configDownloadApi(@PathVariable String deviceId,
@PathVariable String configType,
@RequestParam(required = false) String channelId) {
if (logger.isDebugEnabled()) {
logger.debug("设备状态查询API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.deviceConfigQuery(device, channelId, configType, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData(String.format("获取设备配置失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String >> (3 * 1000L);
result.onTimeout(()->{
logger.warn(String.format("获取设备配置超时"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData("Timeout. Device did not response to this command.");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
return result;
}
}

119
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceConfigController.java

@ -0,0 +1,119 @@
/**
* 设备设置命令API接口
*
* @author lawrencehj
* @date 2021年2月2日
*/
package com.genersoft.iot.vmp.vmanager.device;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.message.Response;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class DeviceConfigController {
private final static Logger logger = LoggerFactory.getLogger(DeviceConfigController.class);
@Autowired
private IVideoManagerStorager storager;
@Autowired
private SIPCommander cmder;
@Autowired
private DeferredResultHolder resultHolder;
/**
* 看守位控制命令API接口
*
* @param deviceId
* @param enabled 看守位使能1:开启,0:关闭
* @param resetTime 自动归位时间间隔可选
* @param presetIndex 调用预置位编号可选
* @param channelId 通道编码可选
*/
@GetMapping("/config/{deviceId}/basicParam")
public DeferredResult<ResponseEntity<String>> homePositionApi(@PathVariable String deviceId,
@RequestParam(required = false) String channelId,
@RequestParam(required = false) String name,
@RequestParam(required = false) String expiration,
@RequestParam(required = false) String heartBeatInterval,
@RequestParam(required = false) String heartBeatCount) {
if (logger.isDebugEnabled()) {
logger.debug("报警复位API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.deviceBasicConfigCmd(device, channelId, name, expiration, heartBeatInterval, heartBeatCount, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData(String.format("设备配置操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("设备配置操作超时, 设备未返回应答指令"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
JSONObject json = new JSONObject();
json.put("DeviceID", deviceId);
json.put("Status", "Timeout");
json.put("Description", "设备配置操作超时, 设备未返回应答指令");
msg.setData(json); //("看守位控制操作超时, 设备未返回应答指令");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
return result;
}
/**
* 设备配置查询请求API接口
*
* @param deviceId
*/
@GetMapping("/config/{deviceId}/query/{configType}")
public DeferredResult<ResponseEntity<String>> configDownloadApi(@PathVariable String deviceId,
@PathVariable String configType,
@RequestParam(required = false) String channelId) {
if (logger.isDebugEnabled()) {
logger.debug("设备状态查询API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.deviceConfigQuery(device, channelId, configType, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData(String.format("获取设备配置失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("获取设备配置超时"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData("Timeout. Device did not response to this command.");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
return result;
}
}

238
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceControl.java

@ -1,238 +0,0 @@
/**
* 设备控制命令API接口
*
* @author lawrencehj
* @date 2021年2月1日
*/
package com.genersoft.iot.vmp.vmanager.device;
import javax.sip.message.Response;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class DeviceControl {
private final static Logger logger = LoggerFactory.getLogger(DeviceQuery.class);
@Autowired
private IVideoManagerStorager storager;
@Autowired
private SIPCommander cmder;
@Autowired
private DeferredResultHolder resultHolder;
/**
* 远程启动控制命令API接口
*
* @param deviceId
*/
@GetMapping("/control/{deviceId}/teleboot")
@PostMapping("/control/{deviceId}/teleboot")
public ResponseEntity<String> teleBootApi(@PathVariable String deviceId) {
if (logger.isDebugEnabled()) {
logger.debug("设备远程启动API调用");
}
Device device = storager.queryVideoDevice(deviceId);
boolean sucsess = cmder.teleBootCmd(device);
if (sucsess) {
JSONObject json = new JSONObject();
json.put("DeviceID", deviceId);
json.put("Result", "OK");
return new ResponseEntity<>(json.toJSONString(), HttpStatus.OK);
} else {
logger.warn("设备远程启动API调用失败!");
return new ResponseEntity<String>("设备远程启动API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 录像控制命令API接口
*
* @param deviceId
* @param recordCmdStr Record手动录像StopRecord停止手动录像
* @param channelId 通道编码可选
*/
@GetMapping("/control/{deviceId}/record/{recordCmdStr}")
public DeferredResult<ResponseEntity<String>> recordApi(@PathVariable String deviceId,
@PathVariable String recordCmdStr, @RequestParam(required = false) String channelId) {
if (logger.isDebugEnabled()) {
logger.debug("开始/停止录像API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.recordCmd(device, channelId, recordCmdStr, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData(String.format("开始/停止录像操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("开始/停止录像操作超时, 设备未返回应答指令"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData("Timeout. Device did not response to this command.");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
return result;
}
/**
* 报警布防/撤防命令API接口
*
* @param deviceId
* @param guardCmdStr SetGuard布防ResetGuard撤防
*/
@GetMapping("/control/{deviceId}/guard/{guardCmdStr}")
public DeferredResult<ResponseEntity<String>> guardApi(@PathVariable String deviceId, @PathVariable String guardCmdStr) {
if (logger.isDebugEnabled()) {
logger.debug("布防/撤防API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.guardCmd(device, guardCmdStr, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
msg.setData(String.format("布防/撤防操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("布防/撤防操作超时, 设备未返回应答指令"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
msg.setData("Timeout. Device did not response to this command.");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId, result);
return result;
}
/**
* 报警复位API接口
*
* @param deviceId
* @param alarmMethod 报警方式可选
* @param alarmType 报警类型可选
*/
@GetMapping("/control/{deviceId}/resetAlarm")
public DeferredResult<ResponseEntity<String>> resetAlarmApi(@PathVariable String deviceId,
@RequestParam(required = false) String alarmMethod,
@RequestParam(required = false) String alarmType) {
if (logger.isDebugEnabled()) {
logger.debug("报警复位API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.alarmCmd(device, alarmMethod, alarmType, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
msg.setData(String.format("报警复位操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("报警复位操作超时, 设备未返回应答指令"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
msg.setData("Timeout. Device did not response to this command.");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId, result);
return result;
}
/**
* 强制关键帧API接口
*
* @param deviceId
* @param channelId
*/
@GetMapping("/control/{deviceId}/iFrame")
@PostMapping("/control/{deviceId}/iFrame")
public ResponseEntity<String> iFrame(@PathVariable String deviceId,
@RequestParam(required = false) String channelId) {
if (logger.isDebugEnabled()) {
logger.debug("强制关键帧API调用");
}
Device device = storager.queryVideoDevice(deviceId);
boolean sucsess = cmder.iFrameCmd(device, channelId);
if (sucsess) {
JSONObject json = new JSONObject();
json.put("DeviceID", deviceId);
json.put("ChannelID", channelId);
json.put("Result", "OK");
return new ResponseEntity<>(json.toJSONString(), HttpStatus.OK);
} else {
logger.warn("强制关键帧API调用失败!");
return new ResponseEntity<String>("强制关键帧API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 看守位控制命令API接口
*
* @param deviceId
* @param enabled 看守位使能1:开启,0:关闭
* @param resetTime 自动归位时间间隔可选
* @param presetIndex 调用预置位编号可选
* @param channelId 通道编码可选
*/
@GetMapping("/control/{deviceId}/homePosition/{enabled}")
public DeferredResult<ResponseEntity<String>> homePositionApi(@PathVariable String deviceId,
@PathVariable String enabled,
@RequestParam(required = false) String resetTime,
@RequestParam(required = false) String presetIndex,
@RequestParam(required = false) String channelId) {
if (logger.isDebugEnabled()) {
logger.debug("报警复位API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.homePositionCmd(device, channelId, enabled, resetTime, presetIndex, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData(String.format("看守位控制操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("看守位控制操作超时, 设备未返回应答指令"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
JSONObject json = new JSONObject();
json.put("DeviceID", deviceId);
json.put("Status", "Timeout");
json.put("Description", "看守位控制操作超时, 设备未返回应答指令");
msg.setData(json); //("看守位控制操作超时, 设备未返回应答指令");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
return result;
}
}

237
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceControlController.java

@ -0,0 +1,237 @@
/**
* 设备控制命令API接口
*
* @author lawrencehj
* @date 2021年2月1日
*/
package com.genersoft.iot.vmp.vmanager.device;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.message.Response;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class DeviceControlController {
private final static Logger logger = LoggerFactory.getLogger(DeviceControlController.class);
@Autowired
private IVideoManagerStorager storager;
@Autowired
private SIPCommander cmder;
@Autowired
private DeferredResultHolder resultHolder;
/**
* 远程启动控制命令API接口
*
* @param deviceId
*/
@GetMapping("/control/{deviceId}/teleboot")
@PostMapping("/control/{deviceId}/teleboot")
public ResponseEntity<String> teleBootApi(@PathVariable String deviceId) {
if (logger.isDebugEnabled()) {
logger.debug("设备远程启动API调用");
}
Device device = storager.queryVideoDevice(deviceId);
boolean sucsess = cmder.teleBootCmd(device);
if (sucsess) {
JSONObject json = new JSONObject();
json.put("DeviceID", deviceId);
json.put("Result", "OK");
return new ResponseEntity<>(json.toJSONString(), HttpStatus.OK);
} else {
logger.warn("设备远程启动API调用失败!");
return new ResponseEntity<String>("设备远程启动API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 录像控制命令API接口
*
* @param deviceId
* @param recordCmdStr Record手动录像StopRecord停止手动录像
* @param channelId 通道编码可选
*/
@GetMapping("/control/{deviceId}/record/{recordCmdStr}")
public DeferredResult<ResponseEntity<String>> recordApi(@PathVariable String deviceId,
@PathVariable String recordCmdStr, @RequestParam(required = false) String channelId) {
if (logger.isDebugEnabled()) {
logger.debug("开始/停止录像API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.recordCmd(device, channelId, recordCmdStr, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData(String.format("开始/停止录像操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("开始/停止录像操作超时, 设备未返回应答指令"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData("Timeout. Device did not response to this command.");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
return result;
}
/**
* 报警布防/撤防命令API接口
*
* @param deviceId
* @param guardCmdStr SetGuard布防ResetGuard撤防
*/
@GetMapping("/control/{deviceId}/guard/{guardCmdStr}")
public DeferredResult<ResponseEntity<String>> guardApi(@PathVariable String deviceId, @PathVariable String guardCmdStr) {
if (logger.isDebugEnabled()) {
logger.debug("布防/撤防API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.guardCmd(device, guardCmdStr, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
msg.setData(String.format("布防/撤防操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("布防/撤防操作超时, 设备未返回应答指令"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
msg.setData("Timeout. Device did not response to this command.");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId, result);
return result;
}
/**
* 报警复位API接口
*
* @param deviceId
* @param alarmMethod 报警方式可选
* @param alarmType 报警类型可选
*/
@GetMapping("/control/{deviceId}/resetAlarm")
public DeferredResult<ResponseEntity<String>> resetAlarmApi(@PathVariable String deviceId,
@RequestParam(required = false) String alarmMethod,
@RequestParam(required = false) String alarmType) {
if (logger.isDebugEnabled()) {
logger.debug("报警复位API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.alarmCmd(device, alarmMethod, alarmType, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
msg.setData(String.format("报警复位操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("报警复位操作超时, 设备未返回应答指令"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId);
msg.setData("Timeout. Device did not response to this command.");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + deviceId, result);
return result;
}
/**
* 强制关键帧API接口
*
* @param deviceId
* @param channelId
*/
@GetMapping("/control/{deviceId}/iFrame")
@PostMapping("/control/{deviceId}/iFrame")
public ResponseEntity<String> iFrame(@PathVariable String deviceId,
@RequestParam(required = false) String channelId) {
if (logger.isDebugEnabled()) {
logger.debug("强制关键帧API调用");
}
Device device = storager.queryVideoDevice(deviceId);
boolean sucsess = cmder.iFrameCmd(device, channelId);
if (sucsess) {
JSONObject json = new JSONObject();
json.put("DeviceID", deviceId);
json.put("ChannelID", channelId);
json.put("Result", "OK");
return new ResponseEntity<>(json.toJSONString(), HttpStatus.OK);
} else {
logger.warn("强制关键帧API调用失败!");
return new ResponseEntity<String>("强制关键帧API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 看守位控制命令API接口
*
* @param deviceId
* @param enabled 看守位使能1:开启,0:关闭
* @param resetTime 自动归位时间间隔可选
* @param presetIndex 调用预置位编号可选
* @param channelId 通道编码可选
*/
@GetMapping("/control/{deviceId}/homePosition/{enabled}")
public DeferredResult<ResponseEntity<String>> homePositionApi(@PathVariable String deviceId,
@PathVariable String enabled,
@RequestParam(required = false) String resetTime,
@RequestParam(required = false) String presetIndex,
@RequestParam(required = false) String channelId) {
if (logger.isDebugEnabled()) {
logger.debug("报警复位API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.homePositionCmd(device, channelId, enabled, resetTime, presetIndex, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
msg.setData(String.format("看守位控制操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("看守位控制操作超时, 设备未返回应答指令"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
JSONObject json = new JSONObject();
json.put("DeviceID", deviceId);
json.put("Status", "Timeout");
json.put("Description", "看守位控制操作超时, 设备未返回应答指令");
msg.setData(json); //("看守位控制操作超时, 设备未返回应答指令");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
return result;
}
}

261
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java

@ -0,0 +1,261 @@
package com.genersoft.iot.vmp.vmanager.device;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.message.Response;
@CrossOrigin
@RestController
@RequestMapping("/api")
public class DeviceController {
private final static Logger logger = LoggerFactory.getLogger(DeviceController.class);
@Autowired
private IVideoManagerStorager storager;
@Autowired
private SIPCommander cmder;
@Autowired
private DeferredResultHolder resultHolder;
@Autowired
private DeviceOffLineDetector offLineDetector;
@GetMapping("/devices/{deviceId}")
public ResponseEntity<Device> devices(@PathVariable String deviceId) {
if (logger.isDebugEnabled()) {
logger.debug("查询视频设备API调用,deviceId:" + deviceId);
}
Device device = storager.queryVideoDevice(deviceId);
return new ResponseEntity<>(device, HttpStatus.OK);
}
@GetMapping("/devices")
public PageInfo<Device> devices(Integer page, Integer count) {
if (logger.isDebugEnabled()) {
logger.debug("查询所有视频设备API调用");
}
if (null == page) {
page = 1;
}
if (null == count) {
count = 10;
}
return storager.queryVideoDeviceList(page, count);
}
/**
* 分页查询通道数
*
* @param deviceId 设备id
* @param page 当前页
* @param count 每页条数
* @param query 查询内容
* @param online 是否在线 在线 true / 离线 false
* @param channelType 设备 false/子目录 true
* @return 通道列表
*/
@GetMapping("/devices/{deviceId}/channels")
public ResponseEntity<PageInfo> channels(@PathVariable String deviceId,
int page, int count,
@RequestParam(required = false) String query,
@RequestParam(required = false) Boolean online,
@RequestParam(required = false) Boolean channelType
) {
if (logger.isDebugEnabled()) {
logger.debug("查询所有视频设备API调用");
}
if (StringUtils.isEmpty(query)) {
query = null;
}
PageInfo pageResult = storager.queryChannelsByDeviceId(deviceId, query, channelType, online, page, count);
return new ResponseEntity<>(pageResult, HttpStatus.OK);
}
@PostMapping("/devices/{deviceId}/sync")
public DeferredResult<ResponseEntity<Device>> devicesSync(@PathVariable String deviceId) {
if (logger.isDebugEnabled()) {
}
logger.debug("设备通道信息同步API调用,deviceId:" + deviceId);
Device device = storager.queryVideoDevice(deviceId);
cmder.catalogQuery(device, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_CATALOG + deviceId);
msg.setData(String.format("同步通道失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<Device>> result = new DeferredResult<ResponseEntity<Device>>(2 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("设备通道信息同步超时"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_CATALOG + deviceId);
msg.setData("Timeout");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CATALOG + deviceId, result);
return result;
}
@PostMapping("/devices/{deviceId}/delete")
public ResponseEntity<String> delete(@PathVariable String deviceId) {
if (logger.isDebugEnabled()) {
logger.debug("设备信息删除API调用,deviceId:" + deviceId);
}
if (offLineDetector.isOnline(deviceId)) {
return new ResponseEntity<String>("不允许删除在线设备!", HttpStatus.NOT_ACCEPTABLE);
}
boolean isSuccess = storager.delete(deviceId);
if (isSuccess) {
JSONObject json = new JSONObject();
json.put("deviceId", deviceId);
return new ResponseEntity<>(json.toString(), HttpStatus.OK);
} else {
logger.warn("设备信息删除API调用失败!");
return new ResponseEntity<>("设备信息删除API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 分页查询通道数
*
* @param channelId 通道id
* @param page 当前页
* @param count 每页条数
* @return 子通道列表
*/
@GetMapping("/subChannels/{deviceId}/{channelId}/channels")
public ResponseEntity<PageInfo> subChannels(@PathVariable String deviceId,
@PathVariable String channelId,
int page,
int count,
@RequestParam(required = false) String query,
@RequestParam(required = false) String online,
@RequestParam(required = false) Boolean channelType) {
if (logger.isDebugEnabled()) {
logger.debug("查询所有视频通道API调用");
}
DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
if (deviceChannel == null) {
PageInfo<DeviceChannel> deviceChannelPageResult = new PageInfo<>();
return new ResponseEntity<>(deviceChannelPageResult, HttpStatus.OK);
}
PageInfo pageResult = storager.querySubChannels(deviceId, channelId, query, channelType, online, page, count);
return new ResponseEntity<>(pageResult, HttpStatus.OK);
}
@PostMapping("/channel/update/{deviceId}")
public ResponseEntity<PageInfo> updateChannel(@PathVariable String deviceId, DeviceChannel channel) {
storager.updateChannel(deviceId, channel);
return new ResponseEntity<>(null, HttpStatus.OK);
}
@GetMapping("/devices/{deviceId}/transport/{streamMode}")
@PostMapping("/devices/{deviceId}/transport/{streamMode}")
public ResponseEntity<PageInfo> updateTransport(@PathVariable String deviceId, @PathVariable String streamMode) {
Device device = storager.queryVideoDevice(deviceId);
device.setStreamMode(streamMode);
storager.updateDevice(device);
return new ResponseEntity<>(null, HttpStatus.OK);
}
/**
* 设备状态查询请求API接口
*
* @param deviceId
*/
@GetMapping("/devices/{deviceId}/status")
public DeferredResult<ResponseEntity<String>> deviceStatusApi(@PathVariable String deviceId) {
if (logger.isDebugEnabled()) {
logger.debug("设备状态查询API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.deviceStatusQuery(device, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId);
msg.setData(String.format("获取设备状态失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(2 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("获取设备状态超时"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId);
msg.setData("Timeout. Device did not response to this command.");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId, result);
return result;
}
/**
* 设备报警查询请求API接口
*
* @param deviceId
*/
@GetMapping("/alarm/{deviceId}")
public DeferredResult<ResponseEntity<String>> alarmApi(@PathVariable String deviceId,
@RequestParam(required = false) String startPriority,
@RequestParam(required = false) String endPriority,
@RequestParam(required = false) String alarmMethod,
@RequestParam(required = false) String alarmType,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime) {
if (logger.isDebugEnabled()) {
logger.debug("设备报警查询API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.alarmInfoQuery(device, startPriority, endPriority, alarmMethod, alarmType, startTime, endTime, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId);
msg.setData(String.format("设备报警查询失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
result.onTimeout(() -> {
logger.warn(String.format("设备报警查询超时"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId);
msg.setData("设备报警查询超时");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId, result);
return result;
}
}

255
src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceQuery.java

@ -1,255 +0,0 @@
package com.genersoft.iot.vmp.vmanager.device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import javax.sip.message.Response;
@SuppressWarnings("rawtypes")
@CrossOrigin
@RestController
@RequestMapping("/api")
public class DeviceQuery {
private final static Logger logger = LoggerFactory.getLogger(DeviceQuery.class);
@Autowired
private IVideoManagerStorager storager;
@Autowired
private SIPCommander cmder;
@Autowired
private DeferredResultHolder resultHolder;
@Autowired
private DeviceOffLineDetector offLineDetector;
@GetMapping("/devices/{deviceId}")
public ResponseEntity<Device> devices(@PathVariable String deviceId){
if (logger.isDebugEnabled()) {
logger.debug("查询视频设备API调用,deviceId:" + deviceId);
}
Device device = storager.queryVideoDevice(deviceId);
return new ResponseEntity<>(device,HttpStatus.OK);
}
@GetMapping("/devices")
public PageInfo<Device> devices(int page, int count){
if (logger.isDebugEnabled()) {
logger.debug("查询所有视频设备API调用");
}
return storager.queryVideoDeviceList(page, count);
}
/**
* 分页查询通道数
*
* @param deviceId 设备id
* @param page 当前页
* @param count 每页条数
* @param query 查询内容
* @param online 是否在线 在线 true / 离线 false
* @param channelType 设备 false/子目录 true
* @return 通道列表
*/
@GetMapping("/devices/{deviceId}/channels")
public ResponseEntity<PageInfo> channels(@PathVariable String deviceId,
int page, int count,
@RequestParam(required = false) String query,
@RequestParam(required = false) Boolean online,
@RequestParam(required = false) Boolean channelType) {
if (logger.isDebugEnabled()) {
logger.debug("查询视频设备通道API调用");
}
if (StringUtils.isEmpty(query)) {
query = null;
}
PageInfo pageResult = storager.queryChannelsByDeviceId(deviceId, query, channelType, online, page, count);
return new ResponseEntity<>(pageResult,HttpStatus.OK);
}
@PostMapping("/devices/{deviceId}/sync")
public DeferredResult<ResponseEntity<Device>> devicesSync(@PathVariable String deviceId){
if (logger.isDebugEnabled()) {
}
logger.debug("设备通道信息同步API调用,deviceId:" + deviceId);
Device device = storager.queryVideoDevice(deviceId);
cmder.catalogQuery(device, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId);
msg.setData(String.format("同步通道失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<Device>> result = new DeferredResult<ResponseEntity<Device>>(2*1000L);
result.onTimeout(()->{
logger.warn(String.format("设备通道信息同步超时"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId);
msg.setData("Timeout");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CATALOG+deviceId, result);
return result;
}
@PostMapping("/devices/{deviceId}/delete")
public ResponseEntity<String> delete(@PathVariable String deviceId){
if (logger.isDebugEnabled()) {
logger.debug("设备信息删除API调用,deviceId:" + deviceId);
}
if (offLineDetector.isOnline(deviceId)) {
return new ResponseEntity<String>("不允许删除在线设备!", HttpStatus.NOT_ACCEPTABLE);
}
boolean isSuccess = storager.delete(deviceId);
if (isSuccess) {
JSONObject json = new JSONObject();
json.put("deviceId", deviceId);
return new ResponseEntity<>(json.toString(),HttpStatus.OK);
} else {
logger.warn("设备信息删除API调用失败!");
return new ResponseEntity<String>("设备信息删除API调用失败!", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 分页查询通道数
* @param channelId 通道id
* @param page 当前页
* @param count 每页条数
* @return 子通道列表
*/
@GetMapping("/subChannels/{deviceId}/{channelId}/channels")
public ResponseEntity<PageInfo> subChannels(@PathVariable String deviceId,
@PathVariable String channelId,
int page,
int count,
@RequestParam(required = false) String query,
@RequestParam(required = false) String online,
@RequestParam(required = false) Boolean channelType){
if (logger.isDebugEnabled()) {
logger.debug("查询所有视频通道API调用");
}
DeviceChannel deviceChannel = storager.queryChannel(deviceId,channelId);
if (deviceChannel == null) {
PageInfo<DeviceChannel> deviceChannelPageResult = new PageInfo<>();
return new ResponseEntity<>(deviceChannelPageResult,HttpStatus.OK);
}
PageInfo pageResult = storager.querySubChannels(deviceId, channelId, query, channelType, online, page, count);
return new ResponseEntity<>(pageResult,HttpStatus.OK);
}
@PostMapping("/channel/update/{deviceId}")
public ResponseEntity<PageInfo> updateChannel(@PathVariable String deviceId,DeviceChannel channel){
storager.updateChannel(deviceId, channel);
return new ResponseEntity<>(null,HttpStatus.OK);
}
@GetMapping("/devices/{deviceId}/transport/{streamMode}")
@PostMapping("/devices/{deviceId}/transport/{streamMode}")
public ResponseEntity<PageInfo> updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){
Device device = storager.queryVideoDevice(deviceId);
device.setStreamMode(streamMode);
storager.updateDevice(device);
return new ResponseEntity<>(null,HttpStatus.OK);
}
/**
* 设备状态查询请求API接口
*
* @param deviceId
*/
@GetMapping("/devices/{deviceId}/status")
public DeferredResult<ResponseEntity<String>> deviceStatusApi(@PathVariable String deviceId) {
if (logger.isDebugEnabled()) {
logger.debug("设备状态查询API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.deviceStatusQuery(device, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId);
msg.setData(String.format("获取设备状态失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(2*1000L);
result.onTimeout(()->{
logger.warn(String.format("获取设备状态超时"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId);
msg.setData("Timeout. Device did not response to this command.");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId, result);
return result;
}
/**
* 设备报警查询请求API接口
*
* @param deviceId
*/
@GetMapping("/alarm/{deviceId}")
public DeferredResult<ResponseEntity<String>> alarmApi(@PathVariable String deviceId,
@RequestParam(required = false) String startPriority,
@RequestParam(required = false) String endPriority,
@RequestParam(required = false) String alarmMethod,
@RequestParam(required = false) String alarmType,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime) {
if (logger.isDebugEnabled()) {
logger.debug("设备报警查询API调用");
}
Device device = storager.queryVideoDevice(deviceId);
cmder.alarmInfoQuery(device, startPriority, endPriority, alarmMethod, alarmType, startTime, endTime, event -> {
Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId);
msg.setData(String.format("设备报警查询失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String >> (3 * 1000L);
result.onTimeout(()->{
logger.warn(String.format("设备报警查询超时"));
// 释放rtpserver
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId);
msg.setData("设备报警查询超时");
resultHolder.invokeResult(msg);
});
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_ALARM + deviceId, result);
return result;
}
}

330
src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java

@ -1,12 +1,18 @@
package com.genersoft.iot.vmp.vmanager.play; package com.genersoft.iot.vmp.vmanager.play;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaServerConfig; import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.play.bean.PlayResult; import com.genersoft.iot.vmp.vmanager.play.bean.PlayResult;
import com.genersoft.iot.vmp.vmanager.service.IPlayService; import com.genersoft.iot.vmp.vmanager.service.IPlayService;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -14,6 +20,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -35,170 +42,195 @@ import javax.sip.message.Response;
@RequestMapping("/api") @RequestMapping("/api")
public class PlayController { public class PlayController {
private final static Logger logger = LoggerFactory.getLogger(PlayController.class); private final static Logger logger = LoggerFactory.getLogger(PlayController.class);
@Autowired @Autowired
private SIPCommander cmder; private SIPCommander cmder;
@Autowired @Autowired
private IVideoManagerStorager storager; private IVideoManagerStorager storager;
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private IRedisCatchStorage redisCatchStorage;
@Autowired @Autowired
private ZLMRESTfulUtils zlmresTfulUtils; private ZLMRESTfulUtils zlmresTfulUtils;
@Autowired @Autowired
private DeferredResultHolder resultHolder; private DeferredResultHolder resultHolder;
@Autowired @Autowired
private IPlayService playService; private IPlayService playService;
@GetMapping("/play/{deviceId}/{channelId}") @Autowired
public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId, private VideoStreamSessionManager streamSession;
@PathVariable String channelId) {
@GetMapping("/play/{deviceId}/{channelId}")
public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId,
@PathVariable String channelId) {
Device device = storager.queryVideoDevice(deviceId);
PlayResult playResult = playService.play(deviceId, channelId, null, null); RequestMessage msg = playService.createCallbackPlayMsg();
DeferredResult<ResponseEntity<String>> result = new DeferredResult<>();
// 超时处理
result.onTimeout(() -> {
logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId));
// 释放rtpserver
cmder.closeRTPServer(device, channelId);
StreamInfo streamInfo = streamSession.getPlayStreamInfo(channelId);
streamSession.remove(streamInfo);
msg.setData("Timeout");
resultHolder.invokeResult(msg);
});
resultHolder.put(msg.getId(), result);
// 超时处理 // 判断是否已经存在点播
playResult.getResult().onTimeout(()->{ StreamInfo oldStreamInfo = streamSession.getPlayStreamInfo(channelId);
logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId)); if (oldStreamInfo == null) {
// 释放rtpserver // 发送点播消息
cmder.closeRTPServer(playResult.getDevice(), channelId); playStreamCmd(device, channelId, msg);
RequestMessage msg = new RequestMessage(); return result;
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + playResult.getUuid()); }
msg.setData("Timeout");
resultHolder.invokeResult(msg);
});
return playResult.getResult();
}
@PostMapping("/play/{streamId}/stop") // 若已有人点播,直接播放
public DeferredResult<ResponseEntity<String>> playStop(@PathVariable String streamId) { String streamId = oldStreamInfo.getStreamId();
String mediaServerIp = oldStreamInfo.getMediaServerIp();
logger.debug(String.format("设备预览/回放停止API调用,streamId:%s", streamId)); JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerIp, streamId);
if (rtpInfo.getBoolean("exist")) {
UUID uuid = UUID.randomUUID(); msg.setData(JSON.toJSONString(oldStreamInfo));
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(); resultHolder.invokeResult(msg);
return result;
// 录像查询以channelId作为deviceId查询 }
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_STOP + uuid, result);
cmder.streamByeCmd(streamId, event -> {
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
if (streamInfo == null) {
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
msg.setData("streamId not found");
resultHolder.invokeResult(msg);
}else {
redisCatchStorage.stopPlay(streamInfo);
storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_STOP + uuid);
//Response response = event.getResponse();
msg.setData(String.format("success"));
resultHolder.invokeResult(msg);
}
});
if (streamId != null) { // 若已有人点播,但已超时自动断开,则重新发起点播
JSONObject json = new JSONObject(); storager.stopPlay(oldStreamInfo.getDeviceID(), oldStreamInfo.getChannelId());
json.put("streamId", streamId); streamSession.remove(oldStreamInfo);
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
msg.setData(json.toString());
resultHolder.invokeResult(msg);
} else {
logger.warn("设备预览/回放停止API调用失败!");
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
msg.setData("streamId null");
resultHolder.invokeResult(msg);
}
// 超时处理 playStreamCmd(device, channelId, msg);
result.onTimeout(()->{ return result;
logger.warn(String.format("设备预览/回放停止超时,streamId:%s ", streamId)); }
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_STOP + uuid);
msg.setData("Timeout");
resultHolder.invokeResult(msg);
});
return result;
}
/** private void playStreamCmd(Device device, String channelId, RequestMessage msg) {
* 将不是h264的视频通过ffmpeg 转码为h264 + aac cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
* @param streamId 流ID logger.info("收到点播回调消息: " + response.toJSONString());
* @return playService.onPublishHandlerForPlay(response, device.getDeviceId(), channelId, msg);
*/ }, event -> {
@PostMapping("/play/{streamId}/convert") StreamInfo streamInfo = streamSession.getPlayStreamInfo(channelId);
public ResponseEntity<String> playConvert(@PathVariable String streamId) { streamSession.remove(streamInfo);
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); Response response = event.getResponse();
if (streamInfo == null) { int statusCode = response.getStatusCode();
logger.warn("视频转码API调用失败!, 视频流已经停止!"); String errMsg;
return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK); if (503 == statusCode) {
} errMsg = "点播失败,请检查在NVR上是否可以正常打开监控,并检查NVR和SIP是否连通, 错误码: %s, %s";
JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); } else {
if (!rtpInfo.getBoolean("exist")) { errMsg = "点播失败,错误码: %s, %s";
logger.warn("视频转码API调用失败!, 视频流已停止推流!"); }
return new ResponseEntity<String>("推流信息在流媒体中不存在, 视频流可能已停止推流", HttpStatus.OK); msg.setData(String.format(errMsg, statusCode, response.getReasonPhrase()));
} else { resultHolder.invokeResult(msg);
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); });
String dstUrl = String.format("rtmp://%s:%s/convert/%s", "127.0.0.1", mediaInfo.getRtmpPort(), }
streamId );
String srcUrl = String.format("rtsp://%s:%s/rtp/%s", "127.0.0.1", mediaInfo.getRtspPort(), streamId);
JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(srcUrl, dstUrl, "1000000");
System.out.println(jsonObject);
JSONObject result = new JSONObject();
if (jsonObject != null && jsonObject.getInteger("code") == 0) {
result.put("code", 0);
JSONObject data = jsonObject.getJSONObject("data");
if (data != null) {
result.put("key", data.getString("key"));
StreamInfo streamInfoResult = new StreamInfo();
streamInfoResult.setRtmp(dstUrl);
streamInfoResult.setRtsp(String.format("rtsp://%s:%s/convert/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId));
streamInfoResult.setStreamId(streamId);
streamInfoResult.setFlv(String.format("http://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
streamInfoResult.setWs_flv(String.format("ws://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
streamInfoResult.setHls(String.format("http://%s:%s/convert/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
streamInfoResult.setWs_hls(String.format("ws://%s:%s/convert/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
streamInfoResult.setFmp4(String.format("http://%s:%s/convert/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
streamInfoResult.setWs_fmp4(String.format("ws://%s:%s/convert/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
streamInfoResult.setTs(String.format("http://%s:%s/convert/%s.live.ts", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
streamInfoResult.setWs_ts(String.format("ws://%s:%s/convert/%s.live.ts", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
result.put("data", streamInfoResult);
}
}else {
result.put("code", 1);
result.put("msg", "cover fail");
}
return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
}
}
/** @PostMapping("/play/{channelId}/{streamId}/stop")
* 结束转码 public DeferredResult<ResponseEntity<String>> playStop(@PathVariable String channelId, @PathVariable String streamId) {
* @param key
* @return logger.debug(String.format("设备预览/回放停止API调用,streamId:%s", streamId));
*/
@PostMapping("/play/convert/stop/{key}") RequestMessage msg = playService.createCallbackPlayMsg();
public ResponseEntity<String> playConvertStop(@PathVariable String key) { DeferredResult<ResponseEntity<String>> result = new DeferredResult<>();
// 超时处理
JSONObject jsonObject = zlmresTfulUtils.delFFmpegSource(key); result.onTimeout(() -> {
System.out.println(jsonObject); logger.warn(String.format("设备预览/回放停止超时,streamId:%s ", streamId));
JSONObject result = new JSONObject(); msg.setData("Timeout");
if (jsonObject != null && jsonObject.getInteger("code") == 0) { resultHolder.invokeResult(msg);
result.put("code", 0); });
JSONObject data = jsonObject.getJSONObject("data");
if (data != null && data.getBoolean("flag")) { // 录像查询以channelId作为deviceId查询
result.put("code", "0"); resultHolder.put(msg.getId(), result);
result.put("msg", "success");
}else { StreamInfo streamInfo = streamSession.getStreamInfo(channelId, streamId);
if (streamInfo == null) {
msg.setData("streamId not found");
resultHolder.invokeResult(msg);
} else {
cmder.stopStreamByeCmd(streamInfo, event -> {
msg.setData(String.format("success"));
resultHolder.invokeResult(msg);
});
}
return result;
}
/**
* 将不是h264的视频通过ffmpeg 转码为h264 + aac
*
* @param streamId 流ID
*/
@PostMapping("/play/{channelId}/{streamId}/convert")
public ResponseEntity<String> playConvert(@PathVariable String channelId, @PathVariable String streamId) {
StreamInfo streamInfo = streamSession.getPlayStreamInfo(channelId);
if (streamInfo == null) {
logger.warn("视频转码API调用失败!, 视频流已经停止!");
return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK);
}
String mediaServerIp = streamInfo.getMediaServerIp();
JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerIp, streamId);
if (!rtpInfo.getBoolean("exist")) {
logger.warn("视频转码API调用失败!, 视频流已停止推流!");
return new ResponseEntity<String>("推流信息在流媒体中不存在, 视频流可能已停止推流", HttpStatus.OK);
} else {
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
String dstUrl = String.format("rtmp://%s:%s/convert/%s", mediaServerIp, mediaInfo.getRtmpPort(),
streamId);
String srcUrl = String.format("rtsp://%s:%s/rtp/%s", mediaServerIp, mediaInfo.getRtspPort(), streamId);
JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(mediaServerIp, srcUrl, dstUrl, "1000000");
System.out.println(jsonObject);
JSONObject result = new JSONObject();
if (jsonObject != null && jsonObject.getInteger("code") == 0) {
result.put("code", 0);
JSONObject data = jsonObject.getJSONObject("data");
if (data != null) {
result.put("key", data.getString("key"));
StreamInfo streamInfoResult = new StreamInfo();
streamInfoResult.setRtmp(dstUrl);
streamInfoResult.setRtsp(String.format("rtsp://%s:%s/convert/%s", mediaServerIp, mediaInfo.getRtspPort(), streamId));
streamInfoResult.setStreamId(streamId);
streamInfoResult.setFlv(String.format("http://%s:%s/convert/%s.flv", mediaServerIp, mediaInfo.getHttpPort(), streamId));
streamInfoResult.setWs_flv(String.format("ws://%s:%s/convert/%s.flv", mediaServerIp, mediaInfo.getHttpPort(), streamId));
streamInfoResult.setHls(String.format("http://%s:%s/convert/%s/hls.m3u8", mediaServerIp, mediaInfo.getHttpPort(), streamId));
streamInfoResult.setWs_hls(String.format("ws://%s:%s/convert/%s/hls.m3u8", mediaServerIp, mediaInfo.getHttpPort(), streamId));
streamInfoResult.setFmp4(String.format("http://%s:%s/convert/%s.live.mp4", mediaServerIp, mediaInfo.getHttpPort(), streamId));
streamInfoResult.setWs_fmp4(String.format("ws://%s:%s/convert/%s.live.mp4", mediaServerIp, mediaInfo.getHttpPort(), streamId));
streamInfoResult.setTs(String.format("http://%s:%s/convert/%s.live.ts", mediaServerIp, mediaInfo.getHttpPort(), streamId));
streamInfoResult.setWs_ts(String.format("ws://%s:%s/convert/%s.live.ts", mediaServerIp, mediaInfo.getHttpPort(), streamId));
result.put("data", streamInfoResult);
}
} else {
result.put("code", 1);
result.put("msg", "cover fail");
}
return new ResponseEntity<>(result.toJSONString(), HttpStatus.OK);
}
}
/**
* 结束转码
*
* @param key
* @return
*/
@PostMapping("/play/convert/stop/{channelId}/{streamId}/{key}")
public ResponseEntity<String> playConvertStop(@PathVariable String channelId, @PathVariable String streamId, @PathVariable String key) {
String mediaServerIp = streamSession.getMediaServerIp(channelId, streamId);
JSONObject jsonObject = zlmresTfulUtils.delFFmpegSource(mediaServerIp, key);
System.out.println(jsonObject);
JSONObject result = new JSONObject();
if (jsonObject != null && jsonObject.getInteger("code") == 0) {
result.put("code", 0);
JSONObject data = jsonObject.getJSONObject("data");
if (data != null && data.getBoolean("flag")) {
result.put("code", "0");
result.put("msg", "success");
} else {
} }
}else { }else {

169
src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java

@ -1,110 +1,101 @@
package com.genersoft.iot.vmp.vmanager.playback; package com.genersoft.iot.vmp.vmanager.playback;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
//import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.service.IPlayService; import com.genersoft.iot.vmp.vmanager.service.IPlayService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.message.Response; import javax.sip.message.Response;
import java.util.UUID;
@CrossOrigin @CrossOrigin
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api")
public class PlaybackController { public class PlaybackController {
private final static Logger logger = LoggerFactory.getLogger(PlaybackController.class); private final static Logger logger = LoggerFactory.getLogger(PlaybackController.class);
@Autowired @Autowired
private SIPCommander cmder; private SIPCommander cmder;
@Autowired @Autowired
private IVideoManagerStorager storager; private IVideoManagerStorager storager;
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private VideoStreamSessionManager streamSession;
// @Autowired @Autowired
// private ZLMRESTfulUtils zlmresTfulUtils; private IPlayService playService;
@Autowired @Autowired
private IPlayService playService; private DeferredResultHolder resultHolder;
@Autowired @GetMapping("/playback/{deviceId}/{channelId}")
private DeferredResultHolder resultHolder; public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId, @PathVariable String channelId, String startTime,
String endTime) {
@GetMapping("/playback/{deviceId}/{channelId}")
public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId, @PathVariable String channelId, String startTime, if (logger.isDebugEnabled()) {
String endTime) { logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId));
}
if (logger.isDebugEnabled()) { RequestMessage msg = playService.createCallbackPlayMsg();
logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId)); DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>();
} // 超时处理
UUID uuid = UUID.randomUUID(); result.onTimeout(() -> {
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(); logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId));
// 超时处理 StreamInfo streamInfo = streamSession.getPlayBackStreamInfo(channelId);
result.onTimeout(()->{ streamSession.remove(streamInfo);
logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId)); msg.setData("Timeout");
RequestMessage msg = new RequestMessage(); resultHolder.invokeResult(msg);
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); });
msg.setData("Timeout"); Device device = storager.queryVideoDevice(deviceId);
resultHolder.invokeResult(msg); StreamInfo oldStreamInfo = streamSession.getPlayBackStreamInfo(channelId);
}); if (oldStreamInfo != null) {
Device device = storager.queryVideoDevice(deviceId); // TODO 只能停止自己之前的回放,不能停止别人的回放,否则会导致别人无法播放
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, channelId); cmder.stopStreamByeCmd(oldStreamInfo, null);
if (streamInfo != null) { }
// 停止之前的回放 resultHolder.put(msg.getId(), result);
cmder.streamByeCmd(streamInfo.getStreamId()); cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> {
} logger.info("收到订阅消息: " + response.toJSONString());
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result); playService.onPublishHandlerForPlayBack(response, deviceId, channelId, msg);
cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> { }, event -> {
logger.info("收到订阅消息: " + response.toJSONString()); StreamInfo streamInfo = streamSession.getPlayBackStreamInfo(channelId);
playService.onPublishHandlerForPlayBack(response, deviceId, channelId, uuid.toString()); streamSession.remove(streamInfo);
}, event -> { Response response = event.getResponse();
Response response = event.getResponse(); msg.setData(String.format("回放失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
RequestMessage msg = new RequestMessage(); resultHolder.invokeResult(msg);
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); });
msg.setData(String.format("回放失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg); return result;
}); }
return result; @RequestMapping("/playback/{channelId}/{ssrc}/stop")
} public ResponseEntity<String> playStop(@PathVariable String channelId, @PathVariable String ssrc) {
if (logger.isDebugEnabled()) {
@RequestMapping("/playback/{ssrc}/stop") logger.debug(String.format("设备录像回放停止 API调用,ssrc:%s", ssrc));
public ResponseEntity<String> playStop(@PathVariable String ssrc) { }
if (ssrc == null) {
cmder.streamByeCmd(ssrc); logger.warn("设备录像回放停止API调用失败!");
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
if (logger.isDebugEnabled()) { }
logger.debug(String.format("设备录像回放停止 API调用,ssrc:%s", ssrc)); StreamInfo streamInfo = streamSession.getStreamInfo(channelId, ssrc);
} if (streamInfo == null) {
logger.warn("回放播流不存在!");
if (ssrc != null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND);
JSONObject json = new JSONObject(); }
json.put("ssrc", ssrc); cmder.stopStreamByeCmd(streamInfo, null);
return new ResponseEntity<String>(json.toString(), HttpStatus.OK); JSONObject json = new JSONObject();
} else { json.put("ssrc", ssrc);
logger.warn("设备录像回放停止API调用失败!"); return new ResponseEntity<>(json.toString(), HttpStatus.OK);
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR); }
}
}
} }

7
src/main/java/com/genersoft/iot/vmp/vmanager/service/IPlayService.java

@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.vmanager.service; package com.genersoft.iot.vmp.vmanager.service;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.vmanager.play.bean.PlayResult; import com.genersoft.iot.vmp.vmanager.play.bean.PlayResult;
@ -9,9 +10,11 @@ import com.genersoft.iot.vmp.vmanager.play.bean.PlayResult;
* 点播处理 * 点播处理
*/ */
public interface IPlayService { public interface IPlayService {
RequestMessage createCallbackPlayMsg();
void onPublishHandlerForPlayBack(JSONObject resonse, String deviceId, String channelId, String uuid); void onPublishHandlerForPlayBack(JSONObject resonse, String deviceId, String channelId, RequestMessage msg);
void onPublishHandlerForPlay(JSONObject resonse, String deviceId, String channelId, String uuid);
void onPublishHandlerForPlay(JSONObject resonse, String deviceId, String channelId, RequestMessage msg);
PlayResult play(String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent); PlayResult play(String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
} }

64
src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java

@ -7,6 +7,7 @@ import com.genersoft.iot.vmp.conf.MediaServerConfig;
import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
@ -26,6 +27,8 @@ import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.message.Response; import javax.sip.message.Response;
import java.util.UUID; import java.util.UUID;
import java.util.UUID;
@Service @Service
public class PlayServiceImpl implements IPlayService { public class PlayServiceImpl implements IPlayService {
@ -43,6 +46,9 @@ public class PlayServiceImpl implements IPlayService {
@Autowired @Autowired
private DeferredResultHolder resultHolder; private DeferredResultHolder resultHolder;
@Autowired
private VideoStreamSessionManager streamSession;
@Autowired @Autowired
private ZLMRESTfulUtils zlmresTfulUtils; private ZLMRESTfulUtils zlmresTfulUtils;
@ -109,10 +115,17 @@ public class PlayServiceImpl implements IPlayService {
} }
@Override @Override
public void onPublishHandlerForPlay(JSONObject resonse, String deviceId, String channelId, String uuid) { public RequestMessage createCallbackPlayMsg() {
String msgId = DeferredResultHolder.CALLBACK_CMD_PlAY + UUID.randomUUID();
RequestMessage msg = new RequestMessage(); RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); msg.setId(msgId);
StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid); return msg;
}
@Override
public void onPublishHandlerForPlay(JSONObject response, String deviceId, String channelId, RequestMessage msg) {
String streamId = response.getString("id");
StreamInfo streamInfo = streamSession.getStreamInfo(channelId, streamId);
if (streamInfo != null) { if (streamInfo != null) {
DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
if (deviceChannel != null) { if (deviceChannel != null) {
@ -120,56 +133,29 @@ public class PlayServiceImpl implements IPlayService {
storager.startPlay(deviceId, channelId, streamInfo.getStreamId()); storager.startPlay(deviceId, channelId, streamInfo.getStreamId());
} }
redisCatchStorage.startPlay(streamInfo); // redisCatchStorage.startPlay(streamInfo);
msg.setData(JSON.toJSONString(streamInfo)); msg.setData(JSON.toJSONString(streamInfo));
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
} else { } else {
logger.warn("设备预览API调用失败!"); logger.warn("设备点播API调用失败!");
msg.setData("设备预览API调用失败!"); msg.setData("设备点播API调用失败!");
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
} }
} }
@Override @Override
public void onPublishHandlerForPlayBack(JSONObject resonse, String deviceId, String channelId, String uuid) { public void onPublishHandlerForPlayBack(JSONObject resonse, String deviceId, String channelId, RequestMessage msg) {
RequestMessage msg = new RequestMessage(); String streamId = resonse.getString("id");
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); StreamInfo streamInfo = streamSession.getStreamInfo(channelId, streamId);
StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid);
if (streamInfo != null) { if (streamInfo != null) {
redisCatchStorage.startPlayback(streamInfo); // redisCatchStorage.startPlayback(streamInfo);
msg.setData(JSON.toJSONString(streamInfo)); msg.setData(JSON.toJSONString(streamInfo));
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
} else { } else {
logger.warn("设备预览API调用失败!"); logger.warn("设备回放API调用失败!");
msg.setData("设备预览API调用失败!"); msg.setData("设备回放API调用失败!");
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
} }
} }
public StreamInfo onPublishHandler(JSONObject resonse, String deviceId, String channelId, String uuid) {
String streamId = resonse.getString("id");
StreamInfo streamInfo = new StreamInfo();
streamInfo.setStreamId(streamId);
streamInfo.setDeviceID(deviceId);
streamInfo.setChannelId(channelId);
MediaServerConfig mediaServerConfig = redisCatchStorage.getMediaInfo();
streamInfo.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
streamInfo.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
streamInfo.setFmp4(String.format("http://%s:%s/rtp/%s.live.mp4", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
streamInfo.setWs_fmp4(String.format("ws://%s:%s/rtp/%s.live.mp4", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
streamInfo.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
streamInfo.setWs_hls(String.format("ws://%s:%s/rtp/%s/hls.m3u8", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
streamInfo.setTs(String.format("http://%s:%s/rtp/%s.live.ts", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
streamInfo.setWs_ts(String.format("ws://%s:%s/rtp/%s.live.ts", mediaServerConfig.getWanIp(), mediaServerConfig.getHttpPort(), streamId));
streamInfo.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaServerConfig.getWanIp(), mediaServerConfig.getRtmpPort(), streamId));
streamInfo.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaServerConfig.getWanIp(), mediaServerConfig.getRtspPort(), streamId));
return streamInfo;
}
} }

126
src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java

@ -5,9 +5,8 @@ import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
// import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.play.PlayController; import com.genersoft.iot.vmp.vmanager.play.PlayController;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -20,7 +19,6 @@ import org.springframework.web.context.request.async.DeferredResult;
/** /**
* 兼容LiveGBS的API实时直播 * 兼容LiveGBS的API实时直播
*/ */
@SuppressWarnings(value = {"rawtypes", "unchecked"})
@CrossOrigin @CrossOrigin
@RestController @RestController
@RequestMapping(value = "/api/v1/stream") @RequestMapping(value = "/api/v1/stream")
@ -30,80 +28,73 @@ public class ApiStreamController {
@Autowired @Autowired
private SIPCommander cmder; private SIPCommander cmder;
@Autowired @Autowired
private IVideoManagerStorager storager; private IVideoManagerStorager storager;
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private VideoStreamSessionManager streamSession;
// @Autowired
// private ZLMRESTfulUtils zlmresTfulUtils;
@Autowired @Autowired
private PlayController playController; private PlayController playController;
/** /**
* 实时直播 - 开始直播 * 实时直播 - 开始直播
* @param serial 设备编号 *
* @param channel 通道序号 默认值: 1 * @param serial 设备编号
* @param code 通道编号,通过 /api/v1/device/channellist 获取的 ChannelList.ID, 该参数和 channel 二选一传递即可 * @param channel 通道序号 默认值: 1
* @param cdn TODO 转推 CDN 地址, 形如: [rtmp|rtsp]://xxx, encodeURIComponent * @param code 通道编号,通过 /api/v1/device/channellist 获取的 ChannelList.ID, 该参数和 channel 二选一传递即可
* @param audio TODO 是否开启音频, 默认 开启 * @param cdn TODO 转推 CDN 地址, 形如: [rtmp|rtsp]://xxx, encodeURIComponent
* @param transport 流传输模式 默认 UDP * @param audio TODO 是否开启音频, 默认 开启
* @param transport 流传输模式 默认 UDP
* @param checkchannelstatus TODO 是否检查通道状态, 默认 false, 表示 拉流前不检查通道状态是否在线 * @param checkchannelstatus TODO 是否检查通道状态, 默认 false, 表示 拉流前不检查通道状态是否在线
* @param transportmode TODO transport=TCP 时有效, 指示流传输主被动模式, 默认被动 * @param transportmode TODO transport=TCP 时有效, 指示流传输主被动模式, 默认被动
* @param timeout TODO 拉流超时(), * @param timeout TODO 拉流超时(),
* @return * @return
*/ */
@RequestMapping(value = "/start") @RequestMapping(value = "/start")
private DeferredResult<JSONObject> start(String serial , private DeferredResult<JSONObject> start(String serial,
@RequestParam(required = false)Integer channel , @RequestParam(required = false) Integer channel,
@RequestParam(required = false)String code, @RequestParam(required = false) String code,
@RequestParam(required = false)String cdn, @RequestParam(required = false) String cdn,
@RequestParam(required = false)String audio, @RequestParam(required = false) String audio,
@RequestParam(required = false)String transport, @RequestParam(required = false) String transport,
@RequestParam(required = false)String checkchannelstatus , @RequestParam(required = false) String checkchannelstatus,
@RequestParam(required = false)String transportmode, @RequestParam(required = false) String transportmode,
@RequestParam(required = false)String timeout @RequestParam(required = false) String timeout
){ ) {
DeferredResult<JSONObject> resultDeferredResult = new DeferredResult<JSONObject>(); DeferredResult<JSONObject> resultDeferredResult = new DeferredResult<JSONObject>();
Device device = storager.queryVideoDevice(serial); Device device = storager.queryVideoDevice(serial);
if (device == null ) { if (device == null) {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("error","device[ " + serial + " ]未找到"); result.put("error", "device[ " + serial + " ]未找到");
resultDeferredResult.setResult(result); resultDeferredResult.setResult(result);
}else if (device.getOnline() == 0) { } else if (device.getOnline() == 0) {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("error","device[ " + code + " ]offline"); result.put("error", "device[ " + code + " ]offline");
resultDeferredResult.setResult(result); resultDeferredResult.setResult(result);
} }
resultDeferredResult.onTimeout(()->{ resultDeferredResult.onTimeout(() -> {
logger.info("播放等待超时"); logger.info("播放等待超时");
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("error","timeout"); result.put("error", "timeout");
resultDeferredResult.setResult(result); resultDeferredResult.setResult(result);
// 清理RTP server // 清理RTP server
}); });
DeviceChannel deviceChannel = storager.queryChannel(serial, code); DeviceChannel deviceChannel = storager.queryChannel(serial, code);
if (deviceChannel == null) { if (deviceChannel == null) {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("error","channel[ " + code + " ]未找到"); result.put("error", "channel[ " + code + " ]未找到");
resultDeferredResult.setResult(result); resultDeferredResult.setResult(result);
}else if (deviceChannel.getStatus() == 0) { } else if (deviceChannel.getStatus() == 0) {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("error","channel[ " + code + " ]offline"); result.put("error", "channel[ " + code + " ]offline");
resultDeferredResult.setResult(result); resultDeferredResult.setResult(result);
} }
DeferredResult<ResponseEntity<String>> play = playController.play(serial, code); DeferredResult<ResponseEntity<String>> play = playController.play(serial, code);
play.setResultHandler((Object o)->{ play.setResultHandler((Object o) -> {
ResponseEntity<String> responseEntity = (ResponseEntity)o; ResponseEntity<String> responseEntity = (ResponseEntity) o;
StreamInfo streamInfo = JSON.parseObject(responseEntity.getBody(), StreamInfo.class); StreamInfo streamInfo = JSON.parseObject(responseEntity.getBody(), StreamInfo.class);
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("StreamID", streamInfo.getStreamId()); result.put("StreamID", streamInfo.getStreamId());
@ -143,49 +134,50 @@ public class ApiStreamController {
/** /**
* 实时直播 - 直播流停止 * 实时直播 - 直播流停止
* @param serial 设备编号 *
* @param channel 通道序号 * @param serial 设备编号
* @param code 通道国标编号 * @param channel 通道序号
* @param code 通道国标编号
* @param check_outputs * @param check_outputs
* @return * @return
*/ */
@RequestMapping(value = "/stop") @RequestMapping(value = "/stop")
@ResponseBody @ResponseBody
private JSONObject stop(String serial , private JSONObject stop(String serial,
@RequestParam(required = false)Integer channel , @RequestParam(required = false) Integer channel,
@RequestParam(required = false)String code, @RequestParam(required = false) String code,
@RequestParam(required = false)String check_outputs @RequestParam(required = false) String check_outputs
){ ) {
JSONObject result = new JSONObject();
StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(serial, code); StreamInfo streamInfo = streamSession.getPlayStreamInfo(code);
if (streamInfo == null) { if (streamInfo == null) {
JSONObject result = new JSONObject(); result.put("error", "未找到流信息");
result.put("error","未找到流信息");
return result; return result;
} }
cmder.streamByeCmd(streamInfo.getStreamId()); cmder.stopStreamByeCmd(streamInfo, null);
redisCatchStorage.stopPlay(streamInfo); result.put("success", "成功");
storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); return result;
return null;
} }
/** /**
* 实时直播 - 直播流保活 * 实时直播 - 直播流保活
* @param serial 设备编号 *
* @param serial 设备编号
* @param channel 通道序号 * @param channel 通道序号
* @param code 通道国标编号 * @param code 通道国标编号
* @return * @return
*/ */
@RequestMapping(value = "/touch") @RequestMapping(value = "/touch")
@ResponseBody @ResponseBody
private JSONObject touch(String serial ,String t, private JSONObject touch(String serial, String t,
@RequestParam(required = false)Integer channel , @RequestParam(required = false) Integer channel,
@RequestParam(required = false)String code, @RequestParam(required = false) String code,
@RequestParam(required = false)String autorestart, @RequestParam(required = false) String autorestart,
@RequestParam(required = false)String audio, @RequestParam(required = false) String audio,
@RequestParam(required = false)String cdn @RequestParam(required = false) String cdn
){ ) {
return null; return null;
} }
} }

7
src/main/resources/application-dev.yml

@ -11,6 +11,9 @@ spring:
password: password:
# [可选] 超时时间 # [可选] 超时时间
timeout: 10000 timeout: 10000
poolMaxTotal: 1000
poolMaxIdle: 50
poolMaxWait: 500
# [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配置 # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配置
datasource: datasource:
# name: eiot # name: eiot
@ -34,7 +37,7 @@ server:
# 作为28181服务器的配置 # 作为28181服务器的配置
sip: sip:
# [必须修改] 本机的IP, 必须是网卡上的IP # [必须修改] 本机的内网IP, 必须是网卡上的IP
ip: 192.168.0.100 ip: 192.168.0.100
# [可选] 28181服务监听的端口 # [可选] 28181服务监听的端口
port: 5060 port: 5060
@ -57,7 +60,7 @@ auth:
#zlm服务器配置 #zlm服务器配置
media: media:
# [必须修改] zlm服务器的内网IP # [必须修改] zlm服务器的IP(内网公网IP均可),配置多台时IP用逗号隔开
ip: 192.168.0.100 ip: 192.168.0.100
# [可选] zlm服务器的公网IP, 内网部署置空即可 # [可选] zlm服务器的公网IP, 内网部署置空即可
wanIp: wanIp:

2
web_src/src/components/channelList.vue

@ -216,7 +216,7 @@ export default {
var that = this; var that = this;
this.$axios({ this.$axios({
method: 'post', method: 'post',
url: '/api/play/' + itemData.streamId + '/stop' url: '/api/play/' + itemData.channelId + "/" + itemData.streamId + '/stop'
}).then(function (res) { }).then(function (res) {
console.log(JSON.stringify(res)); console.log(JSON.stringify(res));
that.initData(); that.initData();

10
web_src/src/components/gb28181/devicePlayer.vue

@ -196,7 +196,9 @@ export default {
if (tab.name == "codec") { if (tab.name == "codec") {
this.$axios({ this.$axios({
method: 'get', method: 'get',
url: '/zlm/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app=rtp&stream='+ this.streamId url: '/zlm/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app=rtp'
+'&channelId='+ this.channelId
+'&stream='+ this.streamId
}).then(function (res) { }).then(function (res) {
that.tracksLoading = false; that.tracksLoading = false;
if (res.data.code == 0 && res.data.online) { if (res.data.code == 0 && res.data.online) {
@ -251,7 +253,7 @@ export default {
this.$refs.videoPlayer.pause() this.$refs.videoPlayer.pause()
that.$axios({ that.$axios({
method: 'post', method: 'post',
url: '/api/play/' + that.streamId + '/convert' url: '/api/play/' + that.channelId + "/" + that.streamId + '/convert'
}).then(function (res) { }).then(function (res) {
if (res.data.code == 0) { if (res.data.code == 0) {
that.convertKey = res.data.key; that.convertKey = res.data.key;
@ -288,7 +290,7 @@ export default {
that.$refs.videoPlayer.pause() that.$refs.videoPlayer.pause()
this.$axios({ this.$axios({
method: 'post', method: 'post',
url: '/api/play/convert/stop/' + this.convertKey url: '/api/play/convert/stop/' + this.channelId + "/" + + this.streamId + "/" + this.convertKey
}).then(function (res) { }).then(function (res) {
if (res.data.code == 0) { if (res.data.code == 0) {
console.log(res.data.msg) console.log(res.data.msg)
@ -398,7 +400,7 @@ export default {
this.videoUrl = ''; this.videoUrl = '';
this.$axios({ this.$axios({
method: 'get', method: 'get',
url: '/api/playback/' + this.streamId + '/stop' url: '/api/playback/' + this.channelId + '/' + this.streamId + '/stop'
}).then(function (res) { }).then(function (res) {
if (callback) callback() if (callback) callback()
}); });

Loading…
Cancel
Save