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
pull/893/head
‘sxh’ 2023-06-15 11:10:20 +08:00
commit 652489b47e
44 changed files with 645 additions and 270 deletions

View File

@ -27,7 +27,7 @@ wvp使用文档 [https://doc.wvp-pro.cn](https://doc.wvp-pro.cn)
ZLM使用文档 [https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit)
> wvp文档由gitee提供服务如果遇到打不开请多刷新几次。
# 社群地址
# 付费社群
[![社群](doc/_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm)
> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。
@ -105,6 +105,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
- [X] 支持打包可执行jar和war
- [X] 支持跨域请求,支持前后端分离部署
- [X] 支持MysqlPostgresql金仓等数据库
- [X] 支持Onvif(目前在onvif分支需要安装onvif服务服务请在知识星球获取)
# 授权协议
本项目自有代码使用宽松的MIT协议在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。 在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议

View File

@ -14,7 +14,7 @@
- 完全开源且使用MIT许可协议。保留版权的情况下可以用于商业项目。
- 支持多流媒体节点负载均衡。
# 社群
# 付费社群
[![社群](_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm)
> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。
@ -62,16 +62,16 @@
- [X] 注册
- [X] 注销
- [X] 实时视音频点播
- [ ] 设备控制
- [ ] 云台控制
- [X] 设备控制
- [X] 云台控制
- [ ] 远程启动
- [ ] 录像控制
- [ ] 报警布防/撤防
- [ ] 报警复位
- [ ] 强制关键帧
- [ ] 拉框放大
- [ ] 拉框缩小
- [ ] 看守位控制
- [X] 录像控制
- [X] 报警布防/撤防
- [X] 报警复位
- [X] 强制关键帧
- [X] 拉框放大
- [X] 拉框缩小
- [X] 看守位控制
- [ ] 设备配置
- [ ] 报警事件通知和分发
- [X] 设备目录订阅
@ -79,7 +79,7 @@
- [X] 设备目录查询
- [X] 设备状态查询
- [ ] 设备配置查询
- [ ] 设备预置位查询
- [X] 设备预置位查询
- [X] 状态信息报送
- [X] 设备视音频文件检索
- [X] 历史视音频的回放
@ -87,7 +87,7 @@
- [x] 暂停
- [x] 进/退
- [x] 停止
- [ ] 视音频文件下载
- [X] 视音频文件下载
- [ ] ~~校时~~
- [X] 订阅和通知
- [X] 事件订阅

BIN
doc/_media/1372762149.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

BIN
doc/_media/903207146.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

View File

@ -11,7 +11,7 @@
<groupId>com.genersoft</groupId>
<artifactId>wvp-pro</artifactId>
<version>2.6.8</version>
<version>2.6.9</version>
<name>web video platform</name>
<description>国标28181视频平台</description>
<packaging>${project.packaging}</packaging>

View File

@ -179,10 +179,6 @@ alter table device_mobile_position
alter table device_mobile_position
change createTime create_time varchar(50) null;
alter table gb_stream
add constraint gb_stream_pk
primary key (gbStreamId);
alter table gb_stream
change gbStreamId gb_stream_id int auto_increment;

View File

@ -0,0 +1,5 @@
package com.genersoft.iot.vmp.common;
public interface GeneralCallback<T>{
void run(int code, String msg, T data);
}

View File

@ -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 线
*/

View File

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

View File

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

View File

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

View File

@ -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";

View File

@ -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

View File

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

View File

@ -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

View File

@ -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:

View File

@ -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());

View File

@ -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());

View File

@ -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());

View File

@ -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());

View File

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

View File

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

View File

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

View File

@ -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拉流时拉流方式0tcp1udp2组播")
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;
}
}

View File

@ -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

View File

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

View File

@ -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] 连接成功 {} - {}:{} ",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 " +

View File

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

View File

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

View File

@ -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, "请登录后重新请求"),

View File

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

View File

@ -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();

View File

@ -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) {

View File

@ -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")

View File

@ -43,10 +43,6 @@ spring:
idle-timeout: 300000 # 允许连接在连接池中空闲的最长时间(以毫秒为单位)
max-lifetime: 1200000 # 是池中连接关闭后的最长生命周期(以毫秒为单位)
# 修改为数据库字段下划线分隔直接对应java驼峰命名
mybatis:
configuration:
map-underscore-to-camel-case: true
# 修改分页插件为 postgresql 数据库类型为mysql不需要
#pagehelper:

View File

@ -22,8 +22,8 @@
{{scope.row.url}}
</el-tag>
<el-tag size="medium" v-if="scope.row.type != 'default'">
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="scope.row.src_url" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
{{scope.row.src_url}}
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="scope.row.srcUrl" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
{{scope.row.srcUrl}}
</el-tag>
</div>
</template>
@ -58,25 +58,25 @@
<el-table-column label="音频" min-width="120" >
<template slot-scope="scope">
<div slot="reference" class="name-wrapper">
<el-tag size="medium" v-if="scope.row.enable_audio"></el-tag>
<el-tag size="medium" type="info" v-if="!scope.row.enable_audio"></el-tag>
<el-tag size="medium" v-if="scope.row.enableAudio"></el-tag>
<el-tag size="medium" type="info" v-if="!scope.row.enableAudio"></el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="录制" min-width="120" >
<template slot-scope="scope">
<div slot="reference" class="name-wrapper">
<el-tag size="medium" v-if="scope.row.enable_mp4"></el-tag>
<el-tag size="medium" type="info" v-if="!scope.row.enable_mp4"></el-tag>
<el-tag size="medium" v-if="scope.row.enableMp4"></el-tag>
<el-tag size="medium" type="info" v-if="!scope.row.enableMp4"></el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="无人观看" min-width="160" >
<template slot-scope="scope">
<div slot="reference" class="name-wrapper">
<el-tag size="medium" v-if="scope.row.enable_remove_none_reader"></el-tag>
<el-tag size="medium" v-if="scope.row.enable_disable_none_reader"></el-tag>
<el-tag size="medium" type="info" v-if="!scope.row.enable_remove_none_reader && !scope.row.enable_disable_none_reader"></el-tag>
<el-tag size="medium" v-if="scope.row.enableRemoveNoneReader"></el-tag>
<el-tag size="medium" v-if="scope.row.enableDisableNoneReader"></el-tag>
<el-tag size="medium" type="info" v-if="!scope.row.enableRemoveNoneReader && !scope.row.enableDisableNoneReader"></el-tag>
</div>
</template>
</el-table-column>
@ -197,7 +197,7 @@
this.$refs.onvifEdit.openDialog(res.data.data, (url)=>{
if (url != null) {
this.$refs.onvifEdit.close();
this.$refs.streamProxyEdit.openDialog({type: "default", url: url, src_url: url}, this.initData())
this.$refs.streamProxyEdit.openDialog({type: "default", url: url, srcUrl: url}, this.initData())
}
})
}else {
@ -245,18 +245,25 @@
},
deleteStreamProxy: function(row){
let that = this;
that.$axios({
method:"delete",
url:"/api/proxy/del",
params:{
app: row.app,
stream: row.stream
}
}).then((res)=>{
that.initData()
}).catch(function (error) {
console.log(error);
});
this.$confirm('确定删除此代理吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
that.$axios({
method:"delete",
url:"/api/proxy/del",
params:{
app: row.app,
stream: row.stream
}
}).then((res)=>{
that.initData()
}).catch(function (error) {
console.log(error);
});
}).catch(() => {
});
},
start: function(row){
this.stopUpdateList()

View File

@ -33,13 +33,13 @@
<el-form-item label="拉流地址" prop="url" v-if="proxyParam.type=='default'">
<el-input v-model="proxyParam.url" clearable></el-input>
</el-form-item>
<el-form-item label="拉流地址" prop="src_url" v-if="proxyParam.type=='ffmpeg'">
<el-input v-model="proxyParam.src_url" clearable></el-input>
<el-form-item label="拉流地址" prop="srcUrl" v-if="proxyParam.type=='ffmpeg'">
<el-input v-model="proxyParam.srcUrl" clearable></el-input>
</el-form-item>
<el-form-item label="超时时间:毫秒" prop="timeout_ms" v-if="proxyParam.type=='ffmpeg'">
<el-input v-model="proxyParam.timeout_ms" clearable></el-input>
<el-form-item label="超时时间:毫秒" prop="timeoutMs" v-if="proxyParam.type=='ffmpeg'">
<el-input v-model="proxyParam.timeoutMs" clearable></el-input>
</el-form-item>
<el-form-item label="节点选择" prop="rtp_type">
<el-form-item label="节点选择" prop="rtpType">
<el-select
v-model="proxyParam.mediaServerId"
@change="mediaServerIdChange"
@ -54,10 +54,9 @@
</el-option>
</el-select>
</el-form-item>
<el-form-item label="FFmpeg命令模板" prop="ffmpeg_cmd_key" v-if="proxyParam.type=='ffmpeg'">
<!-- <el-input v-model="proxyParam.ffmpeg_cmd_key" clearable></el-input>-->
<el-form-item label="FFmpeg命令模板" prop="ffmpegCmdKey" v-if="proxyParam.type=='ffmpeg'">
<el-select
v-model="proxyParam.ffmpeg_cmd_key"
v-model="proxyParam.ffmpegCmdKey"
style="width: 100%"
placeholder="请选择FFmpeg命令模板"
>
@ -72,9 +71,9 @@
<el-form-item label="国标编码" prop="gbId">
<el-input v-model="proxyParam.gbId" placeholder="设置国标编码可推送到国标" clearable></el-input>
</el-form-item>
<el-form-item label="拉流方式" prop="rtp_type" v-if="proxyParam.type=='default'">
<el-form-item label="拉流方式" prop="rtpType" v-if="proxyParam.type=='default'">
<el-select
v-model="proxyParam.rtp_type"
v-model="proxyParam.rtpType"
style="width: 100%"
placeholder="请选择拉流方式"
>
@ -83,10 +82,10 @@
<el-option label="组播" value="2"></el-option>
</el-select>
</el-form-item>
<el-form-item label="无人观看" prop="rtp_type" >
<el-form-item label="无人观看" prop="rtpType" >
<el-select
@change="noneReaderHandler"
v-model="proxyParam.none_reader"
v-model="proxyParam.noneReader"
style="width: 100%"
placeholder="请选择无人观看的处理方式"
>
@ -98,8 +97,8 @@
<el-form-item label="其他选项">
<div style="float: left;">
<el-checkbox label="启用" v-model="proxyParam.enable" ></el-checkbox>
<el-checkbox label="开启音频" v-model="proxyParam.enable_audio" ></el-checkbox>
<el-checkbox label="录制" v-model="proxyParam.enable_mp4" ></el-checkbox>
<el-checkbox label="开启音频" v-model="proxyParam.enableAudio" ></el-checkbox>
<el-checkbox label="录制" v-model="proxyParam.enableMp4" ></el-checkbox>
</div>
</el-form-item>
@ -155,17 +154,17 @@ export default {
app: null,
stream: null,
url: "",
src_url: null,
timeout_ms: null,
ffmpeg_cmd_key: null,
srcUrl: null,
timeoutMs: null,
ffmpegCmdKey: null,
gbId: null,
rtp_type: null,
rtpType: null,
enable: true,
enable_audio: true,
enable_mp4: false,
none_reader: null,
enable_remove_none_reader: false,
enable_disable_none_reader: false,
enableAudio: true,
enableMp4: false,
noneReader: null,
enableRemoveNoneReader: false,
enableDisableNoneReader: false,
platformGbId: null,
mediaServerId: null,
},
@ -177,9 +176,9 @@ export default {
app: [{ required: true, message: "请输入应用名", trigger: "blur" }],
stream: [{ required: true, message: "请输入流ID", trigger: "blur" }],
url: [{ required: true, message: "请输入要代理的流", trigger: "blur" }],
src_url: [{ required: true, message: "请输入要代理的流", trigger: "blur" }],
timeout_ms: [{ required: true, message: "请输入FFmpeg推流成功超时时间", trigger: "blur" }],
ffmpeg_cmd_key: [{ required: false, message: "请输入FFmpeg命令参数模板可选", trigger: "blur" }],
srcUrl: [{ required: true, message: "请输入要代理的流", trigger: "blur" }],
timeoutMs: [{ required: true, message: "请输入FFmpeg推流成功超时时间", trigger: "blur" }],
ffmpegCmdKey: [{ required: false, message: "请输入FFmpeg命令参数模板可选", trigger: "blur" }],
},
};
},
@ -189,7 +188,7 @@ export default {
this.listChangeCallback = callback;
if (proxyParam != null) {
this.proxyParam = proxyParam;
this.proxyParam.none_reader = null;
this.proxyParam.noneReader = null;
}
let that = this;
@ -218,7 +217,7 @@ export default {
}
}).then(function (res) {
that.ffmpegCmdList = res.data.data;
that.proxyParam.ffmpeg_cmd_key = Object.keys(res.data.data)[0];
that.proxyParam.ffmpegCmdKey = Object.keys(res.data.data)[0];
}).catch(function (error) {
console.log(error);
});
@ -275,15 +274,15 @@ export default {
}
},
noneReaderHandler: function() {
if (this.proxyParam.none_reader === null || this.proxyParam.none_reader === "0") {
this.proxyParam.enable_disable_none_reader = false;
this.proxyParam.enable_remove_none_reader = false;
}else if (this.proxyParam.none_reader === "1"){
this.proxyParam.enable_disable_none_reader = true;
this.proxyParam.enable_remove_none_reader = false;
}else if (this.proxyParam.none_reader ==="2"){
this.proxyParam.enable_disable_none_reader = false;
this.proxyParam.enable_remove_none_reader = true;
if (this.proxyParam.noneReader === null || this.proxyParam.noneReader === "0") {
this.proxyParam.enableDisableNoneReader = false;
this.proxyParam.enableRemoveNoneReader = false;
}else if (this.proxyParam.noneReader === "1"){
this.proxyParam.enableDisableNoneReader = true;
this.proxyParam.enableRemoveNoneReader = false;
}else if (this.proxyParam.noneReader ==="2"){
this.proxyParam.enableDisableNoneReader = false;
this.proxyParam.enableRemoveNoneReader = true;
}
},
},

View File

@ -14,7 +14,6 @@
<rtc-player v-if="activePlayer === 'webRTC'" ref="webRTC" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player>
</el-tab-pane>
<el-tab-pane label="h265web">h265web敬请期待</el-tab-pane>
<el-tab-pane label="wsPlayer">wsPlayer 敬请期待</el-tab-pane>
</el-tabs>
<jessibucaPlayer v-if="Object.keys(this.player).length == 1 && this.player.jessibuca" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></jessibucaPlayer>
<rtc-player v-if="Object.keys(this.player).length == 1 && this.player.webRTC" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player>
@ -451,7 +450,15 @@ export default {
playFromStreamInfo: function (realHasAudio, streamInfo) {
this.showVideoDialog = true;
this.hasaudio = realHasAudio && this.hasaudio;
this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
if (this.$refs[this.activePlayer]) {
this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
}else {
this.$nextTick(() => {
this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
});
}
},
close: function () {
console.log('关闭视频');