Merge remote-tracking branch 'origin/master' into wvp-28181-2.0
# Conflicts:
# README.md
# src/main/java/com/genersoft/iot/vmp/common/StreamInfo.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/impl/SIPCommander.java
# src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java
# src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
# src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
# src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java
# src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
# src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
# src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java
# src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java
# src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java
# src/main/resources/application-dev.yml
# web_src/src/components/gb28181/devicePlayer.vue
| | |
| | | <artifactId>wvp</artifactId> |
| | | <name>web video platform</name> |
| | | |
| | | <repositories> |
| | | <repository> |
| | | <id>nexus-aliyun</id> |
| | | <name>Nexus aliyun</name> |
| | | <url>https://maven.aliyun.com/repository/public</url> |
| | | <layout>default</layout> |
| | | <snapshots> |
| | | <enabled>false</enabled> |
| | | </snapshots> |
| | | <releases> |
| | | <enabled>true</enabled> |
| | | </releases> |
| | | </repository> |
| | | </repositories> |
| | | <pluginRepositories> |
| | | <pluginRepository> |
| | | <id>nexus-aliyun</id> |
| | | <name>Nexus aliyun</name> |
| | | <url>https://maven.aliyun.com/repository/public</url> |
| | | <snapshots> |
| | | <enabled>false</enabled> |
| | | </snapshots> |
| | | <releases> |
| | | <enabled>true</enabled> |
| | | </releases> |
| | | </pluginRepository> |
| | | </pluginRepositories> |
| | | |
| | | <properties> |
| | | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| | | |
| | | <!-- 依赖版本 --> |
| | | <mapper.version>4.1.5</mapper.version> |
| | | <mybatis.version>3.5.5</mybatis.version> |
| | | <mybatis.spring.version>2.0.5</mybatis.spring.version> |
| | | <pagehelper.version>5.2.0</pagehelper.version> |
| | | <snippetsDirectory>${project.build.directory}/generated-snippets</snippetsDirectory> |
| | | <asciidoctor.input.directory>${project.basedir}/docs/asciidoc</asciidoctor.input.directory> |
| | |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-jdbc</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-tomcat</artifactId> |
| | | <artifactId>spring-boot-starter-data-redis</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-web</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.springframework</groupId> |
| | | <artifactId>spring-context</artifactId> |
| | | </dependency> |
| | | |
| | | <!-- redis --> |
| | | <dependency> |
| | | <groupId>org.springframework.data</groupId> |
| | | <artifactId>spring-data-redis</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>redis.clients</groupId> |
| | | <artifactId>jedis</artifactId> |
| | | <version>3.3.0</version> |
| | | <groupId>org.mybatis.spring.boot</groupId> |
| | | <artifactId>mybatis-spring-boot-starter</artifactId> |
| | | <version>2.1.4</version> |
| | | </dependency> |
| | | |
| | | <!-- druid数据库连接池 --> |
| | |
| | | <version>8.0.22</version> |
| | | </dependency> |
| | | |
| | | <!--Mybatis --> |
| | | <!-- 添加sqlite-jdbc数据库驱动 --> |
| | | <dependency> |
| | | <groupId>org.mybatis</groupId> |
| | | <artifactId>mybatis</artifactId> |
| | | <version>${mybatis.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.mybatis</groupId> |
| | | <artifactId>mybatis-spring</artifactId> |
| | | <version>${mybatis.spring.version}</version> |
| | | <groupId>org.xerial</groupId> |
| | | <artifactId>sqlite-jdbc</artifactId> |
| | | <version>3.32.3.2</version> |
| | | </dependency> |
| | | |
| | | <!--分页插件 --> |
| | | <!--Mybatis分页插件 --> |
| | | <dependency> |
| | | <groupId>com.github.pagehelper</groupId> |
| | | <artifactId>pagehelper</artifactId> |
| | | <version>${pagehelper.version}</version> |
| | | <artifactId>pagehelper-spring-boot-starter</artifactId> |
| | | <version>1.2.10</version> |
| | | </dependency> |
| | | |
| | | <!--通用Mapper --> |
| | | <dependency> |
| | | <groupId>tk.mybatis</groupId> |
| | | <artifactId>mapper</artifactId> |
| | | <version>${mapper.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.apache.commons</groupId> |
| | | <artifactId>commons-lang3</artifactId> |
| | | <version>3.11</version> |
| | | </dependency> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>org.apache.commons</groupId>--> |
| | | <!-- <artifactId>commons-lang3</artifactId>--> |
| | | <!-- <version>3.11</version>--> |
| | | <!-- </dependency>--> |
| | | |
| | | <!--Swagger2 --> |
| | | <!--在线文档 --> |
| | |
| | | |
| | | public class StreamInfo { |
| | | |
| | | private String ssrc; |
| | | private String streamId; |
| | | private String deviceID; |
| | | private String cahnnelId; |
| | |
| | | private String rtmp; |
| | | private String rtsp; |
| | | private JSONArray tracks; |
| | | |
| | | public String getSsrc() { |
| | | return ssrc; |
| | | } |
| | | |
| | | public void setSsrc(String ssrc) { |
| | | this.ssrc = ssrc; |
| | | } |
| | | |
| | | public String getDeviceID() { |
| | | return deviceID; |
New file |
| | |
| | | package com.genersoft.iot.vmp.conf; |
| | | |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.boot.CommandLineRunner; |
| | | import org.springframework.core.annotation.Order; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | |
| | | /** |
| | | * 对配置文件进行校验 |
| | | */ |
| | | @Component |
| | | @Order(value=2) |
| | | public class ApplicationCheckRunner implements CommandLineRunner { |
| | | |
| | | private Logger logger = LoggerFactory.getLogger("ApplicationCheckRunner"); |
| | | |
| | | @Value("${sip.ip}") |
| | | private String sipIp; |
| | | |
| | | @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}") |
| | | private String serverPort; |
| | | |
| | | @Value("${media.autoConfig}") |
| | | private boolean autoConfig; |
| | | |
| | | |
| | | @Override |
| | | public void run(String... args) throws Exception { |
| | | if (sipIP.equals("localhost") || sipIP.equals("127.0.0.1")) { |
| | | logger.error("sip.ip不能使用 {} ,请使用类似192.168.1.44这样的来自网卡的IP!!!", sipIP ); |
| | | System.exit(1); |
| | | } |
| | | |
| | | if (mediaIp.equals("localhost") || mediaIp.equals("127.0.0.1")) { |
| | | logger.warn("mediaIp.ip使用 {} ,将无法收到网络内其他设备的推流!!!", mediaIp ); |
| | | } |
| | | |
| | | } |
| | | } |
| | |
| | | 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 org.slf4j.Logger;
|
| | | import org.slf4j.LoggerFactory;
|
| | | import org.springframework.beans.factory.annotation.Autowired;
|
| | |
| | |
|
| | | @Autowired
|
| | | private SIPProcessorFactory processorFactory;
|
| | |
|
| | | @Autowired
|
| | | private SipSubscribe sipSubscribe;
|
| | |
|
| | | private SipStack sipStack;
|
| | |
|
| | |
| | | // TODO Auto-generated catch block
|
| | | e.printStackTrace();
|
| | | }
|
| | | if (evt.getResponse() != null && sipSubscribe.getOkSubscribesSize() > 0 ) {
|
| | | CallIdHeader callIdHeader = (CallIdHeader)evt.getResponse().getHeader(CallIdHeader.NAME);
|
| | | if (callIdHeader != null) {
|
| | | SipSubscribe.Event subscribe = sipSubscribe.getOkSubscribe(callIdHeader.getCallId());
|
| | | if (subscribe != null) {
|
| | | subscribe.response(evt);
|
| | | }
|
| | | }
|
| | | }
|
| | | // } else if (status == Response.TRYING) {
|
| | | // trying不会回复
|
| | | } else if ((status >= 100) && (status < 200)) {
|
| | | // 增加其它无需回复的响应,如101、180等
|
| | | } else {
|
| | | logger.warn("接收到失败的response响应!status:" + status + ",message:" + response.getReasonPhrase()/* .getContent().toString()*/);
|
| | | if (evt.getResponse() != null && sipSubscribe.getErrorSubscribesSize() > 0 ) {
|
| | | CallIdHeader callIdHeader = (CallIdHeader)evt.getResponse().getHeader(CallIdHeader.NAME);
|
| | | if (callIdHeader != null) {
|
| | | SipSubscribe.Event subscribe = sipSubscribe.getErrorSubscribe(callIdHeader.getCallId());
|
| | | if (subscribe != null) {
|
| | | subscribe.response(evt);
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | // trying不会回复
|
| | | // if (status == Response.TRYING) {
|
| | |
|
| | | // }
|
| | |
|
| | |
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | | |
| | | import java.security.MessageDigest; |
| | | import java.security.NoSuchAlgorithmException; |
| | | import java.text.DecimalFormat; |
| | | import java.util.Date; |
| | | import java.util.Random; |
| | | |
| | |
| | | .createWWWAuthenticateHeader(DEFAULT_SCHEME); |
| | | proxyAuthenticate.setParameter("realm", realm); |
| | | proxyAuthenticate.setParameter("nonce", generateNonce()); |
| | | |
| | | proxyAuthenticate.setParameter("opaque", ""); |
| | | proxyAuthenticate.setParameter("stale", "FALSE"); |
| | | proxyAuthenticate.setParameter("algorithm", DEFAULT_ALGORITHM); |
| | | |
| | | // proxyAuthenticate.setParameter("qop", "auth"); |
| | | response.setHeader(proxyAuthenticate); |
| | | } catch (Exception ex) { |
| | | InternalErrorHandler.handleException(ex); |
| | |
| | | public boolean doAuthenticatePlainTextPassword(Request request, String pass) { |
| | | AuthorizationHeader authHeader = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); |
| | | if ( authHeader == null ) return false; |
| | | String realm = authHeader.getRealm(); |
| | | String username = authHeader.getUsername(); |
| | | |
| | | |
| | | String realm = authHeader.getRealm().trim(); |
| | | String username = authHeader.getUsername().trim(); |
| | | |
| | | if ( username == null || realm == null ) { |
| | | return false; |
| | | } |
| | | |
| | | |
| | | String nonce = authHeader.getNonce(); |
| | | URI uri = authHeader.getURI(); |
| | | if (uri == null) { |
| | | return false; |
| | | } |
| | | |
| | | // qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略 |
| | | String qop = authHeader.getQop(); |
| | | |
| | | // 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。 |
| | | // 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护 |
| | | String cNonce = authHeader.getCNonce(); |
| | | |
| | | // nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量 |
| | | int nc = authHeader.getNonceCount(); |
| | | String ncStr = new DecimalFormat("00000000").format(nc); |
| | | // String ncStr = new DecimalFormat("00000000").format(Integer.parseInt(nc + "", 16)); |
| | | |
| | | String A1 = username + ":" + realm + ":" + pass; |
| | | String A2 = request.getMethod().toUpperCase() + ":" + uri.toString(); |
| | | byte mdbytes[] = messageDigest.digest(A1.getBytes()); |
| | | String HA1 = toHexString(mdbytes); |
| | | System.out.println("A1: " + A1); |
| | | System.out.println("A2: " + A2); |
| | | |
| | | |
| | | mdbytes = messageDigest.digest(A2.getBytes()); |
| | | String HA2 = toHexString(mdbytes); |
| | | |
| | | System.out.println("HA1: " + HA1); |
| | | System.out.println("HA2: " + HA2); |
| | | String cnonce = authHeader.getCNonce(); |
| | | System.out.println("nonce: " + nonce); |
| | | System.out.println("nc: " + ncStr); |
| | | System.out.println("cnonce: " + cnonce); |
| | | System.out.println("qop: " + qop); |
| | | String KD = HA1 + ":" + nonce; |
| | | if (cnonce != null) { |
| | | KD += ":" + cnonce; |
| | | |
| | | if (qop != null && qop.equals("auth") ) { |
| | | if (nc != -1) { |
| | | KD += ":" + ncStr; |
| | | } |
| | | if (cnonce != null) { |
| | | KD += ":" + cnonce; |
| | | } |
| | | KD += ":" + qop; |
| | | } |
| | | KD += ":" + HA2; |
| | | System.out.println("KD: " + KD); |
| | | mdbytes = messageDigest.digest(KD.getBytes()); |
| | | String mdString = toHexString(mdbytes); |
| | | System.out.println("mdString: " + mdString); |
| | | String response = authHeader.getResponse(); |
| | | System.out.println("response: " + response); |
| | | return mdString.equals(response); |
| | | |
| | | } |
| | | |
| | | |
| | | public static void main(String[] args) throws NoSuchAlgorithmException { |
| | | MessageDigest messageDigest2 = MessageDigest.getInstance(DEFAULT_ALGORITHM); |
| | | String realm = "DS-2CD2520F"; |
| | | String username = "admin"; |
| | | String passwd = "12345"; |
| | | |
| | | String nonce = "4d6a553452444d30525441364e6d4d304e6a68684e47553d"; |
| | | |
| | | String uri = "/ISAPI/Streaming/channels/101/picture"; |
| | | // qop 保护质量 包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略 |
| | | String qop = "auth"; |
| | | |
| | | // 客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。 |
| | | // 这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护 |
| | | String cNonce = "C1A5298F939E87E8F962A5EDFC206918"; |
| | | |
| | | // nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量 |
| | | int nc = 1; |
| | | |
| | | String A1 = username + ":" + realm + ":" + passwd; |
| | | System.out.println("A1: " + A1); |
| | | String A2 = "GET" + ":" + uri.toString(); |
| | | System.out.println("A2: " + A2); |
| | | byte mdbytes[] = messageDigest2.digest(A1.getBytes()); |
| | | String HA1 = toHexString(mdbytes); |
| | | System.out.println("HA1: " + HA1); |
| | | |
| | | mdbytes = messageDigest2.digest(A2.getBytes()); |
| | | String HA2 = toHexString(mdbytes); |
| | | System.out.println("HA2: " + HA2); |
| | | String cnonce = "93d4d37df32e1a85"; |
| | | String KD = HA1 + ":" + nonce; |
| | | |
| | | if (nc != -1) { |
| | | KD += ":" + "00000001"; |
| | | } |
| | | if (cnonce != null) { |
| | | KD += ":" + cnonce; |
| | | } |
| | | if (qop != null) { |
| | | KD += ":" + qop; |
| | | } |
| | | KD += ":" + HA2; |
| | | System.out.println("KD: " + KD); |
| | | mdbytes = messageDigest2.digest(KD.getBytes()); |
| | | String mdString = toHexString(mdbytes); |
| | | String response = "3993a815e5cdaf4470e9b4f9bd41cf4a"; |
| | | System.out.println(mdString); |
| | | } |
| | | } |
| | |
| | | // TODO 后续处理,只有第一次注册时调用查询设备信息,如需更新调用更新API接口
|
| | | cmder.deviceInfoQuery(device);
|
| | |
|
| | | cmder.catalogQuery(device);
|
| | | cmder.catalogQuery(device, null);
|
| | | }
|
| | | }
|
| | |
| | | package com.genersoft.iot.vmp.gb28181.bean; |
| | | |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | public class Device { |
| | | |
| | | /** |
| | |
| | | private String streamMode; |
| | | |
| | | /** |
| | | * wan地址_ip |
| | | */ |
| | | private String ip; |
| | | |
| | | /** |
| | | * wan地址_port |
| | | */ |
| | | private int port; |
| | | |
| | | /** |
| | | * wan地址 |
| | | */ |
| | | private Host host; |
| | | private String hostAddress; |
| | | |
| | | /** |
| | | * 在线 |
| | | */ |
| | | private int online; |
| | | |
| | | |
| | | /** |
| | | * 通道列表 |
| | | * 注册时间 |
| | | */ |
| | | // private Map<String,DeviceChannel> channelMap; |
| | | private Long registerTimeMillis; |
| | | |
| | | /** |
| | | * 通道个数 |
| | | */ |
| | | private int channelCount; |
| | | |
| | | private List<String> channelList; |
| | | |
| | | public String getDeviceId() { |
| | | return deviceId; |
| | |
| | | this.streamMode = streamMode; |
| | | } |
| | | |
| | | public Host getHost() { |
| | | return host; |
| | | public String getIp() { |
| | | return ip; |
| | | } |
| | | |
| | | public void setHost(Host host) { |
| | | this.host = host; |
| | | public void setIp(String ip) { |
| | | this.ip = ip; |
| | | } |
| | | |
| | | public int getPort() { |
| | | return port; |
| | | } |
| | | |
| | | public void setPort(int port) { |
| | | this.port = port; |
| | | } |
| | | |
| | | public String getHostAddress() { |
| | | return hostAddress; |
| | | } |
| | | |
| | | public void setHostAddress(String hostAddress) { |
| | | this.hostAddress = hostAddress; |
| | | } |
| | | |
| | | public int getOnline() { |
| | |
| | | this.channelCount = channelCount; |
| | | } |
| | | |
| | | public List<String> getChannelList() { |
| | | return channelList; |
| | | public Long getRegisterTimeMillis() { |
| | | return registerTimeMillis; |
| | | } |
| | | |
| | | public void setChannelList(List<String> channelList) { |
| | | this.channelList = channelList; |
| | | public void setRegisterTimeMillis(Long registerTimeMillis) { |
| | | this.registerTimeMillis = registerTimeMillis; |
| | | } |
| | | } |
| | |
| | | |
| | | public class DeviceChannel { |
| | | |
| | | |
| | | |
| | | /** |
| | | * 通道id |
| | | */ |
| | | private String channelId; |
| | | |
| | | /** |
| | | * 设备id |
| | | */ |
| | | private String deviceId; |
| | | |
| | | /** |
| | | * 通道名 |
| | |
| | | /** |
| | | * 流唯一编号,存在表示正在直播 |
| | | */ |
| | | private String ssrc; |
| | | private String streamId; |
| | | |
| | | /** |
| | | * 是否含有音频 |
| | | */ |
| | | private boolean hasAudio; |
| | | private boolean hasAudio; |
| | | |
| | | /** |
| | | * 是否正在播放 |
| | | */ |
| | | private boolean play; |
| | | public String getDeviceId() { |
| | | return deviceId; |
| | | } |
| | | |
| | | public void setDeviceId(String deviceId) { |
| | | this.deviceId = deviceId; |
| | | } |
| | | |
| | | public void setPTZType(int PTZType) { |
| | | this.PTZType = PTZType; |
| | |
| | | this.subCount = subCount; |
| | | } |
| | | |
| | | public String getSsrc() { |
| | | return ssrc; |
| | | } |
| | | |
| | | public void setSsrc(String ssrc) { |
| | | this.ssrc = ssrc; |
| | | } |
| | | |
| | | public boolean isHasAudio() { |
| | | return hasAudio; |
| | | } |
| | |
| | | this.hasAudio = hasAudio; |
| | | } |
| | | |
| | | public boolean isPlay() { |
| | | return play; |
| | | public String getStreamId() { |
| | | return streamId; |
| | | } |
| | | |
| | | public void setPlay(boolean play) { |
| | | this.play = play; |
| | | public void setStreamId(String streamId) { |
| | | this.streamId = streamId; |
| | | } |
| | | } |
New file |
| | |
| | | package com.genersoft.iot.vmp.gb28181.event; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.sip.ResponseEvent; |
| | | import javax.sip.message.Request; |
| | | import java.util.EventObject; |
| | | import java.util.Map; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | @Component |
| | | public class SipSubscribe { |
| | | |
| | | private final static Logger logger = LoggerFactory.getLogger(SipSubscribe.class); |
| | | |
| | | private Map<String, SipSubscribe.Event> errorSubscribes = new ConcurrentHashMap<>(); |
| | | |
| | | private Map<String, SipSubscribe.Event> okSubscribes = new ConcurrentHashMap<>(); |
| | | |
| | | public interface Event { |
| | | void response(ResponseEvent event); |
| | | } |
| | | |
| | | public void addErrorSubscribe(String key, SipSubscribe.Event event) { |
| | | errorSubscribes.put(key, event); |
| | | } |
| | | |
| | | public void addOkSubscribe(String key, SipSubscribe.Event event) { |
| | | okSubscribes.put(key, event); |
| | | } |
| | | |
| | | public SipSubscribe.Event getErrorSubscribe(String key) { |
| | | return errorSubscribes.get(key); |
| | | } |
| | | |
| | | public SipSubscribe.Event getOkSubscribe(String key) { |
| | | return okSubscribes.get(key); |
| | | } |
| | | |
| | | public int getErrorSubscribesSize(){ |
| | | return errorSubscribes.size(); |
| | | } |
| | | public int getOkSubscribesSize(){ |
| | | return okSubscribes.size(); |
| | | } |
| | | } |
| | |
| | | import javax.sip.message.Request;
|
| | | import javax.sip.message.Response;
|
| | |
|
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
| | | import com.alibaba.fastjson.JSON;
|
| | | import com.genersoft.iot.vmp.gb28181.transmit.response.impl.*;
|
| | | import com.genersoft.iot.vmp.gb28181.transmit.response.impl.*;
|
| | | import org.slf4j.Logger;
|
| | | import org.slf4j.LoggerFactory;
|
| | |
| | |
|
| | | @Autowired
|
| | | private IVideoManagerStorager storager;
|
| | | |
| | |
|
| | | @Autowired
|
| | | private IRedisCatchStorage redisCatchStorage;
|
| | |
|
| | | @Autowired
|
| | | private EventPublisher publisher;
|
| | |
|
| | |
| | | @Autowired
|
| | | @Lazy
|
| | | private RegisterResponseProcessor registerResponseProcessor;
|
| | | |
| | |
|
| | | @Autowired
|
| | | private OtherResponseProcessor otherResponseProcessor;
|
| | | |
| | |
|
| | |
|
| | | // 注:这里使用注解会导致循环依赖注入,暂用springBean
|
| | | private SipProvider tcpSipProvider;
|
| | |
|
| | |
| | | processor.setOffLineDetector(offLineDetector);
|
| | | processor.setCmder(cmder);
|
| | | processor.setStorager(storager);
|
| | | processor.setRedisCatchStorage(redisCatchStorage);
|
| | | return processor;
|
| | | } else {
|
| | | return new OtherRequestProcessor();
|
| | |
| | | }
|
| | |
|
| | | public ISIPResponseProcessor createResponseProcessor(ResponseEvent evt) {
|
| | |
|
| | | Response response = evt.getResponse();
|
| | | CSeqHeader cseqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
|
| | | String method = cseqHeader.getMethod();
|
| | |
| | |
|
| | | import java.util.HashMap;
|
| | | import java.util.Map;
|
| | | import java.util.concurrent.ConcurrentHashMap;
|
| | |
|
| | | import org.springframework.http.HttpStatus;
|
| | | import org.springframework.http.ResponseEntity;
|
| | |
| | |
|
| | | public static final String CALLBACK_CMD_PlAY = "CALLBACK_PLAY";
|
| | |
|
| | | private Map<String, DeferredResult> map = new HashMap<String, DeferredResult>();
|
| | | |
| | | public static final String CALLBACK_CMD_STOP = "CALLBACK_STOP";
|
| | |
|
| | | private Map<String, DeferredResult> map = new ConcurrentHashMap<String, DeferredResult>();
|
| | |
|
| | | public void put(String key, DeferredResult result) {
|
| | | map.put(key, result);
|
| | | }
|
| | |
| | |
|
| | | import com.genersoft.iot.vmp.common.StreamInfo;
|
| | | import com.genersoft.iot.vmp.gb28181.bean.Device;
|
| | | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
| | | import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
|
| | | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
| | |
|
| | | /**
|
| | | * @Description:设备能力接口,用于定义设备的控制、查询能力
|
| | |
| | | * @param device 视频设备
|
| | | * @param channelId 预览通道
|
| | | */
|
| | | void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event);
|
| | | void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
|
| | |
|
| | | /**
|
| | | * 请求回放视频流
|
| | |
| | | * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
|
| | | * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
|
| | | */
|
| | | void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event);
|
| | | void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
|
| | |
|
| | | /**
|
| | | * 视频流停止
|
| | | *
|
| | | * @param ssrc ssrc
|
| | | */
|
| | | void streamByeCmd(String ssrc, SipSubscribe.Event okEvent);
|
| | | void streamByeCmd(String ssrc);
|
| | | |
| | |
|
| | | /**
|
| | | * 语音广播
|
| | | *
|
| | |
| | | *
|
| | | * @param device 视频设备
|
| | | */
|
| | | boolean catalogQuery(Device device);
|
| | | boolean catalogQuery(Device device, SipSubscribe.Event errorEvent);
|
| | |
|
| | | /**
|
| | | * 查询录像信息
|
| | |
| | | * @param device 视频设备
|
| | | */
|
| | | boolean mobilePostitionQuery(Device device);
|
| | |
|
| | | /**
|
| | | * 释放rtpserver
|
| | | * @param device
|
| | | * @param channelId
|
| | | */
|
| | | void closeRTPServer(Device device, String channelId);
|
| | | }
|
| | |
| | |
|
| | | public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException, PeerUnavailableException {
|
| | | Request request = null;
|
| | | Host host = device.getHost();
|
| | | // sipuri
|
| | | SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
|
| | | SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
|
| | | // via
|
| | | ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
| | | ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(),
|
| | |
| | |
|
| | | request = sipFactory.createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
|
| | | toHeader, viaHeaders, maxForwards);
|
| | | ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
|
| | | ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "MANSCDP+xml");
|
| | | request.setContent(content, contentTypeHeader);
|
| | | return request;
|
| | | }
|
| | |
|
| | | public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException {
|
| | | Request request = null;
|
| | | Host host = device.getHost();
|
| | | //请求行
|
| | | SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, host.getAddress());
|
| | | SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
|
| | | //via
|
| | | ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
| | | // ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), device.getTransport(), viaTag);
|
| | | ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getHost().getIp(), device.getHost().getPort(), device.getTransport(), viaTag);
|
| | | ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getIp(), device.getPort(), device.getTransport(), viaTag);
|
| | | viaHeader.setRPort();
|
| | | viaHeaders.add(viaHeader);
|
| | |
|
| | | //from
|
| | | SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getSipId(),sipConfig.getSipDomain());
|
| | | Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI);
|
| | |
| | | // Subject
|
| | | SubjectHeader subjectHeader = sipFactory.createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getSipId(), 0));
|
| | | request.addHeader(subjectHeader);
|
| | | ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "SDP");
|
| | | ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
|
| | | request.setContent(content, contentTypeHeader);
|
| | | return request;
|
| | | }
|
| | |
|
| | | public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag) throws ParseException, InvalidArgumentException, PeerUnavailableException {
|
| | | Request request = null;
|
| | | Host host = device.getHost();
|
| | | //请求行
|
| | | SipURI requestLine = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), host.getAddress());
|
| | | //via
|
| | | SipURI requestLine = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
|
| | | // via
|
| | | ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
| | | // ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), device.getTransport(), viaTag);
|
| | | ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getHost().getIp(), device.getHost().getPort(), device.getTransport(), viaTag);
|
| | | ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getIp(), device.getPort(), device.getTransport(), viaTag);
|
| | | viaHeader.setRPort();
|
| | | viaHeaders.add(viaHeader);
|
| | | //from
|
| | |
| | | // Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getSipId(), device.getHost().getIp()+":"+device.getHost().getPort()));
|
| | | request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
|
| | |
|
| | | ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "SDP");
|
| | | ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
|
| | | request.setContent(content, contentTypeHeader);
|
| | | return request;
|
| | | }
|
| | |
| | | package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
|
| | |
|
| | | import java.text.ParseException;
|
| | | import java.util.UUID;
|
| | | import java.util.regex.Matcher;
|
| | | import java.util.regex.Pattern;
|
| | |
|
| | | import javax.sip.ClientTransaction;
|
| | | import javax.sip.Dialog;
|
| | | import javax.sip.InvalidArgumentException;
|
| | | import javax.sip.SipException;
|
| | | import javax.sip.SipFactory;
|
| | | import javax.sip.SipProvider;
|
| | | import javax.sip.TransactionDoesNotExistException;
|
| | | import javax.sip.*;
|
| | | import javax.sip.address.SipURI;
|
| | | import javax.sip.header.CallIdHeader;
|
| | | import javax.sip.header.Header;
|
| | | import javax.sip.header.ViaHeader;
|
| | | import javax.sip.message.Request;
|
| | |
|
| | |
| | | import com.genersoft.iot.vmp.common.StreamInfo;
|
| | | import com.genersoft.iot.vmp.conf.MediaServerConfig;
|
| | | import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
| | | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
| | | import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
|
| | | import com.genersoft.iot.vmp.media.zlm.ZLMUtils;
|
| | | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
|
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
| | | 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.beans.factory.annotation.Qualifier;
|
| | | import org.springframework.beans.factory.annotation.Value;
|
| | |
| | | */
|
| | | @Component
|
| | | public class SIPCommander implements ISIPCommander {
|
| | |
|
| | | private final Logger logger = LoggerFactory.getLogger(SIPCommander.class);
|
| | |
|
| | | @Autowired
|
| | | private SipConfig sipConfig;
|
| | |
| | |
|
| | | @Autowired
|
| | | private IVideoManagerStorager storager;
|
| | |
|
| | | @Autowired
|
| | | private IRedisCatchStorage redisCatchStorage;
|
| | |
|
| | | @Autowired
|
| | | @Qualifier(value="tcpSipProvider")
|
| | |
| | | private SipProvider udpSipProvider;
|
| | |
|
| | | @Autowired
|
| | | private ZLMUtils zlmUtils;
|
| | | private ZLMRTPServerFactory zlmrtpServerFactory;
|
| | |
|
| | | @Value("${media.rtp.enable}")
|
| | | private boolean rtpEnable;
|
| | |
|
| | | @Value("${media.seniorSdp}")
|
| | | private boolean seniorSdp;
|
| | |
|
| | | @Autowired
|
| | | private ZLMHttpHookSubscribe subscribe;
|
| | |
|
| | | @Autowired
|
| | | private SipSubscribe sipSubscribe;
|
| | |
|
| | |
|
| | |
|
| | |
| | | * @param moveSpeed 镜头移动速度 默认 0XFF (0-255)
|
| | | * @param zoomSpeed 镜头缩放速度 默认 0X1 (0-255)
|
| | | */
|
| | | public static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) {
|
| | |
|
| | | /**
|
| | | * 云台指令码计算
|
| | | *
|
| | | * @param cmdCode 指令码
|
| | | * @param horizonSpeed 水平移动速度
|
| | | * @param verticalSpeed 垂直移动速度
|
| | | * @param zoomSpeed 缩放速度
|
| | | * @return
|
| | | */
|
| | | public static String frontEndCmdString(int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed) {
|
| | | StringBuilder builder = new StringBuilder("A50F01");
|
| | | String strTmp;
|
| | | strTmp = String.format("%02X", cmdCode);
|
| | | builder.append(strTmp, 0, 2);
|
| | | strTmp = String.format("%02X", parameter1);
|
| | | strTmp = String.format("%02X", horizonSpeed);
|
| | | builder.append(strTmp, 0, 2);
|
| | | strTmp = String.format("%02X", parameter2);
|
| | | strTmp = String.format("%02X", verticalSpeed);
|
| | | builder.append(strTmp, 0, 2);
|
| | | strTmp = String.format("%X", combineCode2);
|
| | | strTmp = String.format("%X", zoomSpeed);
|
| | | builder.append(strTmp, 0, 1).append("0");
|
| | | //计算校验码
|
| | | int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + parameter1 + parameter2 + (combineCode2 & 0XF0)) % 0X100;
|
| | | int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + horizonSpeed + verticalSpeed + (zoomSpeed & 0XF0)) % 0X100;
|
| | | strTmp = String.format("%02X", checkCode);
|
| | | builder.append(strTmp, 0, 2);
|
| | | return builder.toString();
|
| | |
| | | * @param device 控制设备
|
| | | * @param channelId 预览通道
|
| | | * @param cmdCode 指令码
|
| | | * @param parameter1 数据1
|
| | | * @param parameter2 数据2
|
| | | * @param combineCode2 组合码2
|
| | | * @param horizonSpeed 水平移动速度
|
| | | * @param verticalSpeed 垂直移动速度
|
| | | * @param zoomSpeed 缩放速度
|
| | | */
|
| | | @Override
|
| | | public boolean frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) {
|
| | | public boolean frontEndCmd(Device device, String channelId, int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed) {
|
| | | try {
|
| | | String cmdStr= frontEndCmdString(cmdCode, parameter1, parameter2, combineCode2);
|
| | | String cmdStr= frontEndCmdString(cmdCode, horizonSpeed, verticalSpeed, zoomSpeed);
|
| | | System.out.println("控制字符串:" + cmdStr);
|
| | | StringBuffer ptzXml = new StringBuffer(200);
|
| | | ptzXml.append("<?xml version=\"1.0\" ?>\r\n");
|
| | |
| | | ptzXml.append("</Control>\r\n");
|
| | |
|
| | | Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtzTag", "ToPtzTag");
|
| | | |
| | | transmitRequest(device, request);
|
| | | return true;
|
| | | } catch (SipException | ParseException | InvalidArgumentException e) {
|
| | |
| | | }
|
| | | return false;
|
| | | }
|
| | |
|
| | | /**
|
| | | * 请求预览视频流
|
| | | *
|
| | | * 请求预览视频流
|
| | | * @param device 视频设备
|
| | | * @param channelId 预览通道
|
| | | * @param event hook订阅
|
| | | * @param errorEvent sip错误订阅
|
| | | */
|
| | | @Override
|
| | | public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event) {
|
| | | public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
|
| | | try {
|
| | |
|
| | | String ssrc = streamSession.createPlaySsrc();
|
| | | String streamId = null;
|
| | | 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();
|
| | | MediaServerConfig mediaInfo = storager.getMediaInfo();
|
| | | MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
|
| | | if (mediaInfo == null) {
|
| | | logger.warn("点播时发现ZLM尚未连接...");
|
| | | return;
|
| | | }
|
| | | String mediaPort = null;
|
| | | // 使用动态udp端口
|
| | | if (rtpEnable) {
|
| | | mediaPort = zlmUtils.getNewRTPPort(ssrc) + "";
|
| | | mediaPort = zlmrtpServerFactory.createRTPServer(streamId) + "";
|
| | | }else {
|
| | | mediaPort = mediaInfo.getRtpProxyPort();
|
| | | }
|
| | |
|
| | | String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
|
| | | // 添加订阅
|
| | | JSONObject subscribeKey = new JSONObject();
|
| | | subscribeKey.put("app", "rtp");
|
| | |
| | | //
|
| | | StringBuffer content = new StringBuffer(200);
|
| | | content.append("v=0\r\n");
|
| | | content.append("o="+channelId+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
| | | // content.append("o="+channelId+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
| | | content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
| | | content.append("s=Play\r\n");
|
| | | content.append("c=IN IP4 "+mediaInfo.getWanIp()+"\r\n");
|
| | | content.append("t=0 0\r\n");
|
| | |
| | | }
|
| | | content.append("y="+ssrc+"\r\n");//ssrc
|
| | |
|
| | | // String fromTag = UUID.randomUUID().toString();
|
| | | // Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, fromTag, null, ssrc);
|
| | |
|
| | | Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, "live", null, ssrc);
|
| | |
|
| | | ClientTransaction transaction = transmitRequest(device, request);
|
| | | streamSession.put(ssrc, transaction);
|
| | | DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
|
| | | if (deviceChannel != null) {
|
| | | deviceChannel.setSsrc(ssrc);
|
| | | storager.updateChannel(device.getDeviceId(), deviceChannel);
|
| | | }
|
| | | ClientTransaction transaction = transmitRequest(device, request, errorEvent);
|
| | | streamSession.put(streamId, transaction);
|
| | |
|
| | | // TODO 订阅SIP response,处理对方的错误返回
|
| | |
|
| | |
|
| | | } catch ( SipException | ParseException | InvalidArgumentException e) {
|
| | |
| | | * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
|
| | | */
|
| | | @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) {
|
| | | try {
|
| | | MediaServerConfig mediaInfo = storager.getMediaInfo();
|
| | | MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
|
| | | String ssrc = streamSession.createPlayBackSsrc();
|
| | | String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
|
| | | // 添加订阅
|
| | |
| | | String mediaPort = null;
|
| | | // 使用动态udp端口
|
| | | if (rtpEnable) {
|
| | | mediaPort = zlmUtils.getNewRTPPort(ssrc) + "";
|
| | | mediaPort = zlmrtpServerFactory.createRTPServer(streamId) + "";
|
| | | }else {
|
| | | mediaPort = mediaInfo.getRtpProxyPort();
|
| | | }
|
| | | String streamMode = device.getStreamMode().toUpperCase();
|
| | | if("TCP-PASSIVE".equals(streamMode)) {
|
| | | content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
|
| | | }else if ("TCP-ACTIVE".equals(streamMode)) {
|
| | | content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
|
| | | }else if("UDP".equals(streamMode)) {
|
| | | content.append("m=video "+ mediaPort +" RTP/AVP 126 125 99 34 98 97 96\r\n");
|
| | |
|
| | | if (seniorSdp) {
|
| | | if("TCP-PASSIVE".equals(streamMode)) {
|
| | | content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
|
| | | }else if ("TCP-ACTIVE".equals(streamMode)) {
|
| | | content.append("m=video "+ mediaPort +" TCP/RTP/AVP 126 125 99 34 98 97 96\r\n");
|
| | | }else if("UDP".equals(streamMode)) {
|
| | | content.append("m=video "+ mediaPort +" RTP/AVP 126 125 99 34 98 97 96\r\n");
|
| | | }
|
| | | content.append("a=recvonly\r\n");
|
| | | content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
|
| | | content.append("a=rtpmap:126 H264/90000\r\n");
|
| | | content.append("a=rtpmap:125 H264S/90000\r\n");
|
| | | content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
|
| | | content.append("a=rtpmap:99 MP4V-ES/90000\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:97 MPEG4/90000\r\n");
|
| | | content.append("a=rtpmap:96 PS/90000\r\n");
|
| | | if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
|
| | | content.append("a=setup:passive\r\n");
|
| | | content.append("a=connection:new\r\n");
|
| | | }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
|
| | | content.append("a=setup:active\r\n");
|
| | | content.append("a=connection:new\r\n");
|
| | | }
|
| | | }else {
|
| | | if("TCP-PASSIVE".equals(streamMode)) {
|
| | | content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
|
| | | }else if ("TCP-ACTIVE".equals(streamMode)) {
|
| | | content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n");
|
| | | }else if("UDP".equals(streamMode)) {
|
| | | content.append("m=video "+ mediaPort +" RTP/AVP 96 98 97\r\n");
|
| | | }
|
| | | content.append("a=recvonly\r\n");
|
| | | content.append("a=rtpmap:96 PS/90000\r\n");
|
| | | content.append("a=rtpmap:98 H264/90000\r\n");
|
| | | content.append("a=rtpmap:97 MPEG4/90000\r\n");
|
| | | if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
|
| | | content.append("a=setup:passive\r\n");
|
| | | content.append("a=connection:new\r\n");
|
| | | }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
|
| | | content.append("a=setup:active\r\n");
|
| | | content.append("a=connection:new\r\n");
|
| | | }
|
| | | }
|
| | | content.append("a=recvonly\r\n");
|
| | | content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
|
| | | content.append("a=rtpmap:126 H264/90000\r\n");
|
| | | content.append("a=rtpmap:125 H264S/90000\r\n");
|
| | | content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
|
| | | content.append("a=rtpmap:99 MP4V-ES/90000\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:97 MPEG4/90000\r\n");
|
| | | content.append("a=rtpmap:96 PS/90000\r\n");
|
| | | if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式
|
| | | content.append("a=setup:passive\r\n");
|
| | | content.append("a=connection:new\r\n");
|
| | | }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
|
| | | content.append("a=setup:active\r\n");
|
| | | content.append("a=connection:new\r\n");
|
| | | }
|
| | |
|
| | | content.append("y="+ssrc+"\r\n");//ssrc
|
| | |
|
| | | Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "playback", null);
|
| | | |
| | | ClientTransaction transaction = transmitRequest(device, request);
|
| | | streamSession.put(ssrc, transaction);
|
| | |
|
| | | ClientTransaction transaction = transmitRequest(device, request, errorEvent);
|
| | | streamSession.put(streamId, transaction);
|
| | |
|
| | | } catch ( SipException | ParseException | InvalidArgumentException e) {
|
| | | e.printStackTrace();
|
| | | }
|
| | | }
|
| | | |
| | |
|
| | |
|
| | |
|
| | | /**
|
| | | * 视频流停止
|
| | | *
|
| | | */
|
| | | @Override
|
| | | public void streamByeCmd(String ssrc) {
|
| | | streamByeCmd(ssrc, null);
|
| | | }
|
| | | @Override
|
| | | public void streamByeCmd(String streamId, SipSubscribe.Event okEvent) {
|
| | |
|
| | | try {
|
| | | ClientTransaction transaction = streamSession.get(ssrc);
|
| | | ClientTransaction transaction = streamSession.get(streamId);
|
| | | // 服务重启后
|
| | | if (transaction == null) {
|
| | | StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
|
| | | if (streamInfo != null) {
|
| | |
|
| | | }
|
| | | return;
|
| | | }
|
| | |
|
| | |
| | | if (dialog == null) {
|
| | | return;
|
| | | }
|
| | |
|
| | |
|
| | |
|
| | | Request byeRequest = dialog.createRequest(Request.BYE);
|
| | | SipURI byeURI = (SipURI) byeRequest.getRequestURI();
|
| | | String vh = transaction.getRequest().getHeader(ViaHeader.NAME).toString();
|
| | |
| | | } else if("UDP".equals(protocol)) {
|
| | | clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
|
| | | }
|
| | |
|
| | | CallIdHeader callIdHeader = (CallIdHeader) byeRequest.getHeader(CallIdHeader.NAME);
|
| | | if (okEvent != null) {
|
| | | sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
|
| | | }
|
| | |
|
| | | dialog.sendRequest(clientTransaction);
|
| | | streamSession.remove(ssrc);
|
| | |
|
| | | streamSession.remove(streamId);
|
| | | zlmrtpServerFactory.closeRTPServer(streamId);
|
| | | } catch (TransactionDoesNotExistException e) {
|
| | | e.printStackTrace();
|
| | | } catch (SipException e) {
|
| | |
| | | catalogXml.append("</Query>\r\n");
|
| | |
|
| | | Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaDeviceInfoBranch", "FromDeviceInfoTag", "ToDeviceInfoTag");
|
| | |
|
| | | transmitRequest(device, request);
|
| | |
|
| | | } catch (SipException | ParseException | InvalidArgumentException e) {
|
| | |
| | | * @param device 视频设备
|
| | | */
|
| | | @Override
|
| | | public boolean catalogQuery(Device device) {
|
| | | public boolean catalogQuery(Device device, SipSubscribe.Event errorEvent) {
|
| | | // 清空通道
|
| | | storager.cleanChannelsForDevice(device.getDeviceId());
|
| | | try {
|
| | |
| | | catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
|
| | | catalogXml.append("</Query>\r\n");
|
| | |
|
| | | Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCatalogTag", "ToCatalogTag");
|
| | | transmitRequest(device, request);
|
| | | Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaCatalogBranch", "FromCatalogTag", null);
|
| | |
|
| | | transmitRequest(device, request, errorEvent);
|
| | | } catch (SipException | ParseException | InvalidArgumentException e) {
|
| | | e.printStackTrace();
|
| | | return false;
|
| | |
| | | recordInfoXml.append("<Type>all</Type>\r\n");
|
| | | recordInfoXml.append("</Query>\r\n");
|
| | |
|
| | | Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", "ToRecordInfoTag");
|
| | | Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "ViaRecordInfoBranch", "FromRecordInfoTag", null);
|
| | |
|
| | | transmitRequest(device, request);
|
| | | } catch (SipException | ParseException | InvalidArgumentException e) {
|
| | | e.printStackTrace();
|
| | |
| | | // TODO Auto-generated method stub
|
| | | return false;
|
| | | }
|
| | | |
| | |
|
| | | private ClientTransaction transmitRequest(Device device, Request request) throws SipException {
|
| | | return transmitRequest(device, request, null, null);
|
| | | }
|
| | |
|
| | | private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent) throws SipException {
|
| | | return transmitRequest(device, request, errorEvent, null);
|
| | | }
|
| | |
|
| | | private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException {
|
| | | ClientTransaction clientTransaction = null;
|
| | | if("TCP".equals(device.getTransport())) {
|
| | | clientTransaction = tcpSipProvider.getNewClientTransaction(request);
|
| | | } else if("UDP".equals(device.getTransport())) {
|
| | | clientTransaction = udpSipProvider.getNewClientTransaction(request);
|
| | | }
|
| | |
|
| | | CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
|
| | | // 添加错误订阅
|
| | | if (errorEvent != null) {
|
| | | sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), errorEvent);
|
| | | }
|
| | | // 添加订阅
|
| | | if (okEvent != null) {
|
| | | sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
|
| | | }
|
| | |
|
| | | clientTransaction.sendRequest();
|
| | | return clientTransaction;
|
| | | }
|
| | |
|
| | |
|
| | |
|
| | |
|
| | | @Override
|
| | | public void closeRTPServer(Device device, String channelId) {
|
| | | if (rtpEnable) {
|
| | | String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
|
| | | zlmrtpServerFactory.closeRTPServer(streamId);
|
| | | }
|
| | | }
|
| | | }
|
| | |
| | | import javax.sip.message.Request;
|
| | | import javax.sip.message.Response;
|
| | |
|
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
| | | import org.dom4j.Document;
|
| | | import org.dom4j.DocumentException;
|
| | | import org.dom4j.Element;
|
| | |
| | | private SIPCommander cmder;
|
| | |
|
| | | private IVideoManagerStorager storager;
|
| | |
|
| | | private IRedisCatchStorage redisCatchStorage;
|
| | |
|
| | | private EventPublisher publisher;
|
| | |
|
| | |
| | | device.setStreamMode("UDP");
|
| | | }
|
| | | storager.updateDevice(device);
|
| | | cmder.catalogQuery(device);
|
| | | cmder.catalogQuery(device, null);
|
| | | // 回复200 OK
|
| | | responseAck(evt);
|
| | | if (offLineDetector.isOnline(deviceId)) {
|
| | |
| | | try {
|
| | | Element rootElement = getRootElement(evt);
|
| | | String deviceId = XmlUtil.getText(rootElement, "DeviceID");
|
| | | // 回复200 OK
|
| | | responseAck(evt);
|
| | | if (offLineDetector.isOnline(deviceId)) {
|
| | | publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
|
| | | } else {
|
| | | // 检查设备是否存在, 不存在则不回复
|
| | | if (storager.exists(deviceId)) {
|
| | | // 回复200 OK
|
| | | responseAck(evt);
|
| | | if (offLineDetector.isOnline(deviceId)) {
|
| | | publisher.onlineEventPublish(deviceId, VideoManagerConstants.EVENT_ONLINE_KEEPLIVE);
|
| | | } else {
|
| | | }
|
| | | }
|
| | |
|
| | | } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
|
| | | e.printStackTrace();
|
| | | }
|
| | |
| | | String NotifyType =XmlUtil.getText(rootElement, "NotifyType");
|
| | | if (NotifyType.equals("121")){
|
| | | logger.info("媒体播放完毕,通知关流");
|
| | | StreamInfo streamInfo = storager.queryPlaybackByDevice(deviceId, "*");
|
| | | StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, "*");
|
| | | if (streamInfo != null) {
|
| | | storager.stopPlayback(streamInfo);
|
| | | cmder.streamByeCmd(streamInfo.getSsrc());
|
| | | redisCatchStorage.stopPlayback(streamInfo);
|
| | | cmder.streamByeCmd(streamInfo.getStreamId());
|
| | | }
|
| | | }
|
| | | } catch (ParseException | SipException | InvalidArgumentException | DocumentException e) {
|
| | |
| | | this.offLineDetector = offLineDetector;
|
| | | }
|
| | |
|
| | | public IRedisCatchStorage getRedisCatchStorage() {
|
| | | return redisCatchStorage;
|
| | | }
|
| | |
|
| | | public void setRedisCatchStorage(IRedisCatchStorage redisCatchStorage) {
|
| | | this.redisCatchStorage = redisCatchStorage;
|
| | | }
|
| | | }
|
| | |
| | | rPort = viaHeader.getPort();
|
| | | }
|
| | | //
|
| | | Host host = new Host();
|
| | | host.setIp(received);
|
| | | host.setPort(rPort);
|
| | | host.setAddress(received.concat(":").concat(String.valueOf(rPort)));
|
| | | AddressImpl address = (AddressImpl) fromHeader.getAddress();
|
| | | SipUri uri = (SipUri) address.getURI();
|
| | | String deviceId = uri.getUser();
|
| | | device = new Device();
|
| | | device.setStreamMode("UDP");
|
| | | device.setDeviceId(deviceId);
|
| | | device.setHost(host);
|
| | | device.setIp(received);
|
| | | device.setPort(rPort);
|
| | | device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
|
| | | // 注销成功
|
| | | if (expiresHeader != null && expiresHeader.getExpires() == 0) {
|
| | | registerFlag = 2;
|
| | |
| | | // 下发catelog查询目录
|
| | | if (registerFlag == 1 && device != null) {
|
| | | logger.info("注册成功! deviceId:" + device.getDeviceId());
|
| | | boolean exists = storager.exists(device.getDeviceId());
|
| | | device.setRegisterTimeMillis(System.currentTimeMillis());
|
| | | storager.updateDevice(device);
|
| | | publisher.onlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_ONLINE_REGISTER);
|
| | | handler.onRegister(device);
|
| | |
|
| | | // 只有第一次注册才更新通道
|
| | | if (!exists) {
|
| | | handler.onRegister(device);
|
| | | }
|
| | | } else if (registerFlag == 2) {
|
| | | logger.info("注销成功! deviceId:" + device.getDeviceId());
|
| | | publisher.outlineEventPublish(device.getDeviceId(), VideoManagerConstants.EVENT_OUTLINE_UNREGISTER);
|
| | |
| | | import javax.sip.message.Request;
|
| | | import javax.sip.message.Response;
|
| | |
|
| | | import gov.nist.javax.sip.header.CSeq;
|
| | | import org.slf4j.Logger;
|
| | | import org.slf4j.LoggerFactory;
|
| | | import org.springframework.stereotype.Component;
|
| | |
| | |
|
| | |
|
| | | /**
|
| | | * @Description:处理INVITE响应 |
| | | * @Description:处理INVITE响应
|
| | | * @author: swwheihei
|
| | | * @date: 2020年5月3日 下午4:43:52 |
| | | * @date: 2020年5月3日 下午4:43:52
|
| | | */
|
| | | @Component
|
| | | public class InviteResponseProcessor implements ISIPResponseProcessor {
|
| | |
|
| | | private final static Logger logger = LoggerFactory.getLogger(SIPProcessorFactory.class);
|
| | | private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class);
|
| | |
|
| | | /**
|
| | | * 处理invite响应
|
| | |
| | | // 成功响应
|
| | | // 下发ack
|
| | | if (statusCode == Response.OK) {
|
| | | // ClientTransaction clientTransaction = evt.getClientTransaction();
|
| | | // if(clientTransaction == null){
|
| | | // logger.error("回复ACK时,clientTransaction为null >>> {}",response);
|
| | | // return;
|
| | | // }
|
| | | // Dialog clientDialog = clientTransaction.getDialog();
|
| | |
|
| | | // CSeqHeader clientCSeqHeader = (CSeqHeader)
|
| | | // response.getHeader(CSeqHeader.NAME);
|
| | | // long cseqId = clientCSeqHeader.getSeqNumber();
|
| | | // /*
|
| | | // createAck函数,创建的ackRequest,会采用Invite响应的200OK,中的contact字段中的地址,作为目标地址。
|
| | | // 有的终端传上来的可能还是内网地址,会造成ack发送不出去。接受不到音视频流
|
| | | // 所以在此处统一替换地址。和响应消息的Via头中的地址保持一致。
|
| | | // */
|
| | | // Request ackRequest = clientDialog.createAck(cseqId);
|
| | | // SipURI requestURI = (SipURI) ackRequest.getRequestURI();
|
| | | // ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME);
|
| | | // try {
|
| | | // requestURI.setHost(viaHeader.getHost());
|
| | | // } catch (Exception e) {
|
| | | // e.printStackTrace();
|
| | | // }
|
| | | // requestURI.setPort(viaHeader.getPort());
|
| | | // clientDialog.sendAck(ackRequest);
|
| | |
|
| | | Dialog dialog = evt.getDialog();
|
| | | CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
|
| | | Request reqAck = dialog.createAck(cseq.getSeqNumber());
|
| | |
|
| | | SipURI requestURI = (SipURI) reqAck.getRequestURI();
|
| | | ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME);
|
| | | // String viaHost =viaHeader.getHost();
|
| | | //getHost()函数取回的IP地址是“[xxx.xxx.xxx.xxx:yyyy]”的格式,需用正则表达式截取为“xxx.xxx.xxx.xxx"格式
|
| | | // Pattern p = Pattern.compile("(?<=//|)((\\w)+\\.)+\\w+");
|
| | | // Matcher matcher = p.matcher(viaHeader.getHost());
|
| | | // if (matcher.find()) {
|
| | | // requestURI.setHost(matcher.group());
|
| | | // }
|
| | | requestURI.setHost(viaHeader.getHost());
|
| | | requestURI.setPort(viaHeader.getPort());
|
| | | reqAck.setRequestURI(requestURI);
|
| | |
|
| | | dialog.sendAck(reqAck);
|
| | | }
|
| | | } catch (InvalidArgumentException | SipException e) {
|
| | |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.genersoft.iot.vmp.conf.MediaServerConfig; |
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| | | import com.genersoft.iot.vmp.storager.IVideoManagerStorager; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | |
| | | @Autowired |
| | | private IVideoManagerStorager storager; |
| | | |
| | | @Autowired |
| | | private IRedisCatchStorage redisCatchStorage; |
| | | |
| | | @Value("${media.port}") |
| | | private int mediaHttpPort; |
| | | |
| | |
| | | @RequestMapping(value = "/**/**/**", produces = "application/json;charset=UTF-8") |
| | | public Object proxy(HttpServletRequest request, HttpServletResponse response){ |
| | | |
| | | if (storager.getMediaInfo() == null) { |
| | | if (redisCatchStorage.getMediaInfo() == null) { |
| | | return "未接入流媒体"; |
| | | } |
| | | MediaServerConfig mediaInfo = storager.getMediaInfo(); |
| | | MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); |
| | | String requestURI = String.format("http://%s:%s%s?%s&%s", |
| | | mediaInfo.getLocalIP(), |
| | | mediaHttpPort, |
| | |
| | | import java.text.DecimalFormat;
|
| | | import java.util.ArrayList;
|
| | | import java.util.List;
|
| | | import java.util.UUID;
|
| | |
|
| | | import com.alibaba.fastjson.JSON;
|
| | | import com.alibaba.fastjson.JSONArray;
|
| | | import com.genersoft.iot.vmp.common.StreamInfo;
|
| | | import com.genersoft.iot.vmp.conf.MediaServerConfig;
|
| | | import com.genersoft.iot.vmp.gb28181.bean.Device;
|
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
| | | import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
|
| | | import com.genersoft.iot.vmp.utils.IpUtil;
|
| | | import com.genersoft.iot.vmp.vmanager.service.IPlayService;
|
| | | import org.slf4j.Logger;
|
| | | import org.slf4j.LoggerFactory;
|
| | | import org.springframework.beans.factory.annotation.Autowired;
|
| | |
| | | private SIPCommander cmder;
|
| | |
|
| | | @Autowired
|
| | | private IPlayService playService;
|
| | |
|
| | | @Autowired
|
| | | private IVideoManagerStorager storager;
|
| | |
|
| | | @Autowired
|
| | | private IRedisCatchStorage redisCatchStorage;
|
| | |
|
| | | @Autowired
|
| | | private ZLMRESTfulUtils zlmresTfulUtils;
|
| | |
|
| | | @Autowired
|
| | | private ZLMHttpHookSubscribe subscribe;
|
| | |
|
| | | @Value("${media.autoApplyPlay}")
|
| | | private boolean autoApplyPlay;
|
| | |
|
| | | @Value("${media.ip}")
|
| | | private String mediaIp;
|
| | |
| | | ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, json);
|
| | | if (subscribe != null) subscribe.response(json);
|
| | |
|
| | | // if ("rtp".equals(app)) {
|
| | | // String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16));
|
| | | // StreamInfo streamInfoForPlay = storager.queryPlayBySSRC(ssrc);
|
| | | // if ("rtp".equals(app) && streamInfoForPlay != null ) {
|
| | | // MediaServerConfig mediaInfo = storager.getMediaInfo();
|
| | | // streamInfoForPlay.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
|
| | | // streamInfoForPlay.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
|
| | | // streamInfoForPlay.setFmp4(String.format("http://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
|
| | | // streamInfoForPlay.setWs_fmp4(String.format("ws://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
|
| | | // streamInfoForPlay.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId));
|
| | | // streamInfoForPlay.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
|
| | | // streamInfoForPlay.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId));
|
| | | // storager.startPlay(streamInfoForPlay);
|
| | | // }
|
| | | //
|
| | | // StreamInfo streamInfoForPlayBack = storager.queryPlaybackBySSRC(ssrc);
|
| | | // if ("rtp".equals(app) && streamInfoForPlayBack != null ) {
|
| | | // MediaServerConfig mediaInfo = storager.getMediaInfo();
|
| | | // streamInfoForPlayBack.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
|
| | | // streamInfoForPlayBack.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
|
| | | // streamInfoForPlayBack.setFmp4(String.format("http://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
|
| | | // streamInfoForPlayBack.setWs_fmp4(String.format("ws://%s:%s/rtp/%s.live.mp4", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
|
| | | // streamInfoForPlayBack.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId));
|
| | | // streamInfoForPlayBack.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId));
|
| | | // streamInfoForPlayBack.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId));
|
| | | // storager.startPlayback(streamInfoForPlayBack);
|
| | | // }
|
| | | // }
|
| | |
|
| | | // TODO Auto-generated method stub
|
| | |
|
| | |
| | | String app = json.getString("app");
|
| | | String streamId = json.getString("stream");
|
| | | boolean regist = json.getBoolean("regist");
|
| | | // String ssrc = String.format("%10d", Integer.parseInt(streamId, 16)); // ZLM 要求大写且首位补零
|
| | | String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16));
|
| | | StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc);
|
| | | StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
|
| | | if ("rtp".equals(app) && !regist ) {
|
| | | if (streamInfo!=null){
|
| | | storager.stopPlay(streamInfo);
|
| | | redisCatchStorage.stopPlay(streamInfo);
|
| | | }else{
|
| | | streamInfo = storager.queryPlaybackBySSRC(ssrc);
|
| | | storager.stopPlayback(streamInfo);
|
| | | streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
|
| | | redisCatchStorage.stopPlayback(streamInfo);
|
| | | }
|
| | | }
|
| | |
|
| | |
| | | logger.debug("ZLM HOOK on_stream_none_reader API调用,参数:" + json.toString());
|
| | | }
|
| | |
|
| | | BigInteger bigint=new BigInteger(json.getString("stream"), 16);
|
| | | int numb=bigint.intValue();
|
| | | String ssrc = String.format("%010d", numb); |
| | | |
| | | cmder.streamByeCmd(ssrc);
|
| | | StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc);
|
| | | String streamId = json.getString("stream");
|
| | |
|
| | | cmder.streamByeCmd(streamId);
|
| | | StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
|
| | | if (streamInfo!=null){
|
| | | storager.stopPlay(streamInfo);
|
| | | redisCatchStorage.stopPlay(streamInfo);
|
| | | }else{
|
| | | streamInfo = storager.queryPlaybackBySSRC(ssrc);
|
| | | storager.stopPlayback(streamInfo);
|
| | | streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
|
| | | redisCatchStorage.stopPlayback(streamInfo);
|
| | | }
|
| | |
|
| | | JSONObject ret = new JSONObject();
|
| | |
| | | logger.debug("ZLM HOOK on_stream_not_found API调用,参数:" + json.toString());
|
| | | }
|
| | | // TODO Auto-generated method stub
|
| | | |
| | |
|
| | | if (autoApplyPlay) {
|
| | | String app = json.getString("app");
|
| | | String streamId = json.getString("stream");
|
| | | StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
|
| | | if ("rtp".equals(app) && streamId.indexOf("gb_play") > -1 && streamInfo == null) {
|
| | | String[] s = streamId.split("_");
|
| | | if (s.length == 4) {
|
| | | String deviceId = s[2];
|
| | | String channelId = s[3];
|
| | | Device device = storager.queryVideoDevice(deviceId);
|
| | | if (device != null) {
|
| | | UUID uuid = UUID.randomUUID();
|
| | | cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
|
| | | logger.info("收到订阅消息: " + response.toJSONString());
|
| | | playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
|
| | | }, null);
|
| | | }
|
| | |
|
| | | }
|
| | |
|
| | | }
|
| | |
|
| | | }
|
| | |
|
| | | JSONObject ret = new JSONObject();
|
| | | ret.put("code", 0);
|
| | | ret.put("msg", "success");
|
| | |
| | | // MediaServerConfig mediaServerConfig = mediaServerConfigs.get(0);
|
| | | MediaServerConfig mediaServerConfig = JSON.toJavaObject(json, MediaServerConfig.class);
|
| | | mediaServerConfig.setLocalIP(mediaIp);
|
| | | storager.updateMediaInfo(mediaServerConfig);
|
| | | redisCatchStorage.updateMediaInfo(mediaServerConfig);
|
| | | // TODO Auto-generated method stub
|
| | |
|
| | | JSONObject ret = new JSONObject();
|
| | |
| | | public JSONObject openRtpServer(Map<String, Object> param){ |
| | | return sendPost("openRtpServer",param); |
| | | } |
| | | |
| | | public JSONObject closeRtpServer(Map<String, Object> param) { |
| | | return sendPost("closeRtpServer",param); |
| | | } |
| | | } |
New file |
| | |
| | | package com.genersoft.iot.vmp.media.zlm; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | @Component |
| | | public class ZLMRTPServerFactory { |
| | | |
| | | private Logger logger = LoggerFactory.getLogger("ZLMRTPServerFactory"); |
| | | |
| | | @Value("${media.rtp.udpPortRange}") |
| | | private String udpPortRange; |
| | | |
| | | @Autowired |
| | | private ZLMRESTfulUtils zlmresTfulUtils; |
| | | |
| | | private int[] udpPortRangeArray = new int[2]; |
| | | |
| | | private int currentPort = 0; |
| | | |
| | | public int createRTPServer(String streamId) { |
| | | Map<String, Object> param = new HashMap<>(); |
| | | int result = -1; |
| | | int newPort = getPortFromUdpPortRange(); |
| | | param.put("port", newPort); |
| | | param.put("enable_tcp", 1); |
| | | param.put("stream_id", streamId); |
| | | JSONObject jsonObject = zlmresTfulUtils.openRtpServer(param); |
| | | System.out.println(jsonObject); |
| | | |
| | | if (jsonObject != null) { |
| | | switch (jsonObject.getInteger("code")){ |
| | | case 0: |
| | | result= newPort; |
| | | break; |
| | | case -300: // id已经存在 |
| | | result = newPort; |
| | | break; |
| | | case -400: // 端口占用 |
| | | result= createRTPServer(streamId); |
| | | break; |
| | | default: |
| | | logger.error("创建RTP Server 失败: " + jsonObject.getString("msg")); |
| | | break; |
| | | } |
| | | }else { |
| | | // 检查ZLM状态 |
| | | logger.error("创建RTP Server 失败: 请检查ZLM服务"); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | public boolean closeRTPServer(String streamId) { |
| | | boolean result = false; |
| | | Map<String, Object> param = new HashMap<>(); |
| | | param.put("stream_id", streamId); |
| | | JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(param); |
| | | if (jsonObject != null ) { |
| | | if (jsonObject.getInteger("code") == 0) { |
| | | result = jsonObject.getInteger("hit") == 1; |
| | | }else { |
| | | logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg")); |
| | | } |
| | | }else { |
| | | // 检查ZLM状态 |
| | | logger.error("关闭RTP Server 失败: 请检查ZLM服务"); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private int getPortFromUdpPortRange() { |
| | | if (currentPort == 0) { |
| | | String[] udpPortRangeStrArray = udpPortRange.split(","); |
| | | udpPortRangeArray[0] = Integer.parseInt(udpPortRangeStrArray[0]); |
| | | udpPortRangeArray[1] = Integer.parseInt(udpPortRangeStrArray[1]); |
| | | } |
| | | |
| | | if (currentPort == 0 || currentPort++ > udpPortRangeArray[1]) { |
| | | currentPort = udpPortRangeArray[0]; |
| | | return udpPortRangeArray[0]; |
| | | } else { |
| | | if (currentPort % 2 == 1) { |
| | | currentPort++; |
| | | } |
| | | return currentPort++; |
| | | } |
| | | } |
| | | } |
| | |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.genersoft.iot.vmp.conf.MediaServerConfig; |
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| | | import com.genersoft.iot.vmp.storager.IVideoManagerStorager; |
| | | import okhttp3.*; |
| | | import org.slf4j.Logger; |
| | |
| | | |
| | | @Autowired |
| | | private IVideoManagerStorager storager; |
| | | |
| | | @Autowired |
| | | private IRedisCatchStorage redisCatchStorage; |
| | | |
| | | @Value("${media.ip}") |
| | | private String mediaIp; |
| | |
| | | logger.info("zlm接入成功..."); |
| | | if (autoConfig) saveZLMConfig(); |
| | | mediaServerConfig = getMediaServerConfig(); |
| | | storager.updateMediaInfo(mediaServerConfig); |
| | | redisCatchStorage.updateMediaInfo(mediaServerConfig); |
| | | } |
| | | } |
| | | |
New file |
| | |
| | | package com.genersoft.iot.vmp.storager; |
| | | |
| | | import com.genersoft.iot.vmp.common.StreamInfo; |
| | | import com.genersoft.iot.vmp.conf.MediaServerConfig; |
| | | |
| | | import java.util.Map; |
| | | |
| | | public interface IRedisCatchStorage { |
| | | |
| | | /** |
| | | * 开始播放时将流存入 |
| | | * |
| | | * @param stream 流信息 |
| | | * @return |
| | | */ |
| | | boolean startPlay(StreamInfo stream); |
| | | |
| | | |
| | | /** |
| | | * 停止播放时删除 |
| | | * |
| | | * @return |
| | | */ |
| | | boolean stopPlay(StreamInfo streamInfo); |
| | | |
| | | /** |
| | | * 查询播放列表 |
| | | * @return |
| | | */ |
| | | StreamInfo queryPlay(StreamInfo streamInfo); |
| | | |
| | | StreamInfo queryPlayByStreamId(String steamId); |
| | | |
| | | StreamInfo queryPlaybackByStreamId(String steamId); |
| | | |
| | | StreamInfo queryPlayByDevice(String deviceId, String code); |
| | | |
| | | /** |
| | | * 更新流媒体信息 |
| | | * @param mediaServerConfig |
| | | * @return |
| | | */ |
| | | boolean updateMediaInfo(MediaServerConfig mediaServerConfig); |
| | | |
| | | /** |
| | | * 获取流媒体信息 |
| | | * @return |
| | | */ |
| | | MediaServerConfig getMediaInfo(); |
| | | |
| | | Map<String, StreamInfo> queryPlayByDeviceId(String deviceId); |
| | | |
| | | boolean startPlayback(StreamInfo stream); |
| | | |
| | | boolean stopPlayback(StreamInfo streamInfo); |
| | | |
| | | StreamInfo queryPlaybackByDevice(String deviceId, String code); |
| | | } |
| | |
| | | package com.genersoft.iot.vmp.storager;
|
| | |
|
| | | import java.util.List;
|
| | | import java.util.Map;
|
| | |
|
| | | import com.alibaba.fastjson.JSONObject;
|
| | | import com.genersoft.iot.vmp.common.PageResult;
|
| | | import com.genersoft.iot.vmp.common.StreamInfo;
|
| | | import com.genersoft.iot.vmp.conf.MediaServerConfig;
|
| | | import com.genersoft.iot.vmp.gb28181.bean.Device;
|
| | | import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
| | | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
| | | import com.github.pagehelper.PageInfo;
|
| | |
|
| | | /**
|
| | | * @Description:视频设备数据存储接口
|
| | |
| | | * @date: 2020年5月6日 下午2:14:31
|
| | | */
|
| | | public interface IVideoManagerStorager {
|
| | |
|
| | | /**
|
| | | * 更新流媒体信息
|
| | | * @param mediaServerConfig
|
| | | * @return
|
| | | */
|
| | | public boolean updateMediaInfo(MediaServerConfig mediaServerConfig);
|
| | |
|
| | | /**
|
| | | * 获取流媒体信息
|
| | | * @return
|
| | | */
|
| | | public MediaServerConfig getMediaInfo();
|
| | |
|
| | | /**
|
| | | * 根据设备ID判断设备是否存在
|
| | |
| | | * @param count 每页数量
|
| | | * @return
|
| | | */
|
| | | public PageResult queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, String online, int page, int count);
|
| | | public PageInfo queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, int page, int count);
|
| | |
|
| | | /**
|
| | | * 获取某个设备的通道列表
|
| | |
| | | * @return
|
| | | */
|
| | | public List<DeviceChannel> queryChannelsByDeviceId(String deviceId);
|
| | |
|
| | | /**
|
| | | * 获取某个设备的通道
|
| | | * @param deviceId 设备ID
|
| | |
| | | */
|
| | | public DeviceChannel queryChannel(String deviceId, String channelId);
|
| | |
|
| | | /** |
| | | /**
|
| | | * 获取多个设备
|
| | | * |
| | | * @param deviceIds 设备ID数组
|
| | | * @param page 当前页数
|
| | | * @param count 每页数量
|
| | | * @return List<Device> 设备对象数组
|
| | | */
|
| | | public PageResult<Device> queryVideoDeviceList(String[] deviceIds, int page, int count);
|
| | | public PageInfo<Device> queryVideoDeviceList(int page, int count);
|
| | |
|
| | | /**
|
| | | * 获取多个设备
|
| | | *
|
| | | * @param deviceIds 设备ID数组
|
| | | * @return List<Device> 设备对象数组
|
| | | */
|
| | | public List<Device> queryVideoDeviceList(String[] deviceIds);
|
| | | public List<Device> queryVideoDeviceList();
|
| | |
|
| | | /**
|
| | | * 删除设备
|
| | |
| | | */
|
| | | public boolean outline(String deviceId);
|
| | |
|
| | | /**
|
| | | * 开始播放时将流存入
|
| | | *
|
| | | * @param stream 流信息
|
| | | * @return
|
| | | */
|
| | | public boolean startPlay(StreamInfo stream);
|
| | |
|
| | | /**
|
| | | * 停止播放时删除
|
| | | *
|
| | | * @return
|
| | | */
|
| | | public boolean stopPlay(StreamInfo streamInfo);
|
| | |
|
| | | /**
|
| | | * 查找视频流
|
| | | *
|
| | | * @return
|
| | | */
|
| | | public StreamInfo queryPlay(StreamInfo streamInfo);
|
| | |
|
| | | /**
|
| | | * 查询子设备
|
| | |
| | | * @param count
|
| | | * @return
|
| | | */
|
| | | PageResult querySubChannels(String deviceId, String channelId, String query, Boolean hasSubChannel, String online, int page, int count);
|
| | | PageInfo querySubChannels(String deviceId, String channelId, String query, Boolean hasSubChannel, String online, int page, int count);
|
| | |
|
| | | /**
|
| | | * 更新缓存
|
| | | */
|
| | | public void updateCatch();
|
| | |
|
| | | /**
|
| | | * 清空通道
|
| | |
| | | */
|
| | | void cleanChannelsForDevice(String deviceId);
|
| | |
|
| | | StreamInfo queryPlayBySSRC(String ssrc);
|
| | |
|
| | | StreamInfo queryPlayByDevice(String deviceId, String code);
|
| | |
|
| | | Map<String, StreamInfo> queryPlayByDeviceId(String deviceId);
|
| | |
|
| | | boolean startPlayback(StreamInfo streamInfo);
|
| | |
|
| | | boolean stopPlayback(StreamInfo streamInfo);
|
| | |
|
| | | StreamInfo queryPlaybackByDevice(String deviceId, String channelId);
|
| | |
|
| | | StreamInfo queryPlaybackBySSRC(String ssrc);
|
| | |
|
| | | /**
|
| | | * 更新或添加上级平台
|
| | | * @param parentPlatform
|
| | | */
|
| | | boolean updateParentPlatform(ParentPlatform parentPlatform);
|
| | |
|
| | | /**
|
| | | * 删除上级平台
|
| | | * @param parentPlatform
|
| | | */
|
| | | boolean deleteParentPlatform(ParentPlatform parentPlatform);
|
| | |
|
| | |
|
| | | /**
|
| | | * 分页获取上级平台
|
| | | * @param page
|
| | | * @param count
|
| | | * @return
|
| | | */
|
| | | public PageResult<ParentPlatform> queryParentPlatformList(int page, int count);
|
| | |
|
| | | /**
|
| | | * 获取上级平台
|
| | | * @param platformGbId
|
| | | * @return
|
| | | */
|
| | | public ParentPlatform queryParentPlatById(String platformGbId);
|
| | | }
|
New file |
| | |
| | | package com.genersoft.iot.vmp.storager.dao; |
| | | |
| | | import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; |
| | | import org.apache.ibatis.annotations.*; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 用于存储设备通道信息 |
| | | */ |
| | | @Mapper |
| | | public interface DeviceChannelMapper { |
| | | |
| | | @Insert("INSERT INTO device_channel (channelId, deviceId, name, manufacture, model, owner, civilCode, block, " + |
| | | "address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " + |
| | | "ipAddress, port, password, PTZType, status) " + |
| | | "VALUES ('${channelId}', '${deviceId}', '${name}', '${manufacture}', '${model}', '${owner}', '${civilCode}', '${block}'," + |
| | | "'${address}', ${parental}, '${parentId}', ${safetyWay}, ${registerWay}, '${certNum}', ${certifiable}, ${errCode}, '${secrecy}', " + |
| | | "'${ipAddress}', ${port}, '${password}', ${PTZType}, ${status})") |
| | | int add(DeviceChannel channel); |
| | | |
| | | @Update("UPDATE device_channel " + |
| | | "SET name=#{name}, manufacture=#{manufacture}, model=#{model}, owner=#{owner}, civilCode=#{civilCode}, " + |
| | | "block=#{block}, address=#{address}, parental=#{parental}, parentId=#{parentId}, safetyWay=#{safetyWay}, " + |
| | | "registerWay=#{registerWay}, certNum=#{certNum}, certifiable=#{certifiable}, errCode=#{errCode}, secrecy=#{secrecy}, " + |
| | | "ipAddress=#{ipAddress}, port=#{port}, password=#{password}, PTZType=#{PTZType}, status=#{status}, streamId=#{streamId}, " + |
| | | "hasAudio=#{hasAudio}" + |
| | | "WHERE deviceId=#{deviceId} AND channelId=#{channelId}") |
| | | int update(DeviceChannel channel); |
| | | |
| | | @Select(value = {" <script>" + |
| | | "SELECT * FROM ( "+ |
| | | " SELECT * , (SELECT count(0) FROM device_channel WHERE parentId=dc.channelId) as subCount FROM device_channel dc " + |
| | | " WHERE dc.deviceId=#{deviceId} " + |
| | | " <if test=\"query != null\"> AND (dc.channelId LIKE '%${query}%' OR dc.name LIKE '%${query}%' OR dc.name LIKE '%${query}%')</if> " + |
| | | " <if test=\"parentChannelId != null\"> AND dc.parentId=#{parentChannelId} </if> " + |
| | | " <if test=\"online == true\" > AND dc.status=1</if>" + |
| | | " <if test=\"online == false\" > AND dc.status=0</if>) dcr" + |
| | | " WHERE 1=1 " + |
| | | " <if test=\"hasSubChannel == true\" > AND subCount >0</if>" + |
| | | " <if test=\"hasSubChannel == false\" > AND subCount=0</if>" + |
| | | " </script>"}) |
| | | List<DeviceChannel> queryChannelsByDeviceId(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online); |
| | | |
| | | @Select("SELECT * FROM device_channel WHERE deviceId=#{deviceId} AND channelId=#{channelId}") |
| | | DeviceChannel queryChannel(String deviceId, String channelId); |
| | | |
| | | @Delete("DELETE FROM device_channel WHERE deviceId=#{deviceId}") |
| | | int cleanChannelsByDeviceId(String deviceId); |
| | | |
| | | } |
New file |
| | |
| | | package com.genersoft.iot.vmp.storager.dao; |
| | | |
| | | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| | | import org.apache.ibatis.annotations.*; |
| | | import org.springframework.stereotype.Repository; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 用于存储设备信息 |
| | | */ |
| | | @Mapper |
| | | @Repository |
| | | public interface DeviceMapper { |
| | | |
| | | @Select("SELECT * FROM device WHERE deviceId = #{deviceId}") |
| | | Device getDeviceByDeviceId(String deviceId); |
| | | |
| | | @Insert("INSERT INTO device (" + |
| | | "deviceId, " + |
| | | "name, " + |
| | | "manufacturer, " + |
| | | "model, " + |
| | | "firmware, " + |
| | | "transport," + |
| | | "streamMode," + |
| | | "ip," + |
| | | "port," + |
| | | "hostAddress," + |
| | | "online" + |
| | | ") VALUES (" + |
| | | "#{deviceId}," + |
| | | "#{name}," + |
| | | "#{manufacturer}," + |
| | | "#{model}," + |
| | | "#{firmware}," + |
| | | "#{transport}," + |
| | | "#{streamMode}," + |
| | | "#{ip}," + |
| | | "#{port}," + |
| | | "#{hostAddress}," + |
| | | "#{online}" + |
| | | ")") |
| | | int add(Device device); |
| | | |
| | | |
| | | @Update("UPDATE device " + |
| | | "SET name=#{name}, " + |
| | | "manufacturer=#{manufacturer}," + |
| | | "model=#{model}," + |
| | | "firmware=#{firmware}, " + |
| | | "transport=#{transport}," + |
| | | "streamMode=#{streamMode}, " + |
| | | "ip=#{ip}, " + |
| | | "port=#{port}, " + |
| | | "hostAddress=#{hostAddress}, " + |
| | | "online=#{online} " + |
| | | "WHERE deviceId=#{deviceId}") |
| | | int update(Device device); |
| | | |
| | | @Select("SELECT *, (SELECT count(0) FROM device_channel WHERE deviceId=de.deviceId) as channelCount FROM device de") |
| | | List<Device> getDevices(); |
| | | |
| | | @Delete("DELETE FROM device WHERE deviceId=#{deviceId}") |
| | | int del(String deviceId); |
| | | } |
New file |
| | |
| | | package com.genersoft.iot.vmp.storager.impl; |
| | | |
| | | import com.genersoft.iot.vmp.common.StreamInfo; |
| | | import com.genersoft.iot.vmp.common.VideoManagerConstants; |
| | | import com.genersoft.iot.vmp.conf.MediaServerConfig; |
| | | import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; |
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| | | import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; |
| | | import com.genersoft.iot.vmp.utils.redis.RedisUtil; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Component |
| | | public class RedisCatchStorageImpl implements IRedisCatchStorage { |
| | | |
| | | @Autowired |
| | | private RedisUtil redis; |
| | | |
| | | @Autowired |
| | | private DeviceChannelMapper deviceChannelMapper; |
| | | |
| | | |
| | | /** |
| | | * 开始播放时将流存入redis |
| | | * |
| | | * @return |
| | | */ |
| | | @Override |
| | | public boolean startPlay(StreamInfo stream) { |
| | | return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, stream.getStreamId(),stream.getDeviceID(), stream.getCahnnelId()), |
| | | stream); |
| | | } |
| | | |
| | | /** |
| | | * 停止播放时从redis删除 |
| | | * |
| | | * @return |
| | | */ |
| | | @Override |
| | | public boolean stopPlay(StreamInfo streamInfo) { |
| | | if (streamInfo == null) return false; |
| | | DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(streamInfo.getDeviceID(), streamInfo.getCahnnelId()); |
| | | if (deviceChannel != null) { |
| | | deviceChannel.setStreamId(null); |
| | | deviceChannel.setDeviceId(streamInfo.getDeviceID()); |
| | | deviceChannelMapper.update(deviceChannel); |
| | | } |
| | | return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, |
| | | streamInfo.getStreamId(), |
| | | streamInfo.getDeviceID(), |
| | | streamInfo.getCahnnelId())); |
| | | } |
| | | |
| | | /** |
| | | * 查询播放列表 |
| | | * @return |
| | | */ |
| | | @Override |
| | | public StreamInfo queryPlay(StreamInfo streamInfo) { |
| | | return (StreamInfo)redis.get(String.format("%S_%s_%s_%s", |
| | | VideoManagerConstants.PLAYER_PREFIX, |
| | | streamInfo.getStreamId(), |
| | | streamInfo.getDeviceID(), |
| | | streamInfo.getCahnnelId())); |
| | | } |
| | | @Override |
| | | public StreamInfo queryPlayByStreamId(String steamId) { |
| | | List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, steamId)); |
| | | if (playLeys == null || playLeys.size() == 0) return null; |
| | | return (StreamInfo)redis.get(playLeys.get(0).toString()); |
| | | } |
| | | |
| | | @Override |
| | | public StreamInfo queryPlaybackByStreamId(String steamId) { |
| | | List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, steamId)); |
| | | if (playLeys == null || playLeys.size() == 0) return null; |
| | | return (StreamInfo)redis.get(playLeys.get(0).toString()); |
| | | } |
| | | |
| | | @Override |
| | | public StreamInfo queryPlayByDevice(String deviceId, String code) { |
| | | // List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, |
| | | List<Object> playLeys = redis.scan(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, |
| | | deviceId, |
| | | code)); |
| | | if (playLeys == null || playLeys.size() == 0) return null; |
| | | return (StreamInfo)redis.get(playLeys.get(0).toString()); |
| | | } |
| | | |
| | | /** |
| | | * 更新流媒体信息 |
| | | * @param mediaServerConfig |
| | | * @return |
| | | */ |
| | | @Override |
| | | public boolean updateMediaInfo(MediaServerConfig mediaServerConfig) { |
| | | return redis.set(VideoManagerConstants.MEDIA_SERVER_PREFIX,mediaServerConfig); |
| | | } |
| | | |
| | | /** |
| | | * 获取流媒体信息 |
| | | * @return |
| | | */ |
| | | @Override |
| | | public MediaServerConfig getMediaInfo() { |
| | | return (MediaServerConfig)redis.get(VideoManagerConstants.MEDIA_SERVER_PREFIX); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) { |
| | | Map<String, StreamInfo> streamInfos = new HashMap<>(); |
| | | // List<Object> playLeys = redis.keys(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId)); |
| | | 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.getCahnnelId(), streamInfo); |
| | | } |
| | | return streamInfos; |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public boolean startPlayback(StreamInfo stream) { |
| | | return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, stream.getStreamId(),stream.getDeviceID(), stream.getCahnnelId()), |
| | | stream); |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public boolean stopPlayback(StreamInfo streamInfo) { |
| | | if (streamInfo == null) return false; |
| | | DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(streamInfo.getDeviceID(), streamInfo.getCahnnelId()); |
| | | if (deviceChannel != null) { |
| | | deviceChannel.setStreamId(null); |
| | | deviceChannel.setDeviceId(streamInfo.getDeviceID()); |
| | | deviceChannelMapper.update(deviceChannel); |
| | | } |
| | | return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, |
| | | streamInfo.getStreamId(), |
| | | streamInfo.getDeviceID(), |
| | | streamInfo.getCahnnelId())); |
| | | } |
| | | |
| | | @Override |
| | | public StreamInfo queryPlaybackByDevice(String deviceId, String code) { |
| | | 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) { |
| | | playLeys = redis.scan(String.format("%S_*_*_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, |
| | | deviceId)); |
| | | } |
| | | if (playLeys == null || playLeys.size() == 0) return null; |
| | | return (StreamInfo)redis.get(playLeys.get(0).toString()); |
| | | } |
| | | } |
New file |
| | |
| | | package com.genersoft.iot.vmp.storager.impl; |
| | | |
| | | import java.util.*; |
| | | |
| | | import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; |
| | | import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; |
| | | import com.genersoft.iot.vmp.storager.dao.DeviceMapper; |
| | | import com.github.pagehelper.PageHelper; |
| | | import com.github.pagehelper.PageInfo; |
| | | import io.swagger.models.auth.In; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| | | import com.genersoft.iot.vmp.storager.IVideoManagerStorager; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | /** |
| | | * @Description:视频设备数据存储-jdbc实现 |
| | | * @author: swwheihei |
| | | * @date: 2020年5月6日 下午2:31:42 |
| | | */ |
| | | @Component |
| | | public class VideoManagerStoragerImpl implements IVideoManagerStorager { |
| | | |
| | | @Autowired |
| | | private DeviceMapper deviceMapper; |
| | | |
| | | @Autowired |
| | | private DeviceChannelMapper deviceChannelMapper; |
| | | |
| | | |
| | | /** |
| | | * 根据设备ID判断设备是否存在 |
| | | * |
| | | * @param deviceId 设备ID |
| | | * @return true:存在 false:不存在 |
| | | */ |
| | | @Override |
| | | public boolean exists(String deviceId) { |
| | | return deviceMapper.getDeviceByDeviceId(deviceId) != null; |
| | | } |
| | | |
| | | /** |
| | | * 视频设备创建 |
| | | * |
| | | * @param device 设备对象 |
| | | * @return true:创建成功 false:创建失败 |
| | | */ |
| | | @Override |
| | | public synchronized boolean create(Device device) { |
| | | return deviceMapper.add(device) > 0; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * 视频设备更新 |
| | | * |
| | | * @param device 设备对象 |
| | | * @return true:更新成功 false:更新失败 |
| | | */ |
| | | @Override |
| | | public synchronized boolean updateDevice(Device device) { |
| | | Device deviceByDeviceId = deviceMapper.getDeviceByDeviceId(device.getDeviceId()); |
| | | if (deviceByDeviceId == null) { |
| | | return deviceMapper.add(device) > 0; |
| | | }else { |
| | | return deviceMapper.update(device) > 0; |
| | | } |
| | | |
| | | } |
| | | |
| | | @Override |
| | | public synchronized void updateChannel(String deviceId, DeviceChannel channel) { |
| | | String channelId = channel.getChannelId(); |
| | | channel.setDeviceId(deviceId); |
| | | DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(deviceId, channelId); |
| | | if (deviceChannel == null) { |
| | | deviceChannelMapper.add(channel); |
| | | }else { |
| | | deviceChannelMapper.update(channel); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取设备 |
| | | * |
| | | * @param deviceId 设备ID |
| | | * @return Device 设备对象 |
| | | */ |
| | | @Override |
| | | public Device queryVideoDevice(String deviceId) { |
| | | return deviceMapper.getDeviceByDeviceId(deviceId); |
| | | } |
| | | |
| | | @Override |
| | | public PageInfo queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, Boolean online, int page, int count) { |
| | | // 获取到所有正在播放的流 |
| | | PageHelper.startPage(page, count); |
| | | List<DeviceChannel> all = deviceChannelMapper.queryChannelsByDeviceId(deviceId, null, query, hasSubChannel, online); |
| | | return new PageInfo<>(all); |
| | | } |
| | | |
| | | |
| | | |
| | | @Override |
| | | public List<DeviceChannel> queryChannelsByDeviceId(String deviceId) { |
| | | return deviceChannelMapper.queryChannelsByDeviceId(deviceId, null,null, null, null); |
| | | } |
| | | |
| | | @Override |
| | | public PageInfo<DeviceChannel> querySubChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, String online, int page, int count) { |
| | | PageHelper.startPage(page, count); |
| | | List<DeviceChannel> all = deviceChannelMapper.queryChannelsByDeviceId(deviceId, parentChannelId, null, null, null); |
| | | return new PageInfo<>(all); |
| | | } |
| | | |
| | | @Override |
| | | public DeviceChannel queryChannel(String deviceId, String channelId) { |
| | | return deviceChannelMapper.queryChannel(deviceId, channelId); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 获取多个设备 |
| | | * |
| | | * @param page 当前页数 |
| | | * @param count 每页数量 |
| | | * @return PageInfo<Device> 分页设备对象数组 |
| | | */ |
| | | @Override |
| | | public PageInfo<Device> queryVideoDeviceList(int page, int count) { |
| | | PageHelper.startPage(page, count); |
| | | List<Device> all = deviceMapper.getDevices(); |
| | | return new PageInfo<>(all); |
| | | } |
| | | |
| | | /** |
| | | * 获取多个设备 |
| | | * |
| | | * @return List<Device> 设备对象数组 |
| | | */ |
| | | @Override |
| | | public List<Device> queryVideoDeviceList() { |
| | | |
| | | List<Device> deviceList = deviceMapper.getDevices(); |
| | | return deviceList; |
| | | } |
| | | |
| | | /** |
| | | * 删除设备 |
| | | * |
| | | * @param deviceId 设备ID |
| | | * @return true:删除成功 false:删除失败 |
| | | */ |
| | | @Override |
| | | public boolean delete(String deviceId) { |
| | | int result = deviceMapper.del(deviceId); |
| | | |
| | | return result > 0; |
| | | } |
| | | |
| | | /** |
| | | * 更新设备在线 |
| | | * |
| | | * @param deviceId 设备ID |
| | | * @return true:更新成功 false:更新失败 |
| | | */ |
| | | @Override |
| | | public synchronized boolean online(String deviceId) { |
| | | Device device = deviceMapper.getDeviceByDeviceId(deviceId); |
| | | device.setOnline(1); |
| | | System.out.println("更新设备在线"); |
| | | if (device == null) { |
| | | return false; |
| | | } |
| | | return deviceMapper.update(device) > 0; |
| | | } |
| | | |
| | | /** |
| | | * 更新设备离线 |
| | | * |
| | | * @param deviceId 设备ID |
| | | * @return true:更新成功 false:更新失败 |
| | | */ |
| | | @Override |
| | | public synchronized boolean outline(String deviceId) { |
| | | Device device = deviceMapper.getDeviceByDeviceId(deviceId); |
| | | device.setOnline(0); |
| | | System.out.println("更新设备离线"); |
| | | return deviceMapper.update(device) > 0; |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void cleanChannelsForDevice(String deviceId) { |
| | | int result = deviceChannelMapper.cleanChannelsByDeviceId(deviceId); |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | * 获取对象 这里重写了bean方法,起主要作用
|
| | | */
|
| | | public static Object getBean(String beanId) throws BeansException {
|
| | | if (applicationContext == null) return null;
|
| | | return applicationContext.getBean(beanId);
|
| | | }
|
| | |
|
| | |
| | | package com.genersoft.iot.vmp.vmanager.device; |
| | | |
| | | import java.util.List; |
| | | |
| | | import com.genersoft.iot.vmp.common.PageResult; |
| | | 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.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; |
| | | |
| | | @CrossOrigin |
| | | @RestController |
| | |
| | | } |
| | | |
| | | @GetMapping("/devices") |
| | | public PageResult<Device> devices(int page, int count){ |
| | | public PageInfo<Device> devices(int page, int count){ |
| | | |
| | | if (logger.isDebugEnabled()) { |
| | | logger.debug("查询所有视频设备API调用"); |
| | | } |
| | | |
| | | return storager.queryVideoDeviceList(null, page, count); |
| | | return storager.queryVideoDeviceList(page, count); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @param count 每页条数 |
| | | * @return 通道列表 |
| | | */ |
| | | /** |
| | | * 分页查询通道数 |
| | | * |
| | | * @param deviceId 设备id |
| | | * @param page 当前页 |
| | | * @param count 每页条数 |
| | | * @param query 查询内容 |
| | | * @param online 是否在线 在线 true / 离线 false |
| | | * @param channelType 设备 false/子目录 true |
| | | * @return 通道列表 |
| | | */ |
| | | @GetMapping("/devices/{deviceId}/channels") |
| | | public ResponseEntity<PageResult> channels(@PathVariable String deviceId, |
| | | public ResponseEntity<PageInfo> channels(@PathVariable String deviceId, |
| | | int page, int count, |
| | | @RequestParam(required = false) String query, |
| | | @RequestParam(required = false) String online, |
| | | @RequestParam(required = false) Boolean online, |
| | | @RequestParam(required = false) Boolean channelType |
| | | ){ |
| | | |
| | | if (logger.isDebugEnabled()) { |
| | | logger.debug("查询所有视频设备API调用"); |
| | | } |
| | | PageResult pageResult = storager.queryChannelsByDeviceId(deviceId, query, channelType, online, page, count); |
| | | if (StringUtils.isEmpty(query)) { |
| | | query = null; |
| | | } |
| | | |
| | | PageInfo pageResult = storager.queryChannelsByDeviceId(deviceId, query, channelType, online, page, count); |
| | | return new ResponseEntity<>(pageResult,HttpStatus.OK); |
| | | } |
| | | |
| | |
| | | |
| | | if (logger.isDebugEnabled()) { |
| | | } |
| | | logger.debug("设备信息同步API调用,deviceId:" + deviceId); |
| | | logger.debug("设备通道信息同步API调用,deviceId:" + deviceId); |
| | | |
| | | Device device = storager.queryVideoDevice(deviceId); |
| | | cmder.catalogQuery(device); |
| | | DeferredResult<ResponseEntity<Device>> result = new DeferredResult<ResponseEntity<Device>>(); |
| | | 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; |
| | | } |
| | |
| | | * @return 子通道列表 |
| | | */ |
| | | @GetMapping("/subChannels/{deviceId}/{channelId}/channels") |
| | | public ResponseEntity<PageResult> subChannels(@PathVariable String deviceId, |
| | | public ResponseEntity<PageInfo> subChannels(@PathVariable String deviceId, |
| | | @PathVariable String channelId, |
| | | int page, |
| | | int count, |
| | |
| | | } |
| | | DeviceChannel deviceChannel = storager.queryChannel(deviceId,channelId); |
| | | if (deviceChannel == null) { |
| | | PageResult<DeviceChannel> deviceChannelPageResult = new PageResult<>(); |
| | | PageInfo<DeviceChannel> deviceChannelPageResult = new PageInfo<>(); |
| | | return new ResponseEntity<>(deviceChannelPageResult,HttpStatus.OK); |
| | | } |
| | | |
| | | PageResult pageResult = storager.querySubChannels(deviceId, channelId, query, channelType, online, page, count); |
| | | PageInfo pageResult = storager.querySubChannels(deviceId, channelId, query, channelType, online, page, count); |
| | | return new ResponseEntity<>(pageResult,HttpStatus.OK); |
| | | } |
| | | |
| | | @PostMapping("/channel/update/{deviceId}") |
| | | public ResponseEntity<PageResult> updateChannel(@PathVariable String deviceId,DeviceChannel channel){ |
| | | 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<PageResult> updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){ |
| | | public ResponseEntity<PageInfo> updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){ |
| | | Device device = storager.queryVideoDevice(deviceId); |
| | | device.setStreamMode(streamMode); |
| | | storager.updateDevice(device); |
| | |
| | | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; |
| | | import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; |
| | | import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; |
| | | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| | | import com.genersoft.iot.vmp.vmanager.service.IPlayService; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | |
| | | import com.genersoft.iot.vmp.storager.IVideoManagerStorager; |
| | | import org.springframework.web.context.request.async.DeferredResult; |
| | | |
| | | import javax.sip.message.Response; |
| | | import java.text.DecimalFormat; |
| | | import java.util.UUID; |
| | | |
| | |
| | | private IVideoManagerStorager storager; |
| | | |
| | | @Autowired |
| | | private IRedisCatchStorage redisCatchStorage; |
| | | |
| | | @Autowired |
| | | private ZLMRESTfulUtils zlmresTfulUtils; |
| | | |
| | | @Autowired |
| | |
| | | |
| | | |
| | | Device device = storager.queryVideoDevice(deviceId); |
| | | StreamInfo streamInfo = storager.queryPlayByDevice(deviceId, channelId); |
| | | StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId); |
| | | |
| | | UUID uuid = UUID.randomUUID(); |
| | | DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(); |
| | | // 超时处理 |
| | | result.onTimeout(()->{ |
| | | logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId)); |
| | | RequestMessage msg = new RequestMessage(); |
| | | msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); |
| | | msg.setData("Timeout"); |
| | | resultHolder.invokeResult(msg); |
| | | }); |
| | | |
| | | // 录像查询以channelId作为deviceId查询 |
| | | resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result); |
| | | |
| | |
| | | cmder.playStreamCmd(device, channelId, (JSONObject response) -> { |
| | | logger.info("收到订阅消息: " + response.toJSONString()); |
| | | playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString()); |
| | | }, event -> { |
| | | RequestMessage msg = new RequestMessage(); |
| | | msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); |
| | | Response response = event.getResponse(); |
| | | msg.setData(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase())); |
| | | resultHolder.invokeResult(msg); |
| | | }); |
| | | } else { |
| | | String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase(); |
| | | String streamId = streamInfo.getStreamId(); |
| | | JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); |
| | | if (rtpInfo.getBoolean("exist")) { |
| | | RequestMessage msg = new RequestMessage(); |
| | |
| | | msg.setData(JSON.toJSONString(streamInfo)); |
| | | resultHolder.invokeResult(msg); |
| | | } else { |
| | | storager.stopPlay(streamInfo); |
| | | // TODO playStreamCmd 超时处理 |
| | | redisCatchStorage.stopPlay(streamInfo); |
| | | cmder.playStreamCmd(device, channelId, (JSONObject response) -> { |
| | | logger.info("收到订阅消息: " + response.toJSONString()); |
| | | playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString()); |
| | | }, event -> { |
| | | RequestMessage msg = new RequestMessage(); |
| | | msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); |
| | | Response response = event.getResponse(); |
| | | msg.setData(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase())); |
| | | resultHolder.invokeResult(msg); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // 超时处理 |
| | | result.onTimeout(()->{ |
| | | logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId)); |
| | | // 释放rtpserver |
| | | cmder.closeRTPServer(device, channelId); |
| | | RequestMessage msg = new RequestMessage(); |
| | | msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); |
| | | msg.setData("Timeout"); |
| | | resultHolder.invokeResult(msg); |
| | | }); |
| | | return result; |
| | | } |
| | | |
| | | @PostMapping("/play/{ssrc}/stop") |
| | | public ResponseEntity<String> playStop(@PathVariable String ssrc) { |
| | | @PostMapping("/play/{streamId}/stop") |
| | | public DeferredResult<ResponseEntity<String>> playStop(@PathVariable String streamId) { |
| | | |
| | | cmder.streamByeCmd(ssrc); |
| | | StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc); |
| | | if (streamInfo == null) |
| | | return new ResponseEntity<String>("ssrc not found", HttpStatus.OK); |
| | | storager.stopPlay(streamInfo); |
| | | if (logger.isDebugEnabled()) { |
| | | logger.debug(String.format("设备预览停止API调用,ssrc:%s", ssrc)); |
| | | } |
| | | logger.debug(String.format("设备预览/回放停止API调用,streamId:%s", streamId)); |
| | | |
| | | if (ssrc != null) { |
| | | UUID uuid = UUID.randomUUID(); |
| | | DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(); |
| | | |
| | | // 录像查询以channelId作为deviceId查询 |
| | | resultHolder.put(DeferredResultHolder.CALLBACK_CMD_STOP + uuid, result); |
| | | |
| | | cmder.streamByeCmd(streamId, event -> { |
| | | StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); |
| | | if (streamInfo == null) { |
| | | RequestMessage msg = new RequestMessage(); |
| | | msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); |
| | | msg.setData("streamId not found"); |
| | | resultHolder.invokeResult(msg); |
| | | redisCatchStorage.stopPlay(streamInfo); |
| | | } |
| | | |
| | | RequestMessage msg = new RequestMessage(); |
| | | msg.setId(DeferredResultHolder.CALLBACK_CMD_STOP + uuid); |
| | | Response response = event.getResponse(); |
| | | msg.setData(String.format("success")); |
| | | resultHolder.invokeResult(msg); |
| | | }); |
| | | |
| | | |
| | | |
| | | if (streamId != null) { |
| | | JSONObject json = new JSONObject(); |
| | | json.put("ssrc", ssrc); |
| | | return new ResponseEntity<String>(json.toString(), HttpStatus.OK); |
| | | json.put("streamId", streamId); |
| | | RequestMessage msg = new RequestMessage(); |
| | | msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); |
| | | msg.setData(json.toString()); |
| | | resultHolder.invokeResult(msg); |
| | | } else { |
| | | logger.warn("设备预览停止API调用失败!"); |
| | | return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR); |
| | | logger.warn("设备预览/回放停止API调用失败!"); |
| | | RequestMessage msg = new RequestMessage(); |
| | | msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); |
| | | msg.setData("streamId null"); |
| | | resultHolder.invokeResult(msg); |
| | | } |
| | | |
| | | // 超时处理 |
| | | result.onTimeout(()->{ |
| | | logger.warn(String.format("设备预览/回放停止超时,streamId:%s ", streamId)); |
| | | RequestMessage msg = new RequestMessage(); |
| | | msg.setId(DeferredResultHolder.CALLBACK_CMD_STOP + uuid); |
| | | msg.setData("Timeout"); |
| | | resultHolder.invokeResult(msg); |
| | | }); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 将不是h264的视频通过ffmpeg 转码为h264 + aac |
| | | * @param ssrc |
| | | * @param streamId 流ID |
| | | * @return |
| | | */ |
| | | @PostMapping("/play/{ssrc}/convert") |
| | | public ResponseEntity<String> playConvert(@PathVariable String ssrc) { |
| | | StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc); |
| | | @PostMapping("/play/{streamId}/convert") |
| | | public ResponseEntity<String> playConvert(@PathVariable String streamId) { |
| | | StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); |
| | | if (streamInfo == null) { |
| | | logger.warn("视频转码API调用失败!, 视频流已经停止!"); |
| | | return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK); |
| | | } |
| | | String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase(); |
| | | JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); |
| | | if (!rtpInfo.getBoolean("exist")) { |
| | | logger.warn("视频转码API调用失败!, 视频流已停止推流!"); |
| | | return new ResponseEntity<String>("推流信息在流媒体中不存在, 视频流可能已停止推流", HttpStatus.OK); |
| | | } else { |
| | | MediaServerConfig mediaInfo = storager.getMediaInfo(); |
| | | MediaServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); |
| | | String dstUrl = String.format("rtmp://%s:%s/convert/%s", "127.0.0.1", mediaInfo.getRtmpPort(), |
| | | streamId ); |
| | | String srcUrl = String.format("rtsp://%s:%s/rtp/%s", "127.0.0.1", mediaInfo.getRtspPort(), streamId); |
| | |
| | | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; |
| | | import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; |
| | | import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; |
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| | | import com.genersoft.iot.vmp.vmanager.service.IPlayService; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | |
| | | import com.genersoft.iot.vmp.storager.IVideoManagerStorager; |
| | | import org.springframework.web.context.request.async.DeferredResult; |
| | | |
| | | import javax.sip.message.Response; |
| | | import java.util.UUID; |
| | | |
| | | @CrossOrigin |
| | |
| | | |
| | | @Autowired |
| | | private IVideoManagerStorager storager; |
| | | |
| | | @Autowired |
| | | private IRedisCatchStorage redisCatchStorage; |
| | | |
| | | @Autowired |
| | | private ZLMRESTfulUtils zlmresTfulUtils; |
| | |
| | | resultHolder.invokeResult(msg); |
| | | }); |
| | | Device device = storager.queryVideoDevice(deviceId); |
| | | StreamInfo streamInfo = storager.queryPlaybackByDevice(deviceId, channelId); |
| | | StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, channelId); |
| | | if (streamInfo != null) { |
| | | // 停止之前的回放 |
| | | cmder.streamByeCmd(streamInfo.getSsrc()); |
| | | cmder.streamByeCmd(streamInfo.getStreamId()); |
| | | } |
| | | resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result); |
| | | cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> { |
| | | logger.info("收到订阅消息: " + response.toJSONString()); |
| | | playService.onPublishHandlerForPlayBack(response, deviceId, channelId, uuid.toString()); |
| | | }, event -> { |
| | | 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())); |
| | | resultHolder.invokeResult(msg); |
| | | }); |
| | | |
| | | return result; |
| | |
| | | private IVideoManagerStorager storager; |
| | | |
| | | /*** |
| | | * http://localhost:8080/api/ptz/34020000001320000002_34020000001320000008?leftRight=1&upDown=0&inOut=0&moveSpeed=50&zoomSpeed=0 |
| | | * @param deviceId |
| | | * @param channelId |
| | | * @param leftRight |
| | | * @param upDown |
| | | * @param inOut |
| | | * @param moveSpeed |
| | | * @param zoomSpeed |
| | | * @return |
| | | * 云台控制 |
| | | * @param deviceId 设备id |
| | | * @param channelId 通道id |
| | | * @param cmdCode 指令码 |
| | | * @param horizonSpeed 水平移动速度 |
| | | * @param verticalSpeed 垂直移动速度 |
| | | * @param zoomSpeed 缩放速度 |
| | | * @return String 控制结果 |
| | | */ |
| | | @PostMapping("/ptz/{deviceId}/{channelId}") |
| | | public ResponseEntity<String> ptz(@PathVariable String deviceId,@PathVariable String channelId,int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed){ |
| | |
| | | package com.genersoft.iot.vmp.vmanager.record; |
| | | |
| | | import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | |
| | | @Autowired |
| | | private DeferredResultHolder resultHolder; |
| | | |
| | | |
| | | @GetMapping("/record/{deviceId}/{channelId}") |
| | | public DeferredResult<ResponseEntity<RecordInfo>> recordinfo(@PathVariable String deviceId,@PathVariable String channelId, String startTime, String endTime){ |
| | | |
| | |
| | | |
| | | Device device = storager.queryVideoDevice(deviceId); |
| | | cmder.recordInfoQuery(device, channelId, startTime, endTime); |
| | | DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<ResponseEntity<RecordInfo>>(); |
| | | // 指定超时时间 1分钟30秒 |
| | | DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<ResponseEntity<RecordInfo>>(90*1000L); |
| | | // 录像查询以channelId作为deviceId查询 |
| | | resultHolder.put(DeferredResultHolder.CALLBACK_CMD_RECORDINFO+channelId, result); |
| | | result.onTimeout(()->{ |
| | | RequestMessage msg = new RequestMessage(); |
| | | msg.setDeviceId(deviceId); |
| | | msg.setType(DeferredResultHolder.CALLBACK_CMD_RECORDINFO); |
| | | msg.setData("timeout"); |
| | | resultHolder.invokeResult(msg); |
| | | }); |
| | | return result; |
| | | } |
| | | } |
| | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.genersoft.iot.vmp.common.StreamInfo; |
| | | import com.genersoft.iot.vmp.conf.MediaServerConfig; |
| | | import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; |
| | | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; |
| | | import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; |
| | | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| | | import com.genersoft.iot.vmp.storager.IVideoManagerStorager; |
| | | import com.genersoft.iot.vmp.vmanager.play.PlayController; |
| | | import com.genersoft.iot.vmp.vmanager.service.IPlayService; |
| | |
| | | private IVideoManagerStorager storager; |
| | | |
| | | @Autowired |
| | | private IRedisCatchStorage redisCatchStorage; |
| | | |
| | | @Autowired |
| | | private DeferredResultHolder resultHolder; |
| | | |
| | | @Override |
| | |
| | | msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); |
| | | StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid); |
| | | if (streamInfo != null) { |
| | | storager.startPlay(streamInfo); |
| | | DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); |
| | | if (deviceChannel != null) { |
| | | deviceChannel.setStreamId(streamInfo.getStreamId()); |
| | | storager.updateChannel(deviceId, deviceChannel); |
| | | } |
| | | |
| | | redisCatchStorage.startPlay(streamInfo); |
| | | msg.setData(JSON.toJSONString(streamInfo)); |
| | | resultHolder.invokeResult(msg); |
| | | } else { |
| | |
| | | msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); |
| | | StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid); |
| | | if (streamInfo != null) { |
| | | storager.startPlayback(streamInfo); |
| | | redisCatchStorage.startPlayback(streamInfo); |
| | | msg.setData(JSON.toJSONString(streamInfo)); |
| | | resultHolder.invokeResult(msg); |
| | | } else { |
| | |
| | | |
| | | public StreamInfo onPublishHandler(JSONObject resonse, String deviceId, String channelId, String uuid) { |
| | | String streamId = resonse.getString("id"); |
| | | String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16)); |
| | | StreamInfo streamInfo = new StreamInfo(); |
| | | streamInfo.setSsrc(ssrc); |
| | | streamInfo.setStreamId(streamId); |
| | | streamInfo.setDeviceID(deviceId); |
| | | streamInfo.setCahnnelId(channelId); |
| | | MediaServerConfig mediaServerConfig = storager.getMediaInfo(); |
| | | 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)); |
| | |
| | | package com.genersoft.iot.vmp.web; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.genersoft.iot.vmp.common.PageResult; |
| | | 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.cmd.impl.SIPCommander; |
| | | import com.genersoft.iot.vmp.storager.IVideoManagerStorager; |
| | | import com.genersoft.iot.vmp.vmanager.device.DeviceController; |
| | | 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.stereotype.Controller; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.List; |
| | |
| | | JSONObject result = new JSONObject(); |
| | | List<Device> devices; |
| | | if (start == null || limit ==null) { |
| | | devices = storager.queryVideoDeviceList(null); |
| | | devices = storager.queryVideoDeviceList(); |
| | | result.put("DeviceCount", devices.size()); |
| | | }else { |
| | | PageResult<Device> deviceList = storager.queryVideoDeviceList(null, start/limit, limit); |
| | | PageInfo<Device> deviceList = storager.queryVideoDeviceList(start/limit, limit); |
| | | result.put("DeviceCount", deviceList.getTotal()); |
| | | devices = deviceList.getData(); |
| | | devices = deviceList.getList(); |
| | | } |
| | | |
| | | JSONArray deviceJSONList = new JSONArray(); |
| | |
| | | deviceJsonObject.put("Online", device.getOnline() == 1); |
| | | deviceJsonObject.put("Password", ""); |
| | | deviceJsonObject.put("MediaTransport", device.getTransport()); |
| | | deviceJsonObject.put("RemoteIP", device.getHost().getIp()); |
| | | deviceJsonObject.put("RemotePort", device.getHost().getPort()); |
| | | deviceJsonObject.put("RemoteIP", device.getIp()); |
| | | deviceJsonObject.put("RemotePort", device.getPort()); |
| | | deviceJsonObject.put("LastRegisterAt", ""); |
| | | deviceJsonObject.put("LastKeepaliveAt", ""); |
| | | deviceJsonObject.put("UpdatedAt", ""); |
| | |
| | | deviceChannels = storager.queryChannelsByDeviceId(serial); |
| | | result.put("ChannelCount", deviceChannels.size()); |
| | | }else { |
| | | PageResult<DeviceChannel> pageResult = storager.queryChannelsByDeviceId(serial, null, null, null,start/limit, limit); |
| | | PageInfo<DeviceChannel> pageResult = storager.queryChannelsByDeviceId(serial, null, null, null,start/limit, limit); |
| | | result.put("ChannelCount", pageResult.getTotal()); |
| | | deviceChannels = pageResult.getData(); |
| | | deviceChannels = pageResult.getList(); |
| | | } |
| | | |
| | | JSONArray channleJSONList = new JSONArray(); |
| | |
| | | deviceJOSNChannel.put("PTZType ", deviceChannel.getPTZType()); // 云台类型, 0 - 未知, 1 - 球机, 2 - 半球, |
| | | // 3 - 固定枪机, 4 - 遥控枪机 |
| | | deviceJOSNChannel.put("CustomPTZType", ""); |
| | | deviceJOSNChannel.put("StreamID", deviceChannel.getSsrc()); // StreamID 直播流ID, 有值表示正在直播 |
| | | deviceJOSNChannel.put("StreamID", deviceChannel.getStreamId()); // StreamID 直播流ID, 有值表示正在直播 |
| | | deviceJOSNChannel.put("NumOutputs ", -1); // 直播在线人数 |
| | | channleJSONList.add(deviceJOSNChannel); |
| | | } |
| | |
| | | import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; |
| | | 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.vmanager.play.PlayController; |
| | | import org.slf4j.Logger; |
| | |
| | | import org.springframework.http.HttpStatus; |
| | | import org.springframework.http.ResponseEntity; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import org.springframework.web.context.request.async.DeferredResult; |
| | | |
| | | /** |
| | | * 兼容LiveGBS的API:实时直播 |
| | |
| | | @Autowired |
| | | private IVideoManagerStorager storager; |
| | | |
| | | private boolean closeWaitRTPInfo = false; |
| | | @Autowired |
| | | private IRedisCatchStorage redisCatchStorage; |
| | | |
| | | |
| | | @Autowired |
| | | private ZLMRESTfulUtils zlmresTfulUtils; |
| | | |
| | | |
| | | @Autowired |
| | | private PlayController playController; |
| | | |
| | | /** |
| | | * 实时直播 - 开始直播 |
| | |
| | | * @return |
| | | */ |
| | | @RequestMapping(value = "/start") |
| | | private JSONObject start(String serial , |
| | | @RequestParam(required = false)Integer channel , |
| | | @RequestParam(required = false)String code, |
| | | @RequestParam(required = false)String cdn, |
| | | @RequestParam(required = false)String audio, |
| | | @RequestParam(required = false)String transport, |
| | | @RequestParam(required = false)String checkchannelstatus , |
| | | @RequestParam(required = false)String transportmode, |
| | | @RequestParam(required = false)String timeout |
| | | private DeferredResult<JSONObject> start(String serial , |
| | | @RequestParam(required = false)Integer channel , |
| | | @RequestParam(required = false)String code, |
| | | @RequestParam(required = false)String cdn, |
| | | @RequestParam(required = false)String audio, |
| | | @RequestParam(required = false)String transport, |
| | | @RequestParam(required = false)String checkchannelstatus , |
| | | @RequestParam(required = false)String transportmode, |
| | | @RequestParam(required = false)String timeout |
| | | |
| | | ){ |
| | | int getEncoding = closeWaitRTPInfo? 1: 0; |
| | | DeferredResult<JSONObject> resultDeferredResult = new DeferredResult<JSONObject>(); |
| | | Device device = storager.queryVideoDevice(serial); |
| | | |
| | | if (device == null ) { |
| | | JSONObject result = new JSONObject(); |
| | | result.put("error","device[ " + serial + " ]未找到"); |
| | | return result; |
| | | resultDeferredResult.setResult(result); |
| | | }else if (device.getOnline() == 0) { |
| | | JSONObject result = new JSONObject(); |
| | | result.put("error","device[ " + code + " ]offline"); |
| | | return result; |
| | | resultDeferredResult.setResult(result); |
| | | } |
| | | resultDeferredResult.onTimeout(()->{ |
| | | logger.info("播放等待超时"); |
| | | JSONObject result = new JSONObject(); |
| | | result.put("error","timeout"); |
| | | resultDeferredResult.setResult(result); |
| | | |
| | | // 清理RTP server |
| | | }); |
| | | |
| | | DeviceChannel deviceChannel = storager.queryChannel(serial, code); |
| | | if (deviceChannel == null) { |
| | | JSONObject result = new JSONObject(); |
| | | result.put("error","channel[ " + code + " ]未找到"); |
| | | return result; |
| | | resultDeferredResult.setResult(result); |
| | | }else if (deviceChannel.getStatus() == 0) { |
| | | JSONObject result = new JSONObject(); |
| | | result.put("error","channel[ " + code + " ]offline"); |
| | | return result; |
| | | resultDeferredResult.setResult(result); |
| | | } |
| | | DeferredResult<ResponseEntity<String>> play = playController.play(serial, code); |
| | | |
| | | // 查询是否已经在播放 |
| | | StreamInfo streamInfo = storager.queryPlayByDevice(device.getDeviceId(), code); |
| | | if (streamInfo == null) { |
| | | logger.debug("streamInfo 等于null, 重新点播"); |
| | | // streamInfo = cmder.playStreamCmd(device, code); |
| | | }else { |
| | | logger.debug("streamInfo 不等于null, 向流媒体查询是否正在推流"); |
| | | String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase(); |
| | | JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); |
| | | if (rtpInfo.getBoolean("exist")) { |
| | | logger.debug("向流媒体查询正在推流, 直接返回: " + streamInfo.getRtsp()); |
| | | JSONObject result = new JSONObject(); |
| | | result.put("StreamID", streamInfo.getSsrc()); |
| | | result.put("DeviceID", device.getDeviceId()); |
| | | result.put("ChannelID", code); |
| | | result.put("ChannelName", deviceChannel.getName()); |
| | | result.put("ChannelCustomName", ""); |
| | | result.put("FLV", streamInfo.getFlv()); |
| | | result.put("WS_FLV", streamInfo.getWs_flv()); |
| | | result.put("RTMP", streamInfo.getRtmp()); |
| | | result.put("HLS", streamInfo.getHls()); |
| | | result.put("RTSP", streamInfo.getRtsp()); |
| | | result.put("CDN", ""); |
| | | result.put("SnapURL", ""); |
| | | result.put("Transport", device.getTransport()); |
| | | result.put("StartAt", ""); |
| | | result.put("Duration", ""); |
| | | result.put("SourceVideoCodecName", ""); |
| | | result.put("SourceVideoWidth", ""); |
| | | result.put("SourceVideoHeight", ""); |
| | | result.put("SourceVideoFrameRate", ""); |
| | | result.put("SourceAudioCodecName", ""); |
| | | result.put("SourceAudioSampleRate", ""); |
| | | result.put("AudioEnable", ""); |
| | | result.put("Ondemand", ""); |
| | | result.put("InBytes", ""); |
| | | result.put("InBitRate", ""); |
| | | result.put("OutBytes", ""); |
| | | result.put("NumOutputs", ""); |
| | | result.put("CascadeSize", ""); |
| | | result.put("RelaySize", ""); |
| | | result.put("ChannelPTZType", 0); |
| | | return result; |
| | | } else { |
| | | logger.debug("向流媒体查询没有推流, 重新点播"); |
| | | storager.stopPlay(streamInfo); |
| | | // streamInfo = cmder.playStreamCmd(device, code); |
| | | } |
| | | } |
| | | |
| | | if (logger.isDebugEnabled()) { |
| | | logger.debug(String.format("设备预览 API调用,deviceId:%s ,channelId:%s",serial, code)); |
| | | logger.debug("设备预览 API调用,ssrc:"+streamInfo.getSsrc()+",ZLMedia streamId:"+Integer.toHexString(Integer.parseInt(streamInfo.getSsrc()))); |
| | | } |
| | | boolean lockFlag = true; |
| | | long startTime = System.currentTimeMillis(); |
| | | while (lockFlag) { |
| | | try { |
| | | if (System.currentTimeMillis() - startTime > 10 * 1000) { |
| | | storager.stopPlay(streamInfo); |
| | | logger.info("播放等待超时"); |
| | | JSONObject result = new JSONObject(); |
| | | result.put("error","timeout"); |
| | | return result; |
| | | } else { |
| | | |
| | | StreamInfo streamInfoNow = storager.queryPlayByDevice(serial, code); |
| | | logger.debug("正在向流媒体查询"); |
| | | if (streamInfoNow != null && streamInfoNow.getFlv() != null) { |
| | | streamInfo = streamInfoNow; |
| | | logger.debug("向流媒体查询到: " + streamInfoNow.getRtsp()); |
| | | lockFlag = false; |
| | | continue; |
| | | } else { |
| | | Thread.sleep(2000); |
| | | continue; |
| | | } |
| | | } |
| | | } catch (InterruptedException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | if(streamInfo!=null) { |
| | | play.setResultHandler((Object o)->{ |
| | | ResponseEntity<String> responseEntity = (ResponseEntity)o; |
| | | StreamInfo streamInfo = JSON.parseObject(responseEntity.getBody(), StreamInfo.class); |
| | | JSONObject result = new JSONObject(); |
| | | result.put("StreamID", streamInfo.getSsrc()); |
| | | result.put("StreamID", streamInfo.getStreamId()); |
| | | result.put("DeviceID", device.getDeviceId()); |
| | | result.put("ChannelID", code); |
| | | result.put("ChannelName", deviceChannel.getName()); |
| | |
| | | result.put("CascadeSize", ""); |
| | | result.put("RelaySize", ""); |
| | | result.put("ChannelPTZType", 0); |
| | | return result; |
| | | } else { |
| | | logger.warn("设备预览API调用失败!"); |
| | | JSONObject result = new JSONObject(); |
| | | result.put("error","调用失败"); |
| | | return result; |
| | | } |
| | | resultDeferredResult.setResult(result); |
| | | }); |
| | | return resultDeferredResult; |
| | | } |
| | | |
| | | /** |
| | |
| | | @RequestParam(required = false)String check_outputs |
| | | |
| | | ){ |
| | | StreamInfo streamInfo = storager.queryPlayByDevice(serial, code); |
| | | |
| | | StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(serial, code); |
| | | if (streamInfo == null) { |
| | | JSONObject result = new JSONObject(); |
| | | result.put("error","未找到流信息"); |
| | | return result; |
| | | } |
| | | cmder.streamByeCmd(streamInfo.getSsrc()); |
| | | storager.stopPlay(streamInfo); |
| | | cmder.streamByeCmd(streamInfo.getStreamId()); |
| | | redisCatchStorage.stopPlay(streamInfo); |
| | | return null; |
| | | } |
| | | |
| | |
| | | timeout: 10000 |
| | | # [不可用] jdbc数据库配置, 暂不支持 |
| | | datasource: |
| | | # name: eiot |
| | | # url: jdbc:mysql://127.0.0.1:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true |
| | | # username: |
| | | # password: |
| | | # type: com.alibaba.druid.pool.DruidDataSource |
| | | # driver-class-name: com.mysql.jdbc.Driver |
| | | name: eiot |
| | | url: jdbc:mysql://127.0.0.1:3306/eiot?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true |
| | | url: jdbc:sqlite::resource:wvp.sqlite |
| | | username: |
| | | password: |
| | | type: com.alibaba.druid.pool.DruidDataSource |
| | | driver-class-name: com.mysql.jdbc.Driver |
| | | driver-class-name: org.sqlite.JDBC |
| | | max-active: 1 |
| | | min-idle: 1 |
| | | |
| | | # [可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口 |
| | | server: |
| | |
| | | # 作为28181服务器的配置 |
| | | sip: |
| | | # [必须修改] 本机的IP, 必须是网卡上的IP |
| | | ip: 192.168.0.100 |
| | | ip: 192.168.1.44 |
| | | # [可选] 28181服务监听的端口 |
| | | port: 5060 |
| | | # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) |
| | | # 后两位为行业编码,定义参照附录D.3 |
| | | # 3701020049标识山东济南历下区 信息行业接入 |
| | | # [可选] |
| | | domain: 4401020049 |
| | | domain: 3402000000 |
| | | # [可选] |
| | | id: 44010200492000000001 |
| | | id: 34020000002000000001 |
| | | # [可选] 默认设备认证密码,后续扩展使用设备单独密码 |
| | | password: admin123 |
| | | password: 12345678 |
| | | |
| | | # 登陆的用户名密码 |
| | | auth: |
| | |
| | | #zlm服务器配置 |
| | | media: |
| | | # [必须修改] zlm服务器的内网IP |
| | | ip: 192.168.0.100 |
| | | ip: 192.168.1.44 |
| | | # [可选] zlm服务器的公网IP, 内网部署置空即可 |
| | | wanIp: |
| | | # [可选] zlm服务器的hook所使用的IP, 默认使用sip.ip |
| | |
| | | # [可选] zlm服务器的hook.admin_params=secret |
| | | secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc |
| | | # [可选] zlm服务器的general.streamNoneReaderDelayMS |
| | | streamNoneReaderDelayMS: 18000 # 无人观看多久自动关闭流 |
| | | # [可选] 关闭等待收到流编码信息后在返回, |
| | | # 设为false可以获得更好的兼容性,保证返回后流就可以播放, |
| | | # 设为true可以快速打开播放窗口,可以获得更好的体验 |
| | | closeWaitRTPInfo: false |
| | | # 启用udp多端口模式 |
| | | streamNoneReaderDelayMS: 600000 # 无人观看多久自动关闭流, -1表示永不自动关闭,即 关闭按需拉流 |
| | | # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true |
| | | autoApplyPlay: true |
| | | # [可选] 部分设备需要扩展SDP,需要打开此设置 |
| | | seniorSdp: false |
| | | # 启用udp多端口模式, 详细解释参考: https://github.com/xia-chu/ZLMediaKit/wiki/GB28181%E6%8E%A8%E6%B5%81 下的高阶使用 |
| | | rtp: |
| | | # [可选] 是否启用udp多端口模式, 开启后会在udpPortRange范围内选择端口用于媒体流传输 |
| | | enable: true |
| | |
| | | <el-option label="设备" value="false"></el-option> |
| | | <el-option label="子目录" value="true"></el-option> |
| | | </el-select> |
| | | 在线状态: <el-select size="mini" @change="search" v-model="online" placeholder="请选择" default-first-option> |
| | | 在线状态: <el-select size="mini" style="margin-right: 1rem;" @change="search" v-model="online" placeholder="请选择" default-first-option> |
| | | <el-option label="全部" value=""></el-option> |
| | | <el-option label="在线" value="on"></el-option> |
| | | <el-option label="离线" value="off"></el-option> |
| | | <el-option label="在线" value="true"></el-option> |
| | | <el-option label="离线" value="false"></el-option> |
| | | </el-select> |
| | | |
| | | <el-checkbox size="mini" style="margin-right: 1rem; float: right;" v-model="autoList" @change="autoListChange">自动刷新</el-checkbox> |
| | | </div> |
| | | <devicePlayer ref="devicePlayer" v-loading="isLoging"></devicePlayer> |
| | | <!--设备列表--> |
| | |
| | | <el-button-group> |
| | | <!-- <el-button size="mini" icon="el-icon-video-play" v-if="scope.row.parental == 0" @click="sendDevicePush(scope.row)">播放</el-button> --> |
| | | <el-button size="mini" icon="el-icon-video-play" @click="sendDevicePush(scope.row)">播放</el-button> |
| | | <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="scope.row.play" @click="stopDevicePush(scope.row)">停止</el-button> |
| | | <el-button size="mini" icon="el-icon-switch-button" type="danger" v-if="!!scope.row.streamId" @click="stopDevicePush(scope.row)">停止</el-button> |
| | | <el-button size="mini" icon="el-icon-s-open" type="primary" v-if="scope.row.parental == 1" @click="changeSubchannel(scope.row)">查看</el-button> |
| | | <el-button size="mini" icon="el-icon-video-camera" type="primary" @click="queryRecords(scope.row)">设备录象</el-button> |
| | | <!-- <el-button size="mini" @click="sendDevicePush(scope.row)">录像查询</el-button> --> |
| | |
| | | count: parseInt(this.$route.params.count), |
| | | total: 0, |
| | | beforeUrl: "/videoList", |
| | | isLoging: false |
| | | isLoging: false, |
| | | autoList: false |
| | | }; |
| | | }, |
| | | |
| | | mounted() { |
| | | this.initData(); |
| | | this.updateLooper = setInterval(this.initData, 10000); |
| | | if (this.autoList) { |
| | | this.updateLooper = setInterval(this.initData, 1500); |
| | | } |
| | | |
| | | }, |
| | | destroyed() { |
| | | this.$destroy('videojs'); |
| | |
| | | .then(function (res) { |
| | | console.log(res); |
| | | that.total = res.data.total; |
| | | that.deviceChannelList = res.data.data; |
| | | that.deviceChannelList = res.data.list; |
| | | // 防止出现表格错位 |
| | | that.$nextTick(() => { |
| | | that.$refs.channelListTable.doLayout(); |
| | |
| | | let deviceId = this.deviceId; |
| | | this.isLoging = true; |
| | | let channelId = itemData.channelId; |
| | | let getEncoding = itemData.hasAudio ? '1' : '0' |
| | | console.log("通知设备推流1:" + deviceId + " : " + channelId + ":" + getEncoding); |
| | | console.log("通知设备推流1:" + deviceId + " : " + channelId ); |
| | | let that = this; |
| | | this.$axios({ |
| | | method: 'get', |
| | | url: '/api/play/' + deviceId + '/' + channelId + '?getEncoding=' + getEncoding |
| | | url: '/api/play/' + deviceId + '/' + channelId |
| | | }).then(function (res) { |
| | | console.log(res.data) |
| | | let ssrc = res.data.ssrc; |
| | | let streamId = res.data.streamId; |
| | | that.isLoging = false; |
| | | if (!!ssrc) { |
| | | if (!!streamId) { |
| | | // that.$refs.devicePlayer.play(res.data, deviceId, channelId, itemData.hasAudio); |
| | | that.$refs.devicePlayer.openDialog("media", deviceId, channelId, { |
| | | streamInfo: res.data, |
| | |
| | | var that = this; |
| | | this.$axios({ |
| | | method: 'post', |
| | | url: '/api/play/' + itemData.ssrc + '/stop' |
| | | url: '/api/play/' + itemData.streamId + '/stop' |
| | | }).then(function (res) { |
| | | console.log(JSON.stringify(res)); |
| | | that.initData(); |
| | |
| | | }) |
| | | .then(function (res) { |
| | | that.total = res.data.total; |
| | | that.deviceChannelList = res.data.data; |
| | | that.deviceChannelList = res.data.list; |
| | | // 防止出现表格错位 |
| | | that.$nextTick(() => { |
| | | that.$refs.channelListTable.doLayout(); |
| | |
| | | }).then(function (res) { |
| | | console.log(JSON.stringify(res)); |
| | | }); |
| | | }, |
| | | autoListChange: function () { |
| | | if (this.autoList) { |
| | | this.updateLooper = setInterval(this.initData, 1500); |
| | | }else{ |
| | | window.clearInterval(this.updateLooper); |
| | | } |
| | | } |
| | | |
| | | } |
| | |
| | | <template> |
| | | <div id="devicePlayer" v-loading="isLoging"> |
| | | |
| | | |
| | | <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="close()"> |
| | | <!-- <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></LivePlayer> --> |
| | | <player ref="videoPlayer" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></player> |
| | |
| | | <p>采样率: {{item.sample_rate}}</p> |
| | | </div> |
| | | </div> |
| | | |
| | | |
| | | </div> |
| | | |
| | | </el-tab-pane> |
| | |
| | | searchHistoryResult: [] //媒体流历史记录搜索结果 |
| | | }, |
| | | showVideoDialog: false, |
| | | ssrc: '', |
| | | streamId: '', |
| | | convertKey: '', |
| | | deviceId: '', |
| | |
| | | this.tabActiveName = tab; |
| | | this.channelId = channelId; |
| | | this.deviceId = deviceId; |
| | | this.ssrc = ""; |
| | | this.streamId = ""; |
| | | this.videoUrl = "" |
| | | if (!!this.$refs.videoPlayer) { |
| | |
| | | console.log(val) |
| | | }, |
| | | play: function (streamInfo, hasAudio) { |
| | | |
| | | |
| | | this.hasaudio = hasAudio; |
| | | this.isLoging = false; |
| | | this.videoUrl = streamInfo.ws_flv; |
| | | this.ssrc = streamInfo.ssrc; |
| | | this.streamId = streamInfo.streamId; |
| | | this.playFromStreamInfo(false, streamInfo) |
| | | }, |
| | |
| | | this.$refs.videoPlayer.pause() |
| | | that.$axios({ |
| | | method: 'post', |
| | | url: '/api/play/' + that.ssrc + '/convert' |
| | | url: '/api/play/' + that.streamId + '/convert' |
| | | }).then(function (res) { |
| | | if (res.data.code == 0) { |
| | | that.convertKey = res.data.key; |
| | |
| | | } |
| | | this.convertKey = '' |
| | | }, |
| | | |
| | | |
| | | copySharedInfo: function (data) { |
| | | console.log('复制内容:' + data); |
| | | this.coverPlaying = false; |
| | |
| | | }, |
| | | playRecord: function (row) { |
| | | let that = this; |
| | | if (that.ssrc != "") { |
| | | if (that.streamId != "") { |
| | | that.stopPlayRecord(function () { |
| | | that.ssrc = "", |
| | | that.streamId = "", |
| | | that.playRecord(row); |
| | | }) |
| | | } else { |
| | |
| | | row.endTime |
| | | }).then(function (res) { |
| | | var streamInfo = res.data; |
| | | that.ssrc = streamInfo.ssrc; |
| | | that.streamId = streamInfo.streamId; |
| | | that.videoUrl = streamInfo.ws_flv; |
| | | }); |
| | | } |
| | |
| | | this.videoUrl = ''; |
| | | this.$axios({ |
| | | method: 'get', |
| | | url: '/api/playback/' + this.ssrc + '/stop' |
| | | url: '/api/playback/' + this.streamId + '/stop' |
| | | }).then(function (res) { |
| | | if (callback) callback() |
| | | }); |
| | |
| | | <div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;"> |
| | | <span style="font-size: 1rem; font-weight: bold;">设备列表</span> |
| | | <div style="position: absolute; right: 1rem; top: 0.3rem;"> |
| | | <el-button icon="el-icon-refresh-right" circle size="mini" @click="getDeviceList()"></el-button> |
| | | <el-button icon="el-icon-refresh-right" circle size="mini" :loading="getDeviceListLoading" @click="getDeviceList()"></el-button> |
| | | </div> |
| | | </div> |
| | | <devicePlayer ref="devicePlayer"></devicePlayer> |
| | |
| | | <el-table-column label="地址" width="180" align="center"> |
| | | <template slot-scope="scope"> |
| | | <div slot="reference" class="name-wrapper"> |
| | | <el-tag size="medium">{{ scope.row.host.address }}</el-tag> |
| | | <el-tag size="medium">{{ scope.row.hostAddress }}</el-tag> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | |
| | | <el-table-column label="操作" width="240" align="center" fixed="right"> |
| | | <template slot-scope="scope"> |
| | | <el-button size="mini" icon="el-icon-refresh" @click="refDevice(scope.row)">刷新通道</el-button> |
| | | <el-button size="mini" :ref="scope.row.deviceId + 'refbtn' " icon="el-icon-refresh" @click="refDevice(scope.row)">刷新通道</el-button> |
| | | <el-button size="mini" icon="el-icon-s-open" type="primary" @click="showChannelList(scope.row)">查看通道</el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | winHeight: window.innerHeight - 200, |
| | | currentPage:1, |
| | | count:15, |
| | | total:0 |
| | | total:0, |
| | | getDeviceListLoading: false |
| | | }; |
| | | }, |
| | | computed: { |
| | |
| | | }, |
| | | getDeviceList: function() { |
| | | let that = this; |
| | | |
| | | this.getDeviceListLoading = true; |
| | | this.$axios.get(`/api/devices`,{ |
| | | params: { |
| | | page: that.currentPage - 1, |
| | |
| | | } ) |
| | | .then(function (res) { |
| | | console.log(res); |
| | | console.log(res.data.list); |
| | | that.total = res.data.total; |
| | | that.deviceList = res.data.data; |
| | | that.deviceList = res.data.list; |
| | | that.getDeviceListLoading = false; |
| | | }) |
| | | .catch(function (error) { |
| | | console.log(error); |
| | | that.getDeviceListLoading = false; |
| | | }); |
| | | |
| | | }, |
| | |
| | | refDevice: function(itemData) { |
| | | ///api/devices/{deviceId}/sync |
| | | console.log("刷新对应设备:" + itemData.deviceId); |
| | | var that = this; |
| | | that.$refs[itemData.deviceId + 'refbtn' ].loading = true; |
| | | this.$axios({ |
| | | method: 'post', |
| | | url: '/api/devices/' + itemData.deviceId + '/sync' |
| | | }).then(function(res) { |
| | | // console.log("刷新设备结果:"+JSON.stringify(res)); |
| | | console.log("刷新设备结果:"+JSON.stringify(res)); |
| | | if (!res.data.deviceId) { |
| | | that.$message({ |
| | | showClose: true, |
| | | message: res.data, |
| | | type: 'error' |
| | | }); |
| | | }else{ |
| | | that.$message({ |
| | | showClose: true, |
| | | message: '请求成功', |
| | | type: 'success' |
| | | }); |
| | | } |
| | | that.initData() |
| | | that.$refs[itemData.deviceId + 'refbtn' ].loading = false; |
| | | }).catch(function(e) { |
| | | that.$message({ |
| | | showClose: true, |
| | | message: '请求成功', |
| | | type: 'success' |
| | | }); |
| | | console.error(e) |
| | | that.$refs[itemData.deviceId + 'refbtn' ].loading = false; |
| | | });; |
| | | }, |
| | | //通知设备上传媒体流 |