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. 71
      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. 42
      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. 72
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  14. 6
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
  15. 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java
  16. 2
      src/main/java/com/genersoft/iot/vmp/gb28181/event/DeviceOffLineDetector.java
  17. 23
      src/main/java/com/genersoft/iot/vmp/gb28181/session/PlayTypeEnum.java
  18. 93
      src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcUtil.java
  19. 279
      src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java
  20. 9
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
  21. 7
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  22. 245
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  23. 74
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
  24. 16
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java
  25. 43
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java
  26. 156
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  27. 127
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
  28. 51
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
  29. 127
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
  30. 41
      src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
  31. 20
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
  32. 2
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
  33. 211
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  34. 8
      src/main/java/com/genersoft/iot/vmp/utils/ConfigConst.java
  35. 97
      src/main/java/com/genersoft/iot/vmp/utils/redis/JedisUtil.java
  36. 72
      src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
  37. 120
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceConfig.java
  38. 119
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceConfigController.java
  39. 238
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceControl.java
  40. 237
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceControlController.java
  41. 261
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java
  42. 255
      src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceQuery.java
  43. 176
      src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
  44. 73
      src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java
  45. 7
      src/main/java/com/genersoft/iot/vmp/vmanager/service/IPlayService.java
  46. 64
      src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java
  47. 98
      src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java
  48. 7
      src/main/resources/application-dev.yml
  49. 2
      web_src/src/components/channelList.vue
  50. 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_";
} }

71
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();
String[] mediaIpArr = mediaIp.split(",");
mediaConfig.setMediaIpArr(mediaIpArr);
if (mediaIp.equals("localhost") || mediaIp.equals("127.0.0.1")) { for (String mId : mediaIpArr) {
logger.warn("mediaIp.ip使用 {} ,将无法收到网络内其他设备的推流!!!", mediaIp ); 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;
}
} }

42
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,19 +11,48 @@ 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 {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.database}")
private int database;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.poolMaxTotal}")
private int poolMaxTotal;
@Value("${spring.redis.poolMaxIdle}")
private int poolMaxIdle;
@Value("${spring.redis.poolMaxWait}")
private int poolMaxWait;
@Bean
public JedisPool jedisPool() {
if (StringUtils.isBlank(password)) {
password = null;
}
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(poolMaxIdle);
poolConfig.setMaxTotal(poolMaxTotal);
// 秒转毫秒
poolConfig.setMaxWaitMillis(poolMaxWait * 1000);
JedisPool jp = new JedisPool(poolConfig, host, port, timeout * 1000, password, database);
return jp;
}
@Bean("redisTemplate") @Bean("redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>(); RedisTemplate<Object, Object> template = new RedisTemplate<>();
@ -43,7 +76,6 @@ public class RedisConfig extends CachingConfigurerSupport {
* 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理 * 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
* *
* @param connectionFactory * @param connectionFactory
* @param listenerAdapter
* @return * @return
*/ */
@Bean @Bean

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);
}
}
}
}

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

@ -1,30 +1,26 @@
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 {
@ -53,9 +49,9 @@ public class SipLayer implements SipListener {
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;
} }
@ -94,13 +90,42 @@ public class SipLayer implements SipListener {
SipProvider tcpSipProvider = null; SipProvider tcpSipProvider = null;
try { try {
tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "TCP"); tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "TCP");
} catch (TransportNotSupportedException e) {
logger.error("创建SIP服务失败!", e);
System.exit(1);
} catch (InvalidArgumentException e) {
if ("Cannot assign requested address: JVM_Bind".equals(e.getMessage())) {
logger.error("创建SIP服务失败,请检查sip.ip必须是本地网卡上的IP!", e);
} else {
logger.error("创建SIP服务失败!", e);
}
System.exit(1);
}
try {
tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint); tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint);
} catch (ObjectInUseException e) {
logger.error("创建SIP服务失败!", e);
System.exit(1);
}
try {
tcpSipProvider.addSipListener(this); tcpSipProvider.addSipListener(this);
logger.info("Sip Server TCP 启动成功 port {" + sipConfig.getSipPort() + "}"); } catch (TooManyListenersException e) {
} catch (TransportNotSupportedException | InvalidArgumentException | TooManyListenersException | ObjectInUseException e) { logger.error("创建SIP服务失败!", e);
logger.error(String.format("创建SIP服务失败: %s", e.getMessage())); System.exit(1);
} }
logger.info("Sip Server TCP 启动成功 port {" + sipConfig.getSipPort() + "}");
return tcpSipProvider; return tcpSipProvider;
// try {
// tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getSipIp(), sipConfig.getSipPort(), "TCP");
// 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") @Bean("udpSipProvider")
@ -166,7 +191,6 @@ public class SipLayer implements SipListener {
} }
} }
/** /**

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;
}
}

279
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 ConcurrentHashMap<String, ClientTransaction> sessionMap = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, String> ssrcMap = 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;
}
public String createPlaySsrc(){ /**
return SsrcUtil.getPlaySsrc(); * 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;
} }
public String createPlayBackSsrc(){ /**
return SsrcUtil.getPlayBackSsrc(); * 流媒体服务器选举算法
*
* @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;
} }
public void put(String streamId,String ssrc,ClientTransaction transaction){ /**
sessionMap.put(streamId, transaction); * 生成streamId和播流RUL如果此时未连接ZLM会抛出运行时异常
ssrcMap.put(streamId, ssrc); *
* @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));
public ClientTransaction get(String streamId){ return streamInfo;
return sessionMap.get(streamId); }
/**
* 查找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);
}
public ClientTransaction getClientTransaction(String channelId, String streamId) {
String streamKey = getStreamKey(channelId, streamId);
return sessionMap.get(streamKey);
}
public void remove(String channelId, String streamId) {
StreamInfo streamInfo = this.getStreamInfo(channelId, streamId);
if (null == streamId) {
return;
}
this.remove(streamInfo);
}
/**
* 移除会话并释放ssrc主要用完的ssrc一定要释放否则会耗尽
*/
public void remove(StreamInfo streamInfo) {
String streamKey = getStreamKey(streamInfo.getChannelId(), streamInfo.getStreamId());
// 移除会话
sessionMap.remove(streamKey);
String ssrc = streamInfo.getSsrc();
String sn = ssrc.substring(6);
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);
// 会话句柄和ZLM服务器的对应关系,从redis移除
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);
/** /**
* 语音广播 * 语音广播

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

@ -1,23 +1,23 @@
package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
import java.text.ParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.sip.*;
import javax.sip.address.SipURI;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
import com.alibaba.fastjson.JSONObject; 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.conf.SipConfig;
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.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
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 lombok.extern.slf4j.Slf4j;
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;
@ -28,14 +28,14 @@ import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
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.bean.Device; import javax.sip.address.SipURI;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import javax.sip.header.CallIdHeader;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import javax.sip.header.ViaHeader;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; import javax.sip.message.Request;
import com.genersoft.iot.vmp.gb28181.utils.DateUtil; import java.text.ParseException;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil; import java.util.regex.Matcher;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; import java.util.regex.Pattern;
/** /**
* @Description:设备能力接口用于定义设备的控制查询能力 * @Description:设备能力接口用于定义设备的控制查询能力
@ -76,14 +76,8 @@ public class SIPCommander implements ISIPCommander {
@Autowired @Autowired
private ZLMRTPServerFactory zlmrtpServerFactory; private ZLMRTPServerFactory zlmrtpServerFactory;
@Value("${media.rtp.enable}") @Autowired
private boolean rtpEnable; MediaConfig mediaConfig;
@Value("${media.seniorSdp}")
private boolean seniorSdp;
@Value("${media.autoApplyPlay}")
private boolean autoApplyPlay;
@Autowired @Autowired
private ZLMHttpHookSubscribe subscribe; private ZLMHttpHookSubscribe subscribe;
@ -92,7 +86,6 @@ public class SIPCommander implements ISIPCommander {
private SipSubscribe sipSubscribe; private SipSubscribe sipSubscribe;
/** /**
* 云台方向放控制使用配置文件中的默认镜头移动速度 * 云台方向放控制使用配置文件中的默认镜头移动速度
* *
@ -157,18 +150,18 @@ public class SIPCommander implements ISIPCommander {
public static String cmdString(int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) { public static String cmdString(int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) {
int cmdCode = 0; int cmdCode = 0;
if (leftRight == 2) { if (leftRight == 2) {
cmdCode|=0x01; // 右移 cmdCode |= 0x01; // 右移
} else if(leftRight == 1) { } else if (leftRight == 1) {
cmdCode|=0x02; // 左移 cmdCode |= 0x02; // 左移
} }
if (upDown == 2) { if (upDown == 2) {
cmdCode|=0x04; // 下移 cmdCode |= 0x04; // 下移
} else if(upDown == 1) { } else if (upDown == 1) {
cmdCode|=0x08; // 上移 cmdCode |= 0x08; // 上移
} }
if (inOut == 2) { if (inOut == 2) {
cmdCode |= 0x10; // 放大 cmdCode |= 0x10; // 放大
} else if(inOut == 1) { } else if (inOut == 1) {
cmdCode |= 0x20; // 缩小 cmdCode |= 0x20; // 缩小
} }
StringBuilder builder = new StringBuilder("A50F01"); StringBuilder builder = new StringBuilder("A50F01");
@ -185,7 +178,7 @@ public class SIPCommander implements ISIPCommander {
strTmp = String.format("%02X", checkCode); strTmp = String.format("%02X", checkCode);
builder.append(strTmp, 0, 2); builder.append(strTmp, 0, 2);
return builder.toString(); return builder.toString();
} }
/** /**
* 云台指令码计算 * 云台指令码计算
@ -195,7 +188,7 @@ public class SIPCommander implements ISIPCommander {
* @param parameter2 数据2 * @param parameter2 数据2
* @param combineCode2 组合码2 * @param combineCode2 组合码2
*/ */
public static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) { private static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) {
StringBuilder builder = new StringBuilder("A50F01"); StringBuilder builder = new StringBuilder("A50F01");
String strTmp; String strTmp;
strTmp = String.format("%02X", cmdCode); strTmp = String.format("%02X", cmdCode);
@ -331,6 +324,7 @@ public class SIPCommander implements ISIPCommander {
/** /**
* 请求预览视频流 * 请求预览视频流
*
* @param device 视频设备 * @param device 视频设备
* @param channelId 预览通道 * @param channelId 预览通道
* @param event hook订阅 * @param event hook订阅
@ -338,29 +332,19 @@ public class SIPCommander implements ISIPCommander {
*/ */
@Override @Override
public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) { public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
try { StreamInfo streamInfo = streamSession.createPlayStreamInfo(device, channelId);
String mediaServerIp = streamInfo.getMediaServerIp();
String ssrc = streamSession.createPlaySsrc(); String streamId = streamInfo.getStreamId();
String streamId = null; String ssrc = streamInfo.getSsrc();
if (rtpEnable) {
streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
}else {
streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
}
String streamMode = device.getStreamMode().toUpperCase(); String streamMode = device.getStreamMode().toUpperCase();
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
if (mediaInfo == null) { String mediaPort;
logger.warn("点播时发现ZLM尚未连接...");
return;
}
String mediaPort = null;
// 使用动态udp端口 // 使用动态udp端口
if (rtpEnable) { if (mediaConfig.getRtpEnable()) {
mediaPort = zlmrtpServerFactory.createRTPServer(streamId) + ""; mediaPort = zlmrtpServerFactory.createRTPServer(mediaServerIp, streamId) + "";
}else { } else {
mediaPort = mediaInfo.getRtpProxyPort(); mediaPort = mediaInfo.getRtpProxyPort();
} }
// 添加订阅 // 添加订阅
JSONObject subscribeKey = new JSONObject(); JSONObject subscribeKey = new JSONObject();
subscribeKey.put("app", "rtp"); subscribeKey.put("app", "rtp");
@ -370,13 +354,12 @@ public class SIPCommander implements ISIPCommander {
// //
StringBuffer content = new StringBuffer(200); StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n"); content.append("v=0\r\n");
// content.append("o="+channelId+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n"); content.append("o=" + "00000" + " 0 0 IN IP4 " + mediaServerIp + "\r\n");
content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
content.append("s=Play\r\n"); content.append("s=Play\r\n");
content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n"); content.append("c=IN IP4 " + mediaServerIp + "\r\n");
content.append("t=0 0\r\n"); content.append("t=0 0\r\n");
if (seniorSdp) { if (mediaConfig.getSeniorSdp()) {
if("TCP-PASSIVE".equals(streamMode)) { if("TCP-PASSIVE".equals(streamMode)) {
content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
}else if ("TCP-ACTIVE".equals(streamMode)) { }else if ("TCP-ACTIVE".equals(streamMode)) {
@ -429,20 +412,19 @@ public class SIPCommander implements ISIPCommander {
} }
} }
content.append("y="+ssrc+"\r\n");//ssrc content.append("y=" + ssrc + "\r\n");//ssrc
try {
String tm = Long.toString(System.currentTimeMillis()); String tm = Long.toString(System.currentTimeMillis());
CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId() CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
: udpSipProvider.getNewCallId(); : udpSipProvider.getNewCallId();
Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "FromInvt" + tm, null, ssrc, callIdHeader); Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "FromInvt" + tm, null, ssrc, callIdHeader);
ClientTransaction transaction = transmitRequest(device, request, errorEvent); ClientTransaction transaction = transmitRequest(device, request, errorEvent);
streamSession.put(streamId,ssrc, transaction); streamSession.putClientTransaction(channelId, streamId, transaction);
} catch (SipException | ParseException | InvalidArgumentException e) {
} catch ( SipException | ParseException | InvalidArgumentException e) { logger.error("请求直播失败", e);
e.printStackTrace(); streamSession.remove(streamInfo);
} }
} }
@ -457,10 +439,11 @@ public class SIPCommander implements ISIPCommander {
@Override @Override
public void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event public void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event
, SipSubscribe.Event errorEvent) { , SipSubscribe.Event errorEvent) {
try {
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
String ssrc = streamSession.createPlayBackSsrc(); StreamInfo streamInfo = streamSession.createPlayBackStreamInfo(device, channelId);
String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase(); String mediaServerIp = streamInfo.getMediaServerIp();
String streamId = streamInfo.getStreamId();
String ssrc = streamInfo.getSsrc();
// 添加订阅 // 添加订阅
JSONObject subscribeKey = new JSONObject(); JSONObject subscribeKey = new JSONObject();
subscribeKey.put("app", "rtp"); subscribeKey.put("app", "rtp");
@ -470,28 +453,28 @@ public class SIPCommander implements ISIPCommander {
StringBuffer content = new StringBuffer(200); StringBuffer content = new StringBuffer(200);
content.append("v=0\r\n"); content.append("v=0\r\n");
content.append("o="+sipConfig.getSipId()+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n"); content.append("o=" + sipConfig.getSipId() + " 0 0 IN IP4 " + sipConfig.getSipIp() + "\r\n");
content.append("s=Playback\r\n"); content.append("s=Playback\r\n");
content.append("u="+channelId+":0\r\n"); content.append("u=" + channelId + ":0\r\n");
content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n"); content.append("c=IN IP4 " + mediaServerIp + "\r\n");
content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" " content.append("t=" + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime) + " "
+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n"); + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) + "\r\n");
String mediaPort = null; String mediaPort = null;
// 使用动态udp端口 // 使用动态udp端口
if (rtpEnable) { if (mediaConfig.getRtpEnable()) {
mediaPort = zlmrtpServerFactory.createRTPServer(streamId) + ""; mediaPort = zlmrtpServerFactory.createRTPServer(mediaServerIp, streamId) + "";
}else { } else {
mediaPort = mediaInfo.getRtpProxyPort(); mediaPort = mediaInfo.getRtpProxyPort();
} }
String streamMode = device.getStreamMode().toUpperCase(); String streamMode = device.getStreamMode().toUpperCase();
if (seniorSdp) { if (mediaConfig.getSeniorSdp()) {
if("TCP-PASSIVE".equals(streamMode)) { if ("TCP-PASSIVE".equals(streamMode)) {
content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); content.append("m=video " + mediaPort + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
}else if ("TCP-ACTIVE".equals(streamMode)) { } else if ("TCP-ACTIVE".equals(streamMode)) {
content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); content.append("m=video " + mediaPort + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
}else if("UDP".equals(streamMode)) { } else if ("UDP".equals(streamMode)) {
content.append("m=video "+ mediaPort +" RTP/AVP 96 126 125 99 34 98 97\r\n"); content.append("m=video " + mediaPort + " RTP/AVP 96 126 125 99 34 98 97\r\n");
} }
content.append("a=recvonly\r\n"); content.append("a=recvonly\r\n");
content.append("a=rtpmap:96 PS/90000\r\n"); content.append("a=rtpmap:96 PS/90000\r\n");
@ -503,36 +486,36 @@ public class SIPCommander implements ISIPCommander {
content.append("a=fmtp:99 profile-level-id=3\r\n"); content.append("a=fmtp:99 profile-level-id=3\r\n");
content.append("a=rtpmap:98 H264/90000\r\n"); content.append("a=rtpmap:98 H264/90000\r\n");
content.append("a=rtpmap:97 MPEG4/90000\r\n"); content.append("a=rtpmap:97 MPEG4/90000\r\n");
if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式 if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式
content.append("a=setup:passive\r\n"); content.append("a=setup:passive\r\n");
content.append("a=connection:new\r\n"); content.append("a=connection:new\r\n");
}else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式 } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
content.append("a=setup:active\r\n"); content.append("a=setup:active\r\n");
content.append("a=connection:new\r\n"); content.append("a=connection:new\r\n");
} }
}else { } else {
if("TCP-PASSIVE".equals(streamMode)) { if ("TCP-PASSIVE".equals(streamMode)) {
content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n"); content.append("m=video " + mediaPort + " TCP/RTP/AVP 96 98 97\r\n");
}else if ("TCP-ACTIVE".equals(streamMode)) { } else if ("TCP-ACTIVE".equals(streamMode)) {
content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n"); content.append("m=video " + mediaPort + " TCP/RTP/AVP 96 98 97\r\n");
}else if("UDP".equals(streamMode)) { } else if ("UDP".equals(streamMode)) {
content.append("m=video "+ mediaPort +" RTP/AVP 96 98 97\r\n"); content.append("m=video " + mediaPort + " RTP/AVP 96 98 97\r\n");
} }
content.append("a=recvonly\r\n"); content.append("a=recvonly\r\n");
content.append("a=rtpmap:96 PS/90000\r\n"); content.append("a=rtpmap:96 PS/90000\r\n");
content.append("a=rtpmap:98 H264/90000\r\n"); content.append("a=rtpmap:98 H264/90000\r\n");
content.append("a=rtpmap:97 MPEG4/90000\r\n"); content.append("a=rtpmap:97 MPEG4/90000\r\n");
if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式 if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式
content.append("a=setup:passive\r\n"); content.append("a=setup:passive\r\n");
content.append("a=connection:new\r\n"); content.append("a=connection:new\r\n");
}else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式 } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
content.append("a=setup:active\r\n"); content.append("a=setup:active\r\n");
content.append("a=connection:new\r\n"); content.append("a=connection:new\r\n");
} }
} }
content.append("y="+ssrc+"\r\n");//ssrc content.append("y=" + ssrc + "\r\n");//ssrc
try {
String tm = Long.toString(System.currentTimeMillis()); String tm = Long.toString(System.currentTimeMillis());
CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId() CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
@ -541,34 +524,27 @@ public class SIPCommander implements ISIPCommander {
Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader); Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader);
ClientTransaction transaction = transmitRequest(device, request, errorEvent); ClientTransaction transaction = transmitRequest(device, request, errorEvent);
streamSession.put(streamId, ssrc, transaction); streamSession.putClientTransaction(channelId, streamId, transaction);
} catch (SipException | ParseException | InvalidArgumentException e) {
} catch ( SipException | ParseException | InvalidArgumentException e) { logger.error("请求回放失败", e);
e.printStackTrace(); streamSession.remove(streamInfo);
} }
} }
/** /**
* 视频流停止 * 视频流停止
*
*/ */
@Override @Override
public void streamByeCmd(String ssrc) { public void stopStreamByeCmd(StreamInfo streamInfo, SipSubscribe.Event okEvent) {
streamByeCmd(ssrc, null); String channelId = streamInfo.getChannelId();
} String streamId = streamInfo.getStreamId();
@Override ClientTransaction transaction = streamSession.getClientTransaction(channelId, streamId);
public void streamByeCmd(String streamId, SipSubscribe.Event okEvent) { // wvp信令服务重启后
try {
ClientTransaction transaction = streamSession.get(streamId);
// 服务重启后
if (transaction == null) { if (transaction == null) {
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
if (streamInfo != null) { streamSession.remove(streamInfo);
okEvent.response(null);
}
return; return;
} }
@ -576,6 +552,7 @@ public class SIPCommander implements ISIPCommander {
if (dialog == null) { if (dialog == null) {
return; return;
} }
try {
Request byeRequest = dialog.createRequest(Request.BYE); Request byeRequest = dialog.createRequest(Request.BYE);
SipURI byeURI = (SipURI) byeRequest.getRequestURI(); SipURI byeURI = (SipURI) byeRequest.getRequestURI();
String vh = transaction.getRequest().getHeader(ViaHeader.NAME).toString(); String vh = transaction.getRequest().getHeader(ViaHeader.NAME).toString();
@ -590,9 +567,9 @@ public class SIPCommander implements ISIPCommander {
ViaHeader viaHeader = (ViaHeader) byeRequest.getHeader(ViaHeader.NAME); ViaHeader viaHeader = (ViaHeader) byeRequest.getHeader(ViaHeader.NAME);
String protocol = viaHeader.getTransport().toUpperCase(); String protocol = viaHeader.getTransport().toUpperCase();
ClientTransaction clientTransaction = null; ClientTransaction clientTransaction = null;
if("TCP".equals(protocol)) { if ("TCP".equals(protocol)) {
clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest); clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
} else if("UDP".equals(protocol)) { } else if ("UDP".equals(protocol)) {
clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest); clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
} }
@ -603,8 +580,9 @@ public class SIPCommander implements ISIPCommander {
dialog.sendRequest(clientTransaction); dialog.sendRequest(clientTransaction);
streamSession.remove(streamId); storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
zlmrtpServerFactory.closeRTPServer(streamId); streamSession.remove(streamInfo);
zlmrtpServerFactory.closeRTPServer(streamInfo.getMediaServerIp(), streamId);
} catch (TransactionDoesNotExistException e) { } catch (TransactionDoesNotExistException e) {
e.printStackTrace(); e.printStackTrace();
} catch (SipException e) { } catch (SipException e) {
@ -1297,7 +1275,7 @@ public class SIPCommander implements ISIPCommander {
subscribePostitionXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n"); subscribePostitionXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
subscribePostitionXml.append("<Query>\r\n"); subscribePostitionXml.append("<Query>\r\n");
subscribePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n"); subscribePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
subscribePostitionXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n"); subscribePostitionXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
subscribePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n"); subscribePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
if (expires > 0) { if (expires > 0) {
subscribePostitionXml.append("<Interval>" + String.valueOf(interval) + "</Interval>\r\n"); subscribePostitionXml.append("<Interval>" + String.valueOf(interval) + "</Interval>\r\n");
@ -1314,7 +1292,7 @@ public class SIPCommander implements ISIPCommander {
return true; return true;
} catch ( NumberFormatException | ParseException | InvalidArgumentException | SipException e) { } catch (NumberFormatException | ParseException | InvalidArgumentException | SipException e) {
e.printStackTrace(); e.printStackTrace();
return false; return false;
} }
@ -1339,7 +1317,7 @@ public class SIPCommander implements ISIPCommander {
cmdXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n"); cmdXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
cmdXml.append("<Query>\r\n"); cmdXml.append("<Query>\r\n");
cmdXml.append("<CmdType>Alarm</CmdType>\r\n"); cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n"); cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n"); cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
if (!XmlUtil.isEmpty(startPriority)) { if (!XmlUtil.isEmpty(startPriority)) {
cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n"); cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
@ -1362,16 +1340,12 @@ public class SIPCommander implements ISIPCommander {
cmdXml.append("</Query>\r\n"); cmdXml.append("</Query>\r\n");
String tm = Long.toString(System.currentTimeMillis()); String tm = Long.toString(System.currentTimeMillis());
Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), "viaTagPos" + tm, "fromTagPos" + tm, null, expires, "presence" );
CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
: udpSipProvider.getNewCallId();
Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), "z9hG4bK-viaPos-" + tm, "fromTagPos" + tm, null, expires, "presence" , callIdHeader);
transmitRequest(device, request); transmitRequest(device, request);
return true; return true;
} catch ( NumberFormatException | ParseException | InvalidArgumentException | SipException e) { } catch (NumberFormatException | ParseException | InvalidArgumentException | SipException e) {
e.printStackTrace(); e.printStackTrace();
return false; return false;
} }
@ -1386,15 +1360,15 @@ public class SIPCommander implements ISIPCommander {
return transmitRequest(device, request, errorEvent, null); return transmitRequest(device, request, errorEvent, null);
} }
private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException { private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException {
ClientTransaction clientTransaction = null; ClientTransaction clientTransaction = null;
if("TCP".equals(device.getTransport())) { if ("TCP".equals(device.getTransport())) {
clientTransaction = tcpSipProvider.getNewClientTransaction(request); clientTransaction = tcpSipProvider.getNewClientTransaction(request);
} else if("UDP".equals(device.getTransport())) { } else if ("UDP".equals(device.getTransport())) {
clientTransaction = udpSipProvider.getNewClientTransaction(request); clientTransaction = udpSipProvider.getNewClientTransaction(request);
} }
CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME); CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
// 添加错误订阅 // 添加错误订阅
if (errorEvent != null) { if (errorEvent != null) {
sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), errorEvent); sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), errorEvent);
@ -1409,13 +1383,12 @@ public class SIPCommander implements ISIPCommander {
} }
@Override @Override
public void closeRTPServer(Device device, String channelId) { public void closeRTPServer(Device device, String channelId) {
if (rtpEnable) { if (mediaConfig.getRtpEnable()) {
String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId); String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
zlmrtpServerFactory.closeRTPServer(streamId); String mediaServerIp = streamSession.getMediaServerIp(channelId, streamId);
zlmrtpServerFactory.closeRTPServer(mediaServerIp, streamId);
} }
} }
} }

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

@ -26,6 +26,7 @@ import com.genersoft.iot.vmp.conf.UserSetup;
import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector; import com.genersoft.iot.vmp.gb28181.event.DeviceOffLineDetector;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.callback.CheckForAllRecordsThread; import com.genersoft.iot.vmp.gb28181.transmit.callback.CheckForAllRecordsThread;
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;
@ -54,12 +55,24 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.SipException;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.io.ByteArrayInputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
/** /**
* @Description:MESSAGE请求处理器 * @Description:MESSAGE请求处理器
* @author: swwheihei * @author: swwheihei
* @date: 2020年5月3日 下午5:32:41 * @date: 2020年5月3日 下午5:32:41
*/ */
@SuppressWarnings(value={"unchecked", "rawtypes"}) @SuppressWarnings(value = {"unchecked", "rawtypes"})
public class MessageRequestProcessor extends SIPRequestAbstractProcessor { public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
public static volatile List<String> threadNameList = new ArrayList(); public static volatile List<String> threadNameList = new ArrayList();
@ -70,12 +83,15 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
private SIPCommander cmder; private SIPCommander cmder;
private IVideoManagerStorager storager;
private SIPCommanderFroPlatform cmderFroPlatform; private SIPCommanderFroPlatform cmderFroPlatform;
private IVideoManagerStorager storager; private IVideoManagerStorager storager;
private IRedisCatchStorage redisCatchStorage; private IRedisCatchStorage redisCatchStorage;
private VideoStreamSessionManager streamSession;
private EventPublisher publisher; private EventPublisher publisher;
private RedisUtil redis; private RedisUtil redis;
@ -112,52 +128,46 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
try { try {
Element rootElement = getRootElement(evt); Element rootElement = getRootElement(evt);
String cmd = XmlUtil.getText(rootElement, "CmdType"); String cmd = XmlUtil.getText(rootElement, "CmdType");
String deviceID = XmlUtil.getText(rootElement, "DeviceID");
if (MESSAGE_KEEP_ALIVE.equals(cmd) || MESSAGE_ALARM.equals(cmd)) {
// KEEP_ALIVE 和 ALARM 消息太多,改成debug级别
logger.debug("接收到{}消息,deviceID :{}", cmd, deviceID);
} else {
logger.info("接收到{}消息,deviceID :{}", cmd, deviceID);
}
if (MESSAGE_KEEP_ALIVE.equals(cmd)) { if (MESSAGE_KEEP_ALIVE.equals(cmd)) {
logger.info("接收到KeepAlive消息");
processMessageKeepAlive(evt); processMessageKeepAlive(evt);
} else if (MESSAGE_CONFIG_DOWNLOAD.equals(cmd)) { } else if (MESSAGE_CONFIG_DOWNLOAD.equals(cmd)) {
logger.info("接收到ConfigDownload消息");
processMessageConfigDownload(evt); processMessageConfigDownload(evt);
} else if (MESSAGE_CATALOG.equals(cmd)) { } else if (MESSAGE_CATALOG.equals(cmd)) {
logger.info("接收到Catalog消息");
processMessageCatalogList(evt); processMessageCatalogList(evt);
} else if (MESSAGE_DEVICE_INFO.equals(cmd)) { } else if (MESSAGE_DEVICE_INFO.equals(cmd)) {
// DeviceInfo消息处理
processMessageDeviceInfo(evt); processMessageDeviceInfo(evt);
} else if (MESSAGE_DEVICE_STATUS.equals(cmd)) { } else if (MESSAGE_DEVICE_STATUS.equals(cmd)) {
// DeviceStatus消息处理
processMessageDeviceStatus(evt); processMessageDeviceStatus(evt);
} else if (MESSAGE_DEVICE_CONTROL.equals(cmd)) { } else if (MESSAGE_DEVICE_CONTROL.equals(cmd)) {
logger.info("接收到DeviceControl消息");
processMessageDeviceControl(evt); processMessageDeviceControl(evt);
} else if (MESSAGE_DEVICE_CONFIG.equals(cmd)) { } else if (MESSAGE_DEVICE_CONFIG.equals(cmd)) {
logger.info("接收到DeviceConfig消息");
processMessageDeviceConfig(evt); processMessageDeviceConfig(evt);
} else if (MESSAGE_ALARM.equals(cmd)) { } else if (MESSAGE_ALARM.equals(cmd)) {
logger.info("接收到Alarm消息");
processMessageAlarm(evt); processMessageAlarm(evt);
} else if (MESSAGE_RECORD_INFO.equals(cmd)) { } else if (MESSAGE_RECORD_INFO.equals(cmd)) {
logger.info("接收到RecordInfo消息");
processMessageRecordInfo(evt); processMessageRecordInfo(evt);
}else if (MESSAGE_MEDIA_STATUS.equals(cmd)) { } else if (MESSAGE_MEDIA_STATUS.equals(cmd)) {
logger.info("接收到MediaStatus消息"); // TODO channelId
processMessageMediaStatus(evt); processMessageMediaStatus(evt);
} else if (MESSAGE_MOBILE_POSITION.equals(cmd)) { } else if (MESSAGE_MOBILE_POSITION.equals(cmd)) {
logger.info("接收到MobilePosition消息");
processMessageMobilePosition(evt); processMessageMobilePosition(evt);
} else if (MESSAGE_PRESET_QUERY.equals(cmd)) { } else if (MESSAGE_PRESET_QUERY.equals(cmd)) {
logger.info("接收到PresetQuery消息");
processMessagePresetQuery(evt); processMessagePresetQuery(evt);
} else if (MESSAGE_BROADCAST.equals(cmd)) { } else if (MESSAGE_BROADCAST.equals(cmd)) {
// Broadcast消息处理
processMessageBroadcast(evt); processMessageBroadcast(evt);
} else { } else {
logger.info("接收到消息:" + cmd);
responseAck(evt); responseAck(evt);
} }
} catch (DocumentException | SipException |InvalidArgumentException | ParseException e) { } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
e.printStackTrace(); log.error("MessageRequestProcessor.process error!", e);
} }
} }
@ -264,7 +274,6 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
} }
} }
} }
} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) { } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -688,7 +697,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
} }
if (!XmlUtil.isEmpty(deviceAlarm.getAlarmMethod())) { if (!XmlUtil.isEmpty(deviceAlarm.getAlarmMethod())) {
if ( deviceAlarm.getAlarmMethod().equals("4")) { if (deviceAlarm.getAlarmMethod().equals("4")) {
MobilePosition mobilePosition = new MobilePosition(); MobilePosition mobilePosition = new MobilePosition();
mobilePosition.setDeviceId(deviceAlarm.getDeviceId()); mobilePosition.setDeviceId(deviceAlarm.getDeviceId());
mobilePosition.setTime(deviceAlarm.getAlarmTime()); mobilePosition.setTime(deviceAlarm.getAlarmTime());
@ -726,7 +735,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
} }
} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
// } catch (DocumentException e) { // } catch (DocumentException e) {
e.printStackTrace(); log.error("MessageRequestProcessor.processMessageAlarm error!", e);
} }
} }
@ -749,7 +758,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
} }
} }
} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) { } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
e.printStackTrace(); log.error("MessageRequestProcessor.processMessageKeepAlive error!", e);
} }
} }
@ -769,7 +778,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
String deviceId = deviceIdElement.getText().toString(); String deviceId = deviceIdElement.getText().toString();
recordInfo.setDeviceId(deviceId); recordInfo.setDeviceId(deviceId);
recordInfo.setName(XmlUtil.getText(rootElement, "Name")); recordInfo.setName(XmlUtil.getText(rootElement, "Name"));
if (XmlUtil.getText(rootElement, "SumNum")== null || XmlUtil.getText(rootElement, "SumNum") =="") { if (XmlUtil.getText(rootElement, "SumNum") == null || XmlUtil.getText(rootElement, "SumNum") == "") {
recordInfo.setSumNum(0); recordInfo.setSumNum(0);
} else { } else {
recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement, "SumNum"))); recordInfo.setSumNum(Integer.parseInt(XmlUtil.getText(rootElement, "SumNum")));
@ -870,7 +879,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
// deferredResultHolder.invokeResult(msg); // deferredResultHolder.invokeResult(msg);
// logger.info("处理完成,返回结果"); // logger.info("处理完成,返回结果");
} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) { } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
e.printStackTrace(); log.error("MessageRequestProcessor.processMessageRecordInfo error!", e);
} }
} }
@ -879,23 +888,24 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
* *
* @param evt * @param evt
*/ */
private void processMessageMediaStatus(RequestEvent evt){ private void processMessageMediaStatus(RequestEvent evt) {
try { try {
// 回复200 OK // 回复200 OK
responseAck(evt); responseAck(evt);
Element rootElement = getRootElement(evt); Element rootElement = getRootElement(evt);
String deviceId = XmlUtil.getText(rootElement, "DeviceID"); String deviceId = XmlUtil.getText(rootElement, "DeviceID");
String NotifyType =XmlUtil.getText(rootElement, "NotifyType"); String NotifyType = XmlUtil.getText(rootElement, "NotifyType");
if (NotifyType.equals("121")){ if (NotifyType.equals("121")) {
logger.info("媒体播放完毕,通知关流"); logger.info("媒体播放完毕,通知关流");
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, "*"); List<StreamInfo> streamInfos = redisCatchStorage.queryPlayBackByDeviceId(deviceId);
if (streamInfo != null) { if (streamInfos != null && !streamInfos.isEmpty()) {
redisCatchStorage.stopPlayback(streamInfo); for (StreamInfo streamInfo : streamInfos) {
cmder.streamByeCmd(streamInfo.getStreamId()); cmder.stopStreamByeCmd(streamInfo, null);
}
} }
} }
} catch (ParseException | SipException | InvalidArgumentException | DocumentException e) { } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
e.printStackTrace(); log.error("MessageRequestProcessor.processMessageMediaStatus error!", e);
} }
} }

16
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,8 +25,6 @@ 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响应
* *

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;

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

@ -1,29 +1,23 @@
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;
@ -56,18 +50,14 @@ public class ZLMHttpHookListener {
@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配置此事件对回复不敏感
@ -135,7 +125,9 @@ public class ZLMHttpHookListener {
} }
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);
@ -225,114 +217,123 @@ public class ZLMHttpHookListener {
/** /**
* 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());
} }
JSONObject ret = new JSONObject();
ret.put("code", 0);
ret.put("msg", "success");
// 流消失移除redis play // 流消失移除redis play
String app = json.getString("app"); String app = json.getString("app");
String streamId = json.getString("stream"); String streamId = json.getString("stream");
String schema = json.getString("schema");
boolean regist = json.getBoolean("regist"); boolean regist = json.getBoolean("regist");
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); if (!"rtp".equals(app) || regist) {
if ("rtp".equals(app) && !regist ) {
if (streamInfo!=null){
redisCatchStorage.stopPlay(streamInfo);
storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
}else{
streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
redisCatchStorage.stopPlayback(streamInfo);
}
}else {
if (!"rtp".equals(app) && "rtsp".equals(schema)){ if (!"rtp".equals(app) && "rtsp".equals(schema)){
zlmMediaListManager.updateMediaList(); zlmMediaListManager.updateMediaList();
} }
return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
} }
JSONObject ret = new JSONObject();
ret.put("code", 0); String[] s = streamId.split("_");
ret.put("msg", "success"); if (s.length != 4) {
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK); return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
}
String channelId = s[3];
// TODO channelId
StreamInfo streamInfo = streamSession.getStreamInfo(channelId, streamId);
if (null != streamInfo) {
cmder.stopStreamByeCmd(streamInfo, null);
}
return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
} }
/** /**
* 流无人观看时事件用户可以通过此事件选择是否关闭无人看的流 * 流无人观看时事件用户可以通过此事件选择是否关闭无人看的流
*
*/ */
@ResponseBody @ResponseBody
@PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8") @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onStreamNoneReader(@RequestBody JSONObject json){ public ResponseEntity<String> onStreamNoneReader(@RequestBody JSONObject json) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_stream_none_reader API调用,参数:" + json.toString()); 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);
if (streamInfo != null) { String app = json.getString("app");
String streamId = json.getString("stream");
if (!"rtp".equals(app) || streamId.indexOf("gb_play") < 0) {
return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
}
String[] s = streamId.split("_");
if (s.length != 4) {
return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
}
String channelId = s[3];
// TODO channelId
StreamInfo streamInfo = streamSession.getStreamInfo(channelId, streamId);
if (null != streamInfo) {
if (redisCatchStorage.isChannelSendingRTP(streamInfo.getChannelId())) { if (redisCatchStorage.isChannelSendingRTP(streamInfo.getChannelId())) {
ret.put("close", false); ret.put("close", false);
} else { } else {
cmder.streamByeCmd(streamId); cmder.stopStreamByeCmd(streamInfo, null);
redisCatchStorage.stopPlay(streamInfo);
storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
} }
}else{
cmder.streamByeCmd(streamId);
streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
redisCatchStorage.stopPlayback(streamInfo);
} }
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK); return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
} }
/** /**
* 流未找到事件用户可以在此事件触发时立即去拉流这样可以实现按需拉流此事件对回复不敏感 * 流未找到事件用户可以在此事件触发时立即去拉流这样可以实现按需拉流此事件对回复不敏感
*
*/ */
@ResponseBody @ResponseBody
@PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8") @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onStreamNotFound(@RequestBody JSONObject json){ public ResponseEntity<String> onStreamNotFound(@RequestBody JSONObject json) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_stream_not_found API调用,参数:" + json.toString()); logger.debug("ZLM HOOK on_stream_not_found API调用,参数:" + json.toString());
} }
if (autoApplyPlay) {
JSONObject ret = new JSONObject();
ret.put("code", 0);
ret.put("msg", "success");
if (!mediaConfig.getAutoApplyPlay()) {
return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
}
String app = json.getString("app"); String app = json.getString("app");
String streamId = json.getString("stream"); String streamId = json.getString("stream");
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
if ("rtp".equals(app) && streamId.indexOf("gb_play") > -1 && streamInfo == null) { if (!"rtp".equals(app) || streamId.indexOf("gb_play") < 0) {
return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
}
String[] s = streamId.split("_"); String[] s = streamId.split("_");
if (s.length == 4) { if (s.length != 4) {
return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
}
String deviceId = s[2]; String deviceId = s[2];
String channelId = s[3]; String channelId = s[3];
StreamInfo streamInfo = streamSession.getStreamInfo(channelId, streamId);
if (streamInfo != null) {
return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
}
Device device = storager.queryVideoDevice(deviceId); Device device = storager.queryVideoDevice(deviceId);
if (device != null) { if (device != null) {
UUID uuid = UUID.randomUUID(); RequestMessage msg = playService.createCallbackPlayMsg();
cmder.playStreamCmd(device, channelId, (JSONObject response) -> { cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
logger.info("收到订阅消息: " + response.toJSONString()); logger.info("收到订阅消息: " + response.toJSONString());
playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString()); playService.onPublishHandlerForPlay(response, deviceId, channelId, msg);
}, null); }, null);
} }
return new ResponseEntity<>(ret.toString(), HttpStatus.OK);
}
}
}
JSONObject ret = new JSONObject();
ret.put("code", 0);
ret.put("msg", "success");
return new ResponseEntity<String>(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");
// List<MediaServerConfig> mediaServerConfigs = JSON.parseArray(JSON.toJSONString(json), MediaServerConfig.class);
// MediaServerConfig mediaServerConfig = mediaServerConfigs.get(0);
MediaServerConfig mediaServerConfig = JSON.toJavaObject(json, MediaServerConfig.class); MediaServerConfig mediaServerConfig = JSON.toJavaObject(json, MediaServerConfig.class);
mediaServerConfig.setWanIp(StringUtils.isEmpty(mediaWanIp)? mediaIp: mediaWanIp);
mediaServerConfig.setLocalIP(mediaIp);
redisCatchStorage.updateMediaInfo(mediaServerConfig); redisCatchStorage.updateMediaInfo(mediaServerConfig);
// TODO Auto-generated method stub
JSONObject ret = new JSONObject(); JSONObject ret = new JSONObject();
ret.put("code", 0); ret.put("code", 0);
ret.put("msg", "success"); ret.put("msg", "success");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK); 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 getMediaServerConfig(){ public JSONObject setServerConfig(String mediaServerIp, Map<String, Object> param) {
return sendPost("getServerConfig",null); return sendPost(mediaServerIp, "setServerConfig", param);
} }
public JSONObject setServerConfig(Map<String, Object> param){ public JSONObject openRtpServer(String mediaServerIp, Map<String, Object> param) {
return sendPost("setServerConfig",param); return sendPost(mediaServerIp, "openRtpServer", param);
} }
public JSONObject openRtpServer(Map<String, Object> param){ public JSONObject closeRtpServer(String mediaServerIp, Map<String, Object> param) {
return sendPost("openRtpServer",param); return sendPost(mediaServerIp, "closeRtpServer", param);
} }
public JSONObject closeRtpServer(Map<String, Object> param) { /**
return sendPost("closeRtpServer",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);
}
/**
* 开始录制
*
* @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);
}
/**
* 结束录制
*
* @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) {

127
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) {
String[] mediaIpArr = mediaConfig.getMediaIpArr();
for (String mediaIp : mediaIpArr) {
// 获取zlm信息 // 获取zlm信息
logger.info("等待zlm接入..."); log.info("等待zlm {} 接入...", mediaIp);
MediaServerConfig mediaServerConfig = getMediaServerConfig(); MediaServerConfig mediaServerConfig = getMediaServerConfig(mediaIp);
if (mediaServerConfig != null) { if (mediaServerConfig != null) {
logger.info("zlm接入成功..."); log.info("zlm {} 接入成功...", mediaIp);
if (autoConfig) saveZLMConfig(); if (mediaConfig.getAutoConfig()) {
mediaServerConfig = getMediaServerConfig(); // 自动配置zlm
saveZLMConfig(mediaIp);
// 配置后,从zlm重新获取流媒体服务器信息
mediaServerConfig = getMediaServerConfig(mediaIp);
}
// TODO 这里需要把多台zlm服务器的配置,分别存储到redis,不能用同一个key,否则会覆盖
redisCatchStorage.updateMediaInfo(mediaServerConfig); redisCatchStorage.updateMediaInfo(mediaServerConfig);
// 更新流列表 // 更新流列表
zlmMediaListManager.updateMediaList(); 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);

20
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,12 +7,13 @@ 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 {
/** /**
@ -51,6 +50,7 @@ public interface IVideoManagerStorager {
/** /**
* 开始播放 * 开始播放
*
* @param deviceId 设备id * @param deviceId 设备id
* @param channelId 通道ID * @param channelId 通道ID
* @param streamId 流地址 * @param streamId 流地址
@ -59,6 +59,7 @@ public interface IVideoManagerStorager {
/** /**
* 停止播放 * 停止播放
*
* @param deviceId 设备id * @param deviceId 设备id
* @param channelId 通道ID * @param channelId 通道ID
*/ */
@ -92,6 +93,7 @@ public interface IVideoManagerStorager {
/** /**
* 获取某个设备的通道 * 获取某个设备的通道
*
* @param deviceId 设备ID * @param deviceId 设备ID
* @param channelId 通道ID * @param channelId 通道ID
*/ */
@ -99,6 +101,7 @@ public interface IVideoManagerStorager {
/** /**
* 获取多个设备 * 获取多个设备
*
* @param page 当前页数 * @param page 当前页数
* @param count 每页数量 * @param count 每页数量
* @return List<Device> 设备对象数组 * @return List<Device> 设备对象数组
@ -151,10 +154,18 @@ public interface IVideoManagerStorager {
/** /**
* 清空通道 * 清空通道
*
* @param deviceId * @param deviceId
*/ */
void cleanChannelsForDevice(String deviceId); void cleanChannelsForDevice(String deviceId);
/**
* 添加Mobile Position设备移动位置
*
* @param MobilePosition
* @return
*/
public boolean insertMobilePosition(MobilePosition mobilePosition);
/** /**
* 更新上级平台 * 更新上级平台
@ -244,6 +255,7 @@ public interface IVideoManagerStorager {
/** /**
* 查询移动位置轨迹 * 查询移动位置轨迹
*
* @param deviceId * @param deviceId
* @param startTime * @param startTime
* @param endTime * @param endTime
@ -252,12 +264,14 @@ public interface IVideoManagerStorager {
/** /**
* 查询最新移动位置 * 查询最新移动位置
*
* @param deviceId * @param deviceId
*/ */
public MobilePosition queryLatestPosition(String deviceId); public MobilePosition queryLatestPosition(String deviceId);
/** /**
* 删除指定设备的所有移动位置 * 删除指定设备的所有移动位置
*
* @param deviceId * @param deviceId
*/ */
public int clearMobilePositionsByDeviceId(String 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);

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

@ -8,10 +8,13 @@ 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
@ -23,138 +26,169 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
@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());
@Override if (deviceChannel != null) {
public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) { deviceChannel.setStreamId(null);
Map<String, StreamInfo> streamInfos = new HashMap<>(); deviceChannel.setDeviceId(streamInfo.getDeviceID());
// List<Object> playLeys = redis.keys(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId)); deviceChannelMapper.update(deviceChannel);
List<Object> players = redis.scan(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId));
if (players.size() == 0) return streamInfos;
for (int i = 0; i < players.size(); i++) {
String key = (String) players.get(i);
StreamInfo streamInfo = (StreamInfo)redis.get(key);
streamInfos.put(streamInfo.getDeviceID() + "_" + streamInfo.getChannelId(), streamInfo);
} }
return streamInfos; String key = getKey(VideoManagerConstants.PLAY_BLACK_PREFIX,
streamInfo.getStreamId(),
streamInfo.getChannelId(),
streamInfo.getDeviceID()
);
return redis.del(key);
} }
@Override @Override
public boolean startPlayback(StreamInfo stream) { public StreamInfo queryPlaybackByStreamId(String channelId, String steamId) {
return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, stream.getStreamId(),stream.getDeviceID(), stream.getChannelId()), String key = getKey(VideoManagerConstants.PLAY_BLACK_PREFIX,
stream); steamId,
channelId,
null
);
return scanOne(key);
} }
@Override
public StreamInfo queryPlaybackByChannel(String channelId) {
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<>();
}
List<StreamInfo> streamInfos = new ArrayList<>(players.size());
for (int i = 0; i < players.size(); i++) {
String redisKey = (String) players.get(i);
StreamInfo streamInfo = (StreamInfo) redis.get(redisKey);
streamInfos.add(streamInfo);
} }
return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, return streamInfos;
streamInfo.getStreamId(),
streamInfo.getDeviceID(),
streamInfo.getChannelId()));
} }
@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();
}
}
}

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

@ -1,20 +1,19 @@
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
@ -22,6 +21,7 @@ public class RedisUtil {
/** /**
* 指定缓存失效时间 * 指定缓存失效时间
*
* @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,8 +65,9 @@ public class RedisUtil {
/** /**
* 删除缓存 * 删除缓存
* @SuppressWarnings("unchecked") 忽略类型转换警告 *
* @param key 一个或者多个 * @param key 一个或者多个
* @SuppressWarnings("unchecked") 忽略类型转换警告
*/ */
public boolean del(String... key) { public boolean del(String... key) {
try { try {
@ -87,6 +90,7 @@ public class RedisUtil {
/** /**
* 普通缓存获取 * 普通缓存获取
*
* @param key * @param key
* @return * @return
*/ */
@ -96,6 +100,7 @@ public class RedisUtil {
/** /**
* 普通缓存放入 * 普通缓存放入
*
* @param key * @param key
* @param value * @param value
* @return true / false * @return true / false
@ -112,6 +117,7 @@ public class RedisUtil {
/** /**
* 普通缓存放入并设置时间 * 普通缓存放入并设置时间
*
* @param key * @param key
* @param value * @param value
* @param time 时间如果 time < 0 则设置无限时间 * @param time 时间如果 time < 0 则设置无限时间
@ -133,6 +139,7 @@ public class RedisUtil {
/** /**
* 递增 * 递增
*
* @param key * @param key
* @param delta 递增大小 * @param delta 递增大小
* @return * @return
@ -146,6 +153,7 @@ public class RedisUtil {
/** /**
* 递减 * 递减
*
* @param key * @param key
* @param delta 递减大小 * @param delta 递减大小
* @return * @return
@ -161,6 +169,7 @@ 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,6 +207,7 @@ public class RedisUtil {
/** /**
* HashSet 并设置时间 * HashSet 并设置时间
*
* @param key * @param key
* @param map * @param map
* @param time 时间 * @param time 时间
@ -216,6 +228,7 @@ public class RedisUtil {
/** /**
* 向一张 Hash表 中放入数据如不存在则创建 * 向一张 Hash表 中放入数据如不存在则创建
*
* @param key * @param key
* @param item * @param item
* @param value * @param value
@ -233,6 +246,7 @@ public class RedisUtil {
/** /**
* 向一张 Hash表 中放入数据并设置时间如不存在则创建 * 向一张 Hash表 中放入数据并设置时间如不存在则创建
*
* @param key * @param key
* @param item * @param item
* @param value * @param value
@ -254,6 +268,7 @@ public class RedisUtil {
/** /**
* 删除 Hash表 中的值 * 删除 Hash表 中的值
*
* @param key * @param key
* @param item 可以多个no null * @param item 可以多个no null
*/ */
@ -263,6 +278,7 @@ 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,6 +289,7 @@ public class RedisUtil {
/** /**
* Hash递增如果不存在则创建一个并把新增的值返回 * Hash递增如果不存在则创建一个并把新增的值返回
*
* @param key * @param key
* @param item * @param item
* @param by 递增大小 > 0 * @param by 递增大小 > 0
@ -284,6 +301,7 @@ public class RedisUtil {
/** /**
* Hash递减 * Hash递减
*
* @param key * @param key
* @param item * @param item
* @param by 递减大小 * @param by 递减大小
@ -297,6 +315,7 @@ public class RedisUtil {
/** /**
* 根据 key 获取 set 中的所有值 * 根据 key 获取 set 中的所有值
*
* @param key * @param key
* @return * @return
*/ */
@ -311,6 +330,7 @@ public class RedisUtil {
/** /**
* 从键为 key set 根据 value 查询是否存在 * 从键为 key set 根据 value 查询是否存在
*
* @param key * @param key
* @param value * @param value
* @return true / false * @return true / false
@ -326,6 +346,7 @@ public class RedisUtil {
/** /**
* 将数据放入 set缓存 * 将数据放入 set缓存
*
* @param key 键值 * @param key 键值
* @param values 可以多个 * @param values 可以多个
* @return 成功个数 * @return 成功个数
@ -341,6 +362,7 @@ public class RedisUtil {
/** /**
* 将数据放入 set缓存并设置时间 * 将数据放入 set缓存并设置时间
*
* @param key * @param key
* @param time 时间 * @param time 时间
* @param values 可以多个 * @param values 可以多个
@ -361,6 +383,7 @@ public class RedisUtil {
/** /**
* 获取 set缓存的长度 * 获取 set缓存的长度
*
* @param key * @param key
* @return 长度 * @return 长度
*/ */
@ -375,6 +398,7 @@ 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,6 +534,7 @@ public class RedisUtil {
/** /**
* 获取 list缓存的内容 * 获取 list缓存的内容
*
* @param key * @param key
* @param start 开始 * @param start 开始
* @param end 结束0 -1 代表所有值 * @param end 结束0 -1 代表所有值
@ -523,6 +551,7 @@ public class RedisUtil {
/** /**
* 获取 list缓存的长度 * 获取 list缓存的长度
*
* @param key * @param key
* @return 长度 * @return 长度
*/ */
@ -537,6 +566,7 @@ 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:第二个元素}
@ -554,6 +584,7 @@ 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,6 +601,7 @@ public class RedisUtil {
/** /**
* 将值 value 插入键为 key list 并设置时间 * 将值 value 插入键为 key list 并设置时间
*
* @param key * @param key
* @param value * @param value
* @param time 时间 * @param time 时间
@ -590,6 +622,7 @@ public class RedisUtil {
/** /**
* values 插入键为 key list * values 插入键为 key list
*
* @param key * @param key
* @param values * @param values
* @return true / false * @return true / false
@ -606,6 +639,7 @@ public class RedisUtil {
/** /**
* values 插入键为 key list 并设置时间 * values 插入键为 key list 并设置时间
*
* @param key * @param key
* @param values * @param values
* @param time 时间 * @param time 时间
@ -626,6 +660,7 @@ public class RedisUtil {
/** /**
* 根据索引 index 修改键为 key 的值 * 根据索引 index 修改键为 key 的值
*
* @param key * @param key
* @param index 索引 * @param index 索引
* @param value * @param value
@ -643,6 +678,7 @@ 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 的元素
@ -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;
}
}

176
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;
@ -55,103 +62,127 @@ public class PlayController {
@Autowired @Autowired
private IPlayService playService; private IPlayService playService;
@Autowired
private VideoStreamSessionManager streamSession;
@GetMapping("/play/{deviceId}/{channelId}") @GetMapping("/play/{deviceId}/{channelId}")
public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId, public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId,
@PathVariable String channelId) { @PathVariable String channelId) {
Device device = storager.queryVideoDevice(deviceId);
RequestMessage msg = playService.createCallbackPlayMsg();
PlayResult playResult = playService.play(deviceId, channelId, null, null); DeferredResult<ResponseEntity<String>> result = new DeferredResult<>();
// 超时处理 // 超时处理
playResult.getResult().onTimeout(()->{ result.onTimeout(() -> {
logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId)); logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId));
// 释放rtpserver // 释放rtpserver
cmder.closeRTPServer(playResult.getDevice(), channelId); cmder.closeRTPServer(device, channelId);
RequestMessage msg = new RequestMessage(); StreamInfo streamInfo = streamSession.getPlayStreamInfo(channelId);
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + playResult.getUuid()); streamSession.remove(streamInfo);
msg.setData("Timeout"); msg.setData("Timeout");
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
}); });
return playResult.getResult(); resultHolder.put(msg.getId(), result);
}
@PostMapping("/play/{streamId}/stop") // 判断是否已经存在点播
public DeferredResult<ResponseEntity<String>> playStop(@PathVariable String streamId) { StreamInfo oldStreamInfo = streamSession.getPlayStreamInfo(channelId);
if (oldStreamInfo == null) {
logger.debug(String.format("设备预览/回放停止API调用,streamId:%s", streamId)); // 发送点播消息
playStreamCmd(device, channelId, msg);
return result;
}
UUID uuid = UUID.randomUUID(); // 若已有人点播,直接播放
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(); String streamId = oldStreamInfo.getStreamId();
String mediaServerIp = oldStreamInfo.getMediaServerIp();
JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerIp, streamId);
if (rtpInfo.getBoolean("exist")) {
msg.setData(JSON.toJSONString(oldStreamInfo));
resultHolder.invokeResult(msg);
return result;
}
// 录像查询以channelId作为deviceId查询 // 若已有人点播,但已超时自动断开,则重新发起点播
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_STOP + uuid, result); storager.stopPlay(oldStreamInfo.getDeviceID(), oldStreamInfo.getChannelId());
streamSession.remove(oldStreamInfo);
cmder.streamByeCmd(streamId, event -> { playStreamCmd(device, channelId, msg);
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); return result;
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) { private void playStreamCmd(Device device, String channelId, RequestMessage msg) {
JSONObject json = new JSONObject(); cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
json.put("streamId", streamId); logger.info("收到点播回调消息: " + response.toJSONString());
RequestMessage msg = new RequestMessage(); playService.onPublishHandlerForPlay(response, device.getDeviceId(), channelId, msg);
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); }, event -> {
msg.setData(json.toString()); StreamInfo streamInfo = streamSession.getPlayStreamInfo(channelId);
resultHolder.invokeResult(msg); streamSession.remove(streamInfo);
Response response = event.getResponse();
int statusCode = response.getStatusCode();
String errMsg;
if (503 == statusCode) {
errMsg = "点播失败,请检查在NVR上是否可以正常打开监控,并检查NVR和SIP是否连通, 错误码: %s, %s";
} else { } else {
logger.warn("设备预览/回放停止API调用失败!"); errMsg = "点播失败,错误码: %s, %s";
RequestMessage msg = new RequestMessage(); }
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); msg.setData(String.format(errMsg, statusCode, response.getReasonPhrase()));
msg.setData("streamId null");
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
});
} }
@PostMapping("/play/{channelId}/{streamId}/stop")
public DeferredResult<ResponseEntity<String>> playStop(@PathVariable String channelId, @PathVariable String streamId) {
logger.debug(String.format("设备预览/回放停止API调用,streamId:%s", streamId));
RequestMessage msg = playService.createCallbackPlayMsg();
DeferredResult<ResponseEntity<String>> result = new DeferredResult<>();
// 超时处理 // 超时处理
result.onTimeout(()->{ result.onTimeout(() -> {
logger.warn(String.format("设备预览/回放停止超时,streamId:%s ", streamId)); logger.warn(String.format("设备预览/回放停止超时,streamId:%s ", streamId));
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_STOP + uuid);
msg.setData("Timeout"); msg.setData("Timeout");
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
}); });
// 录像查询以channelId作为deviceId查询
resultHolder.put(msg.getId(), result);
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; return result;
} }
/** /**
* 将不是h264的视频通过ffmpeg 转码为h264 + aac * 将不是h264的视频通过ffmpeg 转码为h264 + aac
*
* @param streamId 流ID * @param streamId 流ID
* @return
*/ */
@PostMapping("/play/{streamId}/convert") @PostMapping("/play/{channelId}/{streamId}/convert")
public ResponseEntity<String> playConvert(@PathVariable String streamId) { public ResponseEntity<String> playConvert(@PathVariable String channelId, @PathVariable String streamId) {
StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); StreamInfo streamInfo = streamSession.getPlayStreamInfo(channelId);
if (streamInfo == null) { if (streamInfo == null) {
logger.warn("视频转码API调用失败!, 视频流已经停止!"); logger.warn("视频转码API调用失败!, 视频流已经停止!");
return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK); return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK);
} }
JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); String mediaServerIp = streamInfo.getMediaServerIp();
JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerIp, streamId);
if (!rtpInfo.getBoolean("exist")) { if (!rtpInfo.getBoolean("exist")) {
logger.warn("视频转码API调用失败!, 视频流已停止推流!"); logger.warn("视频转码API调用失败!, 视频流已停止推流!");
return new ResponseEntity<String>("推流信息在流媒体中不存在, 视频流可能已停止推流", HttpStatus.OK); return new ResponseEntity<String>("推流信息在流媒体中不存在, 视频流可能已停止推流", HttpStatus.OK);
} else { } else {
MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
String dstUrl = String.format("rtmp://%s:%s/convert/%s", "127.0.0.1", mediaInfo.getRtmpPort(), String dstUrl = String.format("rtmp://%s:%s/convert/%s", mediaServerIp, mediaInfo.getRtmpPort(),
streamId ); streamId);
String srcUrl = String.format("rtsp://%s:%s/rtp/%s", "127.0.0.1", mediaInfo.getRtspPort(), streamId); String srcUrl = String.format("rtsp://%s:%s/rtp/%s", mediaServerIp, mediaInfo.getRtspPort(), streamId);
JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(srcUrl, dstUrl, "1000000"); JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(mediaServerIp, srcUrl, dstUrl, "1000000");
System.out.println(jsonObject); System.out.println(jsonObject);
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
if (jsonObject != null && jsonObject.getInteger("code") == 0) { if (jsonObject != null && jsonObject.getInteger("code") == 0) {
@ -161,35 +192,36 @@ public class PlayController {
result.put("key", data.getString("key")); result.put("key", data.getString("key"));
StreamInfo streamInfoResult = new StreamInfo(); StreamInfo streamInfoResult = new StreamInfo();
streamInfoResult.setRtmp(dstUrl); streamInfoResult.setRtmp(dstUrl);
streamInfoResult.setRtsp(String.format("rtsp://%s:%s/convert/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId)); streamInfoResult.setRtsp(String.format("rtsp://%s:%s/convert/%s", mediaServerIp, mediaInfo.getRtspPort(), streamId));
streamInfoResult.setStreamId(streamId); streamInfoResult.setStreamId(streamId);
streamInfoResult.setFlv(String.format("http://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), 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", mediaInfo.getWanIp(), 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", mediaInfo.getWanIp(), 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", mediaInfo.getWanIp(), 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", mediaInfo.getWanIp(), 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", mediaInfo.getWanIp(), 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", mediaInfo.getWanIp(), 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", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); streamInfoResult.setWs_ts(String.format("ws://%s:%s/convert/%s.live.ts", mediaServerIp, mediaInfo.getHttpPort(), streamId));
result.put("data", streamInfoResult); result.put("data", streamInfoResult);
} }
}else { } else {
result.put("code", 1); result.put("code", 1);
result.put("msg", "cover fail"); result.put("msg", "cover fail");
} }
return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK); return new ResponseEntity<>(result.toJSONString(), HttpStatus.OK);
} }
} }
/** /**
* 结束转码 * 结束转码
*
* @param key * @param key
* @return * @return
*/ */
@PostMapping("/play/convert/stop/{key}") @PostMapping("/play/convert/stop/{channelId}/{streamId}/{key}")
public ResponseEntity<String> playConvertStop(@PathVariable String key) { public ResponseEntity<String> playConvertStop(@PathVariable String channelId, @PathVariable String streamId, @PathVariable String key) {
String mediaServerIp = streamSession.getMediaServerIp(channelId, streamId);
JSONObject jsonObject = zlmresTfulUtils.delFFmpegSource(key); JSONObject jsonObject = zlmresTfulUtils.delFFmpegSource(mediaServerIp, key);
System.out.println(jsonObject); System.out.println(jsonObject);
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
if (jsonObject != null && jsonObject.getInteger("code") == 0) { if (jsonObject != null && jsonObject.getInteger("code") == 0) {
@ -198,7 +230,7 @@ public class PlayController {
if (data != null && data.getBoolean("flag")) { if (data != null && data.getBoolean("flag")) {
result.put("code", "0"); result.put("code", "0");
result.put("msg", "success"); result.put("msg", "success");
}else { } else {
} }
}else { }else {

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

@ -1,30 +1,23 @@
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
@ -40,10 +33,7 @@ public class PlaybackController {
private IVideoManagerStorager storager; private IVideoManagerStorager storager;
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private VideoStreamSessionManager streamSession;
// @Autowired
// private ZLMRESTfulUtils zlmresTfulUtils;
@Autowired @Autowired
private IPlayService playService; private IPlayService playService;
@ -58,30 +48,30 @@ public class PlaybackController {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId)); logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId));
} }
UUID uuid = UUID.randomUUID(); RequestMessage msg = playService.createCallbackPlayMsg();
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(); DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>();
// 超时处理 // 超时处理
result.onTimeout(()->{ result.onTimeout(() -> {
logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId)); logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId));
RequestMessage msg = new RequestMessage(); StreamInfo streamInfo = streamSession.getPlayBackStreamInfo(channelId);
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); streamSession.remove(streamInfo);
msg.setData("Timeout"); msg.setData("Timeout");
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
}); });
Device device = storager.queryVideoDevice(deviceId); Device device = storager.queryVideoDevice(deviceId);
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, channelId); StreamInfo oldStreamInfo = streamSession.getPlayBackStreamInfo(channelId);
if (streamInfo != null) { if (oldStreamInfo != null) {
// 停止之前的回 // TODO 只能停止自己之前的回放,不能停止别人的回放,否则会导致别人无法播
cmder.streamByeCmd(streamInfo.getStreamId()); cmder.stopStreamByeCmd(oldStreamInfo, null);
} }
resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result); resultHolder.put(msg.getId(), result);
cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> { cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> {
logger.info("收到订阅消息: " + response.toJSONString()); logger.info("收到订阅消息: " + response.toJSONString());
playService.onPublishHandlerForPlayBack(response, deviceId, channelId, uuid.toString()); playService.onPublishHandlerForPlayBack(response, deviceId, channelId, msg);
}, event -> { }, event -> {
StreamInfo streamInfo = streamSession.getPlayBackStreamInfo(channelId);
streamSession.remove(streamInfo);
Response response = event.getResponse(); Response response = event.getResponse();
RequestMessage msg = new RequestMessage();
msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
msg.setData(String.format("回放失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase())); msg.setData(String.format("回放失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
}); });
@ -89,22 +79,23 @@ public class PlaybackController {
return result; return result;
} }
@RequestMapping("/playback/{ssrc}/stop") @RequestMapping("/playback/{channelId}/{ssrc}/stop")
public ResponseEntity<String> playStop(@PathVariable String ssrc) { public ResponseEntity<String> playStop(@PathVariable String channelId, @PathVariable String ssrc) {
cmder.streamByeCmd(ssrc);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("设备录像回放停止 API调用,ssrc:%s", ssrc)); logger.debug(String.format("设备录像回放停止 API调用,ssrc:%s", ssrc));
} }
if (ssrc == null) {
if (ssrc != null) {
JSONObject json = new JSONObject();
json.put("ssrc", ssrc);
return new ResponseEntity<String>(json.toString(), HttpStatus.OK);
} else {
logger.warn("设备录像回放停止API调用失败!"); logger.warn("设备录像回放停止API调用失败!");
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
StreamInfo streamInfo = streamSession.getStreamInfo(channelId, ssrc);
if (streamInfo == null) {
logger.warn("回放播流不存在!");
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} }
cmder.stopStreamByeCmd(streamInfo, null);
JSONObject json = new JSONObject();
json.put("ssrc", ssrc);
return new ResponseEntity<>(json.toString(), HttpStatus.OK);
} }
} }

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;
}
} }

98
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,23 +28,16 @@ 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 serial 设备编号
* @param channel 通道序号 默认值: 1 * @param channel 通道序号 默认值: 1
* @param code 通道编号,通过 /api/v1/device/channellist 获取的 ChannelList.ID, 该参数和 channel 二选一传递即可 * @param code 通道编号,通过 /api/v1/device/channellist 获取的 ChannelList.ID, 该参数和 channel 二选一传递即可
@ -59,32 +50,32 @@ public class ApiStreamController {
* @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
@ -93,17 +84,17 @@ public class ApiStreamController {
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,6 +134,7 @@ public class ApiStreamController {
/** /**
* 实时直播 - 直播流停止 * 实时直播 - 直播流停止
*
* @param serial 设备编号 * @param serial 设备编号
* @param channel 通道序号 * @param channel 通道序号
* @param code 通道国标编号 * @param code 通道国标编号
@ -151,27 +143,27 @@ public class ApiStreamController {
*/ */
@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 通道国标编号
@ -179,13 +171,13 @@ public class ApiStreamController {
*/ */
@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