Merge remote-tracking branch 'origin/wvp-28181-2.0' into commercial

# Conflicts:
#	sql/update.sql
#	src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
#	web_src/src/components/dialog/deviceEdit.vue
#	web_src/src/components/dialog/devicePlayer.vue
This commit is contained in:
648540858
2022-06-27 10:16:21 +08:00
106 changed files with 3638 additions and 1447 deletions

View File

@@ -97,4 +97,5 @@ public class VideoManagerConstants {
//************************** 第三方 ****************************************
public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_";
public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_";
}

View File

@@ -103,12 +103,12 @@ public class DynamicTask {
public void stop(String key) {
if (futureMap.get(key) != null && !futureMap.get(key).isCancelled()) {
futureMap.get(key).cancel(true);
Runnable runnable = runnableMap.get(key);
if (runnable instanceof ISubscribeTask) {
ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
subscribeTask.stop();
}
// Runnable runnable = runnableMap.get(key);
// if (runnable instanceof ISubscribeTask) {
// ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
// subscribeTask.stop();
// }
futureMap.get(key).cancel(false);
}
}

View File

@@ -6,6 +6,10 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.regex.Pattern;
@Configuration("mediaConfig")
public class MediaConfig{
@@ -161,7 +165,18 @@ public class MediaConfig{
if (StringUtils.isEmpty(sdpIp)){
return ip;
}else {
return sdpIp;
if (isValidIPAddress(sdpIp)) {
return sdpIp;
}else {
// 按照域名解析
String hostAddress = null;
try {
hostAddress = InetAddress.getByName(sdpIp).getHostAddress();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
return hostAddress;
}
}
}
@@ -211,4 +226,11 @@ public class MediaConfig{
return mediaServerItem;
}
private boolean isValidIPAddress(String ipAddress) {
if ((ipAddress != null) && (!ipAddress.isEmpty())) {
return Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$", ipAddress);
}
return false;
}
}

View File

@@ -2,7 +2,9 @@ package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.service.impl.RedisAlarmMsgListener;
import com.genersoft.iot.vmp.service.impl.RedisGPSMsgListener;
import com.genersoft.iot.vmp.service.impl.RedisGpsMsgListener;
import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener;
import com.genersoft.iot.vmp.service.impl.RedisStreamMsgListener;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -47,11 +49,17 @@ public class RedisConfig extends CachingConfigurerSupport {
private int poolMaxWait;
@Autowired
private RedisGPSMsgListener redisGPSMsgListener;
private RedisGpsMsgListener redisGPSMsgListener;
@Autowired
private RedisAlarmMsgListener redisAlarmMsgListener;
@Autowired
private RedisStreamMsgListener redisStreamMsgListener;
@Autowired
private RedisGbPlayMsgListener redisGbPlayMsgListener;
@Bean
public JedisPool jedisPool() {
if (StringUtils.isBlank(password)) {
@@ -98,6 +106,8 @@ public class RedisConfig extends CachingConfigurerSupport {
container.setConnectionFactory(connectionFactory);
container.addMessageListener(redisGPSMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_GPS));
container.addMessageListener(redisAlarmMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE));
container.addMessageListener(redisStreamMsgListener, new PatternTopic(VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + "PUSH"));
container.addMessageListener(redisGbPlayMsgListener, new PatternTopic(RedisGbPlayMsgListener.WVP_PUSH_STREAM_KEY));
return container;
}

View File

@@ -11,6 +11,9 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author lin
*/
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

View File

@@ -20,6 +20,7 @@ import java.util.List;
/**
* 配置Spring Security
* @author lin
*/
@Configuration
@EnableWebSecurity
@@ -132,15 +133,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.anyRequest().authenticated()
// 异常处理(权限拒绝、登录失效等)
.and().exceptionHandling()
.authenticationEntryPoint(anonymousAuthenticationEntryPoint)//匿名用户访问无权限资源时的异常处理
//匿名用户访问无权限资源时的异常处理
.authenticationEntryPoint(anonymousAuthenticationEntryPoint)
// .accessDeniedHandler(accessDeniedHandler)//登录用户没有权限访问资源
// 登入
.and().formLogin().permitAll()//允许所有用户
.successHandler(loginSuccessHandler)//登录成功处理逻辑
.failureHandler(loginFailureHandler)//登录失败处理逻辑
// 登入 允许所有用户
.and().formLogin().permitAll()
//登录成功处理逻辑
.successHandler(loginSuccessHandler)
//登录失败处理逻辑
.failureHandler(loginFailureHandler)
// 登出
.and().logout().logoutUrl("/api/user/logout").permitAll()//允许所有用户
.logoutSuccessHandler(logoutHandler)//登出成功处理逻辑
.and().logout().logoutUrl("/api/user/logout").permitAll()
//登出成功处理逻辑
.logoutSuccessHandler(logoutHandler)
.deleteCookies("JSESSIONID")
// 会话管理
// .and().sessionManagement().invalidSessionStrategy(invalidSessionHandler) // 超时处理

View File

@@ -1,6 +1,10 @@
package com.genersoft.iot.vmp.gb28181.bean;
/**
* 国标设备/平台
* @author lin
*/
public class Device {
/**
@@ -127,7 +131,12 @@ public class Device {
/**
* 是否开启ssrc校验默认关闭开启可以防止串流
*/
private boolean ssrcCheck;
private boolean ssrcCheck = true;
/**
* 地理坐标系, 目前支持 WGS84,GCJ02 TODO CGCS2000
*/
private String geoCoordSys;
public String getDeviceId() {
@@ -322,4 +331,12 @@ public class Device {
this.ssrcCheck = ssrcCheck;
}
public String getGeoCoordSys() {
return geoCoordSys;
}
public void setGeoCoordSys(String geoCoordSys) {
this.geoCoordSys = geoCoordSys;
}
}

View File

@@ -154,6 +154,26 @@ public class DeviceChannel {
*/
private double latitude;
/**
* 经度 GCJ02
*/
private double longitudeGcj02;
/**
* 纬度 GCJ02
*/
private double latitudeGcj02;
/**
* 经度 WGS84
*/
private double longitudeWgs84;
/**
* 纬度 WGS84
*/
private double latitudeWgs84;
/**
* 子设备数
*/
@@ -407,6 +427,38 @@ public class DeviceChannel {
this.latitude = latitude;
}
public double getLongitudeGcj02() {
return longitudeGcj02;
}
public void setLongitudeGcj02(double longitudeGcj02) {
this.longitudeGcj02 = longitudeGcj02;
}
public double getLatitudeGcj02() {
return latitudeGcj02;
}
public void setLatitudeGcj02(double latitudeGcj02) {
this.latitudeGcj02 = latitudeGcj02;
}
public double getLongitudeWgs84() {
return longitudeWgs84;
}
public void setLongitudeWgs84(double longitudeWgs84) {
this.longitudeWgs84 = longitudeWgs84;
}
public double getLatitudeWgs84() {
return latitudeWgs84;
}
public void setLatitudeWgs84(double latitudeWgs84) {
this.latitudeWgs84 = latitudeWgs84;
}
public int getSubCount() {
return subCount;
}

View File

@@ -0,0 +1,44 @@
package com.genersoft.iot.vmp.gb28181.bean;
import org.dom4j.Element;
import javax.sip.RequestEvent;
/**
* @author lin
*/
public class HandlerCatchData {
private RequestEvent evt;
private Device device;
private Element rootElement;
public HandlerCatchData(RequestEvent evt, Device device, Element rootElement) {
this.evt = evt;
this.device = device;
this.rootElement = rootElement;
}
public RequestEvent getEvt() {
return evt;
}
public void setEvt(RequestEvent evt) {
this.evt = evt;
}
public Device getDevice() {
return device;
}
public void setDevice(Device device) {
this.device = device;
}
public Element getRootElement() {
return rootElement;
}
public void setRootElement(Element rootElement) {
this.rootElement = rootElement;
}
}

View File

@@ -71,6 +71,11 @@ public class SendRtpItem {
*/
private String mediaServerId;
/**
* 使用的服务的ID
*/
private String serverId;
/**
* invite的callId
*/
@@ -259,4 +264,12 @@ public class SendRtpItem {
public void setOnlyAudio(boolean onlyAudio) {
this.onlyAudio = onlyAudio;
}
public String getServerId() {
return serverId;
}
public void setServerId(String serverId) {
this.serverId = serverId;
}
}

View File

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeHandlerTask;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -38,7 +39,6 @@ public class SubscribeHolder {
catalogMap.put(platformId, subscribeInfo);
// 添加订阅到期
String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId;
dynamicTask.stop(taskOverdueKey);
// 添加任务处理订阅过期
dynamicTask.startDelay(taskOverdueKey, () -> removeCatalogSubscribe(subscribeInfo.getId()),
subscribeInfo.getExpires() * 1000);
@@ -49,10 +49,17 @@ public class SubscribeHolder {
}
public void removeCatalogSubscribe(String platformId) {
catalogMap.remove(platformId);
String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId;
Runnable runnable = dynamicTask.get(taskOverdueKey);
if (runnable instanceof ISubscribeTask) {
ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
subscribeTask.stop();
}
// 添加任务处理订阅过期
dynamicTask.stop(taskOverdueKey);
}
public void putMobilePositionSubscribe(String platformId, SubscribeInfo subscribeInfo) {
@@ -63,7 +70,6 @@ public class SubscribeHolder {
storager, platformId, subscribeInfo.getSn(), key, this, dynamicTask),
subscribeInfo.getGpsInterval() * 1000);
String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId;
dynamicTask.stop(taskOverdueKey);
// 添加任务处理订阅过期
dynamicTask.startDelay(taskOverdueKey, () -> {
removeMobilePositionSubscribe(subscribeInfo.getId());
@@ -81,6 +87,11 @@ public class SubscribeHolder {
// 结束任务处理GPS定时推送
dynamicTask.stop(key);
String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId;
Runnable runnable = dynamicTask.get(taskOverdueKey);
if (runnable instanceof ISubscribeTask) {
ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
subscribeTask.stop();
}
// 添加任务处理订阅过期
dynamicTask.stop(taskOverdueKey);
}

View File

@@ -88,8 +88,8 @@ public class SipSubscribe {
this.type = "timeout";
this.msg = "消息超时未回复";
this.statusCode = -1024;
this.callId = timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId();
this.dialog = timeoutEvent.getClientTransaction().getDialog();
this.callId = this.dialog != null?timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId(): null;
}else if (event instanceof TransactionTerminatedEvent) {
TransactionTerminatedEvent transactionTerminatedEvent = (TransactionTerminatedEvent)event;
this.type = "transactionTerminated";
@@ -109,8 +109,8 @@ public class SipSubscribe {
this.type = "deviceNotFoundEvent";
this.msg = "设备未找到";
this.statusCode = -1024;
this.callId = deviceNotFoundEvent.getDialog().getCallId().getCallId();
this.dialog = deviceNotFoundEvent.getDialog();
this.callId = this.dialog != null ?deviceNotFoundEvent.getDialog().getCallId().getCallId() : null;
}
}
}
@@ -130,6 +130,9 @@ public class SipSubscribe {
}
public void removeErrorSubscribe(String key) {
if(key == null){
return;
}
errorSubscribes.remove(key);
errorTimeSubscribes.remove(key);
}
@@ -139,6 +142,9 @@ public class SipSubscribe {
}
public void removeOkSubscribe(String key) {
if(key == null){
return;
}
okSubscribes.remove(key);
okTimeSubscribes.remove(key);
}

View File

@@ -66,7 +66,7 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
subscribe = subscribeHolder.getCatalogSubscribe(event.getPlatformId());
if (subscribe == null) {
logger.info("发送订阅消息时发现订阅信息已经不存在");
logger.info("发送订阅消息时发现订阅信息已经不存在: {}", event.getPlatformId());
return;
}
}else {

View File

@@ -99,8 +99,8 @@ public class VideoStreamSessionManager {
return dialog;
}
public SIPDialog getDialogByCallId(String deviceId, String channelId, String callID){
SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callID, null);
public SIPDialog getDialogByCallId(String deviceId, String channelId, String callId){
SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, null);
if (ssrcTransaction == null) {
return null;
}
@@ -108,11 +108,17 @@ public class VideoStreamSessionManager {
if (dialogByteArray == null) {
return null;
}
SIPDialog dialog = (SIPDialog)SerializeUtils.deSerialize(dialogByteArray);
return dialog;
return (SIPDialog)SerializeUtils.deSerialize(dialogByteArray);
}
public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){
if (StringUtils.isEmpty(deviceId)) {
deviceId ="*";
}
if (StringUtils.isEmpty(channelId)) {
channelId ="*";
}
if (StringUtils.isEmpty(callId)) {
callId ="*";
}
@@ -179,7 +185,7 @@ public class VideoStreamSessionManager {
public List<SsrcTransaction> getAllSsrc() {
List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId() + "_" ));
List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId()));
List<SsrcTransaction> result= new ArrayList<>();
for (int i = 0; i < ssrcTransactionKeys.size(); i++) {
String key = (String)ssrcTransactionKeys.get(i);

View File

@@ -71,7 +71,9 @@ public class MobilePositionSubscribeHandlerTask implements ISubscribeTask {
String gbId = gbStream.getGbId();
GPSMsgInfo gpsMsgInfo = redisCatchStorage.getGpsMsgInfo(gbId);
if (gpsMsgInfo != null) { // 无最新位置不发送
logger.info("无最新位置不发送");
if (logger.isDebugEnabled()) {
logger.debug("无最新位置不发送");
}
// 经纬度都为0不发送
if (gpsMsgInfo.getLng() == 0 && gpsMsgInfo.getLat() == 0) {
continue;

View File

@@ -150,30 +150,24 @@ public class SIPProcessorObserver implements ISIPProcessorObserver {
public void processTimeout(TimeoutEvent timeoutEvent) {
logger.info("[消息发送超时]");
ClientTransaction clientTransaction = timeoutEvent.getClientTransaction();
eventPublisher.requestTimeOut(timeoutEvent);
if (clientTransaction != null) {
logger.info("[发送错误订阅] clientTransaction != null");
Request request = clientTransaction.getRequest();
if (request != null) {
logger.info("[发送错误订阅] request != null");
CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
if (callIdHeader != null) {
logger.info("[发送错误订阅]");
SipSubscribe.Event subscribe = sipSubscribe.getErrorSubscribe(callIdHeader.getCallId());
SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(timeoutEvent);
subscribe.response(eventResult);
sipSubscribe.removeOkSubscribe(callIdHeader.getCallId());
sipSubscribe.removeErrorSubscribe(callIdHeader.getCallId());
}
}
}
// Timeout timeout = timeoutEvent.getTimeout();
// ServerTransaction serverTransaction = timeoutEvent.getServerTransaction();
// if (serverTransaction != null) {
// Request request = serverTransaction.getRequest();
// URI requestURI = request.getRequestURI();
// Header header = request.getHeader(FromHeader.NAME);
// }
// if(timeoutProcessor != null) {
// timeoutProcessor.process(timeoutEvent);
// }
eventPublisher.requestTimeOut(timeoutEvent);
}
@Override

View File

@@ -148,6 +148,14 @@ public interface ISIPCommander {
* 回放倍速播放
*/
void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed);
/**
* 回放控制
* @param device
* @param streamInfo
* @param content
*/
void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent);
/**

View File

@@ -103,6 +103,14 @@ public interface ISIPCommanderForPlatform {
*/
boolean recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo);
/**
* 录像播放推送完成时发送MediaStatus消息
* @param platform
* @param sendRtpItem
* @return
*/
boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem);
/**
* 向发起点播的上级回复bye
* @param platform 平台信息

View File

@@ -32,7 +32,9 @@ import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.*;
import javax.sip.message.Request;
import java.lang.reflect.Field;
@@ -708,22 +710,19 @@ public class SIPCommander implements ISIPCommander {
}
SIPDialog dialog;
if (callId != null) {
dialog = streamSession.getDialogByCallId(deviceId, channelId, callId);
dialog = streamSession.getDialogByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), callId);
}else {
if (stream == null) {
if (stream == null && ssrcTransaction == null && ssrcTransaction.getStream() == null) {
return;
}
dialog = streamSession.getDialogByStream(deviceId, channelId, stream);
}
if (ssrcTransaction != null) {
MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId());
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
mediaServerService.closeRTPServer(deviceId, channelId, ssrcTransaction.getStream());
streamSession.remove(deviceId, channelId, ssrcTransaction.getStream());
dialog = streamSession.getDialogByStream(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
}
mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
mediaServerService.closeRTPServer(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
if (dialog == null) {
logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", deviceId, channelId);
logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
return;
}
SipStack sipStack = udpSipProvider.getSipStack();
@@ -1456,12 +1455,20 @@ public class SIPCommander implements ISIPCommander {
Request request;
if (dialog != null) {
logger.info("发送移动位置订阅消息时 dialog的状态为 {}", dialog.getState());
SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
request = dialog.createRequest(Request.SUBSCRIBE);
ExpiresHeader expiresHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForCatalog());
request.setExpires(expiresHeader);
request.setRequestURI(requestURI);
ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
request.setContent(subscribePostitionXml.toString(), contentTypeHeader);
ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition());
request.addHeader(expireHeader);
CSeqHeader cSeqHeader = (CSeqHeader)request.getHeader(CSeqHeader.NAME);
cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ(Request.SUBSCRIBE));
request.removeHeader(CSeqHeader.NAME);
request.addHeader(cSeqHeader);
}else {
String tm = Long.toString(System.currentTimeMillis());
CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
@@ -1552,12 +1559,21 @@ public class SIPCommander implements ISIPCommander {
Request request;
if (dialog != null) {
logger.info("发送目录订阅消息时 dialog的状态为 {}", dialog.getState());
SipURI requestURI = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
request = dialog.createRequest(Request.SUBSCRIBE);
ExpiresHeader expiresHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForCatalog());
request.setExpires(expiresHeader);
request.setRequestURI(requestURI);
ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
request.setContent(cmdXml.toString(), contentTypeHeader);
ExpiresHeader expireHeader = sipFactory.createHeaderFactory().createExpiresHeader(device.getSubscribeCycleForMobilePosition());
request.addHeader(expireHeader);
CSeqHeader cSeqHeader = (CSeqHeader)request.getHeader(CSeqHeader.NAME);
cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ(Request.SUBSCRIBE));
request.removeHeader(CSeqHeader.NAME);
request.addHeader(cSeqHeader);
}else {
String tm = Long.toString(System.currentTimeMillis());
@@ -1779,6 +1795,43 @@ public class SIPCommander implements ISIPCommander {
e.printStackTrace();
}
}
@Override
public void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) {
try {
Request request = headerProvider.createInfoRequest(device, streamInfo, content);
if (request == null) {
return;
}
logger.info(request.toString());
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(), (eventResult -> {
errorEvent.response(eventResult);
sipSubscribe.removeErrorSubscribe(eventResult.callId);
sipSubscribe.removeOkSubscribe(eventResult.callId);
}));
}
if(okEvent != null) {
sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
okEvent.response(eventResult);
sipSubscribe.removeOkSubscribe(eventResult.callId);
sipSubscribe.removeErrorSubscribe(eventResult.callId);
});
}
clientTransaction.sendRequest();
} catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
}
}
@Override
public boolean sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) {

View File

@@ -31,6 +31,7 @@ import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.Request;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashSet;
@@ -276,8 +277,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
catalogXml.append("<Owner>" + channel.getOwner() + "</Owner>\r\n");
catalogXml.append("<CivilCode>" + channel.getCivilCode() + "</CivilCode>\r\n");
catalogXml.append("<Address>" + channel.getAddress() + "</Address>\r\n");
catalogXml.append("<Longitude>" + channel.getLongitude() + "</Longitude>\r\n");
catalogXml.append("<Latitude>" + channel.getLatitude() + "</Latitude>\r\n");
catalogXml.append("<Longitude>" + channel.getLongitudeWgs84() + "</Longitude>\r\n");
catalogXml.append("<Latitude>" + channel.getLatitudeWgs84() + "</Latitude>\r\n");
catalogXml.append("<IPAddress>" + channel.getIpAddress() + "</IPAddress>\r\n");
catalogXml.append("<Port>" + channel.getPort() + "</Port>\r\n");
catalogXml.append("<Info>\r\n");
@@ -546,14 +547,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
}
notifyRequest.addHeader(event);
SipURI sipURI = (SipURI) notifyRequest.getRequestURI();
if (subscribeInfo.getTransaction() != null) {
SIPRequest request = (SIPRequest) subscribeInfo.getTransaction().getRequest();
sipURI.setHost(request.getRemoteAddress().getHostAddress());
sipURI.setPort(request.getRemotePort());
}else {
sipURI.setHost(parentPlatform.getServerIP());
sipURI.setPort(parentPlatform.getServerPort());
}
sipURI.setHost(parentPlatform.getServerIP());
sipURI.setPort(parentPlatform.getServerPort());
ClientTransaction transaction = null;
if ("TCP".equals(parentPlatform.getTransport())) {
@@ -750,6 +745,82 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
return true;
}
@Override
public boolean sendMediaStatusNotify(ParentPlatform platform, SendRtpItem sendRtpItem) {
if (sendRtpItem == null) {
return false;
}
if (platform == null) {
return false;
}
byte[] dialogByteArray = sendRtpItem.getDialog();
if (dialogByteArray == null) {
return false;
}
try{
SIPDialog dialog = (SIPDialog) SerializeUtils.deSerialize(dialogByteArray);
SipStack sipStack;
if ("TCP".equals(platform.getTransport())) {
sipStack = tcpSipProvider.getSipStack();
} else {
sipStack = udpSipProvider.getSipStack();
}
SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog);
if (dialog != sipDialog) {
dialog = sipDialog;
}
if ("TCP".equals(platform.getTransport())) {
dialog.setSipProvider(tcpSipProvider);
} else {
dialog.setSipProvider(udpSipProvider);
}
Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
sipStackField.setAccessible(true);
sipStackField.set(dialog, sipStack);
Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
eventListenersField.setAccessible(true);
eventListenersField.set(dialog, new HashSet<>());
SIPRequest messageRequest = (SIPRequest)dialog.createRequest(Request.MESSAGE);
String characterSet = platform.getCharacterSet();
StringBuffer mediaStatusXml = new StringBuffer(200);
mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
mediaStatusXml.append("<Notify>\r\n");
mediaStatusXml.append("<CmdType>MediaStatus</CmdType>\r\n");
mediaStatusXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
mediaStatusXml.append("<DeviceID>" + sendRtpItem.getChannelId() + "</DeviceID>\r\n");
mediaStatusXml.append("<NotifyType>121</NotifyType>\r\n");
mediaStatusXml.append("</Notify>\r\n");
ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
messageRequest.setContent(mediaStatusXml.toString(), contentTypeHeader);
SipURI sipURI = (SipURI) messageRequest.getRequestURI();
sipURI.setHost(platform.getServerIP());
sipURI.setPort(platform.getServerPort());
ClientTransaction clientTransaction;
if ("TCP".equals(platform.getTransport())) {
clientTransaction = tcpSipProvider.getNewClientTransaction(messageRequest);
}else {
clientTransaction = udpSipProvider.getNewClientTransaction(messageRequest);
}
dialog.sendRequest(clientTransaction);
} catch (SipException e) {
e.printStackTrace();
return false;
} catch (ParseException e) {
e.printStackTrace();
return false;
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return true;
}
@Override
public void streamByeCmd(ParentPlatform platform, String callId) {
if (platform == null) {
@@ -766,45 +837,51 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
byte[] dialogByteArray = sendRtpItem.getDialog();
if (dialogByteArray != null) {
SIPDialog dialog = (SIPDialog) SerializeUtils.deSerialize(dialogByteArray);
SipStack sipStack = udpSipProvider.getSipStack();
SipStack sipStack;
if ("TCP".equals(platform.getTransport())) {
sipStack = tcpSipProvider.getSipStack();
} else {
sipStack = udpSipProvider.getSipStack();
}
SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog);
if (dialog != sipDialog) {
dialog = sipDialog;
} else {
try {
dialog.setSipProvider(udpSipProvider);
Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
sipStackField.setAccessible(true);
sipStackField.set(dialog, sipStack);
Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
eventListenersField.setAccessible(true);
eventListenersField.set(dialog, new HashSet<>());
byte[] transactionByteArray = sendRtpItem.getTransaction();
ClientTransaction clientTransaction = (ClientTransaction) SerializeUtils.deSerialize(transactionByteArray);
Request byeRequest = dialog.createRequest(Request.BYE);
SipURI byeURI = (SipURI) byeRequest.getRequestURI();
SIPRequest request = (SIPRequest) clientTransaction.getRequest();
byeURI.setHost(request.getRemoteAddress().getHostAddress());
byeURI.setPort(request.getRemotePort());
if ("TCP".equals(platform.getTransport())) {
clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
} else if ("UDP".equals(platform.getTransport())) {
clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
}
dialog.sendRequest(clientTransaction);
} catch (SipException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
try {
if ("TCP".equals(platform.getTransport())) {
dialog.setSipProvider(tcpSipProvider);
} else {
dialog.setSipProvider(udpSipProvider);
}
Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
sipStackField.setAccessible(true);
sipStackField.set(dialog, sipStack);
Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
eventListenersField.setAccessible(true);
eventListenersField.set(dialog, new HashSet<>());
Request byeRequest = dialog.createRequest(Request.BYE);
SipURI byeURI = (SipURI) byeRequest.getRequestURI();
byeURI.setHost(platform.getServerIP());
byeURI.setPort(platform.getServerPort());
ClientTransaction clientTransaction;
if ("TCP".equals(platform.getTransport())) {
clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
} else {
clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
}
dialog.sendRequest(clientTransaction);
} catch (SipException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -1,23 +0,0 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request;
import gov.nist.javax.sip.SipProviderImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* @description:处理接收IPCamera发来的SIP协议请求消息
* @author: songww
* @date: 2020年5月3日 下午4:42:22
*/
public abstract class SIPRequestProcessorAbstract {
@Autowired
@Qualifier(value="tcpSipProvider")
private SipProviderImpl tcpSipProvider;
@Autowired
@Qualifier(value="udpSipProvider")
private SipProviderImpl udpSipProvider;
}

View File

@@ -15,10 +15,13 @@ import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
import com.genersoft.iot.vmp.service.impl.RedisGbPlayMsgListener;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.stack.SIPDialog;
import com.genersoft.iot.vmp.utils.SerializeUtils;
import org.ehcache.shadow.org.terracotta.offheapstore.storage.IntegerStorageEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,7 +49,7 @@ import java.util.*;
public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
private Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class);
private String method = "ACK";
private final String method = "ACK";
@Autowired
private SIPProcessorObserver sipProcessorObserver;
@@ -84,6 +87,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
@Autowired
private AudioBroadcastManager audioBroadcastManager;
@Autowired
private RedisGbPlayMsgListener redisGbPlayMsgListener;
/**
* 处理 ACK请求
@@ -159,60 +165,41 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
// 向上级平台
commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
}
if (mediaInfo == null) {
RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(),
sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, jsonObject->{
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
});
}else {
JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
}
// if (streamInfo == null) { // 流还没上来对方就回复ack
// logger.info("监听流以等待流上线1 rtp/{}", sendRtpItem.getStreamId());
// // 监听流上线
// // 添加订阅
// JSONObject subscribeKey = new JSONObject();
// subscribeKey.put("app", "rtp");
// subscribeKey.put("stream", sendRtpItem.getStreamId());
// subscribeKey.put("regist", true);
// subscribeKey.put("schema", "rtmp");
// subscribeKey.put("mediaServerId", sendRtpItem.getMediaServerId());
// subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
// (MediaServerItem mediaServerItemInUse, JSONObject json)->{
// Map<String, Object> param = new HashMap<>();
// param.put("vhost","__defaultVhost__");
// param.put("app",json.getString("app"));
// param.put("stream",json.getString("stream"));
// param.put("ssrc", sendRtpItem.getSsrc());
// param.put("dst_url",sendRtpItem.getIp());
// param.put("dst_port", sendRtpItem.getPort());
// param.put("is_udp", is_Udp);
// param.put("src_port", sendRtpItem.getLocalPort());
// zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
// });
// }else {
// Map<String, Object> param = new HashMap<>();
// param.put("vhost","__defaultVhost__");
// param.put("app",streamInfo.getApp());
// param.put("stream",streamInfo.getStream());
// param.put("ssrc", sendRtpItem.getSsrc());
// param.put("dst_url",sendRtpItem.getIp());
// param.put("dst_port", sendRtpItem.getPort());
// param.put("is_udp", is_Udp);
// param.put("src_port", sendRtpItem.getLocalPort());
//
// JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
// if (jsonObject.getInteger("code") != 0) {
// logger.info("监听流以等待流上线2 {}/{}", streamInfo.getApp(), streamInfo.getStream());
// // 监听流上线
// // 添加订阅
// JSONObject subscribeKey = new JSONObject();
// subscribeKey.put("app", "rtp");
// subscribeKey.put("stream", streamInfo.getStream());
// subscribeKey.put("regist", true);
// subscribeKey.put("schema", "rtmp");
// subscribeKey.put("mediaServerId", sendRtpItem.getMediaServerId());
// subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
// (MediaServerItem mediaServerItemInUse, JSONObject json)->{
// zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
// });
// }
// }
}
}
private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform,
JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) {
if (jsonObject == null) {
logger.error("RTP推流失败: 请检查ZLM服务");
} else if (jsonObject.getInteger("code") == 0) {
logger.info("RTP推流成功[ {}/{} ]{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
byte[] dialogByteArray = SerializeUtils.serialize(evt.getDialog());
sendRtpItem.setDialog(dialogByteArray);
byte[] transactionByteArray = SerializeUtils.serialize(evt.getServerTransaction());
sendRtpItem.setTransaction(transactionByteArray);
redisCatchStorage.updateSendRTPSever(sendRtpItem);
} else {
logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"),JSONObject.toJSON(param));
if (sendRtpItem.isOnlyAudio()) {
// TODO 可能是语音对讲
}else {
// 向上级平台
commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
}
}
}
}

View File

@@ -114,13 +114,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
playService.stopAudioBroadcast(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
}
if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
MessageForPushChannel messageForPushChannel = new MessageForPushChannel();
messageForPushChannel.setType(0);
messageForPushChannel.setGbId(sendRtpItem.getChannelId());
messageForPushChannel.setApp(sendRtpItem.getApp());
messageForPushChannel.setStream(sendRtpItem.getStreamId());
messageForPushChannel.setMediaServerId(sendRtpItem.getMediaServerId());
messageForPushChannel.setPlatFormId(sendRtpItem.getPlatformId());
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId());
redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
}
}

View File

@@ -15,7 +15,7 @@ import javax.sip.RequestEvent;
@Component
public class CancelRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
private String method = "CANCEL";
private final String method = "CANCEL";
@Autowired
private SIPProcessorObserver sipProcessorObserver;

View File

@@ -1,7 +1,6 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.*;
@@ -18,6 +17,7 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import org.dom4j.DocumentException;
import org.dom4j.Element;
@@ -25,6 +25,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@@ -35,6 +37,7 @@ import javax.sip.header.FromHeader;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* SIP命令类型 NOTIFY请求
@@ -63,11 +66,19 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
@Autowired
private EventPublisher publisher;
private String method = "NOTIFY";
private final String method = "NOTIFY";
@Autowired
private SIPProcessorObserver sipProcessorObserver;
private boolean taskQueueHandlerRun = false;
private final ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
@Qualifier("taskExecutor")
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Override
public void afterPropertiesSet() throws Exception {
// 添加消息处理的订阅
@@ -77,23 +88,40 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
@Override
public void process(RequestEvent evt) {
try {
Element rootElement = getRootElement(evt);
String cmd = XmlUtil.getText(rootElement, "CmdType");
if (CmdType.CATALOG.equals(cmd)) {
logger.info("接收到Catalog通知");
processNotifyCatalogList(evt);
} else if (CmdType.ALARM.equals(cmd)) {
logger.info("接收到Alarm通知");
processNotifyAlarm(evt);
} else if (CmdType.MOBILE_POSITION.equals(cmd)) {
logger.info("接收到MobilePosition通知");
processNotifyMobilePosition(evt);
} else {
logger.info("接收到消息:" + cmd);
responseAck(evt, Response.OK);
taskQueue.offer(new HandlerCatchData(evt, null, null));
responseAck(evt, Response.OK);
if (!taskQueueHandlerRun) {
taskQueueHandlerRun = true;
taskExecutor.execute(()-> {
while (!taskQueue.isEmpty()) {
try {
HandlerCatchData take = taskQueue.poll();
Element rootElement = getRootElement(take.getEvt());
String cmd = XmlUtil.getText(rootElement, "CmdType");
if (CmdType.CATALOG.equals(cmd)) {
logger.info("接收到Catalog通知");
processNotifyCatalogList(take.getEvt());
} else if (CmdType.ALARM.equals(cmd)) {
logger.info("接收到Alarm通知");
processNotifyAlarm(take.getEvt());
} else if (CmdType.MOBILE_POSITION.equals(cmd)) {
logger.info("接收到MobilePosition通知");
processNotifyMobilePosition(take.getEvt());
} else {
logger.info("接收到消息:" + cmd);
}
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
taskQueueHandlerRun = false;
});
}
} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
} catch (SipException | InvalidArgumentException | ParseException e) {
e.printStackTrace();
}
}
@@ -166,8 +194,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
jsonObject.put("direction", mobilePosition.getDirection());
jsonObject.put("speed", mobilePosition.getSpeed());
redisCatchStorage.sendMobilePositionMsg(jsonObject);
responseAck(evt, Response.OK);
} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
} catch (DocumentException e) {
e.printStackTrace();
}
}
@@ -188,6 +215,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
Device device = redisCatchStorage.getDevice(deviceId);
if (device == null) {
logger.warn("[ NotifyAlarm ] 未找到设备:{}", deviceId);
return;
}
rootElement = getRootElement(evt, device.getCharset());
@@ -195,7 +223,12 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
deviceAlarm.setDeviceId(deviceId);
deviceAlarm.setAlarmPriority(XmlUtil.getText(rootElement, "AlarmPriority"));
deviceAlarm.setAlarmMethod(XmlUtil.getText(rootElement, "AlarmMethod"));
deviceAlarm.setAlarmTime(XmlUtil.getText(rootElement, "AlarmTime"));
String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
if (alarmTime == null) {
logger.warn("[ NotifyAlarm ] AlarmTime cannot be null");
return;
}
deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
if (XmlUtil.getText(rootElement, "AlarmDescription") == null) {
deviceAlarm.setAlarmDescription("");
} else {
@@ -212,7 +245,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
deviceAlarm.setLatitude(0.00);
}
logger.info("[收到Notify-Alarm]{}/{}", device.getDeviceId(), deviceAlarm.getChannelId());
if (deviceAlarm.getAlarmMethod().equals("4")) {
if ("4".equals(deviceAlarm.getAlarmMethod())) {
MobilePosition mobilePosition = new MobilePosition();
mobilePosition.setDeviceId(deviceAlarm.getDeviceId());
mobilePosition.setTime(deviceAlarm.getAlarmTime());
@@ -233,11 +266,10 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
// TODO: 需要实现存储报警信息、报警分类
// 回复200 OK
responseAck(evt, Response.OK);
if (redisCatchStorage.deviceIsOnline(deviceId)) {
publisher.deviceAlarmEventPublish(deviceAlarm);
}
} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
} catch (DocumentException e) {
e.printStackTrace();
}
}
@@ -273,64 +305,60 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
continue;
}
Element eventElement = itemDevice.element("Event");
DeviceChannel channel = XmlUtil.channelContentHander(itemDevice);
String event;
if (eventElement == null) {
logger.warn("[收到 目录订阅]{}, 但是Event为空, 设为默认值 ADD", (device != null ? device.getDeviceId():"" ));
event = CatalogEvent.ADD;
}else {
event = eventElement.getText().toUpperCase();
}
DeviceChannel channel = XmlUtil.channelContentHander(itemDevice, device);
channel.setDeviceId(device.getDeviceId());
logger.info("[收到 目录订阅]{}/{}", device.getDeviceId(), channel.getChannelId());
switch (eventElement.getText().toUpperCase()) {
switch (event) {
case CatalogEvent.ON:
// 上线
logger.info("收到来自设备【{}】的通道【{}】上线通知", device.getDeviceId(), channel.getChannelId());
storager.deviceChannelOnline(deviceId, channel.getChannelId());
// 回复200 OK
responseAck(evt, Response.OK);
break;
case CatalogEvent.OFF :
// 离线
logger.info("收到来自设备【{}】的通道【{}】离线通知", device.getDeviceId(), channel.getChannelId());
storager.deviceChannelOffline(deviceId, channel.getChannelId());
// 回复200 OK
responseAck(evt, Response.OK);
break;
case CatalogEvent.VLOST:
// 视频丢失
logger.info("收到来自设备【{}】的通道【{}】视频丢失通知", device.getDeviceId(), channel.getChannelId());
storager.deviceChannelOffline(deviceId, channel.getChannelId());
// 回复200 OK
responseAck(evt, Response.OK);
break;
case CatalogEvent.DEFECT:
// 故障
// 回复200 OK
responseAck(evt, Response.OK);
break;
case CatalogEvent.ADD:
// 增加
logger.info("收到来自设备【{}】的增加通道【{}】通知", device.getDeviceId(), channel.getChannelId());
storager.updateChannel(deviceId, channel);
responseAck(evt, Response.OK);
break;
case CatalogEvent.DEL:
// 删除
logger.info("收到来自设备【{}】的删除通道【{}】通知", device.getDeviceId(), channel.getChannelId());
storager.delChannel(deviceId, channel.getChannelId());
responseAck(evt, Response.OK);
break;
case CatalogEvent.UPDATE:
// 更新
logger.info("收到来自设备【{}】的更新通道【{}】通知", device.getDeviceId(), channel.getChannelId());
storager.updateChannel(deviceId, channel);
responseAck(evt, Response.OK);
break;
default:
responseAck(evt, Response.BAD_REQUEST, "event not found");
logger.warn("[ NotifyCatalog ] event not found {}", event );
}
// 转发变化信息
eventPublisher.catalogEventPublish(null, channel, eventElement.getText().toUpperCase());
eventPublisher.catalogEventPublish(null, channel, event);
}
}
} catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
} catch (DocumentException e) {
e.printStackTrace();
}
}

View File

@@ -1,17 +1,13 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.WvpSipDate;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.auth.DigestServerAuthenticationHelper;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import gov.nist.javax.sip.RequestEventExt;
import gov.nist.javax.sip.address.AddressImpl;
@@ -45,20 +41,11 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
private final Logger logger = LoggerFactory.getLogger(RegisterRequestProcessor.class);
public String method = "REGISTER";
public final String method = "REGISTER";
@Autowired
private SipConfig sipConfig;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private IVideoManagerStorage storager;
@Autowired
private EventPublisher publisher;
@Autowired
private SIPProcessorObserver sipProcessorObserver;
@@ -86,7 +73,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
ExpiresHeader expiresHeader = (ExpiresHeader) request.getHeader(Expires.NAME);
Response response = null;
boolean passwordCorrect = false;
// 注册标志 0未携带授权头或者密码错误 1注册成功 2注销成功
// 注册标志
boolean registerFlag = false;
FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
AddressImpl address = (AddressImpl) fromHeader.getAddress();
@@ -105,7 +92,6 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
// 校验密码是否正确
passwordCorrect = StringUtils.isEmpty(sipConfig.getPassword()) ||
new DigestServerAuthenticationHelper().doAuthenticatePlainTextPassword(request, sipConfig.getPassword());
// 未携带授权头或者密码错误 回复401
if (!passwordCorrect) {
// 注册失败
@@ -154,6 +140,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen
device = new Device();
device.setStreamMode("UDP");
device.setCharset("GB2312");
device.setGeoCoordSys("WGS84");
device.setDeviceId(deviceId);
}
device.setIp(received);

View File

@@ -82,7 +82,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
@Override
public void process(RequestEvent evt) {
Request request = evt.getRequest();
try {
Element rootElement = getRootElement(evt);
String cmd = XmlUtil.getText(rootElement, "CmdType");
@@ -176,6 +175,8 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
}
private void processNotifyCatalogList(RequestEvent evt, Element rootElement) throws SipException {
System.out.println(evt.getRequest().toString());
String platformId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
String deviceId = XmlUtil.getText(rootElement, "DeviceID");
ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);

View File

@@ -0,0 +1,143 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import gov.nist.javax.sip.message.SIPRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.SipException;
import javax.sip.header.*;
import javax.sip.message.Response;
import java.text.ParseException;
@Component
public class InfoRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
private final static Logger logger = LoggerFactory.getLogger(InfoRequestProcessor.class);
private final String method = "INFO";
@Autowired
private SIPProcessorObserver sipProcessorObserver;
@Autowired
private IVideoManagerStorage storage;
@Autowired
private SipSubscribe sipSubscribe;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private IVideoManagerStorage storager;
@Autowired
private SIPCommander cmder;
@Autowired
private VideoStreamSessionManager sessionManager;
@Override
public void afterPropertiesSet() throws Exception {
// 添加消息处理的订阅
sipProcessorObserver.addRequestProcessor(method, this);
}
@Override
public void process(RequestEvent evt) {
logger.debug("接收到消息:" + evt.getRequest());
String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
// 先从会话内查找
SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
deviceId = ssrcTransaction.getDeviceId();
}
// 查询设备是否存在
Device device = redisCatchStorage.getDevice(deviceId);
// 查询上级平台是否存在
ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId);
try {
if (device != null && parentPlatform != null) {
logger.warn("[重复]平台与设备编号重复:{}", deviceId);
SIPRequest request = (SIPRequest) evt.getRequest();
String hostAddress = request.getRemoteAddress().getHostAddress();
int remotePort = request.getRemotePort();
if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) {
parentPlatform = null;
}else {
device = null;
}
}
if (device == null && parentPlatform == null) {
// 不存在则回复404
responseAck(evt, Response.NOT_FOUND, "device "+ deviceId +" not found");
logger.warn("[设备未找到 ] {}", deviceId);
if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){
SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new DeviceNotFoundEvent(evt.getDialog()));
sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()).response(eventResult);
};
}else {
ContentTypeHeader header = (ContentTypeHeader)evt.getRequest().getHeader(ContentTypeHeader.NAME);
String contentType = header.getContentType();
String contentSubType = header.getContentSubType();
if ("Application".equals(contentType) && "MANSRTSP".equals(contentSubType)) {
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
String streamId = sendRtpItem.getStreamId();
StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null);
if (null == streamInfo) {
responseAck(evt, Response.NOT_FOUND, "stream " + streamId + " not found");
return;
}
Device device1 = storager.queryVideoDevice(streamInfo.getDeviceID());
cmder.playbackControlCmd(device1,streamInfo,new String(evt.getRequest().getRawContent()),eventResult -> {
// 失败的回复
try {
responseAck(evt, eventResult.statusCode, eventResult.msg);
} catch (SipException e) {
e.printStackTrace();
} catch (InvalidArgumentException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}, eventResult -> {
// 成功的回复
try {
responseAck(evt, eventResult.statusCode);
} catch (SipException e) {
e.printStackTrace();
} catch (InvalidArgumentException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
});
}
}
} catch (SipException e) {
logger.warn("SIP 回复错误", e);
} catch (InvalidArgumentException e) {
logger.warn("参数无效", e);
} catch (ParseException e) {
logger.warn("SIP回复时解析异常", e);
}
}
}

View File

@@ -72,7 +72,7 @@ public class MessageRequestProcessor extends SIPRequestProcessorParent implement
String deviceId = SipUtils.getUserIdFromFromHeader(evt.getRequest());
CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
// 先从会话内查找
SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, null, callIdHeader.getCallId());
SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
deviceId = ssrcTransaction.getDeviceId();
}

View File

@@ -9,9 +9,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessag
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
import com.genersoft.iot.vmp.service.IDeviceAlarmService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -84,7 +86,11 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
deviceAlarm.setChannelId(channelId);
deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority"));
deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod"));
deviceAlarm.setAlarmTime(getText(rootElement, "AlarmTime"));
String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
if (alarmTime == null) {
return;
}
deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
String alarmDescription = getText(rootElement, "AlarmDescription");
if (alarmDescription == null) {
deviceAlarm.setAlarmDescription("");
@@ -175,7 +181,11 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
deviceAlarm.setChannelId(channelId);
deviceAlarm.setAlarmPriority(getText(rootElement, "AlarmPriority"));
deviceAlarm.setAlarmMethod(getText(rootElement, "AlarmMethod"));
deviceAlarm.setAlarmTime(getText(rootElement, "AlarmTime"));
String alarmTime = XmlUtil.getText(rootElement, "AlarmTime");
if (alarmTime == null) {
return;
}
deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
String alarmDescription = getText(rootElement, "AlarmDescription");
if (alarmDescription == null) {
deviceAlarm.setAlarmDescription("");

View File

@@ -64,16 +64,14 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
device.setHostAddress(received.concat(":").concat(String.valueOf(rPort)));
}
device.setKeepaliveTime(DateUtil.getNow());
// 回复200 OK
responseAck(evt, Response.OK);
if (device.getOnline() == 1) {
// 回复200 OK
responseAck(evt, Response.OK);
deviceService.updateDevice(device);
}else {
// 对于已经离线的设备判断他的注册是否已经过期
if (!deviceService.expire(device)){
deviceService.online(device);
// 回复200 OK
responseAck(evt, Response.OK);
}
}
} catch (SipException e) {

View File

@@ -3,11 +3,16 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,9 +41,18 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
@Autowired
private SIPCommander cmder;
@Autowired
private SIPCommanderFroPlatform sipCommanderFroPlatform;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private IVideoManagerStorage storage;
@Autowired
private VideoStreamSessionManager sessionManager;
@Override
public void afterPropertiesSet() throws Exception {
notifyMessageHandler.addHandler(cmdType, this);
@@ -59,17 +73,32 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
}
CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
String NotifyType =getText(rootElement, "NotifyType");
if (NotifyType.equals("121")){
if ("121".equals(NotifyType)){
logger.info("[录像流]推送完毕,收到关流通知");
String channelId =getText(rootElement, "DeviceID");
// 查询是设备
StreamInfo streamInfo = redisCatchStorage.queryDownload(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
// 设置进度100%
streamInfo.setProgress(1);
redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId());
cmder.streamByeCmd(device.getDeviceId(), channelId, null, callIdHeader.getCallId());
// TODO 如果级联播放,需要给上级发送此通知
StreamInfo streamInfo = redisCatchStorage.queryDownload(null, null, null, callIdHeader.getCallId());
if (streamInfo != null) {
// 设置进度100%
streamInfo.setProgress(1);
redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId());
}
// 先从会话内查找
SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题
cmder.streamByeCmd(device.getDeviceId(), ssrcTransaction.getChannelId(), null, callIdHeader.getCallId());
// 如果级联播放,需要给上级发送此通知 TODO 多个上级同时观看一个下级 可能存在停错的问题需要将点播CallId进行上下级绑定
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, ssrcTransaction.getChannelId(), null, null);
if (sendRtpItem != null) {
ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
if (parentPlatform == null) {
logger.warn("[级联消息发送]发送MediaStatus发现上级平台{}不存在", sendRtpItem.getPlatformId());
return;
}
sipCommanderFroPlatform.sendMediaStatusNotify(parentPlatform, sendRtpItem);
}
}
}
}

View File

@@ -20,6 +20,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@@ -31,6 +33,7 @@ import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
@Component
public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
@@ -38,9 +41,13 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class);
private final String cmdType = "Catalog";
private boolean taskQueueHandlerRun = false;
@Autowired
private ResponseMessageHandler responseMessageHandler;
private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
@Autowired
private IVideoManagerStorage storager;
@@ -63,6 +70,10 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Qualifier("taskExecutor")
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Override
public void afterPropertiesSet() throws Exception {
responseMessageHandler.addHandler(cmdType, this);
@@ -70,68 +81,88 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
@Override
public void handForDevice(RequestEvent evt, Device device, Element element) {
String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + device.getDeviceId();
Element rootElement = null;
taskQueue.offer(new HandlerCatchData(evt, device, element));
// 回复200 OK
try {
rootElement = getRootElement(evt, device.getCharset());
Element deviceListElement = rootElement.element("DeviceList");
Element sumNumElement = rootElement.element("SumNum");
Element snElement = rootElement.element("SN");
if (snElement == null || sumNumElement == null || deviceListElement == null) {
responseAck(evt, Response.BAD_REQUEST, "xml error");
return;
}
int sumNum = Integer.parseInt(sumNumElement.getText());
if (sumNum == 0) {
// 数据已经完整接收
storager.cleanChannelsForDevice(device.getDeviceId());
catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null);
}else {
Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
if (deviceListIterator != null) {
List<DeviceChannel> channelList = new ArrayList<>();
// 遍历DeviceList
while (deviceListIterator.hasNext()) {
Element itemDevice = deviceListIterator.next();
Element channelDeviceElement = itemDevice.element("DeviceID");
if (channelDeviceElement == null) {
continue;
responseAck(evt, Response.OK);
} catch (SipException e) {
throw new RuntimeException(e);
} catch (InvalidArgumentException e) {
throw new RuntimeException(e);
} catch (ParseException e) {
throw new RuntimeException(e);
}
if (!taskQueueHandlerRun) {
taskQueueHandlerRun = true;
taskExecutor.execute(()-> {
while (!taskQueue.isEmpty()) {
HandlerCatchData take = taskQueue.poll();
String key = DeferredResultHolder.CALLBACK_CMD_CATALOG + take.getDevice().getDeviceId();
Element rootElement = null;
try {
rootElement = getRootElement(take.getEvt(), take.getDevice().getCharset());
Element deviceListElement = rootElement.element("DeviceList");
Element sumNumElement = rootElement.element("SumNum");
Element snElement = rootElement.element("SN");
if (snElement == null || sumNumElement == null || deviceListElement == null) {
responseAck(take.getEvt(), Response.BAD_REQUEST, "xml error");
return;
}
//by brewswang
// if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {//如果包含位置信息,就更新一下位置
// processNotifyMobilePosition(evt, itemDevice);
// }
DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice);
deviceChannel.setDeviceId(device.getDeviceId());
int sumNum = Integer.parseInt(sumNumElement.getText());
channelList.add(deviceChannel);
}
int sn = Integer.parseInt(snElement.getText());
catalogDataCatch.put(device.getDeviceId(), sn, sumNum, device, channelList);
logger.info("收到来自设备【{}】的通道: {}个,{}/{}", device.getDeviceId(), channelList.size(), catalogDataCatch.get(device.getDeviceId()) == null ? 0 :catalogDataCatch.get(device.getDeviceId()).size(), sumNum);
if (catalogDataCatch.get(device.getDeviceId()).size() == sumNum) {
// 数据已经完整接收
boolean resetChannelsResult = storager.resetChannels(device.getDeviceId(), catalogDataCatch.get(device.getDeviceId()));
if (!resetChannelsResult) {
String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(device.getDeviceId()).size() + "";
catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), errorMsg);
if (sumNum == 0) {
// 数据已经完整接收
storager.cleanChannelsForDevice(take.getDevice().getDeviceId());
catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
}else {
catalogDataCatch.setChannelSyncEnd(device.getDeviceId(), null);
Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
if (deviceListIterator != null) {
List<DeviceChannel> channelList = new ArrayList<>();
// 遍历DeviceList
while (deviceListIterator.hasNext()) {
Element itemDevice = deviceListIterator.next();
Element channelDeviceElement = itemDevice.element("DeviceID");
if (channelDeviceElement == null) {
continue;
}
//by brewswang
// if (NumericUtil.isDouble(XmlUtil.getText(itemDevice, "Longitude"))) {//如果包含位置信息,就更新一下位置
// processNotifyMobilePosition(evt, itemDevice);
// }
DeviceChannel deviceChannel = XmlUtil.channelContentHander(itemDevice, device);
deviceChannel.setDeviceId(take.getDevice().getDeviceId());
channelList.add(deviceChannel);
}
int sn = Integer.parseInt(snElement.getText());
catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(), channelList);
logger.info("收到来自设备【{}】的通道: {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.get(take.getDevice().getDeviceId()) == null ? 0 :catalogDataCatch.get(take.getDevice().getDeviceId()).size(), sumNum);
if (catalogDataCatch.get(take.getDevice().getDeviceId()).size() == sumNum) {
// 数据已经完整接收
boolean resetChannelsResult = storager.resetChannels(take.getDevice().getDeviceId(), catalogDataCatch.get(take.getDevice().getDeviceId()));
if (!resetChannelsResult) {
String errorMsg = "接收成功,写入失败,共" + sumNum + "条,已接收" + catalogDataCatch.get(take.getDevice().getDeviceId()).size() + "";
catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), errorMsg);
}else {
catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), null);
}
}
}
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (InvalidArgumentException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (SipException e) {
e.printStackTrace();
}
}
// 回复200 OK
responseAck(evt, Response.OK);
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (InvalidArgumentException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (SipException e) {
e.printStackTrace();
taskQueueHandlerRun = false;
});
}
}

View File

@@ -82,7 +82,7 @@ public class DeviceStatusResponseMessageHandler extends SIPRequestProcessorParen
deviceService.offline(device.getDeviceId());
}
RequestMessage msg = new RequestMessage();
msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId() + channelId);
msg.setKey(DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + device.getDeviceId());
msg.setData(json);
deferredResultHolder.invokeAllResult(msg);
}

View File

@@ -1,9 +1,6 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.RecordInfo;
import com.genersoft.iot.vmp.gb28181.bean.RecordItem;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.session.RecordDataCatch;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
@@ -19,6 +16,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@@ -28,6 +27,9 @@ import javax.sip.SipException;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
@@ -38,10 +40,11 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
private Logger logger = LoggerFactory.getLogger(RecordInfoResponseMessageHandler.class);
public static volatile List<String> threadNameList = new ArrayList();
private final String cmdType = "RecordInfo";
private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_";
private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
private boolean taskQueueHandlerRun = false;
@Autowired
private ResponseMessageHandler responseMessageHandler;
@@ -51,11 +54,13 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
@Autowired
private DeferredResultHolder deferredResultHolder;
@Autowired
private EventPublisher eventPublisher;
@Qualifier("taskExecutor")
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Override
public void afterPropertiesSet() throws Exception {
responseMessageHandler.addHandler(cmdType, this);
@@ -67,67 +72,89 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
// 回复200 OK
try {
responseAck(evt, Response.OK);
taskQueue.offer(new HandlerCatchData(evt, device, rootElement));
if (!taskQueueHandlerRun) {
taskQueueHandlerRun = true;
taskExecutor.execute(()->{
try {
while (!taskQueue.isEmpty()) {
HandlerCatchData take = taskQueue.poll();
Element rootElementForCharset = getRootElement(take.getEvt(), take.getDevice().getCharset());
String sn = getText(rootElementForCharset, "SN");
String channelId = getText(rootElementForCharset, "DeviceID");
RecordInfo recordInfo = new RecordInfo();
recordInfo.setChannelId(channelId);
recordInfo.setDeviceId(take.getDevice().getDeviceId());
recordInfo.setSn(sn);
recordInfo.setName(getText(rootElementForCharset, "Name"));
String sumNumStr = getText(rootElementForCharset, "SumNum");
int sumNum = 0;
if (!StringUtils.isEmpty(sumNumStr)) {
sumNum = Integer.parseInt(sumNumStr);
}
recordInfo.setSumNum(sumNum);
Element recordListElement = rootElementForCharset.element("RecordList");
if (recordListElement == null || sumNum == 0) {
logger.info("无录像数据");
eventPublisher.recordEndEventPush(recordInfo);
recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, new ArrayList<>());
releaseRequest(take.getDevice().getDeviceId(), sn);
} else {
Iterator<Element> recordListIterator = recordListElement.elementIterator();
if (recordListIterator != null) {
List<RecordItem> recordList = new ArrayList<>();
// 遍历DeviceList
while (recordListIterator.hasNext()) {
Element itemRecord = recordListIterator.next();
Element recordElement = itemRecord.element("DeviceID");
if (recordElement == null) {
logger.info("记录为空,下一个...");
continue;
}
RecordItem record = new RecordItem();
record.setDeviceId(getText(itemRecord, "DeviceID"));
record.setName(getText(itemRecord, "Name"));
record.setFilePath(getText(itemRecord, "FilePath"));
record.setFileSize(getText(itemRecord, "FileSize"));
record.setAddress(getText(itemRecord, "Address"));
rootElement = getRootElement(evt, device.getCharset());
String sn = getText(rootElement, "SN");
String startTimeStr = getText(itemRecord, "StartTime");
record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr));
String sumNumStr = getText(rootElement, "SumNum");
int sumNum = 0;
if (!StringUtils.isEmpty(sumNumStr)) {
sumNum = Integer.parseInt(sumNumStr);
}
Element recordListElement = rootElement.element("RecordList");
if (recordListElement == null || sumNum == 0) {
logger.info("无录像数据");
recordDataCatch.put(device.getDeviceId(), sn, sumNum, new ArrayList<>());
releaseRequest(device.getDeviceId(), sn);
} else {
Iterator<Element> recordListIterator = recordListElement.elementIterator();
if (recordListIterator != null) {
List<RecordItem> recordList = new ArrayList<>();
// 遍历DeviceList
while (recordListIterator.hasNext()) {
Element itemRecord = recordListIterator.next();
Element recordElement = itemRecord.element("DeviceID");
if (recordElement == null) {
logger.info("记录为空,下一个...");
continue;
String endTimeStr = getText(itemRecord, "EndTime");
record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr));
record.setSecrecy(itemRecord.element("Secrecy") == null ? 0
: Integer.parseInt(getText(itemRecord, "Secrecy")));
record.setType(getText(itemRecord, "Type"));
record.setRecorderId(getText(itemRecord, "RecorderID"));
recordList.add(record);
}
recordInfo.setRecordList(recordList);
// 发送消息,如果是上级查询此录像,则会通过这里通知给上级
eventPublisher.recordEndEventPush(recordInfo);
int count = recordDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, recordList);
logger.info("[国标录像] {}->{}: {}/{}", take.getDevice().getDeviceId(), sn, count, sumNum);
}
if (recordDataCatch.isComplete(take.getDevice().getDeviceId(), sn)){
releaseRequest(take.getDevice().getDeviceId(), sn);
}
}
}
RecordItem record = new RecordItem();
record.setDeviceId(getText(itemRecord, "DeviceID"));
record.setName(getText(itemRecord, "Name"));
record.setFilePath(getText(itemRecord, "FilePath"));
record.setFileSize(getText(itemRecord, "FileSize"));
record.setAddress(getText(itemRecord, "Address"));
String startTimeStr = getText(itemRecord, "StartTime");
record.setStartTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(startTimeStr));
String endTimeStr = getText(itemRecord, "EndTime");
record.setEndTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(endTimeStr));
record.setSecrecy(itemRecord.element("Secrecy") == null ? 0
: Integer.parseInt(getText(itemRecord, "Secrecy")));
record.setType(getText(itemRecord, "Type"));
record.setRecorderId(getText(itemRecord, "RecorderID"));
recordList.add(record);
taskQueueHandlerRun = false;
}catch (DocumentException e) {
throw new RuntimeException(e);
}
int count = recordDataCatch.put(device.getDeviceId(), sn, sumNum, recordList);
logger.info("[国标录像] {}->{}: {}/{}", device.getDeviceId(), sn, count, sumNum);
}
if (recordDataCatch.isComplete(device.getDeviceId(), sn)){
releaseRequest(device.getDeviceId(), sn);
}
});
}
} catch (SipException e) {
e.printStackTrace();
} catch (InvalidArgumentException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
}

View File

@@ -17,7 +17,7 @@ import javax.sip.ResponseEvent;
@Component
public class ByeResponseProcessor extends SIPResponseProcessorAbstract {
private String method = "BYE";
private final String method = "BYE";
@Autowired
private SipLayer sipLayer;

View File

@@ -17,7 +17,7 @@ import javax.sip.ResponseEvent;
@Component
public class CancelResponseProcessor extends SIPResponseProcessorAbstract {
private String method = "CANCEL";
private final String method = "CANCEL";
@Autowired
private SipLayer sipLayer;

View File

@@ -31,7 +31,7 @@ import java.text.ParseException;
public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
private final static Logger logger = LoggerFactory.getLogger(InviteResponseProcessor.class);
private String method = "INVITE";
private final String method = "INVITE";
@Autowired
private SipLayer sipLayer;

View File

@@ -27,7 +27,7 @@ import javax.sip.message.Response;
public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
private Logger logger = LoggerFactory.getLogger(RegisterResponseProcessor.class);
private String method = "REGISTER";
private final String method = "REGISTER";
@Autowired
private ISIPCommanderForPlatform sipCommanderForPlatform;

View File

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.utils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import org.dom4j.Attribute;
import org.dom4j.Document;
@@ -180,7 +181,7 @@ public class XmlUtil {
return xml.getRootElement();
}
public static DeviceChannel channelContentHander(Element itemDevice){
public static DeviceChannel channelContentHander(Element itemDevice, Device device){
Element channdelNameElement = itemDevice.element("Name");
String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim().toString() : "";
Element statusElement = itemDevice.element("Status");
@@ -254,6 +255,8 @@ public class XmlUtil {
}else if (deviceChannel.getChannelId().length() == 20) {
if (Integer.parseInt(deviceChannel.getChannelId().substring(10, 13)) == 216) { // 虚拟组织
deviceChannel.setParentId(businessGroupID);
}else if (Integer.parseInt(device.getDeviceId().substring(10, 13) )== 118) {//NVR 如果上级设备编号是NVR则直接将NVR的编号设置给通道的上级编号
deviceChannel.setParentId(device.getDeviceId());
}else if (deviceChannel.getCivilCode() != null) {
// 设备, 无parentId的20位是使用CivilCode表示上级的设备
// 注215 业务分组是需要有parentId的
@@ -308,6 +311,31 @@ public class XmlUtil {
} else {
deviceChannel.setLatitude(0.00);
}
if (deviceChannel.getLongitude()*deviceChannel.getLatitude() > 0) {
if ("WGS84".equals(device.getGeoCoordSys())) {
deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude());
deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude());
Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude());
deviceChannel.setLongitudeGcj02(position[0]);
deviceChannel.setLatitudeGcj02(position[1]);
}else if ("GCJ02".equals(device.getGeoCoordSys())) {
deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude());
deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude());
Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude());
deviceChannel.setLongitudeWgs84(position[0]);
deviceChannel.setLatitudeWgs84(position[1]);
}else {
deviceChannel.setLongitudeGcj02(0.00);
deviceChannel.setLatitudeGcj02(0.00);
deviceChannel.setLongitudeWgs84(0.00);
deviceChannel.setLatitudeWgs84(0.00);
}
}else {
deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude());
deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude());
deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude());
deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude());
}
if (XmlUtil.getText(itemDevice, "PTZType") == null || "".equals(XmlUtil.getText(itemDevice, "PTZType"))) {
//兼容INFO中的信息
Element info = itemDevice.element("Info");

View File

@@ -11,7 +11,6 @@ import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.media.zlm.dto.*;
import com.genersoft.iot.vmp.service.*;
@@ -92,10 +91,9 @@ public class ZLMHttpHookListener {
public ResponseEntity<String> onServerKeepalive(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("[ ZLM HOOK ]on_server_keepalive API调用参数" + json.toString());
logger.debug("[ ZLM HOOK ] on_server_keepalive API调用参数" + json.toString());
}
String mediaServerId = json.getString("mediaServerId");
List<ZLMHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(ZLMHttpHookSubscribe.HookType.on_server_keepalive);
if (subscribes != null && subscribes.size() > 0) {
for (ZLMHttpHookSubscribe.Event subscribe : subscribes) {
@@ -165,7 +163,6 @@ public class ZLMHttpHookListener {
if (mediaInfo != null) {
subscribe.response(mediaInfo, json);
}
}
JSONObject ret = new JSONObject();
ret.put("code", 0);
@@ -248,6 +245,23 @@ public class ZLMHttpHookListener {
ret.put("msg", "success");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* 录制hls完成后通知事件此事件对回复不敏感。
*
*/
@ResponseBody
@PostMapping(value = "/on_record_ts", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onRecordTs(@RequestBody JSONObject json){
if (logger.isDebugEnabled()) {
logger.debug("[ ZLM HOOK ]on_record_ts API调用参数" + json.toString());
}
String mediaServerId = json.getString("mediaServerId");
JSONObject ret = new JSONObject();
ret.put("code", 0);
ret.put("msg", "success");
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}
/**
* rtsp专用的鉴权事件先触发on_rtsp_realm事件然后才会触发on_rtsp_auth事件。
@@ -383,21 +397,22 @@ public class ZLMHttpHookListener {
if (item.getOriginType() == OriginType.RTSP_PUSH.ordinal()
|| item.getOriginType() == OriginType.RTMP_PUSH.ordinal()
|| item.getOriginType() == OriginType.RTC_PUSH.ordinal() ) {
streamPushItem = zlmMediaListManager.addPush(item);
item.setSeverId(userSetting.getServerId());
zlmMediaListManager.addPush(item);
}
List<GbStream> gbStreams = new ArrayList<>();
if (streamPushItem == null || streamPushItem.getGbId() == null) {
GbStream gbStream = storager.getGbStream(app, streamId);
gbStreams.add(gbStream);
}else {
if (streamPushItem.getGbId() != null) {
gbStreams.add(streamPushItem);
}
}
if (gbStreams.size() > 0) {
// List<GbStream> gbStreams = new ArrayList<>();
// if (streamPushItem == null || streamPushItem.getGbId() == null) {
// GbStream gbStream = storager.getGbStream(app, streamId);
// gbStreams.add(gbStream);
// }else {
// if (streamPushItem.getGbId() != null) {
// gbStreams.add(streamPushItem);
// }
// }
// if (gbStreams.size() > 0) {
// eventPublisher.catalogEventPublishForStream(null, gbStreams, CatalogEvent.ON);
}
// }
}else {
// 兼容流注销时类型从redis记录获取

View File

@@ -24,6 +24,9 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author lin
*/
@Component
public class ZLMMediaListManager {
@@ -147,7 +150,6 @@ public class ZLMMediaListManager {
}
}
}
// StreamProxyItem streamProxyItem = gbStreamMapper.selectOne(transform.getApp(), transform.getStream());
List<GbStream> gbStreamList = gbStreamMapper.selectByGBId(transform.getGbId());
if (gbStreamList != null && gbStreamList.size() == 1) {
transform.setGbStreamId(gbStreamList.get(0).getGbStreamId());
@@ -162,13 +164,12 @@ public class ZLMMediaListManager {
}
if (transform != null) {
if (channelOnlineEvents.get(transform.getGbId()) != null) {
channelOnlineEvents.get(transform.getGbId()).run(transform.getApp(), transform.getStream());
channelOnlineEvents.get(transform.getGbId()).run(transform.getApp(), transform.getStream(), transform.getServerId());
channelOnlineEvents.remove(transform.getGbId());
}
}
}
storager.updateMedia(transform);
return transform;
}

View File

@@ -12,6 +12,7 @@ import org.springframework.stereotype.Component;
import java.io.*;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -28,6 +29,9 @@ public class ZLMRESTfulUtils {
private OkHttpClient getClient(){
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
//todo 暂时写死超时时间 均为5s
httpClientBuilder.connectTimeout(5,TimeUnit.SECONDS); //设置连接超时时间
httpClientBuilder.readTimeout(5,TimeUnit.SECONDS); //设置读取超时时间
if (logger.isDebugEnabled()) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> {
logger.debug("http请求参数" + message);
@@ -47,7 +51,10 @@ public class ZLMRESTfulUtils {
return null;
}
String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
JSONObject responseJSON = null;
JSONObject responseJSON = new JSONObject();
//-2自定义流媒体 调用错误码
responseJSON.put("code",-2);
responseJSON.put("msg","流媒体调用失败");
FormBody.Builder builder = new FormBody.Builder();
builder.add("secret",mediaServerItem.getSecret());
@@ -78,11 +85,20 @@ public class ZLMRESTfulUtils {
response.close();
Objects.requireNonNull(response.body()).close();
}
} catch (ConnectException e) {
logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
logger.info("请检查media配置并确认ZLM已启动...");
}catch (IOException e) {
logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
if(e instanceof SocketTimeoutException){
//读取超时超时异常
logger.error(String.format("读取ZLM数据失败: %s, %s", url, e.getMessage()));
}
if(e instanceof ConnectException){
//判断连接异常我这里是报Failed to connect to 10.7.5.144
logger.error(String.format("连接ZLM失败: %s, %s", url, e.getMessage()));
}
}catch (Exception e){
logger.error(String.format("访问ZLM失败: %s, %s", url, e.getMessage()));
}
}else {
client.newCall(request).enqueue(new Callback(){
@@ -105,8 +121,16 @@ public class ZLMRESTfulUtils {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
logger.error(String.format("连接ZLM失败: %s, %s", e.getCause().getMessage(), e.getMessage()));
logger.info("请检查media配置并确认ZLM已启动...");
logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage()));
if(e instanceof SocketTimeoutException){
//读取超时超时异常
logger.error(String.format("读取ZLM数据失败: %s, %s", call.request().toString(), e.getMessage()));
}
if(e instanceof ConnectException){
//判断连接异常我这里是报Failed to connect to 10.7.5.144
logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage()));
}
}
});
}
@@ -151,7 +175,7 @@ public class ZLMRESTfulUtils {
}
}
File snapFile = new File(targetPath + "/" + fileName);
File snapFile = new File(targetPath + File.separator + fileName);
FileOutputStream outStream = new FileOutputStream(snapFile);
outStream.write(Objects.requireNonNull(response.body()).bytes());

View File

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import org.slf4j.Logger;
@@ -20,6 +21,9 @@ public class ZLMRTPServerFactory {
@Autowired
private ZLMRESTfulUtils zlmresTfulUtils;
@Autowired
private UserSetting userSetting;
private int[] portRangeArray = new int[2];
public int getFreePort(MediaServerItem mediaServerItem, int startPort, int endPort, List<Integer> usedFreelist) {
@@ -87,10 +91,15 @@ public class ZLMRTPServerFactory {
int result = -1;
// 查询此rtp server 是否已经存在
JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
if (rtpInfo != null && rtpInfo.getInteger("code") == 0 && rtpInfo.getBoolean("exist")) {
result = rtpInfo.getInteger("local_port");
if(rtpInfo.getInteger("code") == 0){
if (rtpInfo.getBoolean("exist")) {
result = rtpInfo.getInteger("local_port");
return result;
}
}else if(rtpInfo.getInteger("code") == -2){
return result;
}
Map<String, Object> param = new HashMap<>();
// 推流端口设置0则使用随机端口
param.put("enable_tcp", 1);
@@ -197,6 +206,7 @@ public class ZLMRTPServerFactory {
sendRtpItem.setTcp(tcp);
sendRtpItem.setApp("rtp");
sendRtpItem.setLocalPort(localPort);
sendRtpItem.setServerId(userSetting.getServerId());
sendRtpItem.setMediaServerId(serverItem.getId());
return sendRtpItem;
}
@@ -238,6 +248,7 @@ public class ZLMRTPServerFactory {
sendRtpItem.setChannelId(channelId);
sendRtpItem.setTcp(tcp);
sendRtpItem.setLocalPort(localPort);
sendRtpItem.setServerId(userSetting.getServerId());
sendRtpItem.setMediaServerId(serverItem.getId());
return sendRtpItem;
}
@@ -279,10 +290,10 @@ public class ZLMRTPServerFactory {
*/
public int totalReaderCount(MediaServerItem mediaServerItem, String app, String streamId) {
JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
Integer code = mediaInfo.getInteger("code");
if (mediaInfo == null) {
return 0;
}
Integer code = mediaInfo.getInteger("code");
if ( code < 0) {
logger.warn("查询流({}/{})是否有其它观看者时得到: {}", app, streamId, mediaInfo.getString("msg"));
return -1;

View File

@@ -1,6 +1,9 @@
package com.genersoft.iot.vmp.media.zlm.dto;
/**
* @author lin
*/
public interface ChannelOnlineEvent {
void run(String app, String stream);
void run(String app, String stream, String serverId);
}

View File

@@ -61,10 +61,15 @@ public class MediaItem {
private String originUrl;
/**
* 服务器id
* 流媒体服务器id
*/
private String mediaServerId;
/**
* 服务器id
*/
private String severId;
/**
* GMT unix系统时间戳单位秒
*/
@@ -414,4 +419,12 @@ public class MediaItem {
public void setStreamInfo(StreamInfo streamInfo) {
this.streamInfo = streamInfo;
}
public String getSeverId() {
return severId;
}
public void setSeverId(String severId) {
this.severId = severId;
}
}

View File

@@ -81,6 +81,11 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte
*/
private String mediaServerId;
/**
* 使用的服务ID
*/
private String serverId;
public String getVhost() {
return vhost;
}
@@ -219,5 +224,13 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte
public void setMediaServerId(String mediaServerId) {
this.mediaServerId = mediaServerId;
}
public String getServerId() {
return serverId;
}
public void setServerId(String serverId) {
this.serverId = serverId;
}
}

View File

@@ -61,13 +61,12 @@ public class ZLMKeepliveTimeoutListener extends RedisKeyExpirationEventMessageLi
// 发起http请求验证zlm是否确实无法连接如果确实无法连接则发送离线事件否则不作处理
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
JSONObject mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
if (mediaServerConfig == null) {
publisher.zlmOfflineEventPublish(mediaServerId);
}else {
if (mediaServerConfig != null && mediaServerConfig.getInteger("code") == 0) {
logger.info("[zlm心跳到期]{}验证后zlm仍在线恢复心跳信息", mediaServerId);
// 添加zlm信息
mediaServerService.updateMediaServerKeepalive(mediaServerId, mediaServerConfig);
}else {
publisher.zlmOfflineEventPublish(mediaServerId);
}
}
}

View File

@@ -42,7 +42,7 @@ public class ZLMStatusEventListener {
logger.info("[ZLM] 上线 ID" + event.getMediaServerId());
streamPushService.zlmServerOnline(event.getMediaServerId());
streamProxyService.zlmServerOnline(event.getMediaServerId());
playService.zlmServerOnline(event.getMediaServerId());
}
@Async

View File

@@ -42,6 +42,8 @@ public interface IPlayService {
StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream);
void zlmServerOnline(String mediaServerId);
void audioBroadcast(Device device, String channelId, int timeout, AudioBroadcastEvent event);
void stopAudioBroadcast(String deviceId, String channelId);
}

View File

@@ -23,7 +23,6 @@ public class StreamGPSSubscribeTask {
private IVideoManagerStorage storager;
@Scheduled(fixedRate = 30 * 1000) //每30秒执行一次
public void execute(){
List<GPSMsgInfo> gpsMsgInfo = redisCatchStorage.getAllGpsMsgInfo();

View File

@@ -1,7 +1,10 @@
package com.genersoft.iot.vmp.service.bean;
import java.util.stream.Stream;
/**
* 当上级平台
* @author lin
*/
public class MessageForPushChannel {
/**
@@ -45,6 +48,20 @@ public class MessageForPushChannel {
*/
private String mediaServerId;
public static MessageForPushChannel getInstance(int type, String app, String stream, String gbId,
String platFormId, String platFormName, String serverId,
String mediaServerId){
MessageForPushChannel messageForPushChannel = new MessageForPushChannel();
messageForPushChannel.setType(type);
messageForPushChannel.setGbId(gbId);
messageForPushChannel.setApp(app);
messageForPushChannel.setStream(stream);
messageForPushChannel.setMediaServerId(mediaServerId);
messageForPushChannel.setPlatFormId(platFormId);
messageForPushChannel.setPlatFormName(platFormName);
return messageForPushChannel;
}
public int getType() {
return type;

View File

@@ -0,0 +1,170 @@
package com.genersoft.iot.vmp.service.bean;
/**
* redis消息请求下级推送流信息
* @author lin
*/
public class RequestPushStreamMsg {
/**
* 下级服务ID
*/
private String mediaServerId;
/**
* 流ID
*/
private String app;
/**
* 应用名
*/
private String stream;
/**
* 目标IP
*/
private String ip;
/**
* 目标端口
*/
private int port;
/**
* ssrc
*/
private String ssrc;
/**
* 是否使用TCP方式
*/
private boolean tcp;
/**
* 本地使用的端口
*/
private int srcPort;
/**
* 发送时rtp的ptuint8_t,不传时默认为96
*/
private int pt;
/**
* 发送时rtp的负载类型。为true时负载为ps为false时为es
*/
private boolean ps;
/**
* 是否只有音频
*/
private boolean onlyAudio;
public static RequestPushStreamMsg getInstance(String mediaServerId, String app, String stream, String ip, int port, String ssrc,
boolean tcp, int srcPort, int pt, boolean ps, boolean onlyAudio) {
RequestPushStreamMsg requestPushStreamMsg = new RequestPushStreamMsg();
requestPushStreamMsg.setMediaServerId(mediaServerId);
requestPushStreamMsg.setApp(app);
requestPushStreamMsg.setStream(stream);
requestPushStreamMsg.setIp(ip);
requestPushStreamMsg.setPort(port);
requestPushStreamMsg.setSsrc(ssrc);
requestPushStreamMsg.setTcp(tcp);
requestPushStreamMsg.setSrcPort(srcPort);
requestPushStreamMsg.setPt(pt);
requestPushStreamMsg.setPs(ps);
requestPushStreamMsg.setOnlyAudio(onlyAudio);
return requestPushStreamMsg;
}
public String getMediaServerId() {
return mediaServerId;
}
public void setMediaServerId(String mediaServerId) {
this.mediaServerId = mediaServerId;
}
public String getApp() {
return app;
}
public void setApp(String app) {
this.app = app;
}
public String getStream() {
return stream;
}
public void setStream(String stream) {
this.stream = stream;
}
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 getSsrc() {
return ssrc;
}
public void setSsrc(String ssrc) {
this.ssrc = ssrc;
}
public boolean isTcp() {
return tcp;
}
public void setTcp(boolean tcp) {
this.tcp = tcp;
}
public int getSrcPort() {
return srcPort;
}
public void setSrcPort(int srcPort) {
this.srcPort = srcPort;
}
public int getPt() {
return pt;
}
public void setPt(int pt) {
this.pt = pt;
}
public boolean isPs() {
return ps;
}
public void setPs(boolean ps) {
this.ps = ps;
}
public boolean isOnlyAudio() {
return onlyAudio;
}
public void setOnlyAudio(boolean onlyAudio) {
this.onlyAudio = onlyAudio;
}
}

View File

@@ -0,0 +1,173 @@
package com.genersoft.iot.vmp.service.bean;
/**
* redis消息请求下级回复推送信息
* @author lin
*/
public class RequestSendItemMsg {
/**
* 下级服务ID
*/
private String serverId;
/**
* 下级服务ID
*/
private String mediaServerId;
/**
* 流ID
*/
private String app;
/**
* 应用名
*/
private String stream;
/**
* 目标IP
*/
private String ip;
/**
* 目标端口
*/
private int port;
/**
* ssrc
*/
private String ssrc;
/**
* 平台国标编号
*/
private String platformId;
/**
* 平台名称
*/
private String platformName;
/**
* 通道ID
*/
private String channelId;
/**
* 是否使用TCP
*/
private Boolean isTcp;
public static RequestSendItemMsg getInstance(String serverId, String mediaServerId, String app, String stream, String ip, int port,
String ssrc, String platformId, String channelId, Boolean isTcp, String platformName) {
RequestSendItemMsg requestSendItemMsg = new RequestSendItemMsg();
requestSendItemMsg.setServerId(serverId);
requestSendItemMsg.setMediaServerId(mediaServerId);
requestSendItemMsg.setApp(app);
requestSendItemMsg.setStream(stream);
requestSendItemMsg.setIp(ip);
requestSendItemMsg.setPort(port);
requestSendItemMsg.setSsrc(ssrc);
requestSendItemMsg.setPlatformId(platformId);
requestSendItemMsg.setPlatformName(platformName);
requestSendItemMsg.setChannelId(channelId);
requestSendItemMsg.setTcp(isTcp);
return requestSendItemMsg;
}
public String getServerId() {
return serverId;
}
public void setServerId(String serverId) {
this.serverId = serverId;
}
public String getMediaServerId() {
return mediaServerId;
}
public void setMediaServerId(String mediaServerId) {
this.mediaServerId = mediaServerId;
}
public String getApp() {
return app;
}
public void setApp(String app) {
this.app = app;
}
public String getStream() {
return stream;
}
public void setStream(String stream) {
this.stream = stream;
}
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 getSsrc() {
return ssrc;
}
public void setSsrc(String ssrc) {
this.ssrc = ssrc;
}
public String getPlatformId() {
return platformId;
}
public void setPlatformId(String platformId) {
this.platformId = platformId;
}
public String getPlatformName() {
return platformName;
}
public void setPlatformName(String platformName) {
this.platformName = platformName;
}
public String getChannelId() {
return channelId;
}
public void setChannelId(String channelId) {
this.channelId = channelId;
}
public Boolean getTcp() {
return isTcp;
}
public void setTcp(Boolean tcp) {
isTcp = tcp;
}
}

View File

@@ -0,0 +1,31 @@
package com.genersoft.iot.vmp.service.bean;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
/**
* redis消息下级回复推送信息
* @author lin
*/
public class ResponseSendItemMsg {
private SendRtpItem sendRtpItem;
private MediaServerItem mediaServerItem;
public SendRtpItem getSendRtpItem() {
return sendRtpItem;
}
public void setSendRtpItem(SendRtpItem sendRtpItem) {
this.sendRtpItem = sendRtpItem;
}
public MediaServerItem getMediaServerItem() {
return mediaServerItem;
}
public void setMediaServerItem(MediaServerItem mediaServerItem) {
this.mediaServerItem = mediaServerItem;
}
}

View File

@@ -0,0 +1,116 @@
package com.genersoft.iot.vmp.service.bean;
/**
* @author lin
*/
public class WvpRedisMsg {
public static WvpRedisMsg getInstance(String fromId, String toId, String type, String cmd, String serial, String content){
WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
wvpRedisMsg.setFromId(fromId);
wvpRedisMsg.setToId(toId);
wvpRedisMsg.setType(type);
wvpRedisMsg.setCmd(cmd);
wvpRedisMsg.setSerial(serial);
wvpRedisMsg.setContent(content);
return wvpRedisMsg;
}
private String fromId;
private String toId;
/**
* req 请求, res 回复
*/
private String type;
private String cmd;
/**
* 消息的ID
*/
private String serial;
private Object content;
private final static String requestTag = "req";
private final static String responseTag = "res";
public static WvpRedisMsg getRequestInstance(String fromId, String toId, String cmd, String serial, Object content) {
WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
wvpRedisMsg.setType(requestTag);
wvpRedisMsg.setFromId(fromId);
wvpRedisMsg.setToId(toId);
wvpRedisMsg.setCmd(cmd);
wvpRedisMsg.setSerial(serial);
wvpRedisMsg.setContent(content);
return wvpRedisMsg;
}
public static WvpRedisMsg getResponseInstance() {
WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
wvpRedisMsg.setType(responseTag);
return wvpRedisMsg;
}
public static WvpRedisMsg getResponseInstance(String fromId, String toId, String cmd, String serial, Object content) {
WvpRedisMsg wvpRedisMsg = new WvpRedisMsg();
wvpRedisMsg.setType(responseTag);
wvpRedisMsg.setFromId(fromId);
wvpRedisMsg.setToId(toId);
wvpRedisMsg.setCmd(cmd);
wvpRedisMsg.setSerial(serial);
wvpRedisMsg.setContent(content);
return wvpRedisMsg;
}
public static boolean isRequest(WvpRedisMsg wvpRedisMsg) {
return requestTag.equals(wvpRedisMsg.getType());
}
public String getSerial() {
return serial;
}
public void setSerial(String serial) {
this.serial = serial;
}
public String getFromId() {
return fromId;
}
public void setFromId(String fromId) {
this.fromId = fromId;
}
public String getToId() {
return toId;
}
public void setToId(String toId) {
this.toId = toId;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public Object getContent() {
return content;
}
public void setContent(Object content) {
this.content = content;
}
}

View File

@@ -0,0 +1,12 @@
package com.genersoft.iot.vmp.service.bean;
/**
* @author lin
*/
public class WvpRedisMsgCmd {
public static final String GET_SEND_ITEM = "GetSendItem";
public static final String REQUEST_PUSH_STREAM = "RequestPushStream";
}

View File

@@ -2,16 +2,21 @@ package com.genersoft.iot.vmp.service.impl;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
import com.genersoft.iot.vmp.gb28181.bean.SyncStatus;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.slf4j.Logger;
@@ -49,6 +54,12 @@ public class DeviceServiceImpl implements IDeviceService {
@Autowired
private DeviceMapper deviceMapper;
@Autowired
private DeviceChannelMapper deviceChannelMapper;
@Autowired
private IVideoManagerStorage storage;
@Autowired
private ISIPCommander commander;
@@ -68,7 +79,6 @@ public class DeviceServiceImpl implements IDeviceService {
if (deviceInRedis != null && deviceInDb == null) {
// redis 存在脏数据
redisCatchStorage.clearCatchByDeviceId(device.getDeviceId());
}
device.setUpdateTime(now);
device.setOnline(1);
@@ -77,13 +87,15 @@ public class DeviceServiceImpl implements IDeviceService {
if (device.getCreateTime() == null) {
device.setCreateTime(now);
logger.info("[设备上线,首次注册]: {},查询设备信息以及通道信息", device.getDeviceId());
deviceMapper.add(device);
redisCatchStorage.updateDevice(device);
commander.deviceInfoQuery(device);
sync(device);
deviceMapper.add(device);
}else {
deviceMapper.update(device);
redisCatchStorage.updateDevice(device);
}
redisCatchStorage.updateDevice(device);
// 上线添加订阅
if (device.getSubscribeCycleForCatalog() > 0) {
// 查询在线设备那些开启了订阅,为设备开启定时的目录订阅
@@ -94,7 +106,6 @@ public class DeviceServiceImpl implements IDeviceService {
}
// 刷新过期任务
String registerExpireTaskKey = registerExpireTaskKeyPrefix + device.getDeviceId();
dynamicTask.stop(registerExpireTaskKey);
dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId()), device.getExpires() * 1000);
}
@@ -143,8 +154,16 @@ public class DeviceServiceImpl implements IDeviceService {
if (device == null || device.getSubscribeCycleForCatalog() < 0) {
return false;
}
logger.info("移除目录订阅: {}", device.getDeviceId());
dynamicTask.stop(device.getDeviceId() + "catalog");
logger.info("[移除目录订阅]: {}", device.getDeviceId());
String taskKey = device.getDeviceId() + "catalog";
if (device.getOnline() == 1) {
Runnable runnable = dynamicTask.get(taskKey);
if (runnable instanceof ISubscribeTask) {
ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
subscribeTask.stop();
}
}
dynamicTask.stop(taskKey);
return true;
}
@@ -168,8 +187,16 @@ public class DeviceServiceImpl implements IDeviceService {
if (device == null || device.getSubscribeCycleForCatalog() < 0) {
return false;
}
logger.info("移除移动位置订阅: {}", device.getDeviceId());
dynamicTask.stop(device.getDeviceId() + "mobile_position");
logger.info("[移除移动位置订阅]: {}", device.getDeviceId());
String taskKey = device.getDeviceId() + "mobile_position";
if (device.getOnline() == 1) {
Runnable runnable = dynamicTask.get(taskKey);
if (runnable instanceof ISubscribeTask) {
ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
subscribeTask.stop();
}
}
dynamicTask.stop(taskKey);
return true;
}
@@ -275,6 +302,10 @@ public class DeviceServiceImpl implements IDeviceService {
removeMobilePositionSubscribe(deviceInStore);
}
}
// 坐标系变化需要重新计算GCJ02坐标和WGS84坐标
if (!deviceInStore.getGeoCoordSys().equals(device.getGeoCoordSys())) {
updateDeviceChannelGeoCoordSys(device);
}
String now = DateUtil.getNow();
device.setUpdateTime(now);
@@ -282,6 +313,32 @@ public class DeviceServiceImpl implements IDeviceService {
device.setUpdateTime(DateUtil.getNow());
if (deviceMapper.update(device) > 0) {
redisCatchStorage.updateDevice(device);
}
}
/**
* 更新通道坐标系
*/
private void updateDeviceChannelGeoCoordSys(Device device) {
List<DeviceChannel> deviceChannels = deviceChannelMapper.getAllChannelWithCoordinate(device.getDeviceId());
if (deviceChannels.size() > 0) {
for (DeviceChannel deviceChannel : deviceChannels) {
if ("WGS84".equals(device.getGeoCoordSys())) {
deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude());
deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude());
Double[] position = Coordtransform.WGS84ToGCJ02(deviceChannel.getLongitude(), deviceChannel.getLatitude());
deviceChannel.setLongitudeGcj02(position[0]);
deviceChannel.setLatitudeGcj02(position[1]);
}else if ("GCJ02".equals(device.getGeoCoordSys())) {
deviceChannel.setLongitudeGcj02(deviceChannel.getLongitude());
deviceChannel.setLatitudeGcj02(deviceChannel.getLatitude());
Double[] position = Coordtransform.GCJ02ToWGS84(deviceChannel.getLongitude(), deviceChannel.getLatitude());
deviceChannel.setLongitudeWgs84(position[0]);
deviceChannel.setLatitudeWgs84(position[1]);
}
}
}
storage.updateChannels(device.getDeviceId(), deviceChannels);
}
}

View File

@@ -106,7 +106,8 @@ public class GbStreamServiceImpl implements IGbStreamService {
deviceChannel.setStatus(1);
deviceChannel.setParentId(catalogId ==null?gbStream.getCatalogId():catalogId);
deviceChannel.setRegisterWay(1);
if (catalogId.length() <= 10) { // 父节点是行政区划,则设置CivilCode使用此行政区划
if (catalogId.length() > 0 && catalogId.length() <= 10) {
// 父节点是行政区划,则设置CivilCode使用此行政区划
deviceChannel.setCivilCode(catalogId);
}else {
deviceChannel.setCivilCode(platform.getAdministrativeDivision());

View File

@@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
@@ -35,7 +36,9 @@ import org.springframework.util.StringUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
* 媒体服务器节点管理
@@ -189,6 +192,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
public void clearRTPServer(MediaServerItem mediaServerItem) {
mediaServerItem.setSsrcConfig(new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getDomain()));
redisUtil.zAdd(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId(), mediaServerItem.getId(), 0);
}
@@ -229,11 +233,10 @@ public class MediaServerServiceImpl implements IMediaServerService {
}
result.sort((serverItem1, serverItem2)->{
int sortResult = 0;
try {
sortResult = DateUtil.format.parse(serverItem1.getCreateTime()).compareTo(DateUtil.format.parse(serverItem2.getCreateTime()));
} catch (ParseException e) {
e.printStackTrace();
}
LocalDateTime localDateTime1 = LocalDateTime.parse(serverItem1.getCreateTime(), DateUtil.formatter);
LocalDateTime localDateTime2 = LocalDateTime.parse(serverItem2.getCreateTime(), DateUtil.formatter);
sortResult = localDateTime1.compareTo(localDateTime2);
return sortResult;
});
return result;
@@ -495,14 +498,14 @@ public class MediaServerServiceImpl implements IMediaServerService {
param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline
param.put("ffmpeg.cmd","%s -fflags nobuffer -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s");
param.put("hook.enable","1");
param.put("hook.on_flow_report","");
param.put("hook.on_flow_report",String.format("%s/on_flow_report", hookPrex));
param.put("hook.on_play",String.format("%s/on_play", hookPrex));
param.put("hook.on_http_access","");
param.put("hook.on_http_access",String.format("%s/on_http_access", hookPrex));
param.put("hook.on_publish", String.format("%s/on_publish", hookPrex));
param.put("hook.on_record_mp4",recordHookPrex != null? String.format("%s/on_record_mp4", recordHookPrex): "");
param.put("hook.on_record_ts","");
param.put("hook.on_rtsp_auth","");
param.put("hook.on_rtsp_realm","");
param.put("hook.on_record_ts",String.format("%s/on_record_ts", hookPrex));
param.put("hook.on_rtsp_auth",String.format("%s/on_rtsp_auth", hookPrex));
param.put("hook.on_rtsp_realm",String.format("%s/on_rtsp_realm", hookPrex));
param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex));
param.put("hook.on_shell_login",String.format("%s/on_shell_login", hookPrex));
param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex));

View File

@@ -21,6 +21,8 @@ import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IMediaService;
import com.genersoft.iot.vmp.service.IPlayService;
import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
import com.genersoft.iot.vmp.service.bean.PlayBackCallback;
import com.genersoft.iot.vmp.service.bean.PlayBackResult;
@@ -32,8 +34,6 @@ import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
import com.genersoft.iot.vmp.service.IMediaService;
import com.genersoft.iot.vmp.service.IPlayService;
import gov.nist.javax.sip.stack.SIPDialog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,6 +49,7 @@ import javax.sip.SipException;
import java.io.FileNotFoundException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -74,9 +75,6 @@ public class PlayServiceImpl implements IPlayService {
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private RedisUtil redis;
@Autowired
private DeferredResultHolder resultHolder;
@@ -140,36 +138,19 @@ public class PlayServiceImpl implements IPlayService {
result.onCompletion(()->{
// 点播结束时调用截图接口
// TODO 应该在上流时调用更好,结束也可能是错误结束
try {
String classPath = ResourceUtils.getURL("classpath:").getPath();
// 兼容打包为jar的class路径
if(classPath.contains("jar")) {
classPath = classPath.substring(0, classPath.lastIndexOf("."));
classPath = classPath.substring(0, classPath.lastIndexOf("/") + 1);
String path = "static/static/snap/";
String fileName = deviceId + "_" + channelId + ".jpg";
ResponseEntity responseEntity = (ResponseEntity)result.getResult();
if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
WVPResult wvpResult = (WVPResult)responseEntity.getBody();
if (Objects.requireNonNull(wvpResult).getCode() == 0) {
StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId());
String streamUrl = streamInfoForSuccess.getFmp4();
// 请求截图
logger.info("[请求截图]: " + fileName);
zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName);
}
if (classPath.startsWith("file:")) {
classPath = classPath.substring(classPath.indexOf(":") + 1);
}
String path = classPath + "static/static/snap/";
// 兼容Windows系统路径去除前面的“/”)
if(System.getProperty("os.name").contains("indows")) {
path = path.substring(1);
}
String fileName = deviceId + "_" + channelId + ".jpg";
ResponseEntity responseEntity = (ResponseEntity)result.getResult();
if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
WVPResult wvpResult = (WVPResult)responseEntity.getBody();
if (Objects.requireNonNull(wvpResult).getCode() == 0) {
StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId());
String streamUrl = streamInfoForSuccess.getFmp4();
// 请求截图
logger.info("[请求截图]: " + fileName);
zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
});
if (streamInfo != null) {
@@ -186,24 +167,33 @@ public class PlayServiceImpl implements IPlayService {
MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
if (rtpInfo != null && rtpInfo.getBoolean("exist")) {
if(rtpInfo.getInteger("code") == 0){
if (rtpInfo.getBoolean("exist")) {
WVPResult wvpResult = new WVPResult();
wvpResult.setCode(0);
wvpResult.setMsg("success");
wvpResult.setData(streamInfo);
msg.setData(wvpResult);
WVPResult wvpResult = new WVPResult();
wvpResult.setCode(0);
wvpResult.setMsg("success");
wvpResult.setData(streamInfo);
msg.setData(wvpResult);
resultHolder.invokeAllResult(msg);
if (hookEvent != null) {
hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo)));
resultHolder.invokeAllResult(msg);
if (hookEvent != null) {
hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo)));
}
}else {
redisCatchStorage.stopPlay(streamInfo);
storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
streamInfo = null;
}
}else {
//zlm连接失败
redisCatchStorage.stopPlay(streamInfo);
storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
streamInfo = null;
}
}
if (streamInfo == null) {
String streamId = null;
@@ -256,33 +246,41 @@ public class PlayServiceImpl implements IPlayService {
if (ssrcInfo == null) {
ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
}
logger.info("[点播开始] deviceId: {}, channelId: {}, SSRC: {}", device.getDeviceId(), channelId, ssrcInfo.getSsrc() );
// 超时处理
String timeOutTaskKey = UUID.randomUUID().toString();
SSRCInfo finalSsrcInfo = ssrcInfo;
dynamicTask.startDelay( timeOutTaskKey,()->{
logger.warn(String.format("设备点播超时deviceId%s channelId%s", device.getDeviceId(), channelId));
SIPDialog dialog = streamSession.getDialogByStream(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
if (dialog != null) {
logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
timeoutCallback.run(1, "收流超时");
// 点播超时回复BYE 同时释放ssrc以及此次点播的资源
cmder.streamByeCmd(device.getDeviceId(), channelId, finalSsrcInfo.getStream(), null);
}else {
logger.info("[点播超时] 消息未响应 deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
timeoutCallback.run(0, "点播超时");
mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
mediaServerService.closeRTPServer(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
}
}, userSetting.getPlayTimeout()*1000);
}, userSetting.getPlayTimeout());
final String ssrc = ssrcInfo.getSsrc();
final String stream = ssrcInfo.getStream();
//端口获取失败的ssrcInfo 没有必要发送点播指令
if(ssrcInfo.getPort() <= 0){
logger.info("[点播端口分配异常]deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
return;
}
cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
logger.info("收到订阅消息: " + response.toJSONString());
dynamicTask.stop(timeOutTaskKey);
// hook响应
onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId, uuid);
hookEvent.response(mediaServerItemInuse, response);
logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
}, (event) -> {
ResponseEvent responseEvent = (ResponseEvent)event.event;
String contentString = new String(responseEvent.getResponse().getRawContent());
@@ -296,8 +294,10 @@ public class PlayServiceImpl implements IPlayService {
if (ssrc.equals(ssrcInResponse)) {
return;
}
logger.info("[SIP 消息] 收到invite 200, 发现下级自定义了ssrc 开启修正");
logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse );
if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
logger.info("[SIP 消息] SSRC修正 {}->{}", ssrc, ssrcInResponse);
if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
// ssrc 不可用
// 释放ssrc
@@ -450,7 +450,7 @@ public class PlayServiceImpl implements IPlayService {
cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null);
// 回复之前所有的点播请求
playBackCallback.call(playBackResult);
}, userSetting.getPlayTimeout()*1000);
}, userSetting.getPlayTimeout());
cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack,
(InviteStreamInfo inviteStreamInfo) -> {
@@ -539,7 +539,7 @@ public class PlayServiceImpl implements IPlayService {
cmder.streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), null);
// 回复之前所有的点播请求
hookCallBack.call(downloadResult);
}, userSetting.getPlayTimeout()*1000);
}, userSetting.getPlayTimeout());
cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack,
inviteStreamInfo -> {
logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
@@ -605,7 +605,7 @@ public class PlayServiceImpl implements IPlayService {
BigDecimal currentCount = new BigDecimal(duration/1000);
BigDecimal totalCount = new BigDecimal(end-start);
BigDecimal divide = currentCount.divide(totalCount,2, BigDecimal.ROUND_HALF_UP);
BigDecimal divide = currentCount.divide(totalCount,2, RoundingMode.HALF_UP);
double process = divide.doubleValue();
streamInfo.setProgress(process);
}
@@ -728,4 +728,9 @@ public class PlayServiceImpl implements IPlayService {
}
@Override
public void zlmServerOnline(String mediaServerId) {
// 似乎没啥需要做的
}
}

View File

@@ -0,0 +1,377 @@
package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 监听下级发送推送信息,并发送国标推流消息上级
* @author lin
*/
@Component
public class RedisGbPlayMsgListener implements MessageListener {
private final static Logger logger = LoggerFactory.getLogger(RedisGbPlayMsgListener.class);
public static final String WVP_PUSH_STREAM_KEY = "WVP_PUSH_STREAM";
/**
* 流媒体不存在的错误玛
*/
public static final int ERROR_CODE_MEDIA_SERVER_NOT_FOUND = -1;
/**
* 离线的错误玛
*/
public static final int ERROR_CODE_OFFLINE = -2;
/**
* 超时的错误玛
*/
public static final int ERROR_CODE_TIMEOUT = -3;
private Map<String, PlayMsgCallback> callbacks = new ConcurrentHashMap<>();
private Map<String, PlayMsgCallbackForStartSendRtpStream> callbacksForStartSendRtpStream = new ConcurrentHashMap<>();
private Map<String, PlayMsgErrorCallback> callbacksForError = new ConcurrentHashMap<>();
@Autowired
private UserSetting userSetting;
@Autowired
private RedisUtil redis;
@Autowired
private ZLMMediaListManager zlmMediaListManager;
@Autowired
private ZLMRTPServerFactory zlmrtpServerFactory;
@Autowired
private IMediaServerService mediaServerService;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private DynamicTask dynamicTask;
@Autowired
private ZLMMediaListManager mediaListManager;
@Autowired
private ZLMHttpHookSubscribe subscribe;
public interface PlayMsgCallback{
void handler(ResponseSendItemMsg responseSendItemMsg);
}
public interface PlayMsgCallbackForStartSendRtpStream{
void handler(JSONObject jsonObject);
}
public interface PlayMsgErrorCallback{
void handler(WVPResult wvpResult);
}
@Override
public void onMessage(Message message, byte[] bytes) {
JSONObject msgJSON = JSON.parseObject(message.getBody(), JSONObject.class);
WvpRedisMsg wvpRedisMsg = JSON.toJavaObject(msgJSON, WvpRedisMsg.class);
if (!userSetting.getServerId().equals(wvpRedisMsg.getToId())) {
return;
}
if (WvpRedisMsg.isRequest(wvpRedisMsg)) {
logger.info("[收到REDIS通知] 请求: {}", new String(message.getBody()));
switch (wvpRedisMsg.getCmd()){
case WvpRedisMsgCmd.GET_SEND_ITEM:
RequestSendItemMsg content = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), RequestSendItemMsg.class);
requestSendItemMsgHand(content, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial());
break;
case WvpRedisMsgCmd.REQUEST_PUSH_STREAM:
RequestPushStreamMsg param = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), RequestPushStreamMsg.class);;
requestPushStreamMsgHand(param, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial());
break;
default:
break;
}
}else {
logger.info("[收到REDIS通知] 回复: {}", new String(message.getBody()));
switch (wvpRedisMsg.getCmd()){
case WvpRedisMsgCmd.GET_SEND_ITEM:
WVPResult content = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), WVPResult.class);
String key = wvpRedisMsg.getSerial();
switch (content.getCode()) {
case 0:
ResponseSendItemMsg responseSendItemMsg =JSON.toJavaObject((JSONObject)content.getData(), ResponseSendItemMsg.class);
PlayMsgCallback playMsgCallback = callbacks.get(key);
if (playMsgCallback != null) {
callbacksForError.remove(key);
playMsgCallback.handler(responseSendItemMsg);
}
break;
case ERROR_CODE_MEDIA_SERVER_NOT_FOUND:
case ERROR_CODE_OFFLINE:
case ERROR_CODE_TIMEOUT:
PlayMsgErrorCallback errorCallback = callbacksForError.get(key);
if (errorCallback != null) {
callbacks.remove(key);
errorCallback.handler(content);
}
break;
default:
break;
}
break;
case WvpRedisMsgCmd.REQUEST_PUSH_STREAM:
WVPResult wvpResult = JSON.toJavaObject((JSONObject)wvpRedisMsg.getContent(), WVPResult.class);
String serial = wvpRedisMsg.getSerial();
switch (wvpResult.getCode()) {
case 0:
JSONObject jsonObject = (JSONObject)wvpResult.getData();
PlayMsgCallbackForStartSendRtpStream playMsgCallback = callbacksForStartSendRtpStream.get(serial);
if (playMsgCallback != null) {
callbacksForError.remove(serial);
playMsgCallback.handler(jsonObject);
}
break;
case ERROR_CODE_MEDIA_SERVER_NOT_FOUND:
case ERROR_CODE_OFFLINE:
case ERROR_CODE_TIMEOUT:
PlayMsgErrorCallback errorCallback = callbacksForError.get(serial);
if (errorCallback != null) {
callbacks.remove(serial);
errorCallback.handler(wvpResult);
}
break;
default:
break;
}
break;
default:
break;
}
}
}
/**
* 处理收到的请求推流的请求
*/
private void requestPushStreamMsgHand(RequestPushStreamMsg requestPushStreamMsg, String fromId, String serial) {
MediaServerItem mediaInfo = mediaServerService.getOne(requestPushStreamMsg.getMediaServerId());
if (mediaInfo == null) {
// TODO 回复错误
return;
}
String is_Udp = requestPushStreamMsg.isTcp() ? "0" : "1";
Map<String, Object> param = new HashMap<>();
param.put("vhost","__defaultVhost__");
param.put("app",requestPushStreamMsg.getApp());
param.put("stream",requestPushStreamMsg.getStream());
param.put("ssrc", requestPushStreamMsg.getSsrc());
param.put("dst_url",requestPushStreamMsg.getIp());
param.put("dst_port", requestPushStreamMsg.getPort());
param.put("is_udp", is_Udp);
param.put("src_port", requestPushStreamMsg.getSrcPort());
param.put("pt", requestPushStreamMsg.getPt());
param.put("use_ps", requestPushStreamMsg.isPs() ? "1" : "0");
param.put("only_audio", requestPushStreamMsg.isOnlyAudio() ? "1" : "0");
JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
// 回复消息
responsePushStream(jsonObject, fromId, serial);
}
private void responsePushStream(JSONObject content, String toId, String serial) {
WVPResult<JSONObject> result = new WVPResult<>();
result.setCode(0);
result.setData(content);
WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), toId,
WvpRedisMsgCmd.REQUEST_PUSH_STREAM, serial, result);
JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
}
/**
* 处理收到的请求sendItem的请求
*/
private void requestSendItemMsgHand(RequestSendItemMsg content, String toId, String serial) {
MediaServerItem mediaServerItem = mediaServerService.getOne(content.getMediaServerId());
if (mediaServerItem == null) {
logger.info("[回复推流信息] 流媒体{}不存在 ", content.getMediaServerId());
WVPResult<SendRtpItem> result = new WVPResult<>();
result.setCode(ERROR_CODE_MEDIA_SERVER_NOT_FOUND);
result.setMsg("流媒体不存在");
WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), toId,
WvpRedisMsgCmd.GET_SEND_ITEM, serial, result);
JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
return;
}
// 确定流是否在线
boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, content.getApp(), content.getStream());
if (streamReady) {
logger.info("[回复推流信息] {}/{}", content.getApp(), content.getStream());
responseSendItem(mediaServerItem, content, toId, serial);
}else {
// 流已经离线
// 发送redis消息以使设备上线
logger.info("[ app={}, stream={} ]通道离线发送redis信息控制设备开始推流",content.getApp(), content.getStream());
String taskKey = UUID.randomUUID().toString();
// 设置超时
dynamicTask.startDelay(taskKey, ()->{
logger.info("[ app={}, stream={} ] 等待设备开始推流超时", content.getApp(), content.getStream());
WVPResult<SendRtpItem> result = new WVPResult<>();
result.setCode(ERROR_CODE_TIMEOUT);
WvpRedisMsg response = WvpRedisMsg.getResponseInstance(
userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, result
);
JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
}, userSetting.getPlatformPlayTimeout());
// 添加订阅
JSONObject subscribeKey = new JSONObject();
subscribeKey.put("app", content.getApp());
subscribeKey.put("stream", content.getStream());
subscribeKey.put("regist", true);
subscribeKey.put("schema", "rtmp");
subscribeKey.put("mediaServerId", mediaServerItem.getId());
subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
(MediaServerItem mediaServerItemInUse, JSONObject json)->{
dynamicTask.stop(taskKey);
responseSendItem(mediaServerItem, content, toId, serial);
});
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(1, content.getApp(), content.getStream(),
content.getChannelId(), content.getPlatformId(), content.getPlatformName(), content.getServerId(),
content.getMediaServerId());
redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
}
}
/**
* 将获取到的sendItem发送出去
*/
private void responseSendItem(MediaServerItem mediaServerItem, RequestSendItemMsg content, String toId, String serial) {
SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, content.getIp(),
content.getPort(), content.getSsrc(), content.getPlatformId(),
content.getApp(), content.getStream(), content.getChannelId(),
content.getTcp());
WVPResult<ResponseSendItemMsg> result = new WVPResult<>();
result.setCode(0);
ResponseSendItemMsg responseSendItemMsg = new ResponseSendItemMsg();
responseSendItemMsg.setSendRtpItem(sendRtpItem);
responseSendItemMsg.setMediaServerItem(mediaServerItem);
result.setData(responseSendItemMsg);
WvpRedisMsg response = WvpRedisMsg.getResponseInstance(
userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, result
);
JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
}
/**
* 发送消息要求下级生成推流信息
* @param serverId 下级服务ID
* @param app 应用名
* @param stream 流ID
* @param ip 目标IP
* @param port 目标端口
* @param ssrc ssrc
* @param platformId 平台国标编号
* @param channelId 通道ID
* @param isTcp 是否使用TCP
* @param callback 得到信息的回调
*/
public void sendMsg(String serverId, String mediaServerId, String app, String stream, String ip, int port, String ssrc,
String platformId, String channelId, boolean isTcp, String platformName, PlayMsgCallback callback, PlayMsgErrorCallback errorCallback) {
RequestSendItemMsg requestSendItemMsg = RequestSendItemMsg.getInstance(
serverId, mediaServerId, app, stream, ip, port, ssrc, platformId, channelId, isTcp, platformName);
requestSendItemMsg.setServerId(serverId);
String key = UUID.randomUUID().toString();
WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId, WvpRedisMsgCmd.GET_SEND_ITEM,
key, requestSendItemMsg);
JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg);
logger.info("[请求推流SendItem] {}: {}", serverId, jsonObject);
callbacks.put(key, callback);
callbacksForError.put(key, errorCallback);
dynamicTask.startDelay(key, ()->{
callbacks.remove(key);
callbacksForError.remove(key);
WVPResult<Object> wvpResult = new WVPResult<>();
wvpResult.setCode(ERROR_CODE_TIMEOUT);
wvpResult.setMsg("timeout");
errorCallback.handler(wvpResult);
}, userSetting.getPlatformPlayTimeout());
redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
}
/**
* 发送请求推流的消息
* @param param 推流参数
* @param callback 回调
*/
public void sendMsgForStartSendRtpStream(String serverId, RequestPushStreamMsg param, PlayMsgCallbackForStartSendRtpStream callback) {
String key = UUID.randomUUID().toString();
WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId,
WvpRedisMsgCmd.REQUEST_PUSH_STREAM, key, param);
JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg);
logger.info("[REDIS 请求其他平台推流] {}: {}", serverId, jsonObject);
dynamicTask.startDelay(key, ()->{
callbacksForStartSendRtpStream.remove(key);
callbacksForError.remove(key);
}, userSetting.getPlatformPlayTimeout());
callbacksForStartSendRtpStream.put(key, callback);
callbacksForError.put(key, (wvpResult)->{
logger.info("[REDIS 请求其他平台推流] 失败: {}", wvpResult.getMsg());
callbacksForStartSendRtpStream.remove(key);
callbacksForError.remove(key);
});
redis.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
}
}

View File

@@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson.JSON;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -10,17 +11,23 @@ import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
/**
* 接收来自redis的GPS更新通知
* @author lin
*/
@Component
public class RedisGPSMsgListener implements MessageListener {
public class RedisGpsMsgListener implements MessageListener {
private final static Logger logger = LoggerFactory.getLogger(RedisGPSMsgListener.class);
private final static Logger logger = LoggerFactory.getLogger(RedisGpsMsgListener.class);
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Override
public void onMessage(Message message, byte[] bytes) {
logger.info("收到来自REDIS的GPS通知 {}", new String(message.getBody()));
public void onMessage(@NotNull Message message, byte[] bytes) {
if (logger.isDebugEnabled()) {
logger.debug("收到来自REDIS的GPS通知 {}", new String(message.getBody()));
}
GPSMsgInfo gpsMsgInfo = JSON.parseObject(message.getBody(), GPSMsgInfo.class);
redisCatchStorage.updateGpsMsgInfo(gpsMsgInfo);
}

View File

@@ -0,0 +1,83 @@
package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.AlarmChannelMessage;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
/**
* @author lin
*/
@Component
public class RedisStreamMsgListener implements MessageListener {
private final static Logger logger = LoggerFactory.getLogger(RedisStreamMsgListener.class);
@Autowired
private ISIPCommander commander;
@Autowired
private ISIPCommanderForPlatform commanderForPlatform;
@Autowired
private IVideoManagerStorage storage;
@Autowired
private UserSetting userSetting;
@Autowired
private ZLMMediaListManager zlmMediaListManager;
@Override
public void onMessage(Message message, byte[] bytes) {
JSONObject steamMsgJson = JSON.parseObject(message.getBody(), JSONObject.class);
if (steamMsgJson == null) {
logger.warn("[REDIS的ALARM通知]消息解析失败");
return;
}
String serverId = steamMsgJson.getString("serverId");
if (userSetting.getServerId().equals(serverId)) {
// 自己发送的消息忽略即可
return;
}
logger.info("[REDIS通知] 流变化: {}", new String(message.getBody()));
String app = steamMsgJson.getString("app");
String stream = steamMsgJson.getString("stream");
boolean register = steamMsgJson.getBoolean("register");
String mediaServerId = steamMsgJson.getString("mediaServerId");
MediaItem mediaItem = new MediaItem();
mediaItem.setSeverId(serverId);
mediaItem.setApp(app);
mediaItem.setStream(stream);
mediaItem.setRegist(register);
mediaItem.setMediaServerId(mediaServerId);
mediaItem.setCreateStamp(System.currentTimeMillis()/1000);
mediaItem.setAliveSecond(0L);
mediaItem.setTotalReaderCount("0");
mediaItem.setOriginType(0);
mediaItem.setOriginTypeStr("0");
mediaItem.setOriginTypeStr("unknown");
zlmMediaListManager.addPush(mediaItem);
}
}

View File

@@ -107,6 +107,7 @@ public class StreamPushServiceImpl implements IStreamPushService {
streamPushItem.setStatus(true);
streamPushItem.setStreamType("push");
streamPushItem.setVhost(item.getVhost());
streamPushItem.setServerId(item.getSeverId());
return streamPushItem;
}

View File

@@ -356,6 +356,15 @@ public interface IVideoManagerStorage {
int removeMedia(String app, String stream);
/**
* 获取但个推流
* @param app
* @param stream
* @return
*/
StreamPushItem getMedia(String app, String stream);
/**
* 清空推流列表
*/

View File

@@ -17,10 +17,10 @@ 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, streamId, longitude, latitude, createTime, updateTime) " +
"ipAddress, port, password, PTZType, status, streamId, longitude, latitude, longitudeGcj02, latitudeGcj02, longitudeWgs84, latitudeWgs84, createTime, updateTime) " +
"VALUES ('${channelId}', '${deviceId}', '${name}', '${manufacture}', '${model}', '${owner}', '${civilCode}', '${block}'," +
"'${address}', ${parental}, '${parentId}', ${safetyWay}, ${registerWay}, '${certNum}', ${certifiable}, ${errCode}, '${secrecy}', " +
"'${ipAddress}', ${port}, '${password}', ${PTZType}, ${status}, '${streamId}', ${longitude}, ${latitude},'${createTime}', '${updateTime}')")
"'${ipAddress}', ${port}, '${password}', ${PTZType}, ${status}, '${streamId}', ${longitude}, ${latitude}, ${longitudeGcj02}, ${latitudeGcj02}, ${longitudeWgs84}, ${latitudeWgs84},'${createTime}', '${updateTime}')")
int add(DeviceChannel channel);
@Update(value = {" <script>" +
@@ -50,6 +50,10 @@ public interface DeviceChannelMapper {
"<if test='hasAudio != null'>, hasAudio=${hasAudio}</if>" +
"<if test='longitude != null'>, longitude=${longitude}</if>" +
"<if test='latitude != null'>, latitude=${latitude}</if>" +
"<if test='longitudeGcj02 != null'>, longitudeGcj02=${longitudeGcj02}</if>" +
"<if test='latitudeGcj02 != null'>, latitudeGcj02=${latitudeGcj02}</if>" +
"<if test='longitudeWgs84 != null'>, longitudeWgs84=${longitudeWgs84}</if>" +
"<if test='latitudeWgs84 != null'>, latitudeWgs84=${latitudeWgs84}</if>" +
"WHERE deviceId='${deviceId}' AND channelId='${channelId}'"+
" </script>"})
int update(DeviceChannel channel);
@@ -67,7 +71,7 @@ public interface DeviceChannelMapper {
" <if test='online == false' > AND dc.status=0</if>" +
" <if test='hasSubChannel == true' > AND dc.subCount > 0 </if>" +
" <if test='hasSubChannel == false' > AND dc.subCount = 0 </if>" +
"GROUP BY dc.channelId " +
"ORDER BY dc.channelId " +
" </script>"})
List<DeviceChannel> queryChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online);
@@ -138,7 +142,8 @@ public interface DeviceChannelMapper {
"insert into device_channel " +
"(channelId, deviceId, name, manufacture, model, owner, civilCode, block, subCount, " +
" address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " +
" ipAddress, port, password, PTZType, status, streamId, longitude, latitude, createTime, updateTime) " +
" ipAddress, port, password, PTZType, status, streamId, longitude, latitude, longitudeGcj02, latitudeGcj02, " +
" longitudeWgs84, latitudeWgs84, createTime, updateTime) " +
"values " +
"<foreach collection='addChannels' index='index' item='item' separator=','> " +
"('${item.channelId}', '${item.deviceId}', '${item.name}', '${item.manufacture}', '${item.model}', " +
@@ -146,7 +151,8 @@ public interface DeviceChannelMapper {
"'${item.address}', ${item.parental}, '${item.parentId}', ${item.safetyWay}, ${item.registerWay}, " +
"'${item.certNum}', ${item.certifiable}, ${item.errCode}, '${item.secrecy}', " +
"'${item.ipAddress}', ${item.port}, '${item.password}', ${item.PTZType}, ${item.status}, " +
"'${item.streamId}', ${item.longitude}, ${item.latitude},'${item.createTime}', '${item.updateTime}')" +
"'${item.streamId}', ${item.longitude}, ${item.latitude},${item.longitudeGcj02}, " +
"${item.latitudeGcj02},${item.longitudeWgs84}, ${item.latitudeWgs84},'${item.createTime}', '${item.updateTime}')" +
"</foreach> " +
"ON DUPLICATE KEY UPDATE " +
"updateTime=VALUES(updateTime), " +
@@ -173,7 +179,11 @@ public interface DeviceChannelMapper {
"status=VALUES(status), " +
"streamId=VALUES(streamId), " +
"longitude=VALUES(longitude), " +
"latitude=VALUES(latitude)" +
"latitude=VALUES(latitude), " +
"longitudeGcj02=VALUES(longitudeGcj02), " +
"latitudeGcj02=VALUES(latitudeGcj02), " +
"longitudeWgs84=VALUES(longitudeWgs84), " +
"latitudeWgs84=VALUES(latitudeWgs84) " +
"</script>")
int batchAdd(List<DeviceChannel> addChannels);
@@ -207,7 +217,11 @@ public interface DeviceChannelMapper {
"<if test='item.hasAudio != null'>, hasAudio=${item.hasAudio}</if>" +
"<if test='item.longitude != null'>, longitude=${item.longitude}</if>" +
"<if test='item.latitude != null'>, latitude=${item.latitude}</if>" +
"WHERE deviceId=#{item.deviceId} AND channelId=#{item.channelId}"+
"<if test='item.longitudeGcj02 != null'>, longitudeGcj02=${item.longitudeGcj02}</if>" +
"<if test='item.latitudeGcj02 != null'>, latitudeGcj02=${item.latitudeGcj02}</if>" +
"<if test='item.longitudeWgs84 != null'>, longitudeWgs84=${item.longitudeWgs84}</if>" +
"<if test='item.latitudeWgs84 != null'>, latitudeWgs84=${item.latitudeWgs84}</if>" +
"WHERE deviceId='${item.deviceId}' AND channelId='${item.channelId}'"+
"</foreach>" +
"</script>"})
int batchUpdate(List<DeviceChannel> updateChannels);
@@ -261,4 +275,6 @@ public interface DeviceChannelMapper {
@Select("SELECT * FROM device_channel WHERE length(trim(streamId)) > 0")
List<DeviceChannel> getAllChannelInPlay();
@Select("select * from device_channel where longitude*latitude > 0 and deviceId = #{deviceId}")
List<DeviceChannel> getAllChannelWithCoordinate(String deviceId);
}

View File

@@ -38,6 +38,7 @@ public interface DeviceMapper {
"mobilePositionSubmissionInterval," +
"subscribeCycleForAlarm," +
"ssrcCheck," +
"geoCoordSys," +
"online" +
") VALUES (" +
"#{deviceId}," +
@@ -61,6 +62,7 @@ public interface DeviceMapper {
"#{mobilePositionSubmissionInterval}," +
"#{subscribeCycleForAlarm}," +
"#{ssrcCheck}," +
"#{geoCoordSys}," +
"#{online}" +
")")
int add(Device device);
@@ -87,6 +89,7 @@ public interface DeviceMapper {
"<if test=\"mobilePositionSubmissionInterval != null\">, mobilePositionSubmissionInterval=${mobilePositionSubmissionInterval}</if>" +
"<if test=\"subscribeCycleForAlarm != null\">, subscribeCycleForAlarm=${subscribeCycleForAlarm}</if>" +
"<if test=\"ssrcCheck != null\">, ssrcCheck=${ssrcCheck}</if>" +
"<if test=\"geoCoordSys != null\">, geoCoordSys=#{geoCoordSys}</if>" +
"WHERE deviceId='${deviceId}'"+
" </script>"})
int update(Device device);

View File

@@ -14,9 +14,9 @@ import java.util.List;
public interface StreamPushMapper {
@Insert("INSERT INTO stream_push (app, stream, totalReaderCount, originType, originTypeStr, " +
"createStamp, aliveSecond, mediaServerId) VALUES" +
"createStamp, aliveSecond, mediaServerId, serverId) VALUES" +
"('${app}', '${stream}', '${totalReaderCount}', '${originType}', '${originTypeStr}', " +
"'${createStamp}', '${aliveSecond}', '${mediaServerId}' )")
"'${createStamp}', '${aliveSecond}', '${mediaServerId}' , '${serverId}' )")
int add(StreamPushItem streamPushItem);
@Update("UPDATE stream_push " +

View File

@@ -587,11 +587,11 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
String scanKey = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetting.getServerId() + "_*";
List<GPSMsgInfo> result = new ArrayList<>();
List<Object> keys = redis.scan(scanKey);
for (int i = 0; i < keys.size(); i++) {
String key = (String) keys.get(i);
for (Object o : keys) {
String key = (String) o;
GPSMsgInfo gpsMsgInfo = (GPSMsgInfo) redis.get(key);
if (!gpsMsgInfo.isStored()) { // 只取没有存过得
result.add((GPSMsgInfo)redis.get(key));
result.add((GPSMsgInfo) redis.get(key));
}
}
@@ -667,7 +667,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
@Override
public void sendStreamPushRequestedMsg(MessageForPushChannel msg) {
String key = VideoManagerConstants.VM_MSG_STREAM_PUSH_REQUESTED;
logger.info("[redis 推流被请求通知] {}: {}-{}", key, msg.getApp(), msg.getStream());
logger.info("[redis 推流被请求通知] {}: {}/{}", key, msg.getApp(), msg.getStream());
redis.convertAndSend(key, (JSONObject)JSON.toJSON(msg));
}

View File

@@ -25,12 +25,13 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
/**
* 视频设备数据存储-jdbc实现
* swwheihei
* 2020年5月6日 下午2:31:42
@@ -195,7 +196,7 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
@Override
public boolean resetChannels(String deviceId, List<DeviceChannel> deviceChannelList) {
if (deviceChannelList == null) {
if (CollectionUtils.isEmpty(deviceChannelList)) {
return false;
}
List<DeviceChannel> allChannelInPlay = deviceChannelMapper.getAllChannelInPlay();
@@ -246,6 +247,10 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
if (stringBuilder.length() > 0) {
logger.info("[目录查询]收到的数据存在重复: {}" , stringBuilder);
}
if(CollectionUtils.isEmpty(channels)){
logger.info("通道重设,数据为空={}" , deviceChannelList);
return false;
}
try {
int cleanChannelsResult = deviceChannelMapper.cleanChannelsNotInList(deviceId, channels);
int limitCount = 300;
@@ -315,6 +320,9 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
List<DeviceChannel> all;
if (catalogUnderDevice != null && catalogUnderDevice) {
all = deviceChannelMapper.queryChannels(deviceId, deviceId, query, hasSubChannel, online);
// 海康设备的parentId是SIP id
List<DeviceChannel> deviceChannels = deviceChannelMapper.queryChannels(deviceId, sipConfig.getId(), query, hasSubChannel, online);
all.addAll(deviceChannels);
}else {
all = deviceChannelMapper.queryChannels(deviceId, null, query, hasSubChannel, online);
}
@@ -876,6 +884,11 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
return streamPushMapper.del(app, stream);
}
@Override
public StreamPushItem getMedia(String app, String stream) {
return streamPushMapper.selectOne(app, stream);
}
@Override
public void clearMediaList() {
streamPushMapper.clear();

View File

@@ -1,7 +1,6 @@
package com.genersoft.iot.vmp.utils;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -18,35 +17,63 @@ import java.util.Locale;
*/
public class DateUtil {
private static final String yyyy_MM_dd_T_HH_mm_ss_SSSXXX = "yyyy-MM-dd'T'HH:mm:ss";
public static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
/**
* 兼容不规范的iso8601时间格式
*/
private static final String ISO8601_COMPATIBLE_PATTERN = "yyyy-M-d'T'H:m:s";
public static final SimpleDateFormat formatISO8601 = new SimpleDateFormat(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault());
public static final SimpleDateFormat format = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss, Locale.getDefault());
/**
* 用以输出标准的iso8601时间格式
*/
private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(yyyy_MM_dd_T_HH_mm_ss_SSSXXX, Locale.getDefault()).withZone(ZoneId.systemDefault());
public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(yyyy_MM_dd_HH_mm_ss, Locale.getDefault()).withZone(ZoneId.systemDefault());
/**
* wvp内部统一时间格式
*/
public static final String PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String zoneStr = "Asia/Shanghai";
public static final DateTimeFormatter formatterCompatibleISO8601 = DateTimeFormatter.ofPattern(ISO8601_COMPATIBLE_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr));
public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(ISO8601_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr));
public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr));
public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) {
return formatterISO8601.format(formatter.parse(formatTime));
}
public static String ISO8601Toyyyy_MM_dd_HH_mm_ss(String formatTime) {
return formatter.format(formatterISO8601.parse(formatTime));
return formatter.format(formatterCompatibleISO8601.parse(formatTime));
}
/**
* yyyy_MM_dd_HH_mm_ss 转时间戳
* @param formatTime
* @return
*/
public static long yyyy_MM_dd_HH_mm_ssToTimestamp(String formatTime) {
TemporalAccessor temporalAccessor = formatter.parse(formatTime);
Instant instant = Instant.from(temporalAccessor);
return instant.getEpochSecond();
}
/**
* 获取当前时间
* @return
*/
public static String getNow() {
LocalDateTime nowDateTime = LocalDateTime.now();
return formatter.format(nowDateTime);
}
/**
* 格式校验
* @param timeStr 时间字符串
* @param dateTimeFormatter 待校验的格式
* @return
*/
public static boolean verification(String timeStr, DateTimeFormatter dateTimeFormatter) {
try {
LocalDate.parse(timeStr, dateTimeFormatter);

View File

@@ -24,6 +24,7 @@ import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
@@ -68,8 +69,8 @@ public class AlarmController {
@ApiImplicitParam(name="alarmMethod", value = "查询内容" ,dataTypeClass = String.class),
@ApiImplicitParam(name="alarmMethod", value = "查询内容" ,dataTypeClass = String.class),
@ApiImplicitParam(name="alarmType", value = "查询内容" ,dataTypeClass = String.class),
@ApiImplicitParam(name="startTime", value = "查询内容" ,dataTypeClass = String.class),
@ApiImplicitParam(name="endTime", value = "查询内容" ,dataTypeClass = String.class),
@ApiImplicitParam(name="startTime", value = "开始时间" ,dataTypeClass = String.class),
@ApiImplicitParam(name="endTime", value = "结束时间" ,dataTypeClass = String.class),
})
public ResponseEntity<PageInfo<DeviceAlarm>> getAll(
@RequestParam int page,
@@ -98,14 +99,7 @@ public class AlarmController {
}
try {
if (startTime != null) {
DateUtil.format.parse(startTime);
}
if (endTime != null) {
DateUtil.format.parse(endTime);
}
} catch (ParseException e) {
if (!DateUtil.verification(startTime, DateUtil.formatter) || !DateUtil.verification(endTime, DateUtil.formatter)){
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
@@ -144,11 +138,7 @@ public class AlarmController {
if (StringUtils.isEmpty(time)) {
time = null;
}
try {
if (time != null) {
DateUtil.format.parse(time);
}
} catch (ParseException e) {
if (!DateUtil.verification(time, DateUtil.formatter) ){
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
List<String> deviceIdList = null;
@@ -189,7 +179,7 @@ public class AlarmController {
deviceAlarm.setAlarmDescription("test");
deviceAlarm.setAlarmMethod("1");
deviceAlarm.setAlarmPriority("1");
deviceAlarm.setAlarmTime(DateUtil.formatISO8601.format(System.currentTimeMillis()));
deviceAlarm.setAlarmTime(DateUtil.formatterISO8601.format(LocalDateTime.now()));
deviceAlarm.setAlarmType("1");
deviceAlarm.setLongitude(115.33333);
deviceAlarm.setLatitude(39.33333);

View File

@@ -21,16 +21,22 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.http.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import javax.servlet.http.HttpServletResponse;
import javax.sip.DialogState;
import java.io.*;
import java.nio.file.Files;
import java.util.*;
@Api(tags = "国标设备查询", value = "国标设备查询")
@@ -200,6 +206,11 @@ public class DeviceQuery {
Set<String> allKeys = dynamicTask.getAllKeys();
for (String key : allKeys) {
if (key.startsWith(deviceId)) {
Runnable runnable = dynamicTask.get(key);
if (runnable instanceof ISubscribeTask) {
ISubscribeTask subscribeTask = (ISubscribeTask) runnable;
subscribeTask.stop();
}
dynamicTask.stop(key);
}
}
@@ -306,12 +317,7 @@ public class DeviceQuery {
public ResponseEntity<WVPResult<String>> updateDevice(Device device){
if (device != null && device.getDeviceId() != null) {
// TODO 报警订阅相关的信息
deviceService.updateDevice(device);
// cmder.deviceInfoQuery(device);
}
WVPResult<String> result = new WVPResult<>();
result.setCode(0);
@@ -336,6 +342,11 @@ public class DeviceQuery {
Device device = storager.queryVideoDevice(deviceId);
String uuid = UUID.randomUUID().toString();
String key = DeferredResultHolder.CALLBACK_CMD_DEVICESTATUS + deviceId;
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(2*1000L);
if(device == null) {
result.setResult(new ResponseEntity(String.format("设备%s不存在", deviceId),HttpStatus.OK));
return result;
}
cmder.deviceStatusQuery(device, event -> {
RequestMessage msg = new RequestMessage();
msg.setId(uuid);
@@ -343,7 +354,6 @@ public class DeviceQuery {
msg.setData(String.format("获取设备状态失败,错误码: %s, %s", event.statusCode, event.msg));
resultHolder.invokeResult(msg);
});
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(2*1000L);
result.onTimeout(()->{
logger.warn(String.format("获取设备状态超时"));
// 释放rtpserver
@@ -456,4 +466,17 @@ public class DeviceQuery {
wvpResult.setData(dialogStateMap);
return wvpResult;
}
@GetMapping("/snap/{deviceId}/{channelId}")
@ApiOperation(value = "请求截图", notes = "请求截图")
public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId) {
try {
final InputStream in = Files.newInputStream(new File("snap" + File.separator + deviceId + "_" + channelId + ".jpg").toPath());
resp.setContentType(MediaType.IMAGE_PNG_VALUE);
IOUtils.copy(in, resp.getOutputStream());
} catch (IOException e) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
}

View File

@@ -72,7 +72,7 @@ public class GBRecordController {
if (!DateUtil.verification(startTime, DateUtil.formatter)){
WVPResult<RecordInfo> wvpResult = new WVPResult<>();
wvpResult.setCode(-1);
wvpResult.setMsg("startTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss);
wvpResult.setMsg("startTime error, format is " + DateUtil.PATTERN);
ResponseEntity<WVPResult<RecordInfo>> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK);
result.setResult(resultResponseEntity);
@@ -81,7 +81,7 @@ public class GBRecordController {
if (!DateUtil.verification(endTime, DateUtil.formatter)){
WVPResult<RecordInfo> wvpResult = new WVPResult<>();
wvpResult.setCode(-1);
wvpResult.setMsg("endTime error, format is " + DateUtil.yyyy_MM_dd_HH_mm_ss);
wvpResult.setMsg("endTime error, format is " + DateUtil.PATTERN);
ResponseEntity<WVPResult<RecordInfo>> resultResponseEntity = new ResponseEntity<>(wvpResult, HttpStatus.OK);
result.setResult(resultResponseEntity);
return result;

View File

@@ -76,14 +76,7 @@ public class LogController {
logger.warn("自动记录日志功能已关闭,查询结果可能不完整。");
}
try {
if (startTime != null) {
DateUtil.format.parse(startTime);
}
if (endTime != null) {
DateUtil.format.parse(endTime);
}
} catch (ParseException e) {
if (!DateUtil.verification(startTime, DateUtil.formatter) || !DateUtil.verification(endTime, DateUtil.formatter)){
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}

View File

@@ -146,8 +146,8 @@ public class ApiDeviceController {
// 2-基于口令的双向认证,
// 3-基于数字证书的双向认证
deviceJOSNChannel.put("Status", deviceChannel.getStatus());
deviceJOSNChannel.put("Longitude", deviceChannel.getLongitude());
deviceJOSNChannel.put("Latitude", deviceChannel.getLatitude());
deviceJOSNChannel.put("Longitude", deviceChannel.getLongitudeWgs84());
deviceJOSNChannel.put("Latitude", deviceChannel.getLatitudeWgs84());
deviceJOSNChannel.put("PTZType ", deviceChannel.getPTZType()); // 云台类型, 0 - 未知, 1 - 球机, 2 - 半球,
// 3 - 固定枪机, 4 - 遥控枪机
deviceJOSNChannel.put("CustomPTZType", "");