Merge remote-tracking branch 'origin/wvp-28181-2.0' into wvp-28181-2.0
# Conflicts: # src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java # src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java # src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
package com.genersoft.iot.vmp.common;
|
||||
|
||||
public interface GeneralCallback<T>{
|
||||
void run(int code, String msg, T data);
|
||||
}
|
||||
@@ -106,6 +106,11 @@ public class VideoManagerConstants {
|
||||
*/
|
||||
public static final String VM_MSG_STREAM_PUSH_RESPONSE = "VM_MSG_STREAM_PUSH_RESPONSE";
|
||||
|
||||
/**
|
||||
* redis 通知平台关闭推流
|
||||
*/
|
||||
public static final String VM_MSG_STREAM_PUSH_CLOSE = "VM_MSG_STREAM_PUSH_CLOSE";
|
||||
|
||||
/**
|
||||
* redis 消息请求所有的在线通道
|
||||
*/
|
||||
|
||||
@@ -43,6 +43,9 @@ public class RedisMsgListenConfig {
|
||||
@Autowired
|
||||
private RedisPushStreamResponseListener redisPushStreamResponseListener;
|
||||
|
||||
@Autowired
|
||||
private RedisCloseStreamMsgListener redisCloseStreamMsgListener;
|
||||
|
||||
|
||||
/**
|
||||
* redis消息监听器容器 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器
|
||||
@@ -63,6 +66,7 @@ public class RedisMsgListenConfig {
|
||||
container.addMessageListener(redisPushStreamStatusMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_PUSH_STREAM_STATUS_CHANGE));
|
||||
container.addMessageListener(redisPushStreamListMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_PUSH_STREAM_LIST_CHANGE));
|
||||
container.addMessageListener(redisPushStreamResponseListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_RESPONSE));
|
||||
container.addMessageListener(redisCloseStreamMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_CLOSE));
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
import javax.sdp.SessionDescription;
|
||||
|
||||
/**
|
||||
* 28181 的SDP解析器
|
||||
*/
|
||||
public class Gb28181Sdp {
|
||||
private SessionDescription baseSdb;
|
||||
private String ssrc;
|
||||
|
||||
private String mediaDescription;
|
||||
|
||||
public static Gb28181Sdp getInstance(SessionDescription baseSdb, String ssrc, String mediaDescription) {
|
||||
Gb28181Sdp gb28181Sdp = new Gb28181Sdp();
|
||||
gb28181Sdp.setBaseSdb(baseSdb);
|
||||
gb28181Sdp.setSsrc(ssrc);
|
||||
gb28181Sdp.setMediaDescription(mediaDescription);
|
||||
return gb28181Sdp;
|
||||
}
|
||||
|
||||
|
||||
public SessionDescription getBaseSdb() {
|
||||
return baseSdb;
|
||||
}
|
||||
|
||||
public void setBaseSdb(SessionDescription baseSdb) {
|
||||
this.baseSdb = baseSdb;
|
||||
}
|
||||
|
||||
public String getSsrc() {
|
||||
return ssrc;
|
||||
}
|
||||
|
||||
public void setSsrc(String ssrc) {
|
||||
this.ssrc = ssrc;
|
||||
}
|
||||
|
||||
public String getMediaDescription() {
|
||||
return mediaDescription;
|
||||
}
|
||||
|
||||
public void setMediaDescription(String mediaDescription) {
|
||||
this.mediaDescription = mediaDescription;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.genersoft.iot.vmp.gb28181.session;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -31,10 +32,13 @@ public class SSRCFactory {
|
||||
@Autowired
|
||||
private SipConfig sipConfig;
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
|
||||
public void initMediaServerSSRC(String mediaServerId, Set<String> usedSet) {
|
||||
String ssrcPrefix = sipConfig.getDomain().substring(3, 8);
|
||||
String redisKey = SSRC_INFO_KEY + mediaServerId;
|
||||
String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId;
|
||||
List<String> ssrcList = new ArrayList<>();
|
||||
for (int i = 1; i < MAX_STREAM_COUNT; i++) {
|
||||
String ssrc = String.format("%s%04d", ssrcPrefix, i);
|
||||
@@ -77,7 +81,7 @@ public class SSRCFactory {
|
||||
return;
|
||||
}
|
||||
String sn = ssrc.substring(1);
|
||||
String redisKey = SSRC_INFO_KEY + mediaServerId;
|
||||
String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId;
|
||||
redisTemplate.opsForSet().add(redisKey, sn);
|
||||
}
|
||||
|
||||
@@ -86,7 +90,7 @@ public class SSRCFactory {
|
||||
*/
|
||||
private String getSN(String mediaServerId) {
|
||||
String sn = null;
|
||||
String redisKey = SSRC_INFO_KEY + mediaServerId;
|
||||
String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId;
|
||||
Long size = redisTemplate.opsForSet().size(redisKey);
|
||||
if (size == null || size == 0) {
|
||||
throw new RuntimeException("ssrc已经用完");
|
||||
@@ -113,20 +117,8 @@ public class SSRCFactory {
|
||||
* @param mediaServerId 流媒体服务ID
|
||||
*/
|
||||
public boolean hasMediaServerSSRC(String mediaServerId) {
|
||||
String redisKey = SSRC_INFO_KEY + mediaServerId;
|
||||
String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId;
|
||||
return redisTemplate.opsForSet().members(redisKey) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询ssrc是否可用
|
||||
*
|
||||
* @param mediaServerId
|
||||
* @param ssrc
|
||||
* @return
|
||||
*/
|
||||
public boolean checkSsrc(String mediaServerId, String ssrc) {
|
||||
String sn = ssrc.substring(1);
|
||||
String redisKey = SSRC_INFO_KEY + mediaServerId;
|
||||
return redisTemplate.opsForSet().isMember(redisKey, sn) != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +39,13 @@ public class DeferredResultHolder {
|
||||
|
||||
public static final String CALLBACK_CMD_DOWNLOAD = "CALLBACK_DOWNLOAD";
|
||||
|
||||
public static final String CALLBACK_CMD_PROXY = "CALLBACK_PROXY";
|
||||
|
||||
public static final String CALLBACK_CMD_STOP = "CALLBACK_STOP";
|
||||
|
||||
public static final String UPLOAD_FILE_CHANNEL = "UPLOAD_FILE_CHANNEL";
|
||||
|
||||
public static final String CALLBACK_CMD_MOBILEPOSITION = "CALLBACK_MOBILEPOSITION";
|
||||
public static final String CALLBACK_CMD_MOBILE_POSITION = "CALLBACK_CMD_MOBILE_POSITION";
|
||||
|
||||
public static final String CALLBACK_CMD_PRESETQUERY = "CALLBACK_PRESETQUERY";
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ public class SIPRequestHeaderPlarformProvider {
|
||||
parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort());
|
||||
//via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getServerIP(),
|
||||
parentPlatform.getServerPort(), parentPlatform.getTransport(), SipUtils.getNewViaTag());
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(),
|
||||
Integer.parseInt(parentPlatform.getDevicePort()), parentPlatform.getTransport(), SipUtils.getNewViaTag());
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
//from
|
||||
|
||||
@@ -472,7 +472,7 @@ public class SIPCommander implements ISIPCommander {
|
||||
}
|
||||
subscribe.removeSubscribe(hookSubscribe);
|
||||
});
|
||||
Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()), ssrcInfo.getSsrc());
|
||||
Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()), ssrcInfo.getSsrc());
|
||||
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
|
||||
ResponseEvent responseEvent = (ResponseEvent) event.event;
|
||||
@@ -588,17 +588,13 @@ public class SIPCommander implements ISIPCommander {
|
||||
});
|
||||
});
|
||||
|
||||
Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,newCallIdHeader, ssrcInfo.getSsrc());
|
||||
Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,newCallIdHeader, ssrcInfo.getSsrc());
|
||||
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
|
||||
ResponseEvent responseEvent = (ResponseEvent) event.event;
|
||||
SIPResponse response = (SIPResponse) responseEvent.getResponse();
|
||||
String contentString =new String(response.getRawContent());
|
||||
int ssrcIndex = contentString.indexOf("y=");
|
||||
String ssrc=ssrcInfo.getSsrc();
|
||||
if (ssrcIndex >= 0) {
|
||||
ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
|
||||
}
|
||||
String ssrc = SipUtils.getSsrcFromSdp(contentString);
|
||||
streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, InviteSessionType.DOWNLOAD);
|
||||
okEvent.response(event);
|
||||
});
|
||||
|
||||
@@ -241,18 +241,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
// 解析sdp消息, 使用jainsip 自带的sdp解析方式
|
||||
String contentString = new String(request.getRawContent());
|
||||
|
||||
// jainSip不支持y=字段, 移除以解析。
|
||||
// 检查是否有y字段
|
||||
int ssrcIndex = contentString.indexOf("y=");
|
||||
|
||||
SessionDescription sdp;
|
||||
if (ssrcIndex >= 0) {
|
||||
//ssrc规定长度为10个字节,不取余下长度以避免后续还有“f=”字段
|
||||
String substring = contentString.substring(0, ssrcIndex);
|
||||
sdp = SdpFactory.getInstance().createSessionDescription(substring);
|
||||
} else {
|
||||
sdp = SdpFactory.getInstance().createSessionDescription(contentString);
|
||||
}
|
||||
Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
|
||||
SessionDescription sdp = gb28181Sdp.getBaseSdb();
|
||||
String sessionName = sdp.getSessionName().getValue();
|
||||
|
||||
Long startTime = null;
|
||||
@@ -340,11 +330,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
}
|
||||
|
||||
String ssrc;
|
||||
if (userSetting.getUseCustomSsrcForParentInvite() || ssrcIndex < 0) {
|
||||
if (userSetting.getUseCustomSsrcForParentInvite() || gb28181Sdp.getSsrc() == null) {
|
||||
// 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式
|
||||
ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
|
||||
}else {
|
||||
ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
|
||||
ssrc = gb28181Sdp.getSsrc();
|
||||
}
|
||||
String streamTypeStr = null;
|
||||
if (mediaTransmissionTCP) {
|
||||
@@ -513,11 +503,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
} else if (gbStream != null) {
|
||||
|
||||
String ssrc;
|
||||
if (userSetting.getUseCustomSsrcForParentInvite() || ssrcIndex < 0) {
|
||||
if (userSetting.getUseCustomSsrcForParentInvite() || gb28181Sdp.getSsrc() == null) {
|
||||
// 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式
|
||||
ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
|
||||
}else {
|
||||
ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
|
||||
ssrc = gb28181Sdp.getSsrc();
|
||||
}
|
||||
|
||||
if("push".equals(gbStream.getStreamType())) {
|
||||
@@ -891,20 +881,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
}
|
||||
String contentString = new String(request.getRawContent());
|
||||
// jainSip不支持y=字段, 移除移除以解析。
|
||||
String substring = contentString;
|
||||
String ssrc = "0000000404";
|
||||
int ssrcIndex = contentString.indexOf("y=");
|
||||
if (ssrcIndex > 0) {
|
||||
substring = contentString.substring(0, ssrcIndex);
|
||||
ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
|
||||
}
|
||||
ssrcIndex = substring.indexOf("f=");
|
||||
if (ssrcIndex > 0) {
|
||||
substring = contentString.substring(0, ssrcIndex);
|
||||
}
|
||||
SessionDescription sdp = null;
|
||||
|
||||
try {
|
||||
sdp = SdpFactory.getInstance().createSessionDescription(substring);
|
||||
Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
|
||||
SessionDescription sdp = gb28181Sdp.getBaseSdb();
|
||||
// 获取支持的格式
|
||||
Vector mediaDescriptions = sdp.getMediaDescriptions(true);
|
||||
// 查看是否支持PS 负载96
|
||||
|
||||
@@ -175,6 +175,11 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
|
||||
}
|
||||
}else {
|
||||
addChannelMap.put(channel.getChannelId(), channel);
|
||||
if (userSetting.getDeviceStatusNotify()) {
|
||||
// 发送redis消息
|
||||
redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), channel.getChannelId(), true);
|
||||
}
|
||||
|
||||
if (addChannelMap.keySet().size() > 300) {
|
||||
executeSaveForAdd();
|
||||
}
|
||||
@@ -185,6 +190,10 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
|
||||
// 删除
|
||||
logger.info("[收到删除通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
|
||||
deleteChannelList.add(channel);
|
||||
if (userSetting.getDeviceStatusNotify()) {
|
||||
// 发送redis消息
|
||||
redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), channel.getChannelId(), false);
|
||||
}
|
||||
if (deleteChannelList.size() > 300) {
|
||||
executeSaveForDelete();
|
||||
}
|
||||
@@ -205,6 +214,10 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
|
||||
if (addChannelMap.keySet().size() > 300) {
|
||||
executeSaveForAdd();
|
||||
}
|
||||
if (userSetting.getDeviceStatusNotify()) {
|
||||
// 发送redis消息
|
||||
redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), channel.getChannelId(), true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -192,7 +192,12 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
|
||||
mobilePosition.setDeviceId(device.getDeviceId());
|
||||
mobilePosition.setChannelId(channelId);
|
||||
String time = XmlUtil.getText(rootElement, "Time");
|
||||
mobilePosition.setTime(time);
|
||||
if (ObjectUtils.isEmpty(time)){
|
||||
mobilePosition.setTime(DateUtil.getNow());
|
||||
}else {
|
||||
mobilePosition.setTime(SipUtils.parseTime(time));
|
||||
}
|
||||
|
||||
mobilePosition.setLongitude(Double.parseDouble(XmlUtil.getText(rootElement, "Longitude")));
|
||||
mobilePosition.setLatitude(Double.parseDouble(XmlUtil.getText(rootElement, "Latitude")));
|
||||
if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Speed"))) {
|
||||
@@ -237,7 +242,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
|
||||
|
||||
// 发送redis消息。 通知位置信息的变化
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("time", time);
|
||||
jsonObject.put("time", DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(mobilePosition.getTime()));
|
||||
jsonObject.put("serial", deviceId);
|
||||
jsonObject.put("code", channelId);
|
||||
jsonObject.put("longitude", mobilePosition.getLongitude());
|
||||
@@ -339,7 +344,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
|
||||
storager.updateChannelPosition(deviceChannel);
|
||||
// 发送redis消息。 通知位置信息的变化
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("time", mobilePosition.getTime());
|
||||
jsonObject.put("time", DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(mobilePosition.getTime()));
|
||||
jsonObject.put("serial", deviceChannel.getDeviceId());
|
||||
jsonObject.put("code", deviceChannel.getChannelId());
|
||||
jsonObject.put("longitude", mobilePosition.getLongitude());
|
||||
|
||||
@@ -164,7 +164,7 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme
|
||||
|
||||
// 发送redis消息。 通知位置信息的变化
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("time", mobilePosition.getTime());
|
||||
jsonObject.put("time", DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(mobilePosition.getTime()));
|
||||
jsonObject.put("serial", deviceChannel.getDeviceId());
|
||||
jsonObject.put("code", deviceChannel.getChannelId());
|
||||
jsonObject.put("longitude", mobilePosition.getLongitude());
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
|
||||
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.gb28181.utils.NumericUtil;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.service.IDeviceChannelService;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||
@@ -95,7 +96,12 @@ public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParen
|
||||
}
|
||||
mobilePosition.setDeviceId(sipMsgInfo.getDevice().getDeviceId());
|
||||
mobilePosition.setChannelId(getText(rootElementAfterCharset, "DeviceID"));
|
||||
mobilePosition.setTime(getText(rootElementAfterCharset, "Time"));
|
||||
String time = getText(rootElementAfterCharset, "Time");
|
||||
if (ObjectUtils.isEmpty(time)){
|
||||
mobilePosition.setTime(DateUtil.getNow());
|
||||
}else {
|
||||
mobilePosition.setTime(SipUtils.parseTime(time));
|
||||
}
|
||||
mobilePosition.setLongitude(Double.parseDouble(getText(rootElementAfterCharset, "Longitude")));
|
||||
mobilePosition.setLatitude(Double.parseDouble(getText(rootElementAfterCharset, "Latitude")));
|
||||
if (NumericUtil.isDouble(getText(rootElementAfterCharset, "Speed"))) {
|
||||
@@ -138,7 +144,7 @@ public class MobilePositionNotifyMessageHandler extends SIPRequestProcessorParen
|
||||
|
||||
// 发送redis消息。 通知位置信息的变化
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("time", mobilePosition.getTime());
|
||||
jsonObject.put("time", DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(mobilePosition.getTime()));
|
||||
jsonObject.put("serial", deviceChannel.getDeviceId());
|
||||
jsonObject.put("code", deviceChannel.getChannelId());
|
||||
jsonObject.put("longitude", mobilePosition.getLongitude());
|
||||
|
||||
@@ -2,17 +2,21 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.respon
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.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.response.ResponseMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.Coordtransform;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.service.IDeviceChannelService;
|
||||
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.GpsUtil;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import org.dom4j.DocumentException;
|
||||
import org.dom4j.Element;
|
||||
@@ -56,6 +60,9 @@ public class MobilePositionResponseMessageHandler extends SIPRequestProcessorPar
|
||||
@Autowired
|
||||
private IDeviceChannelService deviceChannelService;
|
||||
|
||||
@Autowired
|
||||
private DeferredResultHolder resultHolder;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
responseMessageHandler.addHandler(cmdType, this);
|
||||
@@ -83,7 +90,13 @@ public class MobilePositionResponseMessageHandler extends SIPRequestProcessorPar
|
||||
}
|
||||
mobilePosition.setDeviceId(device.getDeviceId());
|
||||
mobilePosition.setChannelId(getText(rootElement, "DeviceID"));
|
||||
mobilePosition.setTime(getText(rootElement, "Time"));
|
||||
//兼容ISO 8601格式时间
|
||||
String time = getText(rootElement, "Time");
|
||||
if (ObjectUtils.isEmpty(time)){
|
||||
mobilePosition.setTime(DateUtil.getNow());
|
||||
}else {
|
||||
mobilePosition.setTime(SipUtils.parseTime(time));
|
||||
}
|
||||
mobilePosition.setLongitude(Double.parseDouble(getText(rootElement, "Longitude")));
|
||||
mobilePosition.setLatitude(Double.parseDouble(getText(rootElement, "Latitude")));
|
||||
if (NumericUtil.isDouble(getText(rootElement, "Speed"))) {
|
||||
@@ -121,11 +134,18 @@ public class MobilePositionResponseMessageHandler extends SIPRequestProcessorPar
|
||||
if (userSetting.getSavePositionHistory()) {
|
||||
storager.insertMobilePosition(mobilePosition);
|
||||
}
|
||||
|
||||
storager.updateChannelPosition(deviceChannel);
|
||||
|
||||
String key = DeferredResultHolder.CALLBACK_CMD_MOBILE_POSITION + device.getDeviceId();
|
||||
RequestMessage msg = new RequestMessage();
|
||||
msg.setKey(key);
|
||||
msg.setData(mobilePosition);
|
||||
resultHolder.invokeAllResult(msg);
|
||||
|
||||
// 发送redis消息。 通知位置信息的变化
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("time", mobilePosition.getTime());
|
||||
jsonObject.put("time", DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(mobilePosition.getTime()));
|
||||
jsonObject.put("serial", deviceChannel.getDeviceId());
|
||||
jsonObject.put("code", deviceChannel.getChannelId());
|
||||
jsonObject.put("longitude", mobilePosition.getLongitude());
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.SipLayer;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Gb28181Sdp;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import gov.nist.javax.sip.ResponseEventExt;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
import org.slf4j.Logger;
|
||||
@@ -12,7 +14,6 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sdp.SdpFactory;
|
||||
import javax.sdp.SdpParseException;
|
||||
import javax.sdp.SessionDescription;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
@@ -79,18 +80,8 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
|
||||
ResponseEventExt event = (ResponseEventExt)evt;
|
||||
|
||||
String contentString = new String(response.getRawContent());
|
||||
// jainSip不支持y=字段, 移除以解析。
|
||||
int ssrcIndex = contentString.indexOf("y=");
|
||||
// 检查是否有y字段
|
||||
SessionDescription sdp;
|
||||
if (ssrcIndex >= 0) {
|
||||
//ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段
|
||||
String substring = contentString.substring(0, contentString.indexOf("y="));
|
||||
sdp = SdpFactory.getInstance().createSessionDescription(substring);
|
||||
} else {
|
||||
sdp = SdpFactory.getInstance().createSessionDescription(contentString);
|
||||
}
|
||||
|
||||
Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
|
||||
SessionDescription sdp = gb28181Sdp.getBaseSdb();
|
||||
SipURI requestUri = SipFactory.getInstance().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
|
||||
Request reqAck = headerProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response);
|
||||
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
package com.genersoft.iot.vmp.gb28181.utils;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Gb28181Sdp;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.RemoteAddressInfo;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.utils.GitUtil;
|
||||
import gov.nist.javax.sip.address.AddressImpl;
|
||||
import gov.nist.javax.sip.address.SipUri;
|
||||
import gov.nist.javax.sip.header.Subject;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.sdp.SdpFactory;
|
||||
import javax.sdp.SdpParseException;
|
||||
import javax.sdp.SessionDescription;
|
||||
import javax.sip.PeerUnavailableException;
|
||||
import javax.sip.SipFactory;
|
||||
import javax.sip.header.FromHeader;
|
||||
@@ -16,6 +24,8 @@ import javax.sip.header.Header;
|
||||
import javax.sip.header.UserAgentHeader;
|
||||
import javax.sip.message.Request;
|
||||
import java.text.ParseException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -28,6 +38,8 @@ import java.util.UUID;
|
||||
*/
|
||||
public class SipUtils {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(SipUtils.class);
|
||||
|
||||
public static String getUserIdFromFromHeader(Request request) {
|
||||
FromHeader fromHeader = (FromHeader)request.getHeader(FromHeader.NAME);
|
||||
return getUserIdFromFromHeader(fromHeader);
|
||||
@@ -51,7 +63,7 @@ public class SipUtils {
|
||||
}
|
||||
|
||||
public static String getNewViaTag() {
|
||||
return "z9hG4bK" + System.currentTimeMillis();
|
||||
return "z9hG4bK" + RandomStringUtils.randomNumeric(10);
|
||||
}
|
||||
|
||||
public static UserAgentHeader createUserAgentHeader(GitUtil gitUtil) throws PeerUnavailableException, ParseException {
|
||||
@@ -189,4 +201,67 @@ public class SipUtils {
|
||||
}
|
||||
return deviceChannel;
|
||||
}
|
||||
|
||||
public static Gb28181Sdp parseSDP(String sdpStr) throws SdpParseException {
|
||||
|
||||
// jainSip不支持y= f=字段, 移除以解析。
|
||||
int ssrcIndex = sdpStr.indexOf("y=");
|
||||
int mediaDescriptionIndex = sdpStr.indexOf("f=");
|
||||
// 检查是否有y字段
|
||||
SessionDescription sdp;
|
||||
String ssrc = null;
|
||||
String mediaDescription = null;
|
||||
if (mediaDescriptionIndex == 0 && ssrcIndex == 0) {
|
||||
sdp = SdpFactory.getInstance().createSessionDescription(sdpStr);
|
||||
}else {
|
||||
String lines[] = sdpStr.split("\\r?\\n");
|
||||
StringBuilder sdpBuffer = new StringBuilder();
|
||||
for (String line : lines) {
|
||||
if (line.trim().startsWith("y=")) {
|
||||
ssrc = line.substring(2);
|
||||
}else if (line.trim().startsWith("f=")) {
|
||||
mediaDescription = line.substring(2);
|
||||
}else {
|
||||
sdpBuffer.append(line.trim()).append("\r\n");
|
||||
}
|
||||
}
|
||||
sdp = SdpFactory.getInstance().createSessionDescription(sdpBuffer.toString());
|
||||
}
|
||||
return Gb28181Sdp.getInstance(sdp, ssrc, mediaDescription);
|
||||
}
|
||||
|
||||
public static String getSsrcFromSdp(String sdpStr) {
|
||||
|
||||
// jainSip不支持y= f=字段, 移除以解析。
|
||||
int ssrcIndex = sdpStr.indexOf("y=");
|
||||
if (ssrcIndex == 0) {
|
||||
return null;
|
||||
}
|
||||
String lines[] = sdpStr.split("\\r?\\n");
|
||||
for (String line : lines) {
|
||||
if (line.trim().startsWith("y=")) {
|
||||
return line.substring(2);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String parseTime(String timeStr) {
|
||||
if (ObjectUtils.isEmpty(timeStr)){
|
||||
return null;
|
||||
}
|
||||
System.out.println(timeStr);
|
||||
LocalDateTime localDateTime;
|
||||
try {
|
||||
localDateTime = LocalDateTime.parse(timeStr);
|
||||
}catch (DateTimeParseException e) {
|
||||
try {
|
||||
localDateTime = LocalDateTime.parse(timeStr, DateUtil.formatterISO8601);
|
||||
}catch (DateTimeParseException e2) {
|
||||
logger.error("[格式化时间] 无法格式化时间: {}", timeStr);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return localDateTime.format(DateUtil.formatterISO8601);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.CommonCallback;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.*;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRtpServerTimeout;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@@ -20,28 +20,26 @@ public class StreamProxyItem extends GbStream {
|
||||
@Schema(description = "拉流地址")
|
||||
private String url;
|
||||
@Schema(description = "拉流地址")
|
||||
private String src_url;
|
||||
private String srcUrl;
|
||||
@Schema(description = "目标地址")
|
||||
private String dst_url;
|
||||
private String dstUrl;
|
||||
@Schema(description = "超时时间")
|
||||
private int timeout_ms;
|
||||
private int timeoutMs;
|
||||
@Schema(description = "ffmpeg模板KEY")
|
||||
private String ffmpeg_cmd_key;
|
||||
private String ffmpegCmdKey;
|
||||
@Schema(description = "rtsp拉流时,拉流方式,0:tcp,1:udp,2:组播")
|
||||
private String rtp_type;
|
||||
private String rtpType;
|
||||
@Schema(description = "是否启用")
|
||||
private boolean enable;
|
||||
@Schema(description = "是否启用音频")
|
||||
private boolean enable_audio;
|
||||
private boolean enableAudio;
|
||||
@Schema(description = "是否启用MP4")
|
||||
private boolean enable_mp4;
|
||||
private boolean enableMp4;
|
||||
@Schema(description = "是否 无人观看时删除")
|
||||
private boolean enable_remove_none_reader;
|
||||
private boolean enableRemoveNoneReader;
|
||||
|
||||
@Schema(description = "是否 无人观看时自动停用")
|
||||
private boolean enable_disable_none_reader;
|
||||
@Schema(description = "创建时间")
|
||||
private String createTime;
|
||||
private boolean enableDisableNoneReader;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
@@ -89,44 +87,44 @@ public class StreamProxyItem extends GbStream {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getSrc_url() {
|
||||
return src_url;
|
||||
public String getSrcUrl() {
|
||||
return srcUrl;
|
||||
}
|
||||
|
||||
public void setSrc_url(String src_url) {
|
||||
this.src_url = src_url;
|
||||
public void setSrcUrl(String src_url) {
|
||||
this.srcUrl = src_url;
|
||||
}
|
||||
|
||||
public String getDst_url() {
|
||||
return dst_url;
|
||||
public String getDstUrl() {
|
||||
return dstUrl;
|
||||
}
|
||||
|
||||
public void setDst_url(String dst_url) {
|
||||
this.dst_url = dst_url;
|
||||
public void setDstUrl(String dst_url) {
|
||||
this.dstUrl = dst_url;
|
||||
}
|
||||
|
||||
public int getTimeout_ms() {
|
||||
return timeout_ms;
|
||||
public int getTimeoutMs() {
|
||||
return timeoutMs;
|
||||
}
|
||||
|
||||
public void setTimeout_ms(int timeout_ms) {
|
||||
this.timeout_ms = timeout_ms;
|
||||
public void setTimeoutMs(int timeout_ms) {
|
||||
this.timeoutMs = timeout_ms;
|
||||
}
|
||||
|
||||
public String getFfmpeg_cmd_key() {
|
||||
return ffmpeg_cmd_key;
|
||||
public String getFfmpegCmdKey() {
|
||||
return ffmpegCmdKey;
|
||||
}
|
||||
|
||||
public void setFfmpeg_cmd_key(String ffmpeg_cmd_key) {
|
||||
this.ffmpeg_cmd_key = ffmpeg_cmd_key;
|
||||
public void setFfmpegCmdKey(String ffmpeg_cmd_key) {
|
||||
this.ffmpegCmdKey = ffmpeg_cmd_key;
|
||||
}
|
||||
|
||||
public String getRtp_type() {
|
||||
return rtp_type;
|
||||
public String getRtpType() {
|
||||
return rtpType;
|
||||
}
|
||||
|
||||
public void setRtp_type(String rtp_type) {
|
||||
this.rtp_type = rtp_type;
|
||||
public void setRtpType(String rtp_type) {
|
||||
this.rtpType = rtp_type;
|
||||
}
|
||||
|
||||
public boolean isEnable() {
|
||||
@@ -137,45 +135,37 @@ public class StreamProxyItem extends GbStream {
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
public boolean isEnable_mp4() {
|
||||
return enable_mp4;
|
||||
public boolean isEnableMp4() {
|
||||
return enableMp4;
|
||||
}
|
||||
|
||||
public void setEnable_mp4(boolean enable_mp4) {
|
||||
this.enable_mp4 = enable_mp4;
|
||||
public void setEnableMp4(boolean enable_mp4) {
|
||||
this.enableMp4 = enable_mp4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCreateTime() {
|
||||
return createTime;
|
||||
public boolean isEnableRemoveNoneReader() {
|
||||
return enableRemoveNoneReader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreateTime(String createTime) {
|
||||
this.createTime = createTime;
|
||||
public void setEnableRemoveNoneReader(boolean enable_remove_none_reader) {
|
||||
this.enableRemoveNoneReader = enable_remove_none_reader;
|
||||
}
|
||||
|
||||
public boolean isEnable_remove_none_reader() {
|
||||
return enable_remove_none_reader;
|
||||
public boolean isEnableDisableNoneReader() {
|
||||
return enableDisableNoneReader;
|
||||
}
|
||||
|
||||
public void setEnable_remove_none_reader(boolean enable_remove_none_reader) {
|
||||
this.enable_remove_none_reader = enable_remove_none_reader;
|
||||
public void setEnableDisableNoneReader(boolean enable_disable_none_reader) {
|
||||
this.enableDisableNoneReader = enable_disable_none_reader;
|
||||
}
|
||||
|
||||
public boolean isEnable_disable_none_reader() {
|
||||
return enable_disable_none_reader;
|
||||
public boolean isEnableAudio() {
|
||||
return enableAudio;
|
||||
}
|
||||
|
||||
public void setEnable_disable_none_reader(boolean enable_disable_none_reader) {
|
||||
this.enable_disable_none_reader = enable_disable_none_reader;
|
||||
public void setEnableAudio(boolean enable_audio) {
|
||||
this.enableAudio = enable_audio;
|
||||
}
|
||||
|
||||
public boolean isEnable_audio() {
|
||||
return enable_audio;
|
||||
}
|
||||
|
||||
public void setEnable_audio(boolean enable_audio) {
|
||||
this.enable_audio = enable_audio;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.genersoft.iot.vmp.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.GeneralCallback;
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
|
||||
@@ -13,7 +14,7 @@ public interface IStreamProxyService {
|
||||
* 保存视频代理
|
||||
* @param param
|
||||
*/
|
||||
StreamInfo save(StreamProxyItem param);
|
||||
void save(StreamProxyItem param, GeneralCallback<StreamInfo> callback);
|
||||
|
||||
/**
|
||||
* 添加视频代理到zlm
|
||||
|
||||
@@ -187,7 +187,7 @@ public class DeviceServiceImpl implements IDeviceService {
|
||||
|
||||
@Override
|
||||
public void offline(String deviceId, String reason) {
|
||||
logger.error("[设备离线],{}, device:{}", reason, deviceId);
|
||||
logger.warn("[设备离线],{}, device:{}", reason, deviceId);
|
||||
Device device = deviceMapper.getDeviceByDeviceId(deviceId);
|
||||
if (device == null) {
|
||||
return;
|
||||
|
||||
@@ -418,7 +418,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
}
|
||||
final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + serverItem.getId();
|
||||
dynamicTask.stop(zlmKeepaliveKey);
|
||||
dynamicTask.startDelay(zlmKeepaliveKey, new KeepAliveTimeoutRunnable(serverItem), (Math.getExponent(serverItem.getHookAliveInterval()) + 5) * 1000);
|
||||
dynamicTask.startDelay(zlmKeepaliveKey, new KeepAliveTimeoutRunnable(serverItem), (serverItem.getHookAliveInterval().intValue() + 5) * 1000);
|
||||
publisher.zlmOnlineEventPublish(serverItem.getId());
|
||||
|
||||
logger.info("[ZLM] 连接成功 {} - {}:{} ",
|
||||
|
||||
@@ -2,12 +2,16 @@ package com.genersoft.iot.vmp.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.GeneralCallback;
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
|
||||
import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
|
||||
@@ -85,6 +89,9 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
|
||||
@Autowired
|
||||
private IMediaServerService mediaServerService;
|
||||
|
||||
@Autowired
|
||||
private ZlmHttpHookSubscribe hookSubscribe;
|
||||
|
||||
@Autowired
|
||||
DataSourceTransactionManager dataSourceTransactionManager;
|
||||
|
||||
@@ -93,7 +100,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
|
||||
|
||||
|
||||
@Override
|
||||
public StreamInfo save(StreamProxyItem param) {
|
||||
public void save(StreamProxyItem param, GeneralCallback<StreamInfo> callback) {
|
||||
MediaServerItem mediaInfo;
|
||||
if (ObjectUtils.isEmpty(param.getMediaServerId()) || "auto".equals(param.getMediaServerId())){
|
||||
mediaInfo = mediaServerService.getMediaServerForMinimumLoad(null);
|
||||
@@ -104,10 +111,43 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
|
||||
logger.warn("保存代理未找到在线的ZLM...");
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "保存代理未找到在线的ZLM");
|
||||
}
|
||||
String dstUrl = String.format("rtmp://%s:%s/%s/%s", "127.0.0.1", mediaInfo.getRtmpPort(), param.getApp(),
|
||||
param.getStream() );
|
||||
param.setDst_url(dstUrl);
|
||||
StringBuffer resultMsg = new StringBuffer();
|
||||
String dstUrl;
|
||||
if ("ffmpeg".equalsIgnoreCase(param.getType())) {
|
||||
JSONObject jsonObject = zlmresTfulUtils.getMediaServerConfig(mediaInfo);
|
||||
if (jsonObject.getInteger("code") != 0) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取流媒体配置失败");
|
||||
}
|
||||
JSONArray dataArray = jsonObject.getJSONArray("data");
|
||||
JSONObject mediaServerConfig = dataArray.getJSONObject(0);
|
||||
String ffmpegCmd = mediaServerConfig.getString(param.getFfmpegCmdKey());
|
||||
String schema = getSchemaFromFFmpegCmd(ffmpegCmd);
|
||||
if (schema == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "ffmpeg拉流代理无法从ffmpeg cmd中获取到输出格式");
|
||||
}
|
||||
int port;
|
||||
String schemaForUri;
|
||||
if (schema.equalsIgnoreCase("rtsp")) {
|
||||
port = mediaInfo.getRtspPort();
|
||||
schemaForUri = schema;
|
||||
}else if (schema.equalsIgnoreCase("flv")) {
|
||||
port = mediaInfo.getHttpPort();
|
||||
schemaForUri = "http";
|
||||
}else if (schema.equalsIgnoreCase("rtmp")) {
|
||||
port = mediaInfo.getRtmpPort();
|
||||
schemaForUri = schema;
|
||||
}else {
|
||||
port = mediaInfo.getRtmpPort();
|
||||
schemaForUri = schema;
|
||||
}
|
||||
|
||||
dstUrl = String.format("%s://%s:%s/%s/%s", schemaForUri, "127.0.0.1", port, param.getApp(),
|
||||
param.getStream());
|
||||
}else {
|
||||
dstUrl = String.format("rtmp://%s:%s/%s/%s", "127.0.0.1", mediaInfo.getRtmpPort(), param.getApp(),
|
||||
param.getStream());
|
||||
}
|
||||
param.setDstUrl(dstUrl);
|
||||
logger.info("[拉流代理] 输出地址为:{}", dstUrl);
|
||||
param.setMediaServerId(mediaInfo.getId());
|
||||
boolean saveResult;
|
||||
// 更新
|
||||
@@ -117,29 +157,60 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
|
||||
saveResult = addStreamProxy(param);
|
||||
}
|
||||
if (!saveResult) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(),"保存失败");
|
||||
callback.run(ErrorCode.ERROR100.getCode(), "保存失败", null);
|
||||
return;
|
||||
}
|
||||
StreamInfo resultForStreamInfo = null;
|
||||
resultMsg.append("保存成功");
|
||||
|
||||
HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed(param.getApp(), param.getStream(), true, "rtsp", mediaInfo.getId());
|
||||
hookSubscribe.addSubscribe(hookSubscribeForStreamChange, (mediaServerItem, response) -> {
|
||||
StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(
|
||||
mediaInfo, param.getApp(), param.getStream(), null, null);
|
||||
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
|
||||
});
|
||||
|
||||
if (param.isEnable()) {
|
||||
JSONObject jsonObject = addStreamProxyToZlm(param);
|
||||
if (jsonObject == null || jsonObject.getInteger("code") != 0) {
|
||||
resultMsg.append(", 但是启用失败,请检查流地址是否可用");
|
||||
if (jsonObject != null && jsonObject.getInteger("code") == 0) {
|
||||
hookSubscribe.removeSubscribe(hookSubscribeForStreamChange);
|
||||
StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(
|
||||
mediaInfo, param.getApp(), param.getStream(), null, null);
|
||||
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
|
||||
}else {
|
||||
param.setEnable(false);
|
||||
// 直接移除
|
||||
if (param.isEnable_remove_none_reader()) {
|
||||
if (param.isEnableRemoveNoneReader()) {
|
||||
del(param.getApp(), param.getStream());
|
||||
}else {
|
||||
updateStreamProxy(param);
|
||||
}
|
||||
if (jsonObject == null){
|
||||
callback.run(ErrorCode.ERROR100.getCode(), "记录已保存,启用失败", null);
|
||||
return;
|
||||
}else {
|
||||
callback.run(ErrorCode.ERROR100.getCode(), jsonObject.getString("msg"), null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}else {
|
||||
resultForStreamInfo = mediaService.getStreamInfoByAppAndStream(
|
||||
mediaInfo, param.getApp(), param.getStream(), null, null);
|
||||
private String getSchemaFromFFmpegCmd(String ffmpegCmd) {
|
||||
ffmpegCmd = ffmpegCmd.replaceAll(" + ", " ");
|
||||
String[] paramArray = ffmpegCmd.split(" ");
|
||||
if (paramArray.length == 0) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < paramArray.length; i++) {
|
||||
if (paramArray[i].equalsIgnoreCase("-f")) {
|
||||
if (i + 1 < paramArray.length - 1) {
|
||||
return paramArray[i+1];
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return resultForStreamInfo;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,11 +299,11 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
|
||||
}
|
||||
if ("default".equals(param.getType())){
|
||||
result = zlmresTfulUtils.addStreamProxy(mediaServerItem, param.getApp(), param.getStream(), param.getUrl(),
|
||||
param.isEnable_audio(), param.isEnable_mp4(), param.getRtp_type());
|
||||
param.isEnableAudio(), param.isEnableMp4(), param.getRtpType());
|
||||
}else if ("ffmpeg".equals(param.getType())) {
|
||||
result = zlmresTfulUtils.addFFmpegSource(mediaServerItem, param.getSrc_url(), param.getDst_url(),
|
||||
param.getTimeout_ms() + "", param.isEnable_audio(), param.isEnable_mp4(),
|
||||
param.getFfmpeg_cmd_key());
|
||||
result = zlmresTfulUtils.addFFmpegSource(mediaServerItem, param.getSrcUrl(), param.getDstUrl(),
|
||||
param.getTimeoutMs() + "", param.isEnableAudio(), param.isEnableMp4(),
|
||||
param.getFfmpegCmdKey());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -286,7 +357,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
|
||||
updateStreamProxy(streamProxy);
|
||||
}else {
|
||||
logger.info("启用代理失败: {}/{}->{}({})", app, stream, jsonObject.getString("msg"),
|
||||
streamProxy.getSrc_url() == null? streamProxy.getUrl():streamProxy.getSrc_url());
|
||||
streamProxy.getSrcUrl() == null? streamProxy.getUrl():streamProxy.getSrcUrl());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -183,6 +183,7 @@ public class StreamPushServiceImpl implements IStreamPushService {
|
||||
|
||||
@Override
|
||||
public boolean stop(String app, String streamId) {
|
||||
logger.info("[推流 ] 停止流: {}/{}", app, streamId);
|
||||
StreamPushItem streamPushItem = streamPushMapper.selectOne(app, streamId);
|
||||
if (streamPushItem != null) {
|
||||
gbStreamService.sendCatalogMsg(streamPushItem, CatalogEvent.DEL);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.genersoft.iot.vmp.service.redisMsg;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.service.IStreamPushService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.redis.connection.Message;
|
||||
import org.springframework.data.redis.connection.MessageListener;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
* 接收来自redis的关闭流更新通知
|
||||
* @author lin
|
||||
*/
|
||||
@Component
|
||||
public class RedisCloseStreamMsgListener implements MessageListener {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(RedisCloseStreamMsgListener.class);
|
||||
|
||||
|
||||
@Autowired
|
||||
private IStreamPushService pushService;
|
||||
|
||||
private ConcurrentLinkedQueue<Message> taskQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@Qualifier("taskExecutor")
|
||||
@Autowired
|
||||
private ThreadPoolTaskExecutor taskExecutor;
|
||||
|
||||
@Override
|
||||
public void onMessage(@NotNull Message message, byte[] bytes) {
|
||||
boolean isEmpty = taskQueue.isEmpty();
|
||||
taskQueue.offer(message);
|
||||
if (isEmpty) {
|
||||
taskExecutor.execute(() -> {
|
||||
while (!taskQueue.isEmpty()) {
|
||||
Message msg = taskQueue.poll();
|
||||
try {
|
||||
JSONObject jsonObject = JSON.parseObject(msg.getBody());
|
||||
String app = jsonObject.getString("app");
|
||||
String stream = jsonObject.getString("stream");
|
||||
pushService.stop(app, stream);
|
||||
|
||||
}catch (Exception e) {
|
||||
logger.warn("[REDIS的关闭推流通知] 发现未处理的异常, \r\n{}", JSON.toJSONString(message));
|
||||
logger.error("[REDIS的关闭推流通知] 异常内容: ", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,4 +202,5 @@ public interface IRedisCatchStorage {
|
||||
void removeAllDevice();
|
||||
|
||||
void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online);
|
||||
void sendChannelAddOrDelete(String deviceId, String channelId, boolean add);
|
||||
}
|
||||
|
||||
@@ -96,6 +96,6 @@ public interface ParentPlatformMapper {
|
||||
|
||||
@Select("select 'channel' as name, count(pgc.platform_id) count from wvp_platform_gb_channel pgc left join wvp_device_channel dc on dc.id = pgc.device_channel_id where pgc.platform_id=#{platform_id} and dc.channel_id =#{gbId} " +
|
||||
"union " +
|
||||
"select 'stream' as name, count(pgs.platform_id) count from wvp_platform_gb_stream pgs left join wvp_gb_stream gs on pgs.gb_stream_id = gs.gb_stream_id where pgs.platform_id=#{platform_id} and gs.gb_id #{gbId}")
|
||||
"select 'stream' as name, count(pgs.platform_id) count from wvp_platform_gb_stream pgs left join wvp_gb_stream gs on pgs.gb_stream_id = gs.gb_stream_id where pgs.platform_id=#{platform_id} and gs.gb_id =#{gbId}")
|
||||
List<ChannelSourceInfo> getChannelSource(String platform_id, String gbId);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.List;
|
||||
@Repository
|
||||
public interface PlatformCatalogMapper {
|
||||
|
||||
@Insert("INSERT INTO platform_catalog (id, name, platform_id, parent_id, civil_code, business_group_id) VALUES" +
|
||||
@Insert("INSERT INTO wvp_platform_catalog (id, name, platform_id, parent_id, civil_code, business_group_id) VALUES" +
|
||||
"(#{id}, #{name}, #{platformId}, #{parentId}, #{civilCode}, #{businessGroupId})")
|
||||
int add(PlatformCatalog platformCatalog);
|
||||
|
||||
@@ -32,7 +32,7 @@ public interface PlatformCatalogMapper {
|
||||
PlatformCatalog select(String id);
|
||||
|
||||
@Update(value = {" <script>" +
|
||||
"UPDATE platform_catalog " +
|
||||
"UPDATE wvp_platform_catalog " +
|
||||
"SET name=#{name}" +
|
||||
"WHERE id=#{id}"+
|
||||
"</script>"})
|
||||
@@ -41,11 +41,11 @@ public interface PlatformCatalogMapper {
|
||||
@Select("SELECT *, (SELECT COUNT(1) from wvp_platform_catalog where parent_id = pc.id) as children_count from wvp_platform_catalog pc WHERE pc.platform_id=#{platformId}")
|
||||
List<PlatformCatalog> selectByPlatForm(String platformId);
|
||||
|
||||
@Select("SELECT pc.* FROM platform_catalog pc WHERE pc.id = (SELECT pp.catalog_id from wvp_platform pp WHERE pp.server_gb_id=#{platformId})")
|
||||
@Select("SELECT pc.* FROM wvp_platform_catalog pc WHERE pc.id = (SELECT pp.catalog_id from wvp_platform pp WHERE pp.server_gb_id=#{platformId})")
|
||||
PlatformCatalog selectDefaultByPlatFormId(String platformId);
|
||||
|
||||
|
||||
@Select("SELECT pc.* FROM platform_catalog pc WHERE pc.id = #{id}")
|
||||
@Select("SELECT pc.* FROM wvp_platform_catalog pc WHERE pc.id = #{id}")
|
||||
PlatformCatalog selectParentCatalog(String id);
|
||||
|
||||
@Select("SELECT pc.id as channel_id, pc.name, pc.civil_code, pc.business_group_id,'1' as parental, pc.parent_id " +
|
||||
|
||||
@@ -13,9 +13,9 @@ public interface StreamProxyMapper {
|
||||
|
||||
@Insert("INSERT INTO wvp_stream_proxy (type, name, app, stream,media_server_id, url, src_url, dst_url, " +
|
||||
"timeout_ms, ffmpeg_cmd_key, rtp_type, enable_audio, enable_mp4, enable, status, enable_remove_none_reader, enable_disable_none_reader, create_time) VALUES" +
|
||||
"(#{type}, #{name}, #{app}, #{stream}, #{mediaServerId}, #{url}, #{src_url}, #{dst_url}, " +
|
||||
"#{timeout_ms}, #{ffmpeg_cmd_key}, #{rtp_type}, #{enable_audio}, #{enable_mp4}, #{enable}, #{status}, " +
|
||||
"#{enable_remove_none_reader}, #{enable_disable_none_reader}, #{createTime} )")
|
||||
"(#{type}, #{name}, #{app}, #{stream}, #{mediaServerId}, #{url}, #{srcUrl}, #{dstUrl}, " +
|
||||
"#{timeoutMs}, #{ffmpegCmdKey}, #{rtpType}, #{enableAudio}, #{enableMp4}, #{enable}, #{status}, " +
|
||||
"#{enableRemoveNoneReader}, #{enableDisableNoneReader}, #{createTime} )")
|
||||
int add(StreamProxyItem streamProxyDto);
|
||||
|
||||
@Update("UPDATE wvp_stream_proxy " +
|
||||
@@ -25,17 +25,17 @@ public interface StreamProxyMapper {
|
||||
"stream=#{stream}," +
|
||||
"url=#{url}, " +
|
||||
"media_server_id=#{mediaServerId}, " +
|
||||
"src_url=#{src_url}," +
|
||||
"dst_url=#{dst_url}, " +
|
||||
"timeout_ms=#{timeout_ms}, " +
|
||||
"ffmpeg_cmd_key=#{ffmpeg_cmd_key}, " +
|
||||
"rtp_type=#{rtp_type}, " +
|
||||
"enable_audio=#{enable_audio}, " +
|
||||
"src_url=#{srcUrl}," +
|
||||
"dst_url=#{dstUrl}, " +
|
||||
"timeout_ms=#{timeoutMs}, " +
|
||||
"ffmpeg_cmd_key=#{ffmpegCmdKey}, " +
|
||||
"rtp_type=#{rtpType}, " +
|
||||
"enable_audio=#{enableAudio}, " +
|
||||
"enable=#{enable}, " +
|
||||
"status=#{status}, " +
|
||||
"enable_remove_none_reader=#{enable_remove_none_reader}, " +
|
||||
"enable_disable_none_reader=#{enable_disable_none_reader}, " +
|
||||
"enable_mp4=#{enable_mp4} " +
|
||||
"enable_remove_none_reader=#{enableRemoveNoneReader}, " +
|
||||
"enable_disable_none_reader=#{enableDisableNoneReader}, " +
|
||||
"enable_mp4=#{enableMp4} " +
|
||||
"WHERE app=#{app} AND stream=#{stream}")
|
||||
int update(StreamProxyItem streamProxyDto);
|
||||
|
||||
|
||||
@@ -596,18 +596,29 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
|
||||
@Override
|
||||
public void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online) {
|
||||
String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_DEVICE_STATUS;
|
||||
if (channelId == null) {
|
||||
logger.info("[redis通知] 推送设备状态, {}-{}", deviceId, online);
|
||||
}else {
|
||||
logger.info("[redis通知] 推送通道状态, {}/{}-{}", deviceId, channelId, online);
|
||||
}
|
||||
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.append(deviceId);
|
||||
if (channelId != null) {
|
||||
msg.append(":").append(channelId);
|
||||
}
|
||||
msg.append(" ").append(online? "ON":"OFF");
|
||||
logger.info("[redis通知] 推送状态-> {} ", msg);
|
||||
// 使用 RedisTemplate<Object, Object> 发送字符串消息会导致发送的消息多带了双引号
|
||||
stringRedisTemplate.convertAndSend(key, msg.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendChannelAddOrDelete(String deviceId, String channelId, boolean add) {
|
||||
String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_DEVICE_STATUS;
|
||||
|
||||
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.append(deviceId);
|
||||
if (channelId != null) {
|
||||
msg.append(":").append(channelId);
|
||||
}
|
||||
msg.append(" ").append(add? "ADD":"DELETE");
|
||||
logger.info("[redis通知] 推送通道-> {}", msg);
|
||||
// 使用 RedisTemplate<Object, Object> 发送字符串消息会导致发送的消息多带了双引号
|
||||
stringRedisTemplate.convertAndSend(key, msg.toString());
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ package com.genersoft.iot.vmp.vmanager.bean;
|
||||
public enum ErrorCode {
|
||||
SUCCESS(0, "成功"),
|
||||
ERROR100(100, "失败"),
|
||||
ERROR400(400, "参数不全或者错误"),
|
||||
ERROR400(400, "参数或方法错误"),
|
||||
ERROR404(404, "资源未找到"),
|
||||
ERROR403(403, "无权限操作"),
|
||||
ERROR401(401, "请登录后重新请求"),
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.genersoft.iot.vmp.vmanager.bean;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "截图地址信息")
|
||||
public class SnapPath {
|
||||
|
||||
@Schema(description = "相对地址")
|
||||
private String path;
|
||||
|
||||
@Schema(description = "绝对地址")
|
||||
private String absoluteFilePath;
|
||||
|
||||
@Schema(description = "请求地址")
|
||||
private String url;
|
||||
|
||||
|
||||
public static SnapPath getInstance(String path, String absoluteFilePath, String url) {
|
||||
SnapPath snapPath = new SnapPath();
|
||||
snapPath.setPath(path);
|
||||
snapPath.setAbsoluteFilePath(absoluteFilePath);
|
||||
snapPath.setUrl(url);
|
||||
return snapPath;
|
||||
}
|
||||
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getAbsoluteFilePath() {
|
||||
return absoluteFilePath;
|
||||
}
|
||||
|
||||
public void setAbsoluteFilePath(String absoluteFilePath) {
|
||||
this.absoluteFilePath = absoluteFilePath;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ public class MobilePositionController {
|
||||
public DeferredResult<MobilePosition> realTimePosition(@PathVariable String deviceId) {
|
||||
Device device = storager.queryVideoDevice(deviceId);
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
String key = DeferredResultHolder.CALLBACK_CMD_MOBILEPOSITION + deviceId;
|
||||
String key = DeferredResultHolder.CALLBACK_CMD_MOBILE_POSITION + deviceId;
|
||||
try {
|
||||
cmder.mobilePostitionQuery(device, event -> {
|
||||
RequestMessage msg = new RequestMessage();
|
||||
|
||||
@@ -466,10 +466,12 @@ public class DeviceQuery {
|
||||
@Operation(summary = "请求截图")
|
||||
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
|
||||
@Parameter(name = "channelId", description = "通道国标编号", required = true)
|
||||
public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId) {
|
||||
@Parameter(name = "mark", description = "标识", required = false)
|
||||
public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId, @RequestParam(required = false) String mark) {
|
||||
|
||||
try {
|
||||
final InputStream in = Files.newInputStream(new File("snap" + File.separator + deviceId + "_" + channelId + ".jpg").toPath());
|
||||
|
||||
final InputStream in = Files.newInputStream(new File("snap" + File.separator + deviceId + "_" + channelId + (mark == null? ".jpg": ("_" + mark + ".jpg"))).toPath());
|
||||
resp.setContentType(MediaType.IMAGE_PNG_VALUE);
|
||||
IOUtils.copy(in, resp.getOutputStream());
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package com.genersoft.iot.vmp.vmanager.streamProxy;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
|
||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.service.IStreamProxyService;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -18,6 +23,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
/**
|
||||
@@ -37,6 +45,12 @@ public class StreamProxyController {
|
||||
@Autowired
|
||||
private IStreamProxyService streamProxyService;
|
||||
|
||||
@Autowired
|
||||
private DeferredResultHolder resultHolder;
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
|
||||
@Operation(summary = "分页查询流代理")
|
||||
@Parameter(name = "page", description = "当前页")
|
||||
@@ -58,7 +72,7 @@ public class StreamProxyController {
|
||||
})
|
||||
@PostMapping(value = "/save")
|
||||
@ResponseBody
|
||||
public StreamContent save(@RequestBody StreamProxyItem param){
|
||||
public DeferredResult<Object> save(@RequestBody StreamProxyItem param){
|
||||
logger.info("添加代理: " + JSONObject.toJSONString(param));
|
||||
if (ObjectUtils.isEmpty(param.getMediaServerId())) {
|
||||
param.setMediaServerId("auto");
|
||||
@@ -69,7 +83,33 @@ public class StreamProxyController {
|
||||
if (ObjectUtils.isEmpty(param.getGbId())) {
|
||||
param.setGbId(null);
|
||||
}
|
||||
return new StreamContent(streamProxyService.save(param));
|
||||
|
||||
RequestMessage requestMessage = new RequestMessage();
|
||||
String key = DeferredResultHolder.CALLBACK_CMD_PROXY + param.getApp() + param.getStream();
|
||||
requestMessage.setKey(key);
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
requestMessage.setId(uuid);
|
||||
DeferredResult<Object> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
|
||||
// 录像查询以channelId作为deviceId查询
|
||||
resultHolder.put(key, uuid, result);
|
||||
result.onTimeout(()->{
|
||||
WVPResult<StreamInfo> wvpResult = new WVPResult<>();
|
||||
wvpResult.setCode(ErrorCode.ERROR100.getCode());
|
||||
wvpResult.setMsg("超时");
|
||||
requestMessage.setData(wvpResult);
|
||||
resultHolder.invokeAllResult(requestMessage);
|
||||
});
|
||||
|
||||
streamProxyService.save(param, (code, msg, streamInfo) -> {
|
||||
logger.info("[拉流代理] {}", code == ErrorCode.SUCCESS.getCode()? "成功":"失败: " + msg);
|
||||
if (code == ErrorCode.SUCCESS.getCode()) {
|
||||
requestMessage.setData(new StreamContent(streamInfo));
|
||||
}else {
|
||||
requestMessage.setData(WVPResult.fail(code, msg));
|
||||
}
|
||||
resultHolder.invokeAllResult(requestMessage);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@GetMapping(value = "/ffmpeg_cmd/list")
|
||||
|
||||
Reference in New Issue
Block a user