diff --git a/pom.xml b/pom.xml index 86b4c176..2f45a6d4 100644 --- a/pom.xml +++ b/pom.xml @@ -13,13 +13,38 @@ wvp web video platform + + + nexus-aliyun + Nexus aliyun + https://maven.aliyun.com/repository/public + default + + false + + + true + + + + + + nexus-aliyun + Nexus aliyun + https://maven.aliyun.com/repository/public + + false + + + true + + + + UTF-8 - 4.1.5 - 3.5.5 - 2.0.5 5.2.0 ${project.build.directory}/generated-snippets ${project.basedir}/docs/asciidoc @@ -31,30 +56,16 @@ org.springframework.boot - spring-boot-starter-jdbc - - - org.springframework.boot - spring-boot-starter-tomcat + spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-web - org.springframework - spring-context - - - - - org.springframework.data - spring-data-redis - - - redis.clients - jedis - 3.3.0 + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.1.4 @@ -71,36 +82,25 @@ 8.0.22 - - - org.mybatis - mybatis - ${mybatis.version} - + - org.mybatis - mybatis-spring - ${mybatis.spring.version} + org.xerial + sqlite-jdbc + 3.32.3.2 - + com.github.pagehelper - pagehelper - ${pagehelper.version} + pagehelper-spring-boot-starter + 1.2.10 - - - tk.mybatis - mapper - ${mapper.version} - - - org.apache.commons - commons-lang3 - 3.11 - + + + + + diff --git a/src/main/java/com/genersoft/iot/vmp/common/PageResult.java b/src/main/java/com/genersoft/iot/vmp/common/PageResult.java deleted file mode 100644 index 6d7c89e6..00000000 --- a/src/main/java/com/genersoft/iot/vmp/common/PageResult.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.genersoft.iot.vmp.common; - - -import java.util.List; - -public class PageResult { - - private int page; - private int count; - private int total; - - private List data; - - public List getData() { - return data; - } - - public int getPage() { - return page; - } - - public void setPage(int page) { - this.page = page; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } - - public int getTotal() { - return total; - } - - public void setTotal(int total) { - this.total = total; - } - - public void setData(List data) { - this.data = data; - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java index 53bda91a..0fb76b93 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java @@ -4,7 +4,6 @@ import com.alibaba.fastjson.JSONArray; public class StreamInfo { - private String ssrc; private String streamId; private String deviceID; private String cahnnelId; @@ -20,14 +19,6 @@ public class StreamInfo { private String rtsp; private JSONArray tracks; - public String getSsrc() { - return ssrc; - } - - public void setSsrc(String ssrc) { - this.ssrc = ssrc; - } - public String getDeviceID() { return deviceID; } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ApplicationCheckRunner.java b/src/main/java/com/genersoft/iot/vmp/conf/ApplicationCheckRunner.java new file mode 100644 index 00000000..faa9ef12 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ApplicationCheckRunner.java @@ -0,0 +1,63 @@ +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 ); + } + + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java index 54c0711f..0b4ecbb4 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java @@ -8,8 +8,10 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.sip.*; +import javax.sip.header.CallIdHeader; import javax.sip.message.Response; +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -34,6 +36,9 @@ public class SipLayer implements SipListener { @Autowired private SIPProcessorFactory processorFactory; + @Autowired + private SipSubscribe sipSubscribe; + private SipStack sipStack; private SipFactory sipFactory; @@ -133,17 +138,34 @@ public class SipLayer implements SipListener { // 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) { - // } + + } /** diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java index 637ee9a7..16ed56a3 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/auth/DigestServerAuthenticationHelper.java @@ -27,6 +27,7 @@ package com.genersoft.iot.vmp.gb28181.auth; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.text.DecimalFormat; import java.util.Date; import java.util.Random; @@ -103,9 +104,12 @@ public class DigestServerAuthenticationHelper { .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); @@ -170,42 +174,116 @@ public class DigestServerAuthenticationHelper { 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); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/auth/RegisterLogicHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/auth/RegisterLogicHandler.java index 6e4588d2..6fe63cc5 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/auth/RegisterLogicHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/auth/RegisterLogicHandler.java @@ -21,6 +21,6 @@ public class RegisterLogicHandler { // TODO 后续处理,只有第一次注册时调用查询设备信息,如需更新调用更新API接口 cmder.deviceInfoQuery(device); - cmder.catalogQuery(device); + cmder.catalogQuery(device, null); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java index de52ac69..9393106e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java @@ -1,9 +1,6 @@ package com.genersoft.iot.vmp.gb28181.bean; -import java.util.List; -import java.util.Map; - public class Device { /** @@ -45,25 +42,37 @@ 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 channelMap; + private Long registerTimeMillis; + /** + * 通道个数 + */ private int channelCount; - private List channelList; - public String getDeviceId() { return deviceId; } @@ -120,12 +129,28 @@ public class Device { this.streamMode = streamMode; } - public Host getHost() { - return host; + public String getIp() { + return ip; + } + + 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 setHost(Host host) { - this.host = host; + public void setHostAddress(String hostAddress) { + this.hostAddress = hostAddress; } public int getOnline() { @@ -144,11 +169,11 @@ public class Device { this.channelCount = channelCount; } - public List getChannelList() { - return channelList; + public Long getRegisterTimeMillis() { + return registerTimeMillis; } - public void setChannelList(List channelList) { - this.channelList = channelList; + public void setRegisterTimeMillis(Long registerTimeMillis) { + this.registerTimeMillis = registerTimeMillis; } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java index 810feabd..ca6ef60f 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java @@ -2,10 +2,17 @@ package com.genersoft.iot.vmp.gb28181.bean; public class DeviceChannel { + + /** * 通道id */ private String channelId; + + /** + * 设备id + */ + private String deviceId; /** * 通道名 @@ -141,18 +148,20 @@ public class DeviceChannel { /** * 流唯一编号,存在表示正在直播 */ - 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; @@ -379,14 +388,6 @@ public class DeviceChannel { this.subCount = subCount; } - public String getSsrc() { - return ssrc; - } - - public void setSsrc(String ssrc) { - this.ssrc = ssrc; - } - public boolean isHasAudio() { return hasAudio; } @@ -395,11 +396,11 @@ public class DeviceChannel { 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; } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java new file mode 100644 index 00000000..176a435e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java @@ -0,0 +1,50 @@ +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 errorSubscribes = new ConcurrentHashMap<>(); + + private Map 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(); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java index f063b49b..1e374ad6 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java @@ -7,8 +7,10 @@ import javax.sip.header.CSeqHeader; 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; import org.springframework.beans.factory.annotation.Autowired; @@ -54,7 +56,10 @@ public class SIPProcessorFactory { @Autowired private IVideoManagerStorager storager; - + + @Autowired + private IRedisCatchStorage redisCatchStorage; + @Autowired private EventPublisher publisher; @@ -82,10 +87,11 @@ public class SIPProcessorFactory { @Autowired @Lazy private RegisterResponseProcessor registerResponseProcessor; - + @Autowired private OtherResponseProcessor otherResponseProcessor; - + + // 注:这里使用注解会导致循环依赖注入,暂用springBean private SipProvider tcpSipProvider; @@ -140,6 +146,7 @@ public class SIPProcessorFactory { processor.setOffLineDetector(offLineDetector); processor.setCmder(cmder); processor.setStorager(storager); + processor.setRedisCatchStorage(redisCatchStorage); return processor; } else { return new OtherRequestProcessor(); @@ -147,6 +154,7 @@ public class SIPProcessorFactory { } public ISIPResponseProcessor createResponseProcessor(ResponseEvent evt) { + Response response = evt.getResponse(); CSeqHeader cseqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME); String method = cseqHeader.getMethod(); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java index 0c1e63d6..0759d7fc 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.callback; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -24,8 +25,10 @@ public class DeferredResultHolder { public static final String CALLBACK_CMD_PlAY = "CALLBACK_PLAY"; - private Map map = new HashMap(); - + public static final String CALLBACK_CMD_STOP = "CALLBACK_STOP"; + + private Map map = new ConcurrentHashMap(); + public void put(String key, DeferredResult result) { map.put(key, result); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java index d3f36cd1..67fd9967 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java @@ -2,8 +2,8 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.Device; +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; -import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; /** * @Description:设备能力接口,用于定义设备的控制、查询能力 @@ -84,7 +84,7 @@ public interface ISIPCommander { * @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); /** * 请求回放视频流 @@ -94,15 +94,16 @@ public interface ISIPCommander { * @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); - + /** * 语音广播 * @@ -176,7 +177,7 @@ public interface ISIPCommander { * * @param device 视频设备 */ - boolean catalogQuery(Device device); + boolean catalogQuery(Device device, SipSubscribe.Event errorEvent); /** * 查询录像信息 @@ -214,4 +215,11 @@ public interface ISIPCommander { * @param device 视频设备 */ boolean mobilePostitionQuery(Device device); + + /** + * 释放rtpserver + * @param device + * @param channelId + */ + void closeRTPServer(Device device, String channelId); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java index 6982144f..739d5a98 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java @@ -47,9 +47,8 @@ public class SIPRequestHeaderProvider { 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 viaHeaders = new ArrayList(); ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(sipConfig.getSipIp(), sipConfig.getSipPort(), @@ -75,22 +74,21 @@ public class SIPRequestHeaderProvider { 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 viaHeaders = new ArrayList(); - // 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); @@ -122,20 +120,18 @@ public class SIPRequestHeaderProvider { // 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 viaHeaders = new ArrayList(); - // 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 @@ -167,7 +163,7 @@ public class SIPRequestHeaderProvider { // 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; } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java index 3d5aaccf..e1d474ff 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java @@ -1,17 +1,14 @@ 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; @@ -19,9 +16,13 @@ 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.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; @@ -41,6 +42,8 @@ import com.genersoft.iot.vmp.gb28181.utils.DateUtil; */ @Component public class SIPCommander implements ISIPCommander { + + private final Logger logger = LoggerFactory.getLogger(SIPCommander.class); @Autowired private SipConfig sipConfig; @@ -53,6 +56,9 @@ public class SIPCommander implements ISIPCommander { @Autowired private IVideoManagerStorager storager; + + @Autowired + private IRedisCatchStorage redisCatchStorage; @Autowired @Qualifier(value="tcpSipProvider") @@ -63,14 +69,20 @@ public class SIPCommander implements ISIPCommander { 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; + /** @@ -176,19 +188,29 @@ public class SIPCommander implements ISIPCommander { * @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(); @@ -237,14 +259,14 @@ public class SIPCommander implements ISIPCommander { * @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("\r\n"); @@ -258,7 +280,6 @@ public class SIPCommander implements ISIPCommander { ptzXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), "ViaPtzBranch", "FromPtzTag", "ToPtzTag"); - transmitRequest(device, request); return true; } catch (SipException | ParseException | InvalidArgumentException e) { @@ -266,28 +287,39 @@ public class SIPCommander implements ISIPCommander { } 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"); @@ -297,7 +329,8 @@ public class SIPCommander implements ISIPCommander { // 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"); @@ -327,17 +360,14 @@ public class SIPCommander implements ISIPCommander { } 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) { @@ -354,9 +384,10 @@ public class SIPCommander implements ISIPCommander { * @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(); // 添加订阅 @@ -378,57 +409,91 @@ public class SIPCommander implements ISIPCommander { 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"); - } - 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"); + + 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("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; } @@ -436,6 +501,9 @@ public class SIPCommander implements ISIPCommander { if (dialog == null) { return; } + + + Request byeRequest = dialog.createRequest(Request.BYE); SipURI byeURI = (SipURI) byeRequest.getRequestURI(); String vh = transaction.getRequest().getHeader(ViaHeader.NAME).toString(); @@ -452,8 +520,16 @@ public class SIPCommander implements ISIPCommander { } 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) { @@ -571,6 +647,7 @@ public class SIPCommander implements ISIPCommander { catalogXml.append("\r\n"); Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), "ViaDeviceInfoBranch", "FromDeviceInfoTag", "ToDeviceInfoTag"); + transmitRequest(device, request); } catch (SipException | ParseException | InvalidArgumentException e) { @@ -586,7 +663,7 @@ public class SIPCommander implements ISIPCommander { * @param device 视频设备 */ @Override - public boolean catalogQuery(Device device) { + public boolean catalogQuery(Device device, SipSubscribe.Event errorEvent) { // 清空通道 storager.cleanChannelsForDevice(device.getDeviceId()); try { @@ -598,8 +675,9 @@ public class SIPCommander implements ISIPCommander { catalogXml.append("" + device.getDeviceId() + "\r\n"); catalogXml.append("\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; @@ -631,7 +709,8 @@ public class SIPCommander implements ISIPCommander { recordInfoXml.append("all\r\n"); recordInfoXml.append("\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(); @@ -683,17 +762,45 @@ public class SIPCommander implements ISIPCommander { // 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); + } + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java index c987f5ee..cab4a9bb 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java @@ -10,6 +10,7 @@ import javax.sip.SipException; 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; @@ -48,6 +49,8 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { private IVideoManagerStorager storager; + private IRedisCatchStorage redisCatchStorage; + private EventPublisher publisher; private RedisUtil redis; @@ -294,7 +297,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { device.setStreamMode("UDP"); } storager.updateDevice(device); - cmder.catalogQuery(device); + cmder.catalogQuery(device, null); // 回复200 OK responseAck(evt); if (offLineDetector.isOnline(deviceId)) { @@ -315,12 +318,16 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { 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(); } @@ -447,10 +454,10 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { 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) { @@ -503,4 +510,11 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor { this.offLineDetector = offLineDetector; } + public IRedisCatchStorage getRedisCatchStorage() { + return redisCatchStorage; + } + + public void setRedisCatchStorage(IRedisCatchStorage redisCatchStorage) { + this.redisCatchStorage = redisCatchStorage; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java index be076bd8..4faab0ef 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/RegisterRequestProcessor.java @@ -107,17 +107,15 @@ public class RegisterRequestProcessor extends SIPRequestAbstractProcessor { 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; @@ -141,9 +139,15 @@ public class RegisterRequestProcessor extends SIPRequestAbstractProcessor { // 下发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); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java index ae7182d9..93f533f9 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/response/impl/InviteResponseProcessor.java @@ -12,6 +12,7 @@ import javax.sip.header.ViaHeader; 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; @@ -23,14 +24,14 @@ import com.genersoft.iot.vmp.gb28181.transmit.response.ISIPResponseProcessor; /** - * @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响应 @@ -49,48 +50,16 @@ public class InviteResponseProcessor implements ISIPResponseProcessor { // 成功响应 // 下发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) { diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java index f76cdd90..9daef230 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.media.zlm; 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; @@ -29,6 +30,9 @@ public class ZLMHTTPProxyController { @Autowired private IVideoManagerStorager storager; + @Autowired + private IRedisCatchStorage redisCatchStorage; + @Value("${media.port}") private int mediaHttpPort; @@ -36,10 +40,10 @@ public class ZLMHTTPProxyController { @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, diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java index 99da6243..cb8ad055 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -4,13 +4,17 @@ import java.math.BigInteger; 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; @@ -43,15 +47,24 @@ public class ZLMHttpHookListener { @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; @@ -135,34 +148,6 @@ public class ZLMHttpHookListener { 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 @@ -268,15 +253,13 @@ public class ZLMHttpHookListener { 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); } } @@ -299,17 +282,15 @@ public class ZLMHttpHookListener { 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(); @@ -330,7 +311,31 @@ public class ZLMHttpHookListener { 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"); @@ -354,7 +359,7 @@ public class ZLMHttpHookListener { // 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(); diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java index 1e38bdc7..ac1f51a4 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java @@ -116,4 +116,8 @@ public class ZLMRESTfulUtils { public JSONObject openRtpServer(Map param){ return sendPost("openRtpServer",param); } + + public JSONObject closeRtpServer(Map param) { + return sendPost("closeRtpServer",param); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java new file mode 100644 index 00000000..f69ff0f0 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java @@ -0,0 +1,95 @@ +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 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 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++; + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java index 3f88b2a3..282699f9 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java @@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON; 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; @@ -30,6 +31,9 @@ public class ZLMRunner implements CommandLineRunner { @Autowired private IVideoManagerStorager storager; + @Autowired + private IRedisCatchStorage redisCatchStorage; + @Value("${media.ip}") private String mediaIp; @@ -69,7 +73,7 @@ public class ZLMRunner implements CommandLineRunner { logger.info("zlm接入成功..."); if (autoConfig) saveZLMConfig(); mediaServerConfig = getMediaServerConfig(); - storager.updateMediaInfo(mediaServerConfig); + redisCatchStorage.updateMediaInfo(mediaServerConfig); } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMUtils.java deleted file mode 100644 index 8195b656..00000000 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMUtils.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.genersoft.iot.vmp.media.zlm; - -import com.alibaba.fastjson.JSONObject; -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 ZLMUtils { - - @Value("${media.rtp.udpPortRange}") - private String udpPortRange; - - @Autowired - private ZLMRESTfulUtils zlmresTfulUtils; - - private int[] udpPortRangeArray = new int[2]; - - private int currentPort = 0; - - public int getNewRTPPort(String ssrc) { - String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase(); - Map param = new HashMap<>(); - int newPort = getPortFromUdpPortRange(); - param.put("port", newPort); - param.put("enable_tcp", 1); - param.put("stream_id", streamId); - JSONObject jsonObject = zlmresTfulUtils.openRtpServer(param); - if (jsonObject != null && jsonObject.getInteger("code") == 0) { - return newPort; - } else { - return getNewRTPPort(ssrc); - } - } - - 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++; - } - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java new file mode 100644 index 00000000..8bc78b93 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java @@ -0,0 +1,58 @@ +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 queryPlayByDeviceId(String deviceId); + + boolean startPlayback(StreamInfo stream); + + boolean stopPlayback(StreamInfo streamInfo); + + StreamInfo queryPlaybackByDevice(String deviceId, String code); +} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java index b58caae3..c601f7e5 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java @@ -1,15 +1,10 @@ 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:视频设备数据存储接口 @@ -18,19 +13,6 @@ import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; */ public interface IVideoManagerStorager { - /** - * 更新流媒体信息 - * @param mediaServerConfig - * @return - */ - public boolean updateMediaInfo(MediaServerConfig mediaServerConfig); - - /** - * 获取流媒体信息 - * @return - */ - public MediaServerConfig getMediaInfo(); - /** * 根据设备ID判断设备是否存在 * @@ -79,7 +61,7 @@ public interface IVideoManagerStorager { * @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); /** * 获取某个设备的通道列表 @@ -88,6 +70,7 @@ public interface IVideoManagerStorager { * @return */ public List queryChannelsByDeviceId(String deviceId); + /** * 获取某个设备的通道 * @param deviceId 设备ID @@ -95,21 +78,20 @@ public interface IVideoManagerStorager { */ public DeviceChannel queryChannel(String deviceId, String channelId); - /** + /** * 获取多个设备 - * - * @param deviceIds 设备ID数组 + * @param page 当前页数 + * @param count 每页数量 * @return List 设备对象数组 */ - public PageResult queryVideoDeviceList(String[] deviceIds, int page, int count); + public PageInfo queryVideoDeviceList(int page, int count); /** * 获取多个设备 * - * @param deviceIds 设备ID数组 * @return List 设备对象数组 */ - public List queryVideoDeviceList(String[] deviceIds); + public List queryVideoDeviceList(); /** * 删除设备 @@ -135,27 +117,6 @@ public interface IVideoManagerStorager { */ 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); /** * 查询子设备 @@ -166,12 +127,8 @@ public interface IVideoManagerStorager { * @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(); /** * 清空通道 @@ -179,45 +136,4 @@ public interface IVideoManagerStorager { */ void cleanChannelsForDevice(String deviceId); - StreamInfo queryPlayBySSRC(String ssrc); - - StreamInfo queryPlayByDevice(String deviceId, String code); - - Map 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 queryParentPlatformList(int page, int count); - - /** - * 获取上级平台 - * @param platformGbId - * @return - */ - public ParentPlatform queryParentPlatById(String platformGbId); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/VideoManagerStoragerFactory.java b/src/main/java/com/genersoft/iot/vmp/storager/VideoManagerStoragerFactory.java deleted file mode 100644 index 70bdad76..00000000 --- a/src/main/java/com/genersoft/iot/vmp/storager/VideoManagerStoragerFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.genersoft.iot.vmp.storager; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.stereotype.Component; - -import com.genersoft.iot.vmp.conf.VManagerConfig; - -/** - * @Description:视频设备数据存储工厂,根据存储策略,返回对应的存储器 - * @author: swwheihei - * @date: 2020年5月6日 下午2:15:16 - */ -@Component -public class VideoManagerStoragerFactory { - - @Autowired - private VManagerConfig vmConfig; - - @Autowired - private IVideoManagerStorager jdbcStorager; - - @Autowired - private IVideoManagerStorager redisStorager; - - @Bean("storager") - public IVideoManagerStorager getStorager() { - if ("redis".equals(vmConfig.getDatabase().toLowerCase())) { - return redisStorager; - } else if ("jdbc".equals(vmConfig.getDatabase().toLowerCase())) { - return jdbcStorager; - } - return redisStorager; - } - -} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/VodeoMannagerTask.java b/src/main/java/com/genersoft/iot/vmp/storager/VodeoMannagerTask.java deleted file mode 100644 index c96e4bb0..00000000 --- a/src/main/java/com/genersoft/iot/vmp/storager/VodeoMannagerTask.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.genersoft.iot.vmp.storager; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.stereotype.Component; - -@Component -public class VodeoMannagerTask implements CommandLineRunner { - - @Autowired - private IVideoManagerStorager storager; - - @Override - public void run(String... strings) throws Exception { - storager.updateCatch(); - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java new file mode 100644 index 00000000..eecc0bb5 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java @@ -0,0 +1,51 @@ +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 = {" "}) + List 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); + +} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java new file mode 100644 index 00000000..3c10618e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java @@ -0,0 +1,66 @@ +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 getDevices(); + + @Delete("DELETE FROM device WHERE deviceId=#{deviceId}") + int del(String deviceId); +} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java new file mode 100644 index 00000000..cebb30b8 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java @@ -0,0 +1,166 @@ +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 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 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 playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, + List 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 queryPlayByDeviceId(String deviceId) { + Map streamInfos = new HashMap<>(); +// List playLeys = redis.keys(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId)); + List 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 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()); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java new file mode 100644 index 00000000..01ed247e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java @@ -0,0 +1,202 @@ +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 all = deviceChannelMapper.queryChannelsByDeviceId(deviceId, null, query, hasSubChannel, online); + return new PageInfo<>(all); + } + + + + @Override + public List queryChannelsByDeviceId(String deviceId) { + return deviceChannelMapper.queryChannelsByDeviceId(deviceId, null,null, null, null); + } + + @Override + public PageInfo querySubChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, String online, int page, int count) { + PageHelper.startPage(page, count); + List 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 分页设备对象数组 + */ + @Override + public PageInfo queryVideoDeviceList(int page, int count) { + PageHelper.startPage(page, count); + List all = deviceMapper.getDevices(); + return new PageInfo<>(all); + } + + /** + * 获取多个设备 + * + * @return List 设备对象数组 + */ + @Override + public List queryVideoDeviceList() { + + List 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); + } + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java deleted file mode 100644 index 5e8ba1a8..00000000 --- a/src/main/java/com/genersoft/iot/vmp/storager/jdbc/VideoManagerJdbcStoragerImpl.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.genersoft.iot.vmp.storager.jdbc; - -import java.util.List; -import java.util.Map; - -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.DeviceChannel; -import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; -import org.springframework.stereotype.Component; -import org.springframework.stereotype.Service; - -import com.genersoft.iot.vmp.common.VideoManagerConstants; -import com.genersoft.iot.vmp.gb28181.bean.Device; -import com.genersoft.iot.vmp.storager.IVideoManagerStorager; - -/** - * @Description:视频设备数据存储-jdbc实现 - * @author: swwheihei - * @date: 2020年5月6日 下午2:28:12 - */ -@Component("jdbcStorager") -public class VideoManagerJdbcStoragerImpl implements IVideoManagerStorager { - - @Override - public boolean updateMediaInfo(MediaServerConfig mediaServerConfig) { - return false; - } - - @Override - public MediaServerConfig getMediaInfo() { - return null; - } - - /** - * 根据设备ID判断设备是否存在 - * - * @param deviceId 设备ID - * @return true:存在 false:不存在 - */ - @Override - public boolean exists(String deviceId) { - // TODO Auto-generated method stub - return false; - } - - /** - * 视频设备创建 - * - * @param device 设备对象 - * @return true:创建成功 false:创建失败 - */ - @Override - public boolean create(Device device) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean updateDevice(Device device) { - return false; - } - - @Override - public void updateChannel(String deviceId, DeviceChannel channel) { - - } - - - /** - * 获取设备 - * - * @param deviceId 设备ID - * @return Device 设备对象 - */ - @Override - public Device queryVideoDevice(String deviceId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public PageResult queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, String online, int page, int count) { - return null; - } - - - @Override - public List queryChannelsByDeviceId(String deviceId) { - return null; - } - - @Override - public DeviceChannel queryChannel(String deviceId, String channelId) { - return null; - } - - @Override - public PageResult queryVideoDeviceList(String[] deviceIds, int page, int count) { - return null; - } - - /** - * 获取多个设备 - * - * @param deviceIds 设备ID数组 - * @return List 设备对象数组 - */ - @Override - public List queryVideoDeviceList(String[] deviceIds) { - // TODO Auto-generated method stub - return null; - } - - /** - * 删除设备 - * - * @param deviceId 设备ID - * @return true:删除成功 false:删除失败 - */ - @Override - public boolean delete(String deviceId) { - // TODO Auto-generated method stub - return false; - } - - /** - * 更新设备在线 - * - * @param deviceId 设备ID - * @return true:更新成功 false:更新失败 - */ - @Override - public boolean online(String deviceId) { - // TODO Auto-generated method stub - return false; - } - - /** - * 更新设备离线 - * - * @param deviceId 设备ID - * @return true:更新成功 false:更新失败 - */ - @Override - public boolean outline(String deviceId) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean stopPlay(StreamInfo streamInfo) { - return false; - } - - @Override - public StreamInfo queryPlay(StreamInfo streamInfo) { - return null; - } - - @Override - public PageResult querySubChannels(String deviceId, String channelId, String query, Boolean hasSubChannel, String online, int page, int count) { - return null; - } - - @Override - public void updateCatch() { - System.out.println("##################"); - } - - @Override - public void cleanChannelsForDevice(String deviceId) { - - } - - @Override - public boolean startPlay(StreamInfo stream) { - return false; - } - - @Override - public StreamInfo queryPlayBySSRC(String ssrc) { - return null; - } - - @Override - public StreamInfo queryPlayByDevice(String deviceId, String code) { - return null; - } - - @Override - public Map queryPlayByDeviceId(String deviceId) { - - return null; - } - - @Override - public boolean startPlayback(StreamInfo streamInfo) { - return false; - } - - @Override - public boolean stopPlayback(StreamInfo streamInfo) { - return false; - } - - @Override - public StreamInfo queryPlaybackByDevice(String deviceId, String channelId) { - return null; - } - - @Override - public StreamInfo queryPlaybackBySSRC(String ssrc) { - return null; - } - - @Override - public boolean updateParentPlatform(ParentPlatform parentPlatform) { - return false; - } - - @Override - public boolean deleteParentPlatform(ParentPlatform parentPlatform) { - return false; - } - - @Override - public PageResult queryParentPlatformList(int page, int count) { - return null; - } - - @Override - public ParentPlatform queryParentPlatById(String platformGbId) { - return null; - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java deleted file mode 100644 index 99c7f06c..00000000 --- a/src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java +++ /dev/null @@ -1,600 +0,0 @@ -package com.genersoft.iot.vmp.storager.redis; - -import java.util.*; - -import com.alibaba.fastjson.JSON; -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.DeviceChannel; -import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.genersoft.iot.vmp.common.VideoManagerConstants; -import com.genersoft.iot.vmp.gb28181.bean.Device; -import com.genersoft.iot.vmp.storager.IVideoManagerStorager; -import com.genersoft.iot.vmp.utils.redis.RedisUtil; -import org.springframework.util.StringUtils; - -/** - * @Description:视频设备数据存储-redis实现 - * @author: swwheihei - * @date: 2020年5月6日 下午2:31:42 - */ -@Component("redisStorager") -public class VideoManagerRedisStoragerImpl implements IVideoManagerStorager { - - @Autowired - private RedisUtil redis; - - private HashMap>> deviceMap = new HashMap<>(); - - - /** - * 根据设备ID判断设备是否存在 - * - * @param deviceId 设备ID - * @return true:存在 false:不存在 - */ - @Override - public boolean exists(String deviceId) { - return redis.hasKey(VideoManagerConstants.DEVICE_PREFIX+deviceId); - } - - /** - * 视频设备创建 - * - * @param device 设备对象 - * @return true:创建成功 false:创建失败 - */ - @Override - public boolean create(Device device) { - return redis.set(VideoManagerConstants.DEVICE_PREFIX+device.getDeviceId(), device); - } - - - - /** - * 视频设备更新 - * - * @param device 设备对象 - * @return true:更新成功 false:更新失败 - */ - @Override - public boolean updateDevice(Device device) { - if (deviceMap.get(device.getDeviceId()) == null) { - deviceMap.put(device.getDeviceId(), new HashMap>()); - } - // 更新device中的通道数量 - device.setChannelCount(deviceMap.get(device.getDeviceId()).size()); - // 存储device - return redis.set(VideoManagerConstants.DEVICE_PREFIX+device.getDeviceId(), device); - - - } - - @Override - public void updateChannel(String deviceId, DeviceChannel channel) { - String channelId = channel.getChannelId(); - HashMap> channelMap = deviceMap.get(deviceId); - if (channelMap == null) return; - // 作为父设备, 确定自己的子节点数 - if (channelMap.get(channelId) == null) { - channelMap.put(channelId, new HashSet()); - }else if (channelMap.get(channelId).size() > 0) { - channel.setSubCount(channelMap.get(channelId).size()); - } - - // 存储通道 - redis.set(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + - "_" + channel.getChannelId() + - "_" + (channel.getStatus() == 1 ? "on":"off") + - "_" + (channelMap.get(channelId).size() > 0)+ - "_" + (StringUtils.isEmpty(channel.getParentId())?null:channel.getParentId()), - channel); - // 更新device中的通道数量 - Device device = (Device)redis.get(VideoManagerConstants.DEVICE_PREFIX+deviceId); - device.setChannelCount(deviceMap.get(deviceId).size()); - redis.set(VideoManagerConstants.DEVICE_PREFIX+device.getDeviceId(), device); - - - // 如果有父设备,更新父设备内子节点数 - String parentId = channel.getParentId(); - if (!StringUtils.isEmpty(parentId) && !parentId.equals(deviceId)) { - - if (channelMap.get(parentId) == null) { - channelMap.put(parentId, new HashSet()); - } - channelMap.get(parentId).add(channelId); - - DeviceChannel deviceChannel = queryChannel(deviceId, parentId); - if (deviceChannel != null) { - deviceChannel.setSubCount(channelMap.get(parentId).size()); - redis.set(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + - "_" + deviceChannel.getChannelId() + - "_" + (deviceChannel.getStatus() == 1 ? "on":"off") + - "_" + (channelMap.get(deviceChannel.getChannelId()).size() > 0)+ - "_" + (StringUtils.isEmpty(deviceChannel.getParentId())?null:deviceChannel.getParentId()), - deviceChannel); - - } - } - - } - - /** - * 获取设备 - * - * @param deviceId 设备ID - * @return Device 设备对象 - */ - @Override - public Device queryVideoDevice(String deviceId) { - return (Device)redis.get(VideoManagerConstants.DEVICE_PREFIX+deviceId); - } - - @Override - public PageResult queryChannelsByDeviceId(String deviceId, String query, Boolean hasSubChannel, String online, int page, int count) { - // 获取到所有正在播放的流 - Map stringStreamInfoMap = queryPlayByDeviceId(deviceId); - List result = new ArrayList<>(); - PageResult pageResult = new PageResult(); - String queryContent = "*"; - if (!StringUtils.isEmpty(query)) queryContent = String.format("*%S*",query); - String queryHasSubChannel = "*"; - if (hasSubChannel != null) queryHasSubChannel = hasSubChannel?"true":"false"; - String queryOnline = "*"; - if (!StringUtils.isEmpty(online)) queryOnline = online; - String queryStr = VideoManagerConstants.CACHEKEY_PREFIX + deviceId + - "_" + queryContent + // 搜索编号和名称 - "_" + queryOnline + // 搜索是否在线 - "_" + queryHasSubChannel + // 搜索是否含有子节点 - "_" + "*"; -// List deviceChannelList = redis.keys(queryStr); - List deviceChannelList = redis.scan(queryStr); - //对查询结果排序,避免出现通道排列顺序乱序的情况 - Collections.sort(deviceChannelList,new Comparator(){ - @Override - public int compare(Object o1, Object o2) { - return o1.toString().compareToIgnoreCase(o2.toString()); - } - }); - pageResult.setPage(page); - pageResult.setCount(count); - pageResult.setTotal(deviceChannelList.size()); - int maxCount = (page + 1 ) * count; - if (deviceChannelList != null && deviceChannelList.size() > 0 ) { - for (int i = page * count; i < (pageResult.getTotal() > maxCount ? maxCount : pageResult.getTotal() ); i++) { - DeviceChannel deviceChannel = (DeviceChannel)redis.get((String)deviceChannelList.get(i)); - StreamInfo streamInfo = stringStreamInfoMap.get(deviceId + "_" + deviceChannel.getChannelId()); - deviceChannel.setPlay(streamInfo != null); - if (streamInfo != null) deviceChannel.setSsrc(streamInfo.getSsrc()); - result.add(deviceChannel); - } - pageResult.setData(result); - } - - return pageResult; - } - - - - @Override - public List queryChannelsByDeviceId(String deviceId) { - List result = new ArrayList<>(); -// List deviceChannelList = redis.keys(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + "_" + "*"); - List deviceChannelList = redis.scan(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + "_" + "*"); - - if (deviceChannelList != null && deviceChannelList.size() > 0 ) { - for (int i = 0; i < deviceChannelList.size(); i++) { - result.add((DeviceChannel)redis.get((String) deviceChannelList.get(i))); - } - } - return result; - } - - @Override - public PageResult querySubChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, String online, int page, int count) { - List allDeviceChannels = new ArrayList<>(); - String queryContent = "*"; - if (!StringUtils.isEmpty(query)) queryContent = String.format("*%S*",query); - String queryHasSubChannel = "*"; - if (hasSubChannel != null) queryHasSubChannel = hasSubChannel?"true":"false"; - String queryOnline = "*"; - if (!StringUtils.isEmpty(online)) queryOnline = online; - String queryStr = VideoManagerConstants.CACHEKEY_PREFIX + deviceId + - "_" + queryContent + // 搜索编号和名称 - "_" + queryOnline + // 搜索是否在线 - "_" + queryHasSubChannel + // 搜索是否含有子节点 - "_" + parentChannelId; - -// List deviceChannelList = redis.keys(queryStr); - List deviceChannelList = redis.scan(queryStr); - - if (deviceChannelList != null && deviceChannelList.size() > 0 ) { - for (int i = 0; i < deviceChannelList.size(); i++) { - DeviceChannel deviceChannel = (DeviceChannel)redis.get((String)deviceChannelList.get(i)); - if (deviceChannel.getParentId() != null && deviceChannel.getParentId().equals(parentChannelId)) { - allDeviceChannels.add(deviceChannel); - } - } - } - int maxCount = (page + 1 ) * count; - PageResult pageResult = new PageResult(); - pageResult.setPage(page); - pageResult.setCount(count); - pageResult.setTotal(allDeviceChannels.size()); - - if (allDeviceChannels.size() > 0) { - pageResult.setData(allDeviceChannels.subList( - page * count, pageResult.getTotal() > maxCount ? maxCount : pageResult.getTotal() - )); - } - return pageResult; - } - - public List querySubChannels(String deviceId, String parentChannelId) { - List allDeviceChannels = new ArrayList<>(); -// List deviceChannelList = redis.keys(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + "_" + "*"); - List deviceChannelList = redis.scan(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + "_" + "*"); - - if (deviceChannelList != null && deviceChannelList.size() > 0 ) { - for (int i = 0; i < deviceChannelList.size(); i++) { - DeviceChannel deviceChannel = (DeviceChannel)redis.get((String)deviceChannelList.get(i)); - if (deviceChannel.getParentId() != null && deviceChannel.getParentId().equals(parentChannelId)) { - allDeviceChannels.add(deviceChannel); - } - } - } - - return allDeviceChannels; - } - - @Override - public DeviceChannel queryChannel(String deviceId, String channelId) { - DeviceChannel deviceChannel = null; -// List deviceChannelList = redis.keys(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + - List deviceChannelList = redis.scan(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + - "_" + channelId + "*"); - if (deviceChannelList != null && deviceChannelList.size() > 0 ) { - deviceChannel = (DeviceChannel)redis.get((String)deviceChannelList.get(0)); - } - return deviceChannel; - } - - - /** - * 获取多个设备 - * - * @param deviceIds 设备ID数组 - * @return List 设备对象数组 - */ - @Override - public PageResult queryVideoDeviceList(String[] deviceIds, int page, int count) { - List devices = new ArrayList<>(); - PageResult pageResult = new PageResult(); - pageResult.setPage(page); - pageResult.setCount(count); - Device device = null; - - if (deviceIds == null || deviceIds.length == 0) { - -// List deviceIdList = redis.keys(VideoManagerConstants.DEVICE_PREFIX+"*"); - List deviceIdList = redis.scan(VideoManagerConstants.DEVICE_PREFIX+"*"); - pageResult.setTotal(deviceIdList.size()); - int maxCount = (page + 1)* count; - for (int i = page * count; i < (pageResult.getTotal() > maxCount ? maxCount : pageResult.getTotal() ); i++) { - // devices.add((Device)redis.get((String)deviceIdList.get(i))); - device =(Device)redis.get((String)deviceIdList.get(i)); - if (redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX+device.getDeviceId()).size() == 0){ - // outline(device.getDeviceId()); - } - devices.add(device); - } - } else { - for (int i = 0; i < deviceIds.length; i++) { - // devices.add((Device)redis.get(VideoManagerConstants.DEVICE_PREFIX+deviceIds[i])); - device = (Device)redis.get(VideoManagerConstants.DEVICE_PREFIX+deviceIds[i]); - if (redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX+device.getDeviceId()).size() == 0){ - // outline(device.getDeviceId()); - } - devices.add(device); - } - } - pageResult.setData(devices); - return pageResult; - } - - /** - * 获取多个设备 - * - * @param deviceIds 设备ID数组 - * @return List 设备对象数组 - */ - @Override - public List queryVideoDeviceList(String[] deviceIds) { - List devices = new ArrayList<>(); - Device device = null; - - if (deviceIds == null || deviceIds.length == 0) { -// List deviceIdList = redis.keys(VideoManagerConstants.DEVICE_PREFIX+"*"); - List deviceIdList = redis.scan(VideoManagerConstants.DEVICE_PREFIX+"*"); - for (int i = 0; i < deviceIdList.size(); i++) { - device =(Device)redis.get((String)deviceIdList.get(i)); - if (redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX+device.getDeviceId()).size() == 0){ - outline(device.getDeviceId()); - } - devices.add(device); - } - } else { - for (int i = 0; i < deviceIds.length; i++) { - device = (Device)redis.get(VideoManagerConstants.DEVICE_PREFIX+deviceIds[i]); - if (redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX+device.getDeviceId()).size() == 0){ - outline(device.getDeviceId()); - } - devices.add(device); - } - } - return devices; - } - - /** - * 删除设备 - * - * @param deviceId 设备ID - * @return true:删除成功 false:删除失败 - */ - @Override - public boolean delete(String deviceId) { - return redis.del(VideoManagerConstants.DEVICE_PREFIX+deviceId); - } - - /** - * 更新设备在线 - * - * @param deviceId 设备ID - * @return true:更新成功 false:更新失败 - */ - @Override - public boolean online(String deviceId) { - Device device = (Device)redis.get(VideoManagerConstants.DEVICE_PREFIX+deviceId); - device.setOnline(1); - return redis.set(VideoManagerConstants.DEVICE_PREFIX+device.getDeviceId(), device); - } - - /** - * 更新设备离线 - * - * @param deviceId 设备ID - * @return true:更新成功 false:更新失败 - */ - @Override - public boolean outline(String deviceId) { - Device device = (Device)redis.get(VideoManagerConstants.DEVICE_PREFIX+deviceId); - if (device == null) return false; - device.setOnline(0); - return redis.set(VideoManagerConstants.DEVICE_PREFIX+device.getDeviceId(), device); - } - - /** - * 开始播放时将流存入redis - * - * @return - */ - @Override - public boolean startPlay(StreamInfo stream) { - return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, stream.getSsrc(),stream.getDeviceID(), stream.getCahnnelId()), - stream); - } - - /** - * 停止播放时从redis删除 - * - * @return - */ - @Override - public boolean stopPlay(StreamInfo streamInfo) { - if (streamInfo == null) return false; - DeviceChannel deviceChannel = queryChannel(streamInfo.getDeviceID(), streamInfo.getCahnnelId()); - if (deviceChannel != null) { - deviceChannel.setSsrc(null); - deviceChannel.setPlay(false); - updateChannel(streamInfo.getDeviceID(), deviceChannel); - } - return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, - streamInfo.getSsrc(), - 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.getSsrc(), - streamInfo.getDeviceID(), - streamInfo.getCahnnelId())); - } - @Override - public StreamInfo queryPlayBySSRC(String ssrc) { -// List playLeys = redis.keys(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, ssrc)); - List playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, ssrc)); - if (playLeys == null || playLeys.size() == 0) return null; - return (StreamInfo)redis.get(playLeys.get(0).toString()); - } - - @Override - public StreamInfo queryPlaybackBySSRC(String ssrc) { -// List playLeys = redis.keys(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, ssrc)); - List playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, ssrc)); - 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 playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, - List 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 void updateCatch() { - deviceMap = new HashMap<>(); - // 更新设备 - List devices = queryVideoDeviceList(null); - if (devices == null && devices.size() == 0) return; - for (Device device : devices) { - // 更新设备下的通道 - HashMap> channelMap = new HashMap>(); - List deviceChannelList = redis.scan(VideoManagerConstants.CACHEKEY_PREFIX + - device.getDeviceId() + "_" + "*"); - if (deviceChannelList != null && deviceChannelList.size() > 0 ) { - for (int i = 0; i < deviceChannelList.size(); i++) { - String key = (String)deviceChannelList.get(i); - String[] s = key.split("_"); - String channelId = s[3]; - HashSet subChannel = channelMap.get(channelId); - if (subChannel == null) { - subChannel = new HashSet<>(); - } - System.out.println(key); - if (s.length == 6 && !"null".equals(s[5])) { - subChannel.add(s[5]); - } - channelMap.put(channelId, subChannel); - } - } - deviceMap.put(device.getDeviceId(),channelMap); - } - System.out.println(); - } - - @Override - public void cleanChannelsForDevice(String deviceId) { - List result = new ArrayList<>(); -// List deviceChannelList = redis.keys(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + "_" + "*"); - List deviceChannelList = redis.scan(VideoManagerConstants.CACHEKEY_PREFIX + deviceId + "_" + "*"); - if (deviceChannelList != null && deviceChannelList.size() > 0 ) { - for (int i = 0; i < deviceChannelList.size(); i++) { - redis.del((String)deviceChannelList.get(i)); - } - } - } - - @Override - public Map queryPlayByDeviceId(String deviceId) { - Map streamInfos = new HashMap<>(); -// List playLeys = redis.keys(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId)); - List playLeys = redis.scan(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId)); - if (playLeys.size() == 0) return streamInfos; - for (int i = 0; i < playLeys.size(); i++) { - String key = (String) playLeys.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.getSsrc(),stream.getDeviceID(), stream.getCahnnelId()), - stream); - } - - - @Override - public boolean stopPlayback(StreamInfo streamInfo) { - if (streamInfo == null) return false; - DeviceChannel deviceChannel = queryChannel(streamInfo.getDeviceID(), streamInfo.getCahnnelId()); - if (deviceChannel != null) { - deviceChannel.setSsrc(null); - deviceChannel.setPlay(false); - updateChannel(streamInfo.getDeviceID(), deviceChannel); - } - return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, - streamInfo.getSsrc(), - 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 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()); - } - - @Override - public boolean updateParentPlatform(ParentPlatform parentPlatform) { - - // 存储device - return redis.set(VideoManagerConstants.PLATFORM_PREFIX + parentPlatform.getDeviceGBId(), parentPlatform); - } - - @Override - public boolean deleteParentPlatform(ParentPlatform parentPlatform) { - return redis.del(VideoManagerConstants.PLATFORM_PREFIX + parentPlatform.getDeviceGBId()); - } - - @Override - public PageResult queryParentPlatformList(int page, int count) { - PageResult pageResult = new PageResult(); - pageResult.setPage(page); - pageResult.setCount(count); - List resultData = new ArrayList<>(); - List parentPlatformList = redis.scan(VideoManagerConstants.PLATFORM_PREFIX + "*"); - pageResult.setTotal(parentPlatformList.size()); - int maxCount = (page + 1)* count; - for (int i = page * count; i < (pageResult.getTotal() > maxCount ? maxCount : pageResult.getTotal() ); i++) { - ParentPlatform parentPlatform =(ParentPlatform)redis.get((String)parentPlatformList.get(i)); - resultData.add(parentPlatform); - - } - pageResult.setData(resultData); - return pageResult; - } - - @Override - public ParentPlatform queryParentPlatById(String platformGbId) { - return (ParentPlatform)redis.get(VideoManagerConstants.PLATFORM_PREFIX + platformGbId); - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java b/src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java index 3fe7dcc7..ccbe94d6 100644 --- a/src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java +++ b/src/main/java/com/genersoft/iot/vmp/utils/SpringBeanFactory.java @@ -34,6 +34,7 @@ public class SpringBeanFactory implements ApplicationContextAware { * 获取对象 这里重写了bean方法,起主要作用 */ public static Object getBean(String beanId) throws BeansException { + if (applicationContext == null) return null; return applicationContext.getBean(beanId); } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java index 34a02ee2..d64b6327 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java @@ -1,14 +1,14 @@ 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; @@ -19,6 +19,8 @@ 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 @RequestMapping("/api") @@ -50,13 +52,13 @@ public class DeviceController { } @GetMapping("/devices") - public PageResult devices(int page, int count){ + public PageInfo devices(int page, int count){ if (logger.isDebugEnabled()) { logger.debug("查询所有视频设备API调用"); } - return storager.queryVideoDeviceList(null, page, count); + return storager.queryVideoDeviceList(page, count); } /** @@ -66,18 +68,33 @@ public class DeviceController { * @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 channels(@PathVariable String deviceId, + public ResponseEntity 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); } @@ -86,11 +103,25 @@ public class DeviceController { if (logger.isDebugEnabled()) { } - logger.debug("设备信息同步API调用,deviceId:" + deviceId); + logger.debug("设备通道信息同步API调用,deviceId:" + deviceId); Device device = storager.queryVideoDevice(deviceId); - cmder.catalogQuery(device); - DeferredResult> result = new DeferredResult>(); + 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> result = new DeferredResult>(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; } @@ -124,7 +155,7 @@ public class DeviceController { * @return 子通道列表 */ @GetMapping("/subChannels/{deviceId}/{channelId}/channels") - public ResponseEntity subChannels(@PathVariable String deviceId, + public ResponseEntity subChannels(@PathVariable String deviceId, @PathVariable String channelId, int page, int count, @@ -137,23 +168,23 @@ public class DeviceController { } DeviceChannel deviceChannel = storager.queryChannel(deviceId,channelId); if (deviceChannel == null) { - PageResult deviceChannelPageResult = new PageResult<>(); + PageInfo 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 updateChannel(@PathVariable String deviceId,DeviceChannel channel){ + public ResponseEntity 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 updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){ + public ResponseEntity updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){ Device device = storager.queryVideoDevice(deviceId); device.setStreamMode(streamMode); storager.updateDevice(device); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/device/entity/Device.java b/src/main/java/com/genersoft/iot/vmp/vmanager/device/entity/Device.java deleted file mode 100644 index e47f796c..00000000 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/device/entity/Device.java +++ /dev/null @@ -1,401 +0,0 @@ -package com.genersoft.iot.vmp.vmanager.device.entity; - -import java.util.List; - -import javax.persistence.Column; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Transient; -import javax.validation.constraints.Max; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; - -/** - * @Description:视频设备信息 - * @author: songww - * @date: 2020年5月8日 下午2:05:56 - */ -@ApiModel(value = "视频设备信息", description = "视频设备信息") -@Table(name="VMP_VIDEODEVICES") -public class Device { - - /** - * 设备Id - */ - @ApiModelProperty("设备编号") - @Id - @Column(name="DEVICE_ID") - @NotNull(message = "deviceId 不能为 null") - @Size(min = 4, max = 32, message = "deviceId 必须大于 4 位并且小于 32 位") - private String deviceId; - - /** - * 设备名称 - */ - @ApiModelProperty("设备名称") - @Column(name="DEVICE_NAME") - @Size(max = 32, message = "deviceName 必须小于 32 位") - private String deviceName; - - /** - * 生产厂商 - */ - @ApiModelProperty("生产厂商") - @Column(name="MANUFACTURER") - @Size(max = 64, message = "manufacturer 必须小于 64 位") - private String manufacturer; - - /** - * 型号 - */ - @ApiModelProperty("型号") - @Column(name="MODEL") - @Size(max = 64, message = "manufacturer 必须小于 64 位") - private String model; - - /** - * 固件版本 - */ - @ApiModelProperty("固件版本") - @Column(name="FIRMWARE") - @Size(max = 64, message = "firmware 必须小于 64 位") - private String firmware; - - /** - * 通信协议 - * GB28181 ONVIF - */ - @ApiModelProperty("通信协议") - @Column(name="PROTOCOL") - @NotNull(message = "protocol 不能为 null") - @Size(max = 16, message = "protocol 必须小于 16 位") - private String protocol; - - /** - * SIP 传输协议 - * UDP/TCP - */ - @ApiModelProperty("SIP 传输协议") - @Column(name="TRANSPORT") - @Size(min = 3,max = 3 ,message = "transport 必须为 3 位") - private String transport; - - /** - * 数据流传输模式 - * UDP:udp传输 - * TCP-ACTIVE:tcp主动模式 - * TCP-PASSIVE:tcp被动模式 - */ - @ApiModelProperty("数据流传输模式") - @Column(name="STREAM_MODE") - @Size(max = 64, message = "streamMode 必须小于 16 位") - private String streamMode; - - /** - * IP地址 - */ - @ApiModelProperty("IP地址") - @Column(name="IP") - @Size(max = 15, message = "streamMode 必须小于 15 位") - private String ip; - - /** - * 端口号 - */ - @ApiModelProperty("端口号") - @Column(name="PORT") - @Max(value = 65535,message = "port 最大值为 65535") - private Integer port; - - /** - * 在线状态 1在线, 0离线 - */ - @ApiModelProperty("在线状态") - @Size(min = 1,max = 1 ,message = "online 必须为 1 位") - @Column(name="ONLINE") - private String online; - - /** - * 通道数量 - */ - @ApiModelProperty("通道数量") - @Column(name="CHANNEL_SUM") - @Max(value = 1000000000,message = "channelSum 最大值为 1000000000") - private Integer channelSum; - - @Override - public String toString() { - return "Device{" + - "deviceId='" + deviceId + '\'' + - ", deviceName='" + deviceName + '\'' + - ", manufacturer='" + manufacturer + '\'' + - ", model='" + model + '\'' + - ", firmware='" + firmware + '\'' + - ", protocol='" + protocol + '\'' + - ", transport='" + transport + '\'' + - ", streamMode='" + streamMode + '\'' + - ", ip='" + ip + '\'' + - ", port=" + port + - ", online='" + online + '\'' + - ", channelSum=" + channelSum + - ", createTime='" + createTime + '\'' + - ", registerTime='" + registerTime + '\'' + - ", heartbeatTime='" + heartbeatTime + '\'' + - ", updateTime='" + updateTime + '\'' + - ", updatePerson='" + updatePerson + '\'' + - ", syncTime='" + syncTime + '\'' + - ", syncPerson='" + syncPerson + '\'' + - ", username='" + username + '\'' + - ", password='" + password + '\'' + - ", channelList=" + channelList + - '}'; - } - - /** - * 创建时间 - */ - @ApiModelProperty("创建时间") - @Column(name="CREATE_TIME") - private String createTime; - - /** - * 注册时间 - */ - @ApiModelProperty("注册时间") - @Column(name="REGISTER_TIME") - private String registerTime; - - /** - * 心跳时间 - */ - @ApiModelProperty("心跳时间") - @Column(name="HEARTBEAT_TIME") - private String heartbeatTime; - - /** - * 修改时间 - */ - @ApiModelProperty("更新时间") - @Column(name="UPDATE_TIME") - private String updateTime; - - /** - * 修改人 - */ - @ApiModelProperty("修改人") - @Column(name="UPDATE_PERSON") - private String updatePerson; - - /** - * 同步时间 - */ - @ApiModelProperty("同步时间") - @Column(name="SYNC_TIME") - private String syncTime; - - /** - * 同步人 - */ - @ApiModelProperty("同步人") - @Column(name="SYNC_PERSON") - private String syncPerson; - - /** - * ONVIF协议-用户名 - */ - @ApiModelProperty("用户名") - @Column(name="USERNAME") - @Size(max = 32, message = "username 必须小于 32 位") - private String username; - - /** - * ONVIF协议-密码 - */ - @ApiModelProperty("密码") - @Size(max = 32, message = "password 必须小于 32 位") - @Column(name="PASSWORD") - private String password; - - @Transient - private List channelList; - - - public String getDeviceId() { - return deviceId; - } - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - public String getDeviceName() { - return deviceName; - } - - public void setDeviceName(String deviceName) { - this.deviceName = deviceName; - } - - public String getManufacturer() { - return manufacturer; - } - - public void setManufacturer(String manufacturer) { - this.manufacturer = manufacturer; - } - - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public String getFirmware() { - return firmware; - } - - public void setFirmware(String firmware) { - this.firmware = firmware; - } - - public String getProtocol() { - return protocol; - } - - public void setProtocol(String protocol) { - this.protocol = protocol; - } - - public String getTransport() { - return transport; - } - - public void setTransport(String transport) { - this.transport = transport; - } - - public String getStreamMode() { - return streamMode; - } - - public void setStreamMode(String streamMode) { - this.streamMode = streamMode; - } - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public Integer getPort() { - return port; - } - - public void setPort(Integer port) { - this.port = port; - } - - public String getOnline() { - return online; - } - - public void setOnline(String online) { - this.online = online; - } - - public Integer getChannelSum() { - return channelSum; - } - - public void setChannelSum(Integer channelSum) { - this.channelSum = channelSum; - } - - public String getCreateTime() { - return createTime; - } - - public void setCreateTime(String createTime) { - this.createTime = createTime; - } - - public String getRegisterTime() { - return registerTime; - } - - public void setRegisterTime(String registerTime) { - this.registerTime = registerTime; - } - - public String getHeartbeatTime() { - return heartbeatTime; - } - - public void setHeartbeatTime(String heartbeatTime) { - this.heartbeatTime = heartbeatTime; - } - - public String getUpdateTime() { - return updateTime; - } - - public void setUpdateTime(String updateTime) { - this.updateTime = updateTime; - } - - public String getUpdatePerson() { - return updatePerson; - } - - public void setUpdatePerson(String updatePerson) { - this.updatePerson = updatePerson; - } - - public String getSyncTime() { - return syncTime; - } - - public void setSyncTime(String syncTime) { - this.syncTime = syncTime; - } - - public String getSyncPerson() { - return syncPerson; - } - - public void setSyncPerson(String syncPerson) { - this.syncPerson = syncPerson; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public List getChannelList() { - return channelList; - } - - public void setChannelList(List channelList) { - this.channelList = channelList; - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/device/entity/DeviceChannel.java b/src/main/java/com/genersoft/iot/vmp/vmanager/device/entity/DeviceChannel.java deleted file mode 100644 index bedd0755..00000000 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/device/entity/DeviceChannel.java +++ /dev/null @@ -1,385 +0,0 @@ -package com.genersoft.iot.vmp.vmanager.device.entity; - -import javax.persistence.Column; -import javax.persistence.Id; -import javax.persistence.Table; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; - -/** - * @Description:设备通道信息 - * @author: songww - * @date: 2020年5月20日 下午9:00:46 - */ -@ApiModel(value = "设备通道信息", description = "设备通道信息") -@Table(name="VMP_VIDEOCHANNELS") -public class DeviceChannel { - - /** - * 通道编号 - */ - @ApiModelProperty("通道编号") - @Id - @Column(name="CHANNEL_ID") - private String channelId; - - /** - * 设备编号 - */ - @ApiModelProperty("设备编号") - @Column(name="DEVICE_ID") - private String deviceId; - - /** - * 通道名 - */ - @ApiModelProperty("通道名") - @Column(name="CHANNEL_NAME") - private String channelName; - - /** - * 生产厂商 - */ - @ApiModelProperty("生产厂商") - @Column(name="MANUFACTURER") - private String manufacture; - - /** - * 型号 - */ - @ApiModelProperty("型号") - @Column(name="MODEL") - private String model; - - /** - * 设备归属 - */ - @ApiModelProperty("设备归属") - @Column(name="OWNER") - private String owner; - - /** - * 行政区域 - */ - @ApiModelProperty("行政区域") - @Column(name="CIVIL_CODE") - private String civilCode; - - /** - * 警区 - */ - @ApiModelProperty("警区") - @Column(name="BLOCK") - private String block; - - /** - * 安装地址 - */ - @ApiModelProperty("安装地址") - @Column(name="ADDRESS") - private String address; - - /** - * 是否有子设备 1有, 0没有 - */ - @ApiModelProperty("是否有子设备") - @Column(name="PARENTAL") - private String parental; - - /** - * 父级id - */ - @ApiModelProperty("父级编码") - @Column(name="PARENT_ID") - private String parentId; - - /** - * 信令安全模式 缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式 - */ - @ApiModelProperty("信令安全模式") - @Column(name="SAFETY_WAY") - private String safetyWay; - - /** - * 注册方式 缺省为1;1:符合IETFRFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式 - */ - @ApiModelProperty("注册方式") - @Column(name="REGISTER_WAY") - private String registerWay; - - /** - * 证书序列号 - */ - @ApiModelProperty("证书序列号") - @Column(name="CERT_NUM") - private String certNum; - - /** - * 证书有效标识 缺省为0;证书有效标识:0:无效1: 有效 - */ - @ApiModelProperty("证书有效标识") - @Column(name="CERT_VALID") - private String certValid; - - /** - * 证书无效原因码 - */ - @ApiModelProperty("证书无效原因码") - @Column(name="CERT_ERRCODE") - private String certErrCode; - - /** - * 证书终止有效期 - */ - @ApiModelProperty("证书终止有效期") - @Column(name="CERT_ENDTIME") - private String certEndTime; - - /** - * 保密属性 缺省为0; 0:不涉密, 1:涉密 - */ - @ApiModelProperty("保密属性") - @Column(name="SECRECY") - private String secrecy; - - /** - * IP地址 - */ - @ApiModelProperty("IP地址") - @Column(name="IP") - private String ip; - - /** - * 端口号 - */ - @ApiModelProperty("端口号") - @Column(name="PORT") - private Integer port; - - /** - * 密码 - */ - @ApiModelProperty("密码") - @Column(name="PASSWORD") - private String password; - - /** - * 在线/离线 - * 1在线,0离线 - * 默认在线 - * 信令: - * ON - * OFF - * 遇到过NVR下的IPC下发信令可以推流, 但是 Status 响应 OFF - */ - @ApiModelProperty("状态") - @Column(name="ONLINE") - private String online; - - /** - * 经度 - */ - @ApiModelProperty("经度") - @Column(name="LONGITUDE") - private double longitude; - - /** - * 纬度 - */ - @ApiModelProperty("纬度") - @Column(name="LATITUDE") - private double latitude; - - public String getChannelId() { - return channelId; - } - - public void setChannelId(String channelId) { - this.channelId = channelId; - } - - public String getDeviceId() { - return deviceId; - } - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - public String getChannelName() { - return channelName; - } - - public void setChannelName(String channelName) { - this.channelName = channelName; - } - - public String getManufacture() { - return manufacture; - } - - public void setManufacture(String manufacture) { - this.manufacture = manufacture; - } - - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public String getOwner() { - return owner; - } - - public void setOwner(String owner) { - this.owner = owner; - } - - public String getCivilCode() { - return civilCode; - } - - public void setCivilCode(String civilCode) { - this.civilCode = civilCode; - } - - public String getBlock() { - return block; - } - - public void setBlock(String block) { - this.block = block; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getParental() { - return parental; - } - - public void setParental(String parental) { - this.parental = parental; - } - - public String getParentId() { - return parentId; - } - - public void setParentId(String parentId) { - this.parentId = parentId; - } - - public String getSafetyWay() { - return safetyWay; - } - - public void setSafetyWay(String safetyWay) { - this.safetyWay = safetyWay; - } - - public String getRegisterWay() { - return registerWay; - } - - public void setRegisterWay(String registerWay) { - this.registerWay = registerWay; - } - - public String getCertNum() { - return certNum; - } - - public void setCertNum(String certNum) { - this.certNum = certNum; - } - - public String getCertValid() { - return certValid; - } - - public void setCertValid(String certValid) { - this.certValid = certValid; - } - - public String getCertErrCode() { - return certErrCode; - } - - public void setCertErrCode(String certErrCode) { - this.certErrCode = certErrCode; - } - - public String getCertEndTime() { - return certEndTime; - } - - public void setCertEndTime(String certEndTime) { - this.certEndTime = certEndTime; - } - - public String getSecrecy() { - return secrecy; - } - - public void setSecrecy(String secrecy) { - this.secrecy = secrecy; - } - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public Integer getPort() { - return port; - } - - public void setPort(Integer port) { - this.port = port; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getOnline() { - return online; - } - - public void setOnline(String online) { - this.online = online; - } - - public double getLongitude() { - return longitude; - } - - public void setLongitude(double longitude) { - this.longitude = longitude; - } - - public double getLatitude() { - return latitude; - } - - public void setLatitude(double latitude) { - this.latitude = latitude; - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java index e741b5af..59667d09 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java @@ -7,6 +7,8 @@ import com.genersoft.iot.vmp.conf.MediaServerConfig; 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; @@ -27,6 +29,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import org.springframework.web.context.request.async.DeferredResult; +import javax.sip.message.Response; import java.text.DecimalFormat; import java.util.UUID; @@ -43,6 +46,9 @@ public class PlayController { @Autowired private IVideoManagerStorager storager; + @Autowired + private IRedisCatchStorage redisCatchStorage; + @Autowired private ZLMRESTfulUtils zlmresTfulUtils; @@ -58,18 +64,11 @@ public class PlayController { Device device = storager.queryVideoDevice(deviceId); - StreamInfo streamInfo = storager.queryPlayByDevice(deviceId, channelId); + StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId); UUID uuid = UUID.randomUUID(); DeferredResult> result = new DeferredResult>(); - // 超时处理 - 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); @@ -78,9 +77,15 @@ public class PlayController { 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(); @@ -88,58 +93,107 @@ public class PlayController { 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 playStop(@PathVariable String ssrc) { + @PostMapping("/play/{streamId}/stop") + public DeferredResult> playStop(@PathVariable String streamId) { - cmder.streamByeCmd(ssrc); - StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc); - if (streamInfo == null) - return new ResponseEntity("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)); + + UUID uuid = UUID.randomUUID(); + DeferredResult> result = new DeferredResult>(); - if (ssrc != null) { + // 录像查询以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(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(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 playConvert(@PathVariable String ssrc) { - StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc); + @PostMapping("/play/{streamId}/convert") + public ResponseEntity playConvert(@PathVariable String streamId) { + StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); if (streamInfo == null) { logger.warn("视频转码API调用失败!, 视频流已经停止!"); return new ResponseEntity("未找到视频流信息, 视频流可能已经停止", 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("推流信息在流媒体中不存在, 视频流可能已停止推流", 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); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java index 2e32b1bb..9449d263 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/playback/PlaybackController.java @@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.common.StreamInfo; 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; @@ -27,6 +28,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import org.springframework.web.context.request.async.DeferredResult; +import javax.sip.message.Response; import java.util.UUID; @CrossOrigin @@ -42,6 +44,9 @@ public class PlaybackController { @Autowired private IVideoManagerStorager storager; + @Autowired + private IRedisCatchStorage redisCatchStorage; + @Autowired private ZLMRESTfulUtils zlmresTfulUtils; @@ -69,15 +74,21 @@ public class PlaybackController { 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; diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java index 1a90977b..4c41e16f 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/ptz/PtzController.java @@ -29,15 +29,14 @@ public class PtzController { 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 ptz(@PathVariable String deviceId,@PathVariable String channelId,int cmdCode, int horizonSpeed, int verticalSpeed, int zoomSpeed){ diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java index 502087ef..519d299e 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java @@ -1,5 +1,6 @@ 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; @@ -32,7 +33,7 @@ public class RecordController { @Autowired private DeferredResultHolder resultHolder; - + @GetMapping("/record/{deviceId}/{channelId}") public DeferredResult> recordinfo(@PathVariable String deviceId,@PathVariable String channelId, String startTime, String endTime){ @@ -42,9 +43,17 @@ public class RecordController { Device device = storager.queryVideoDevice(deviceId); cmder.recordInfoQuery(device, channelId, startTime, endTime); - DeferredResult> result = new DeferredResult>(); + // 指定超时时间 1分钟30秒 + DeferredResult> result = new DeferredResult>(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; } } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java index e9528d1d..f4bdd2b7 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/service/impl/PlayServiceImpl.java @@ -4,8 +4,10 @@ import com.alibaba.fastjson.JSON; 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; @@ -24,6 +26,9 @@ public class PlayServiceImpl implements IPlayService { @Autowired private IVideoManagerStorager storager; + @Autowired + private IRedisCatchStorage redisCatchStorage; + @Autowired private DeferredResultHolder resultHolder; @@ -33,7 +38,13 @@ public class PlayServiceImpl implements IPlayService { 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 { @@ -49,7 +60,7 @@ public class PlayServiceImpl implements IPlayService { 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 { @@ -61,13 +72,11 @@ public class PlayServiceImpl implements IPlayService { 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)); diff --git a/src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java b/src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java index ce3b7ec8..199a9e69 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java @@ -1,22 +1,17 @@ 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; @@ -65,12 +60,12 @@ public class ApiDeviceController { JSONObject result = new JSONObject(); List devices; if (start == null || limit ==null) { - devices = storager.queryVideoDeviceList(null); + devices = storager.queryVideoDeviceList(); result.put("DeviceCount", devices.size()); }else { - PageResult deviceList = storager.queryVideoDeviceList(null, start/limit, limit); + PageInfo deviceList = storager.queryVideoDeviceList(start/limit, limit); result.put("DeviceCount", deviceList.getTotal()); - devices = deviceList.getData(); + devices = deviceList.getList(); } JSONArray deviceJSONList = new JSONArray(); @@ -86,8 +81,8 @@ public class ApiDeviceController { 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", ""); @@ -123,9 +118,9 @@ public class ApiDeviceController { deviceChannels = storager.queryChannelsByDeviceId(serial); result.put("ChannelCount", deviceChannels.size()); }else { - PageResult pageResult = storager.queryChannelsByDeviceId(serial, null, null, null,start/limit, limit); + PageInfo 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(); @@ -159,7 +154,7 @@ public class ApiDeviceController { 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); } diff --git a/src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java b/src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java index 6180fbb6..8ebdf647 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java @@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.gb28181.bean.Device; 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; @@ -17,6 +18,7 @@ import org.springframework.beans.factory.annotation.Value; 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:实时直播 @@ -34,12 +36,17 @@ public class ApiStreamController { @Autowired private IVideoManagerStorager storager; - private boolean closeWaitRTPInfo = false; + @Autowired + private IRedisCatchStorage redisCatchStorage; @Autowired private ZLMRESTfulUtils zlmresTfulUtils; + + @Autowired + private PlayController playController; + /** * 实时直播 - 开始直播 * @param serial 设备编号 @@ -54,126 +61,54 @@ public class ApiStreamController { * @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 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 resultDeferredResult = new DeferredResult(); 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; - } - - // 查询是否已经在播放 - 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()))); + resultDeferredResult.setResult(result); } - 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 { + DeferredResult> play = playController.play(serial, code); - 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 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()); @@ -203,13 +138,9 @@ public class ApiStreamController { 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; } /** @@ -228,14 +159,15 @@ public class ApiStreamController { @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; } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 638d5aea..7b98bd10 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -20,12 +20,20 @@ spring: 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: @@ -34,18 +42,18 @@ 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: @@ -57,7 +65,7 @@ auth: #zlm服务器配置 media: # [必须修改] zlm服务器的内网IP - ip: 192.168.0.100 + ip: 192.168.1.44 # [可选] zlm服务器的公网IP, 内网部署置空即可 wanIp: # [可选] zlm服务器的hook所使用的IP, 默认使用sip.ip @@ -69,12 +77,12 @@ media: # [可选] 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 diff --git a/src/main/resources/wvp.sqlite b/src/main/resources/wvp.sqlite new file mode 100644 index 00000000..023ea355 Binary files /dev/null and b/src/main/resources/wvp.sqlite differ diff --git a/web_src/src/components/channelList.vue b/web_src/src/components/channelList.vue index 4c39a3f8..e221974b 100644 --- a/web_src/src/components/channelList.vue +++ b/web_src/src/components/channelList.vue @@ -19,12 +19,12 @@ - 在线状态: + 在线状态: - - + + - + 自动刷新 @@ -56,7 +56,7 @@ 播放 - 停止 + 停止 查看 设备录象 @@ -98,13 +98,17 @@ export default { 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'); @@ -161,7 +165,7 @@ export default { .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(); @@ -179,17 +183,16 @@ export default { 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, @@ -212,7 +215,7 @@ export default { 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(); @@ -258,7 +261,7 @@ export default { }) .then(function (res) { that.total = res.data.total; - that.deviceChannelList = res.data.data; + that.deviceChannelList = res.data.list; // 防止出现表格错位 that.$nextTick(() => { that.$refs.channelListTable.doLayout(); @@ -283,6 +286,13 @@ export default { }).then(function (res) { console.log(JSON.stringify(res)); }); + }, + autoListChange: function () { + if (this.autoList) { + this.updateLooper = setInterval(this.initData, 1500); + }else{ + window.clearInterval(this.updateLooper); + } } } diff --git a/web_src/src/components/gb28181/devicePlayer.vue b/web_src/src/components/gb28181/devicePlayer.vue index 8442a82f..babf0fee 100644 --- a/web_src/src/components/gb28181/devicePlayer.vue +++ b/web_src/src/components/gb28181/devicePlayer.vue @@ -1,6 +1,6 @@