Merge branch 'refs/heads/master' into dev/abl支持

This commit is contained in:
648540858 2024-05-28 11:02:05 +08:00
commit 271bb19c99
99 changed files with 3651 additions and 1795 deletions

View File

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

View File

@ -72,6 +72,9 @@ public class VideoManagerConstants {
public static final String REGISTER_EXPIRE_TASK_KEY_PREFIX = "VMP_device_register_expire_";
public static final String PUSH_STREAM_LIST = "VMP_PUSH_STREAM_LIST_";
public static final String WAITE_SEND_PUSH_STREAM = "VMP_WAITE_SEND_PUSH_STREAM:";
public static final String START_SEND_PUSH_STREAM = "VMP_START_SEND_PUSH_STREAM:";
public static final String PUSH_STREAM_ONLINE = "VMP_PUSH_STREAM_ONLINE:";

View File

@ -7,6 +7,7 @@ import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.beans.factory.annotation.Value;
@ -18,6 +19,7 @@ import org.springframework.context.annotation.Configuration;
*/
@Configuration
@Order(1)
@ConditionalOnProperty(value = "user-settings.doc-enable", havingValue = "true", matchIfMissing = true)
public class SpringDocConfig {
@Value("${doc.enabled: true}")

View File

@ -54,6 +54,8 @@ public class UserSetting {
private Boolean deviceStatusNotify = Boolean.TRUE;
private Boolean useCustomSsrcForParentInvite = Boolean.TRUE;
private Boolean docEnable = Boolean.TRUE;
private String serverId = "000000";
private String thirdPartyGBIdReg = "[\\s\\S]*";
@ -66,7 +68,7 @@ public class UserSetting {
private List<String> allowedOrigins = new ArrayList<>();
private int maxNotifyCountQueue = 10000;
private int maxNotifyCountQueue = 100000;
private int registerAgainAfterTime = 60;
@ -315,4 +317,12 @@ public class UserSetting {
public void setRegisterKeepIntDialog(boolean registerKeepIntDialog) {
this.registerKeepIntDialog = registerKeepIntDialog;
}
public Boolean getDocEnable() {
return docEnable;
}
public void setDocEnable(Boolean docEnable) {
this.docEnable = docEnable;
}
}

View File

@ -28,26 +28,22 @@ public class RedisMsgListenConfig {
@Autowired
private RedisAlarmMsgListener redisAlarmMsgListener;
@Autowired
private RedisStreamMsgListener redisStreamMsgListener;
@Autowired
private RedisGbPlayMsgListener redisGbPlayMsgListener;
@Autowired
private RedisPushStreamStatusMsgListener redisPushStreamStatusMsgListener;
@Autowired
private RedisPushStreamStatusListMsgListener redisPushStreamListMsgListener;
@Autowired
private RedisPushStreamResponseListener redisPushStreamResponseListener;
@Autowired
private RedisCloseStreamMsgListener redisCloseStreamMsgListener;
@Autowired
private RedisPushStreamCloseResponseListener redisPushStreamCloseResponseListener;
private RedisRpcConfig redisRpcConfig;
@Autowired
private RedisPushStreamResponseListener redisPushStreamCloseResponseListener;
/**
@ -64,13 +60,11 @@ public class RedisMsgListenConfig {
container.setConnectionFactory(connectionFactory);
container.addMessageListener(redisGPSMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_GPS));
container.addMessageListener(redisAlarmMsgListener, new PatternTopic(VideoManagerConstants.VM_MSG_SUBSCRIBE_ALARM_RECEIVE));
container.addMessageListener(redisStreamMsgListener, new PatternTopic(VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + "PUSH"));
container.addMessageListener(redisGbPlayMsgListener, new PatternTopic(RedisGbPlayMsgListener.WVP_PUSH_STREAM_KEY));
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));
container.addMessageListener(redisPushStreamCloseResponseListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_CLOSE_REQUESTED));
container.addMessageListener(redisRpcConfig, new PatternTopic(RedisRpcConfig.REDIS_REQUEST_CHANNEL_KEY));
container.addMessageListener(redisPushStreamCloseResponseListener, new PatternTopic(VideoManagerConstants.VM_MSG_STREAM_PUSH_RESPONSE));
return container;
}
}

View File

@ -0,0 +1,221 @@
package com.genersoft.iot.vmp.conf.redis;
import com.alibaba.fastjson2.JSON;
import com.genersoft.iot.vmp.common.CommonCallback;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse;
import com.genersoft.iot.vmp.service.redisMsg.control.RedisRpcController;
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.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
@Component
public class RedisRpcConfig implements MessageListener {
private final static Logger logger = LoggerFactory.getLogger(RedisRpcConfig.class);
public final static String REDIS_REQUEST_CHANNEL_KEY = "WVP_REDIS_REQUEST_CHANNEL_KEY";
private final Random random = new Random();
@Autowired
private UserSetting userSetting;
@Autowired
private RedisRpcController redisRpcController;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
private ConcurrentLinkedQueue<Message> taskQueue = new ConcurrentLinkedQueue<>();
@Qualifier("taskExecutor")
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Override
public void onMessage(Message message, byte[] pattern) {
boolean isEmpty = taskQueue.isEmpty();
taskQueue.offer(message);
if (isEmpty) {
taskExecutor.execute(() -> {
while (!taskQueue.isEmpty()) {
Message msg = taskQueue.poll();
try {
RedisRpcMessage redisRpcMessage = JSON.parseObject(new String(msg.getBody()), RedisRpcMessage.class);
if (redisRpcMessage.getRequest() != null) {
handlerRequest(redisRpcMessage.getRequest());
} else if (redisRpcMessage.getResponse() != null){
handlerResponse(redisRpcMessage.getResponse());
} else {
logger.error("[redis rpc 解析失败] {}", JSON.toJSONString(redisRpcMessage));
}
} catch (Exception e) {
logger.error("[redis rpc 解析异常] ", e);
}
}
});
}
}
private void handlerResponse(RedisRpcResponse response) {
if (userSetting.getServerId().equals(response.getToId())) {
return;
}
logger.info("[redis-rpc] << {}", response);
response(response);
}
private void handlerRequest(RedisRpcRequest request) {
try {
if (userSetting.getServerId().equals(request.getFromId())) {
return;
}
logger.info("[redis-rpc] << {}", request);
Method method = getMethod(request.getUri());
// 没有携带目标ID的可以理解为哪个wvp有结果就哪个回复携带目标ID但是如果是不存在的uri则直接回复404
if (userSetting.getServerId().equals(request.getToId())) {
if (method == null) {
// 回复404结果
RedisRpcResponse response = request.getResponse();
response.setStatusCode(404);
sendResponse(response);
return;
}
RedisRpcResponse response = (RedisRpcResponse)method.invoke(redisRpcController, request);
if(response != null) {
sendResponse(response);
}
}else {
if (method == null) {
return;
}
RedisRpcResponse response = (RedisRpcResponse)method.invoke(redisRpcController, request);
if (response != null) {
sendResponse(response);
}
}
}catch (InvocationTargetException | IllegalAccessException e) {
logger.error("[redis rpc ] 处理请求失败 ", e);
}
}
private Method getMethod(String name) {
// 启动后扫描所有的路径注解
Method[] methods = redisRpcController.getClass().getMethods();
for (Method method : methods) {
if (method.getName().equals(name)) {
return method;
}
}
return null;
}
private void sendResponse(RedisRpcResponse response){
logger.info("[redis-rpc] >> {}", response);
response.setToId(userSetting.getServerId());
RedisRpcMessage message = new RedisRpcMessage();
message.setResponse(response);
redisTemplate.convertAndSend(REDIS_REQUEST_CHANNEL_KEY, message);
}
private void sendRequest(RedisRpcRequest request){
logger.info("[redis-rpc] >> {}", request);
RedisRpcMessage message = new RedisRpcMessage();
message.setRequest(request);
redisTemplate.convertAndSend(REDIS_REQUEST_CHANNEL_KEY, message);
}
private final Map<Long, SynchronousQueue<RedisRpcResponse>> topicSubscribers = new ConcurrentHashMap<>();
private final Map<Long, CommonCallback<RedisRpcResponse>> callbacks = new ConcurrentHashMap<>();
public RedisRpcResponse request(RedisRpcRequest request, int timeOut) {
request.setSn((long) random.nextInt(1000) + 1);
SynchronousQueue<RedisRpcResponse> subscribe = subscribe(request.getSn());
try {
sendRequest(request);
return subscribe.poll(timeOut, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.warn("[redis rpc timeout] uri: {}, sn: {}", request.getUri(), request.getSn(), e);
} finally {
this.unsubscribe(request.getSn());
}
return null;
}
public void request(RedisRpcRequest request, CommonCallback<RedisRpcResponse> callback) {
request.setSn((long) random.nextInt(1000) + 1);
setCallback(request.getSn(), callback);
sendRequest(request);
}
public Boolean response(RedisRpcResponse response) {
SynchronousQueue<RedisRpcResponse> queue = topicSubscribers.get(response.getSn());
CommonCallback<RedisRpcResponse> callback = callbacks.get(response.getSn());
if (queue != null) {
try {
return queue.offer(response, 2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.error("{}", e.getMessage(), e);
}
}else if (callback != null) {
callback.run(response);
callbacks.remove(response.getSn());
}
return false;
}
private void unsubscribe(long key) {
topicSubscribers.remove(key);
}
private SynchronousQueue<RedisRpcResponse> subscribe(long key) {
SynchronousQueue<RedisRpcResponse> queue = null;
if (!topicSubscribers.containsKey(key))
topicSubscribers.put(key, queue = new SynchronousQueue<>());
return queue;
}
private void setCallback(long key, CommonCallback<RedisRpcResponse> callback) {
// TODO 如果多个上级点播同一个通道会有问题
callbacks.put(key, callback);
}
public void removeCallback(long key) {
callbacks.remove(key);
}
public int getCallbackCount(){
return callbacks.size();
}
// @Scheduled(fixedRate = 1000) //每1秒执行一次
// public void execute(){
// logger.info("callbacks的长度: " + callbacks.size());
// logger.info("队列的长度: " + topicSubscribers.size());
// logger.info("HOOK监听的长度: " + hookSubscribe.size());
// logger.info("");
// }
}

View File

@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.conf.redis;
import com.alibaba.fastjson2.support.spring.data.redis.GenericFastJsonRedisSerializer;
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@ -25,4 +26,20 @@ public class RedisTemplateConfig {
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
@Bean
public RedisTemplate<String, MobilePosition> getRedisTemplateForMobilePosition(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, MobilePosition> redisTemplate = new RedisTemplate<>();
// 使用fastJson序列化
GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
// value值的序列化采用fastJsonRedisSerializer
redisTemplate.setValueSerializer(fastJsonRedisSerializer);
redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
// key的序列化采用StringRedisSerializer
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}

View File

@ -0,0 +1,24 @@
package com.genersoft.iot.vmp.conf.redis.bean;
public class RedisRpcMessage {
private RedisRpcRequest request;
private RedisRpcResponse response;
public RedisRpcRequest getRequest() {
return request;
}
public void setRequest(RedisRpcRequest request) {
this.request = request;
}
public RedisRpcResponse getResponse() {
return response;
}
public void setResponse(RedisRpcResponse response) {
this.response = response;
}
}

View File

@ -0,0 +1,93 @@
package com.genersoft.iot.vmp.conf.redis.bean;
/**
* 通过redis发送请求
*/
public class RedisRpcRequest {
/**
* 来自的WVP ID
*/
private String fromId;
/**
* 目标的WVP ID
*/
private String toId;
/**
* 序列号
*/
private long sn;
/**
* 访问的路径
*/
private String uri;
/**
* 参数
*/
private Object param;
public String getFromId() {
return fromId;
}
public void setFromId(String fromId) {
this.fromId = fromId;
}
public String getToId() {
return toId;
}
public void setToId(String toId) {
this.toId = toId;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public Object getParam() {
return param;
}
public void setParam(Object param) {
this.param = param;
}
public long getSn() {
return sn;
}
public void setSn(long sn) {
this.sn = sn;
}
@Override
public String toString() {
return "RedisRpcRequest{" +
"uri='" + uri + '\'' +
", fromId='" + fromId + '\'' +
", toId='" + toId + '\'' +
", sn=" + sn +
", param=" + param +
'}';
}
public RedisRpcResponse getResponse() {
RedisRpcResponse response = new RedisRpcResponse();
response.setFromId(fromId);
response.setToId(toId);
response.setSn(sn);
response.setUri(uri);
return response;
}
}

View File

@ -0,0 +1,99 @@
package com.genersoft.iot.vmp.conf.redis.bean;
/**
* 通过redis发送回复
*/
public class RedisRpcResponse {
/**
* 来自的WVP ID
*/
private String fromId;
/**
* 目标的WVP ID
*/
private String toId;
/**
* 序列号
*/
private long sn;
/**
* 状态码
*/
private int statusCode;
/**
* 访问的路径
*/
private String uri;
/**
* 参数
*/
private Object body;
public String getFromId() {
return fromId;
}
public void setFromId(String fromId) {
this.fromId = fromId;
}
public String getToId() {
return toId;
}
public void setToId(String toId) {
this.toId = toId;
}
public long getSn() {
return sn;
}
public void setSn(long sn) {
this.sn = sn;
}
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public Object getBody() {
return body;
}
public void setBody(Object body) {
this.body = body;
}
@Override
public String toString() {
return "RedisRpcResponse{" +
"uri='" + uri + '\'' +
", fromId='" + fromId + '\'' +
", toId='" + toId + '\'' +
", sn=" + sn +
", statusCode=" + statusCode +
", body=" + body +
'}';
}
}

View File

@ -35,10 +35,15 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
// 忽略登录请求的token验证
String requestURI = request.getRequestURI();
if ((requestURI.startsWith("/doc.html") || requestURI.startsWith("/swagger-ui") ) && !userSetting.getDocEnable()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (requestURI.equalsIgnoreCase("/api/user/login")) {
chain.doFilter(request, response);
return;
}
if (!userSetting.isInterfaceAuthentication()) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(null, null, new ArrayList<>() );
SecurityContextHolder.getContext().setAuthentication(token);

View File

@ -117,7 +117,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.antMatchers(userSetting.getInterfaceAuthenticationExcludes().toArray(new String[0])).permitAll()
.antMatchers("/api/user/login", "/index/hook/**","/index/hook/abl/**", "/swagger-ui/**", "/doc.html").permitAll()
.antMatchers("/api/user/login", "/index/hook/**","/index/hook/abl/**", "/swagger-ui/**", "/doc.html#/**").permitAll()
.anyRequest().authenticated()
// 异常处理器
.and()

View File

@ -186,6 +186,18 @@ public class DeviceChannel {
@Schema(description = "纬度")
private double latitude;
/**
* 经度
*/
@Schema(description = "自定义经度")
private double customLongitude;
/**
* 纬度
*/
@Schema(description = "自定义纬度")
private double customLatitude;
/**
* 经度 GCJ02
*/
@ -226,7 +238,7 @@ public class DeviceChannel {
* 是否含有音频
*/
@Schema(description = "是否含有音频")
private boolean hasAudio;
private Boolean hasAudio;
/**
* 标记通道的类型0->国标通道 1->直播流通道 2->业务分组/虚拟组织/行政区划
@ -523,11 +535,11 @@ public class DeviceChannel {
this.subCount = subCount;
}
public boolean isHasAudio() {
public Boolean getHasAudio() {
return hasAudio;
}
public void setHasAudio(boolean hasAudio) {
public void setHasAudio(Boolean hasAudio) {
this.hasAudio = hasAudio;
}
@ -586,4 +598,20 @@ public class DeviceChannel {
public void setStreamIdentification(String streamIdentification) {
this.streamIdentification = streamIdentification;
}
public double getCustomLongitude() {
return customLongitude;
}
public void setCustomLongitude(double customLongitude) {
this.customLongitude = customLongitude;
}
public double getCustomLatitude() {
return customLatitude;
}
public void setCustomLatitude(double customLatitude) {
this.customLatitude = customLatitude;
}
}

View File

@ -2,6 +2,8 @@ package com.genersoft.iot.vmp.gb28181.bean;
import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
public class SendRtpItem {
/**
@ -24,6 +26,11 @@ public class SendRtpItem {
*/
private String platformId;
/**
* 平台名称
*/
private String platformName;
/**
* 对应设备id
*/
@ -63,6 +70,11 @@ public class SendRtpItem {
*/
private boolean tcpActive;
/**
* 自己推流使用的IP
*/
private String localIp;
/**
* 自己推流使用的端口
*/
@ -81,7 +93,7 @@ public class SendRtpItem {
/**
* invite callId
*/
private String CallId;
private String callId;
/**
* invite fromTag
@ -124,6 +136,11 @@ public class SendRtpItem {
*/
private String receiveStream;
/**
* 上级的点播类型
*/
private String sessionName;
public static SendRtpItem getInstance(RequestPushStreamMsg requestPushStreamMsg) {
SendRtpItem sendRtpItem = new SendRtpItem();
sendRtpItem.setMediaServerId(requestPushStreamMsg.getMediaServerId());
@ -262,11 +279,11 @@ public class SendRtpItem {
}
public String getCallId() {
return CallId;
return callId;
}
public void setCallId(String callId) {
CallId = callId;
this.callId = callId;
}
public InviteStreamType getPlayType() {
@ -341,6 +358,30 @@ public class SendRtpItem {
this.receiveStream = receiveStream;
}
public String getPlatformName() {
return platformName;
}
public void setPlatformName(String platformName) {
this.platformName = platformName;
}
public String getLocalIp() {
return localIp;
}
public void setLocalIp(String localIp) {
this.localIp = localIp;
}
public String getSessionName() {
return sessionName;
}
public void setSessionName(String sessionName) {
this.sessionName = sessionName;
}
@Override
public String toString() {
return "SendRtpItem{" +
@ -348,6 +389,7 @@ public class SendRtpItem {
", port=" + port +
", ssrc='" + ssrc + '\'' +
", platformId='" + platformId + '\'' +
", platformName='" + platformName + '\'' +
", deviceId='" + deviceId + '\'' +
", app='" + app + '\'' +
", channelId='" + channelId + '\'' +
@ -355,10 +397,11 @@ public class SendRtpItem {
", stream='" + stream + '\'' +
", tcp=" + tcp +
", tcpActive=" + tcpActive +
", localIp='" + localIp + '\'' +
", localPort=" + localPort +
", mediaServerId='" + mediaServerId + '\'' +
", serverId='" + serverId + '\'' +
", CallId='" + CallId + '\'' +
", CallId='" + callId + '\'' +
", fromTag='" + fromTag + '\'' +
", toTag='" + toTag + '\'' +
", pt=" + pt +
@ -367,6 +410,18 @@ public class SendRtpItem {
", rtcp=" + rtcp +
", playType=" + playType +
", receiveStream='" + receiveStream + '\'' +
", sessionName='" + sessionName + '\'' +
'}';
}
public String getRedisKey() {
String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX +
serverId + "_"
+ mediaServerId + "_"
+ platformId + "_"
+ channelId + "_"
+ stream + "_"
+ callId;
return key;
}
}

View File

@ -49,6 +49,7 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
ParentPlatform parentPlatform = null;
Map<String, List<ParentPlatform>> parentPlatformMap = new HashMap<>();
Map<String, DeviceChannel> channelMap = new HashMap<>();
if (!ObjectUtils.isEmpty(event.getPlatformId())) {
subscribe = subscribeHolder.getCatalogSubscribe(event.getPlatformId());
if (subscribe == null) {
@ -67,6 +68,7 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
for (DeviceChannel deviceChannel : event.getDeviceChannels()) {
List<ParentPlatform> parentPlatformsForGB = storager.queryPlatFormListForGBWithGBId(deviceChannel.getChannelId(), platforms);
parentPlatformMap.put(deviceChannel.getChannelId(), parentPlatformsForGB);
channelMap.put(deviceChannel.getChannelId(), deviceChannel);
}
}
}else if (event.getGbStreams() != null) {
@ -174,7 +176,7 @@ public class CatalogEventLister implements ApplicationListener<CatalogEvent> {
}
logger.info("[Catalog事件: {}]平台:{},影响通道{}", event.getType(), platform.getServerGBId(), gbId);
List<DeviceChannel> deviceChannelList = new ArrayList<>();
DeviceChannel deviceChannel = storager.queryChannelInParentPlatform(platform.getServerGBId(), gbId);
DeviceChannel deviceChannel = channelMap.get(gbId);
deviceChannelList.add(deviceChannel);
GbStream gbStream = storager.queryStreamInParentPlatform(platform.getServerGBId(), gbId);
if(gbStream != null){

View File

@ -104,7 +104,7 @@ public class SipRunner implements CommandLineRunner {
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(),sendRtpItem.getChannelId(), sendRtpItem.getCallId(),sendRtpItem.getStream());
if (mediaServerItem != null) {
ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
boolean stopResult = mediaServerService.stopSendRtp(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc());
boolean stopResult = mediaServerService.initStopSendRtp(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc());
if (stopResult) {
ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
if (platform != null) {

View File

@ -168,6 +168,7 @@ public class SIPRequestHeaderProvider {
// via
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
// viaHeader.setRPort();
viaHeaders.add(viaHeader);
//from
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());

View File

@ -515,9 +515,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
if (parentPlatform == null) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("[发送 移动位置订阅] {}/{}->{},{}", parentPlatform.getServerGBId(), gpsMsgInfo.getId(), gpsMsgInfo.getLng(), gpsMsgInfo.getLat());
}
logger.info("[发送 移动位置订阅] {}/{}->{},{}", parentPlatform.getServerGBId(), gpsMsgInfo.getId(), gpsMsgInfo.getLng(), gpsMsgInfo.getLat());
String characterSet = parentPlatform.getCharacterSet();
StringBuffer deviceStatusXml = new StringBuffer(600);
@ -592,6 +590,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
Integer finalIndex = index;
String catalogXmlContent = getCatalogXmlContentForCatalogAddOrUpdate(parentPlatform, channels,
deviceChannels.size(), type, subscribeInfo);
System.out.println(catalogXmlContent);
logger.info("[发送NOTIFY通知]类型: {},发送数量: {}", type, channels.size());
sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> {
logger.error("发送NOTIFY通知消息失败。错误{} {}", eventResult.statusCode, eventResult.msg);
@ -621,7 +620,6 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
private String getCatalogXmlContentForCatalogAddOrUpdate(ParentPlatform parentPlatform, List<DeviceChannel> channels, int sumNum, String type, SubscribeInfo subscribeInfo) {
StringBuffer catalogXml = new StringBuffer(600);
String characterSet = parentPlatform.getCharacterSet();
catalogXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n")
.append("<Notify>\r\n")
@ -660,6 +658,8 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
.append("<Owner> " + channel.getOwner()+ "</Owner>\r\n")
.append("<CivilCode>" + channel.getCivilCode() + "</CivilCode>\r\n")
.append("<Address>" + channel.getAddress() + "</Address>\r\n");
catalogXml.append("<Longitude>" + channel.getLongitude() + "</Longitude>\r\n");
catalogXml.append("<Latitude>" + channel.getLatitude() + "</Latitude>\r\n");
}
if (!"presence".equals(subscribeInfo.getEventType())) {
catalogXml.append("<Event>" + type + "</Event>\r\n");

View File

@ -6,7 +6,6 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.google.common.primitives.Bytes;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
@ -172,6 +171,7 @@ public abstract class SIPRequestProcessorParent {
return getRootElement(evt, "gb2312");
}
public Element getRootElement(RequestEvent evt, String charset) throws DocumentException {
if (charset == null) {
charset = "gb2312";
}

View File

@ -13,10 +13,11 @@ import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.service.IPlayService;
import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
@ -51,6 +52,8 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private IRedisRpcService redisRpcService;
@Autowired
private UserSetting userSetting;
@ -67,9 +70,6 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
@Autowired
private DynamicTask dynamicTask;
@Autowired
private RedisGbPlayMsgListener redisGbPlayMsgListener;
@Autowired
private IPlayService playService;
@ -86,7 +86,7 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
logger.info("[收到ACK] 来自->{}", fromUserId);
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
if (sendRtpItem == null) {
logger.warn("[收到ACK]:未找到来自{}目标为({})的推流信息",fromUserId, toUserId);
logger.warn("[收到ACK]:未找到来自{}callId: {}", fromUserId, callIdHeader.getCallId());
return;
}
// tcp主动时此时是级联下级平台在回复200ok时本地已经请求zlm开启监听跳过下面步骤
@ -106,10 +106,14 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
if (parentPlatform != null) {
if (!userSetting.getServerId().equals(sendRtpItem.getServerId())) {
RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(sendRtpItem);
redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, () -> {
playService.startSendRtpStreamFailHand(sendRtpItem, parentPlatform, callIdHeader);
});
WVPResult wvpResult = redisRpcService.startSendRtp(sendRtpItem.getRedisKey(), sendRtpItem);
if (wvpResult.getCode() == 0) {
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, sendRtpItem.getApp(), sendRtpItem.getStream(),
sendRtpItem.getChannelId(), parentPlatform.getServerGBId(), parentPlatform.getName(), userSetting.getServerId(),
sendRtpItem.getMediaServerId());
messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
redisCatchStorage.sendPlatformStartPlayMsg(messageForPushChannel);
}
} else {
try {
if (sendRtpItem.isTcpActive()) {

View File

@ -15,11 +15,8 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.service.*;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
import com.genersoft.iot.vmp.service.bean.RequestStopPushStreamMsg;
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import gov.nist.javax.sip.message.SIPRequest;
@ -91,7 +88,8 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
private IStreamPushService pushService;
@Autowired
private RedisGbPlayMsgListener redisGbPlayMsgListener;
private IRedisRpcService redisRpcService;
@Override
public void afterPropertiesSet() throws Exception {
@ -121,34 +119,23 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
logger.info("[收到bye] 停止推流:{}, 媒体节点: {}", streamId, sendRtpItem.getMediaServerId());
if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) {
// 查询这路流是否是本平台的
StreamPushItem push = pushService.getPush(sendRtpItem.getApp(), sendRtpItem.getStream());
if (push!= null && !push.isSelf()) {
// 不是本平台的就发送redis消息让其他wvp停止发流
ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
if (platform != null) {
RequestStopPushStreamMsg streamMsg = RequestStopPushStreamMsg.getInstance(sendRtpItem, platform.getName(), platform.getId());
redisGbPlayMsgListener.sendMsgForStopSendRtpStream(sendRtpItem.getServerId(), streamMsg);
// 不是本平台的就发送redis消息让其他wvp停止发流
ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
if (platform != null) {
redisCatchStorage.sendPlatformStopPlayMsg(sendRtpItem, platform);
if (!userSetting.getServerId().equals(sendRtpItem.getServerId())) {
redisRpcService.stopSendRtp(sendRtpItem.getRedisKey());
redisCatchStorage.deleteSendRTPServer(null, null, sendRtpItem.getCallId(), null);
}else {
MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
redisCatchStorage.deleteSendRTPServer(null, null, callIdHeader.getCallId(), null);
mediaServerService.stopSendRtp(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc());
if (userSetting.getUseCustomSsrcForParentInvite()) {
mediaServerService.releaseSsrc(mediaServer.getId(), sendRtpItem.getSsrc());
}
}
}else {
MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(),
callIdHeader.getCallId(), null);
mediaServerService.stopSendRtp(mediaInfo, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc());
if (userSetting.getUseCustomSsrcForParentInvite()) {
mediaServerService.releaseSsrc(mediaInfo.getId(), sendRtpItem.getSsrc());
}
ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
if (platform != null) {
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
sendRtpItem.getPlatformId(), platform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
messageForPushChannel.setPlatFormIndex(platform.getId());
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
}else {
logger.info("[上级平台停止观看] 未找到平台{}的信息发送redis消息失败", sendRtpItem.getPlatformId());
}
logger.info("[上级平台停止观看] 未找到平台{}的信息发送redis消息失败", sendRtpItem.getPlatformId());
}
}else {
MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
@ -188,13 +175,13 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
}
}
}
System.out.println(callIdHeader.getCallId());
// 可能是设备发送的停止
SsrcTransaction ssrcTransaction = streamSession.getSsrcTransactionByCallId(callIdHeader.getCallId());
if (ssrcTransaction == null) {
return;
}
logger.info("[收到bye] 来自设备:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
logger.info("[收到bye] 来自设备:{}, 通道: {}, 类型: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getType());
ParentPlatform platform = platformService.queryPlatformByServerGBId(ssrcTransaction.getDeviceId());
if (platform != null ) {
@ -223,13 +210,35 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
logger.info("[收到bye] 未找到通道,设备:{} 通道:{}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
return;
}
storager.stopPlay(device.getDeviceId(), channel.getChannelId());
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId());
if (inviteInfo != null) {
inviteStreamService.removeInviteInfo(inviteInfo);
if (inviteInfo.getStreamInfo() != null) {
mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStreamInfo().getStream());
}
switch (ssrcTransaction.getType()){
case PLAY:
case PLAYBACK:
case DOWNLOAD:
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId());
if (inviteInfo != null) {
storager.stopPlay(device.getDeviceId(), channel.getChannelId());
inviteStreamService.removeInviteInfo(inviteInfo);
if (inviteInfo.getStreamInfo() != null) {
mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStreamInfo().getStream());
}
}
break;
case BROADCAST:
case TALK:
// 查找来源的对讲设备发送停止
Device sourceDevice = storager.queryVideoDeviceByPlatformIdAndChannelId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(ssrcTransaction.getDeviceId(), channel.getChannelId());
if (sourceDevice != null) {
playService.stopAudioBroadcast(sourceDevice.getDeviceId(), channel.getChannelId());
}
if (audioBroadcastCatch != null) {
// 来自上级平台的停止对讲
logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", ssrcTransaction.getDeviceId(), channel.getChannelId());
audioBroadcastManager.del(ssrcTransaction.getDeviceId(), channel.getChannelId());
}
break;
}
// 释放ssrc
MediaServer mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId());
@ -237,19 +246,6 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
}
streamSession.removeByCallId(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getCallId());
if (ssrcTransaction.getType() == InviteSessionType.BROADCAST) {
// 查找来源的对讲设备发送停止
Device sourceDevice = storager.queryVideoDeviceByPlatformIdAndChannelId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
if (sourceDevice != null) {
playService.stopAudioBroadcast(sourceDevice.getDeviceId(), channel.getChannelId());
}
}
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(ssrcTransaction.getDeviceId(), channel.getChannelId());
if (audioBroadcastCatch != null) {
// 来自上级平台的停止对讲
logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", ssrcTransaction.getDeviceId(), channel.getChannelId());
audioBroadcastManager.del(ssrcTransaction.getDeviceId(), channel.getChannelId());
}
}
}
}

View File

@ -19,23 +19,23 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.event.hook.Hook;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.service.IInviteStreamService;
import com.genersoft.iot.vmp.service.IPlayService;
import com.genersoft.iot.vmp.service.IStreamProxyService;
import com.genersoft.iot.vmp.service.IStreamPushService;
import com.genersoft.iot.vmp.media.zlm.SendRtpPortManager;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
import com.genersoft.iot.vmp.service.redisMsg.RedisPushStreamResponseListener;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
@ -51,6 +51,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.sdp.*;
@ -64,6 +65,7 @@ import java.time.Instant;
import java.util.Map;
import java.util.Random;
import java.util.Vector;
import java.util.*;
/**
* SIP命令类型 INVITE请求
@ -92,7 +94,10 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
private IRedisCatchStorage redisCatchStorage;
@Autowired
private IInviteStreamService inviteStreamService;
private IRedisRpcService redisRpcService;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private SSRCFactory ssrcFactory;
@ -100,9 +105,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
@Autowired
private DynamicTask dynamicTask;
@Autowired
private RedisPushStreamResponseListener redisPushStreamResponseListener;
@Autowired
private IPlayService playService;
@ -124,19 +126,18 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
@Autowired
private UserSetting userSetting;
@Autowired
private ZLMMediaListManager mediaListManager;
@Autowired
private SipConfig config;
@Autowired
private RedisGbPlayMsgListener redisGbPlayMsgListener;
@Autowired
private VideoStreamSessionManager streamSession;
@Autowired
private SendRtpPortManager sendRtpPortManager;
@Autowired
private RedisPushStreamResponseListener redisPushStreamResponseListener;
@Override
public void afterPropertiesSet() throws Exception {
@ -334,8 +335,12 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
return;
}
String username = sdp.getOrigin().getUsername();
String addressStr = sdp.getConnection().getAddress();
String addressStr;
if(StringUtils.isEmpty(platform.getSendStreamIp())){
addressStr = sdp.getConnection().getAddress();
}else {
addressStr = platform.getSendStreamIp();
}
Device device = null;
// 通过 channel gbStream 是否为null 值判断来源是直播流合适国标
@ -484,7 +489,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
String startTimeStr = DateUtil.urlFormatter.format(start);
String endTimeStr = DateUtil.urlFormatter.format(end);
String stream = device.getDeviceId() + "_" + channelId + "_" + startTimeStr + "_" + endTimeStr;
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, stream, null, device.isSsrcCheck(), true, 0,false,!channel.isHasAudio(), false, device.getStreamModeForParam());
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, stream, null, device.isSsrcCheck(), true, 0,false,!channel.getHasAudio(), false, device.getStreamModeForParam());
sendRtpItem.setStream(stream);
// 写入redis 超时时回复
redisCatchStorage.updateSendRTPSever(sendRtpItem);
@ -514,7 +519,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
}
sendRtpItem.setPlayType(InviteStreamType.DOWNLOAD);
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false,!channel.isHasAudio(), false, device.getStreamModeForParam());
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false,!channel.getHasAudio(), false, device.getStreamModeForParam());
sendRtpItem.setStream(ssrcInfo.getStream());
// 写入redis 超时时回复
redisCatchStorage.updateSendRTPSever(sendRtpItem);
@ -552,43 +557,78 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
}
} else if (gbStream != null) {
String ssrc;
if (userSetting.getUseCustomSsrcForParentInvite() || gb28181Sdp.getSsrc() == null) {
// 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
}else {
ssrc = gb28181Sdp.getSsrc();
SendRtpItem sendRtpItem = new SendRtpItem();
if (!userSetting.getUseCustomSsrcForParentInvite() && gb28181Sdp.getSsrc() != null) {
sendRtpItem.setSsrc(gb28181Sdp.getSsrc());
}
if (tcpActive != null) {
sendRtpItem.setTcpActive(tcpActive);
}
sendRtpItem.setTcp(mediaTransmissionTCP);
sendRtpItem.setRtcp(platform.isRtcp());
sendRtpItem.setPlatformName(platform.getName());
sendRtpItem.setPlatformId(platform.getServerGBId());
sendRtpItem.setMediaServerId(mediaServerItem.getId());
sendRtpItem.setChannelId(channelId);
sendRtpItem.setIp(addressStr);
sendRtpItem.setPort(port);
sendRtpItem.setUsePs(true);
sendRtpItem.setApp(gbStream.getApp());
sendRtpItem.setStream(gbStream.getStream());
sendRtpItem.setCallId(callIdHeader.getCallId());
sendRtpItem.setFromTag(request.getFromTag());
sendRtpItem.setOnlyAudio(false);
sendRtpItem.setStatus(0);
sendRtpItem.setSessionName(sessionName);
// 清理可能存在的缓存避免用到旧的数据
List<SendRtpItem> sendRtpItemList = redisCatchStorage.querySendRTPServer(platform.getServerGBId(), channelId, gbStream.getStream());
if (!sendRtpItemList.isEmpty()) {
for (SendRtpItem rtpItem : sendRtpItemList) {
redisCatchStorage.deleteSendRTPServer(rtpItem);
}
}
if ("push".equals(gbStream.getStreamType())) {
sendRtpItem.setPlayType(InviteStreamType.PUSH);
if (streamPushItem != null) {
// 从redis查询是否正在接收这个推流
StreamPushItem pushListItem = redisCatchStorage.getPushListItem(gbStream.getApp(), gbStream.getStream());
if (pushListItem != null) {
sendRtpItem.setServerId(pushListItem.getServerId());
sendRtpItem.setMediaServerId(pushListItem.getMediaServerId());
pushListItem.setSelf(userSetting.getServerId().equals(pushListItem.getServerId()));
// 推流状态
pushStream(evt, request, gbStream, pushListItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
redisCatchStorage.updateSendRTPSever(sendRtpItem);
// 开始推流
sendPushStream(sendRtpItem, mediaServerItem, platform, request);
}else {
// 未推流 拉起
notifyStreamOnline(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
if (!platform.isStartOfflinePush()) {
// 平台设置中关闭了拉起离线的推流则直接回复
try {
logger.info("[上级点播] 失败推流设备未推流channel: {}, app: {}, stream: {}", sendRtpItem.getChannelId(), sendRtpItem.getApp(), sendRtpItem.getStream());
responseAck(request, Response.TEMPORARILY_UNAVAILABLE, "channel stream not pushing");
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite 通道未推流: {}", e.getMessage());
}
return;
}
notifyPushStreamOnline(sendRtpItem, mediaServerItem, platform, request);
}
}
} else if ("proxy".equals(gbStream.getStreamType())) {
if (null != proxyByAppAndStream) {
if (sendRtpItem.getSsrc() == null) {
// 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
String ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
sendRtpItem.setSsrc(ssrc);
}
if (proxyByAppAndStream.isStatus()) {
pushProxyStream(evt, request, gbStream, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
sendProxyStream(sendRtpItem, mediaServerItem, platform, request);
} else {
//开启代理拉流
notifyStreamOnline(evt, request, gbStream, null, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
notifyProxyStreamOnline(sendRtpItem, mediaServerItem, platform, request);
}
}
}
}
}
@ -614,58 +654,14 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
/**
* 安排推流
*/
private void pushProxyStream(RequestEvent evt, SIPRequest request, GbStream gbStream, ParentPlatform platform,
CallIdHeader callIdHeader, MediaServer mediaServer,
int port, Boolean tcpActive, boolean mediaTransmissionTCP,
String channelId, String addressStr, String ssrc, String requesterId) {
Boolean streamReady = mediaServerService.isStreamReady(mediaServer, gbStream.getApp(), gbStream.getStream());
if (streamReady != null && streamReady) {
private void sendProxyStream(SendRtpItem sendRtpItem, MediaServer mediaServerItem, ParentPlatform platform, SIPRequest request) {
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStream());
if (mediaInfo != null) {
// 自平台内容
SendRtpItem sendRtpItem = mediaServerService.createSendRtpItem(mediaServer, addressStr, port, ssrc, requesterId,
gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
if (sendRtpItem == null) {
logger.warn("服务器端口资源不足");
try {
responseAck(request, Response.BUSY_HERE);
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage());
}
return;
}
if (tcpActive != null) {
sendRtpItem.setTcpActive(tcpActive);
}
sendRtpItem.setPlayType(InviteStreamType.PUSH);
// 写入redis 超时时回复
sendRtpItem.setStatus(1);
sendRtpItem.setCallId(callIdHeader.getCallId());
sendRtpItem.setFromTag(request.getFromTag());
SIPResponse response = sendStreamAck(mediaServer, request, sendRtpItem, platform, evt);
if (response != null) {
sendRtpItem.setToTag(response.getToTag());
}
redisCatchStorage.updateSendRTPSever(sendRtpItem);
}
}
private void pushStream(RequestEvent evt, SIPRequest request, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
CallIdHeader callIdHeader, MediaServer mediaServerItem,
int port, Boolean tcpActive, boolean mediaTransmissionTCP,
String channelId, String addressStr, String ssrc, String requesterId) {
// 推流
if (streamPushItem.isSelf()) {
Boolean streamReady = mediaServerService.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
if (streamReady != null && streamReady) {
// 自平台内容
SendRtpItem sendRtpItem = mediaServerService.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp());
if (sendRtpItem == null) {
int localPort = sendRtpPortManager.getNextPort(mediaServerItem);
if (localPort == 0) {
logger.warn("服务器端口资源不足");
try {
responseAck(request, Response.BUSY_HERE);
@ -674,226 +670,196 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
}
return;
}
if (tcpActive != null) {
sendRtpItem.setTcpActive(tcpActive);
sendRtpItem.setPlayType(InviteStreamType.PROXY);
// 写入redis 超时时回复
sendRtpItem.setStatus(1);
sendRtpItem.setLocalIp(mediaServerItem.getSdpIp());
SIPResponse response = sendStreamAck(request, sendRtpItem, platform);
if (response != null) {
sendRtpItem.setToTag(response.getToTag());
}
redisCatchStorage.updateSendRTPSever(sendRtpItem);
}
}
private void sendPushStream(SendRtpItem sendRtpItem, MediaServer mediaServerItem, ParentPlatform platform, SIPRequest request) {
// 推流
if (sendRtpItem.getServerId().equals(userSetting.getServerId())) {
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStream());
if (mediaInfo != null ) {
// 自平台内容
int localPort = sendRtpPortManager.getNextPort(mediaServerItem);
if (localPort == 0) {
logger.warn("服务器端口资源不足");
try {
responseAck(request, Response.BUSY_HERE);
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage());
}
return;
}
sendRtpItem.setPlayType(InviteStreamType.PUSH);
// 写入redis 超时时回复
sendRtpItem.setStatus(1);
sendRtpItem.setCallId(callIdHeader.getCallId());
sendRtpItem.setFromTag(request.getFromTag());
SIPResponse response = sendStreamAck(mediaServerItem, request, sendRtpItem, platform, evt);
SIPResponse response = sendStreamAck(request, sendRtpItem, platform);
if (response != null) {
sendRtpItem.setToTag(response.getToTag());
}
if (sendRtpItem.getSsrc() == null) {
// 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
String ssrc = "Play".equalsIgnoreCase(sendRtpItem.getSessionName()) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
sendRtpItem.setSsrc(ssrc);
}
redisCatchStorage.updateSendRTPSever(sendRtpItem);
} else {
// 不在线 拉起
notifyStreamOnline(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
notifyPushStreamOnline(sendRtpItem, mediaServerItem, platform, request);
}
} else {
// 其他平台内容
otherWvpPushStream(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
otherWvpPushStream(sendRtpItem, request, platform);
}
}
/**
* 通知流上线
*/
private void notifyStreamOnline(RequestEvent evt, SIPRequest request, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
CallIdHeader callIdHeader, MediaServer mediaServerItem,
int port, Boolean tcpActive, boolean mediaTransmissionTCP,
String channelId, String addressStr, String ssrc, String requesterId) {
if ("proxy".equals(gbStream.getStreamType())) {
// TODO 控制启用以使设备上线
logger.info("[ app={}, stream={} ]通道未推流,启用流后开始推流", gbStream.getApp(), gbStream.getStream());
// 监听流上线
Hook hook = Hook.getInstance(HookType.on_media_arrival, gbStream.getApp(), gbStream.getStream(), mediaServerItem.getId());
this.hookSubscribe.addSubscribe(hook, (hookData) -> {
logger.info("[上级点播]拉流代理已经就绪, {}/{}", hookData.getApp(), hookData.getStream());
dynamicTask.stop(callIdHeader.getCallId());
pushProxyStream(evt, request, gbStream, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
});
dynamicTask.startDelay(callIdHeader.getCallId(), () -> {
logger.info("[ app={}, stream={} ] 等待拉流代理流超时", gbStream.getApp(), gbStream.getStream());
this.hookSubscribe.removeSubscribe(hook);
}, userSetting.getPlatformPlayTimeout());
boolean start = streamProxyService.start(gbStream.getApp(), gbStream.getStream());
if (!start) {
try {
responseAck(request, Response.BUSY_HERE, "channel [" + gbStream.getGbId() + "] offline");
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite 通道未推流: {}", e.getMessage());
}
this.hookSubscribe.removeSubscribe(hook);
dynamicTask.stop(callIdHeader.getCallId());
private void notifyProxyStreamOnline(SendRtpItem sendRtpItem, MediaServer mediaServerItem, ParentPlatform platform, SIPRequest request) {
// TODO 控制启用以使设备上线
logger.info("[ app={}, stream={} ]通道未推流,启用流后开始推流", sendRtpItem.getApp(), sendRtpItem.getStream());
// 监听流上线
Hook hook = Hook.getInstance(HookType.on_media_arrival, sendRtpItem.getApp(), sendRtpItem.getStream(), mediaServerItem.getId());
hookSubscribe.addSubscribe(hook, (hookData)->{
logger.info("[上级点播]拉流代理已经就绪, {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream());
dynamicTask.stop(sendRtpItem.getCallId());
sendProxyStream(sendRtpItem, mediaServerItem, platform, request);
});
dynamicTask.startDelay(sendRtpItem.getCallId(), () -> {
logger.info("[ app={}, stream={} ] 等待拉流代理流超时", sendRtpItem.getApp(), sendRtpItem.getStream());
hookSubscribe.removeSubscribe(hook);
}, userSetting.getPlatformPlayTimeout());
boolean start = streamProxyService.start(sendRtpItem.getApp(), sendRtpItem.getStream());
if (!start) {
try {
responseAck(request, Response.BUSY_HERE, "channel [" + sendRtpItem.getChannelId() + "] offline");
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite 通道未推流: {}", e.getMessage());
}
} else if ("push".equals(gbStream.getStreamType())) {
if (!platform.isStartOfflinePush()) {
// 平台设置中关闭了拉起离线的推流则直接回复
try {
logger.info("[上级点播] 失败推流设备未推流channel: {}, app: {}, stream: {}", gbStream.getGbId(), gbStream.getApp(), gbStream.getStream());
responseAck(request, Response.TEMPORARILY_UNAVAILABLE, "channel stream not pushing");
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] invite 通道未推流: {}", e.getMessage());
}
return;
}
// 发送redis消息以使设备上线
logger.info("[ app={}, stream={} ]通道未推流发送redis信息控制设备开始推流", gbStream.getApp(), gbStream.getStream());
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(1,
gbStream.getApp(), gbStream.getStream(), gbStream.getGbId(), gbStream.getPlatformId(),
platform.getName(), null, gbStream.getMediaServerId());
redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
// 设置超时
dynamicTask.startDelay(callIdHeader.getCallId(), () -> {
logger.info("[ app={}, stream={} ] 等待设备开始推流超时", gbStream.getApp(), gbStream.getStream());
try {
redisPushStreamResponseListener.removeEvent(gbStream.getApp(), gbStream.getStream());
mediaListManager.removedChannelOnlineEventLister(gbStream.getApp(), gbStream.getStream());
responseAck(request, Response.REQUEST_TIMEOUT); // 超时
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("未处理的异常 ", e);
}
}, userSetting.getPlatformPlayTimeout());
// 添加监听
int finalPort = port;
Boolean finalTcpActive = tcpActive;
// 添加在本机上线的通知
mediaListManager.addChannelOnlineEventLister(gbStream.getApp(), gbStream.getStream(), (app, stream, serverId) -> {
dynamicTask.stop(callIdHeader.getCallId());
redisPushStreamResponseListener.removeEvent(gbStream.getApp(), gbStream.getStream());
if (serverId.equals(userSetting.getServerId())) {
SendRtpItem sendRtpItem = mediaServerService.createSendRtpItem(mediaServerItem, addressStr, finalPort, ssrc, requesterId,
app, stream, channelId, mediaTransmissionTCP, platform.isRtcp());
if (sendRtpItem == null) {
logger.warn("上级点时创建sendRTPItem失败可能是服务器端口资源不足");
try {
responseAck(request, Response.BUSY_HERE);
} catch (SipException e) {
logger.error("未处理的异常 ", e);
} catch (InvalidArgumentException e) {
logger.error("未处理的异常 ", e);
} catch (ParseException e) {
logger.error("未处理的异常 ", e);
}
return;
}
if (finalTcpActive != null) {
sendRtpItem.setTcpActive(finalTcpActive);
}
sendRtpItem.setPlayType(InviteStreamType.PUSH);
// 写入redis 超时时回复
sendRtpItem.setStatus(1);
sendRtpItem.setCallId(callIdHeader.getCallId());
sendRtpItem.setFromTag(request.getFromTag());
SIPResponse response = sendStreamAck(mediaServerItem, request, sendRtpItem, platform, evt);
if (response != null) {
sendRtpItem.setToTag(response.getToTag());
}
redisCatchStorage.updateSendRTPSever(sendRtpItem);
} else {
// 其他平台内容
otherWvpPushStream(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
}
});
// 添加回复的拒绝或者错误的通知
redisPushStreamResponseListener.addEvent(gbStream.getApp(), gbStream.getStream(), response -> {
if (response.getCode() != 0) {
dynamicTask.stop(callIdHeader.getCallId());
mediaListManager.removedChannelOnlineEventLister(gbStream.getApp(), gbStream.getStream());
try {
responseAck(request, Response.TEMPORARILY_UNAVAILABLE, response.getMsg());
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 国标级联 点播回复: {}", e.getMessage());
}
}
});
hookSubscribe.removeSubscribe(hook);
dynamicTask.stop(sendRtpItem.getCallId());
}
}
/**
* 来自其他wvp的推流
* 通知流上线
*/
private void otherWvpPushStream(RequestEvent evt, SIPRequest request, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
CallIdHeader callIdHeader, MediaServer mediaServerItem,
int port, Boolean tcpActive, boolean mediaTransmissionTCP,
String channelId, String addressStr, String ssrc, String requesterId) {
logger.info("[级联点播]直播流来自其他平台发送redis消息");
// 发送redis消息
redisGbPlayMsgListener.sendMsg(streamPushItem.getServerId(), streamPushItem.getMediaServerId(),
streamPushItem.getApp(), streamPushItem.getStream(), addressStr, port, ssrc, requesterId,
channelId, mediaTransmissionTCP, platform.isRtcp(),platform.getName(), responseSendItemMsg -> {
SendRtpItem sendRtpItem = responseSendItemMsg.getSendRtpItem();
if (sendRtpItem == null || responseSendItemMsg.getMediaServerItem() == null) {
logger.warn("服务器端口资源不足");
try {
responseAck(request, Response.BUSY_HERE);
} catch (SipException e) {
logger.error("未处理的异常 ", e);
} catch (InvalidArgumentException e) {
logger.error("未处理的异常 ", e);
} catch (ParseException e) {
logger.error("未处理的异常 ", e);
}
return;
}
// 收到sendItem
if (tcpActive != null) {
sendRtpItem.setTcpActive(tcpActive);
}
sendRtpItem.setPlayType(InviteStreamType.PUSH);
// 写入redis 超时时回复
sendRtpItem.setStatus(1);
sendRtpItem.setCallId(callIdHeader.getCallId());
sendRtpItem.setFromTag(request.getFromTag());
SIPResponse response = sendStreamAck(responseSendItemMsg.getMediaServerItem(), request, sendRtpItem, platform, evt);
if (response != null) {
sendRtpItem.setToTag(response.getToTag());
}
redisCatchStorage.updateSendRTPSever(sendRtpItem);
}, (wvpResult) -> {
// 错误
if (wvpResult.getCode() == RedisGbPlayMsgListener.ERROR_CODE_OFFLINE) {
// 离线
// 查询是否在本机上线了
StreamPushItem currentStreamPushItem = streamPushService.getPush(streamPushItem.getApp(), streamPushItem.getStream());
if (currentStreamPushItem.isPushIng()) {
// 在线状态
pushStream(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
} else {
// 不在线 拉起
notifyStreamOnline(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive,
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
}
}
private void notifyPushStreamOnline(SendRtpItem sendRtpItem, MediaServer mediaServerItem, ParentPlatform platform, SIPRequest request) {
// 发送redis消息以使设备上线流上线后被
logger.info("[ app={}, stream={} ]通道未推流发送redis信息控制设备开始推流", sendRtpItem.getApp(), sendRtpItem.getStream());
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(1,
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(), sendRtpItem.getPlatformId(),
platform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel);
// 设置超时
dynamicTask.startDelay(sendRtpItem.getCallId(), () -> {
redisRpcService.stopWaitePushStreamOnline(sendRtpItem);
logger.info("[ app={}, stream={} ] 等待设备开始推流超时", sendRtpItem.getApp(), sendRtpItem.getStream());
try {
responseAck(request, Response.REQUEST_TIMEOUT); // 超时
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("未处理的异常 ", e);
}
}, userSetting.getPlatformPlayTimeout());
//
long key = redisRpcService.waitePushStreamOnline(sendRtpItem, (sendRtpItemKey) -> {
dynamicTask.stop(sendRtpItem.getCallId());
if (sendRtpItemKey == null) {
logger.warn("[级联点播] 等待推流得到结果未空: {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream());
try {
responseAck(request, Response.BUSY_HERE);
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("未处理的异常 ", e);
}
return;
}
SendRtpItem sendRtpItemFromRedis = (SendRtpItem)redisTemplate.opsForValue().get(sendRtpItemKey);
if (sendRtpItemFromRedis == null) {
logger.warn("[级联点播] 等待推流, 未找到redis中缓存的发流信息 {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream());
try {
responseAck(request, Response.BUSY_HERE);
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("未处理的异常 ", e);
}
return;
}
if (sendRtpItemFromRedis.getServerId().equals(userSetting.getServerId())) {
logger.info("[级联点播] 等待的推流在本平台上线 {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream());
int localPort = sendRtpPortManager.getNextPort(mediaServerItem);
if (localPort == 0) {
logger.warn("上级点时创建sendRTPItem失败可能是服务器端口资源不足");
try {
responseAck(request, Response.BUSY_HERE);
} catch (InvalidArgumentException | ParseException | SipException e) {
logger.error("[命令发送失败] 国标级联 点播回复 BUSY_HERE: {}", e.getMessage());
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("未处理的异常 ", e);
}
});
return;
}
sendRtpItem.setLocalPort(localPort);
if (!ObjectUtils.isEmpty(platform.getSendStreamIp())) {
sendRtpItem.setLocalIp(platform.getSendStreamIp());
}
// 写入redis 超时时回复
sendRtpItem.setStatus(1);
SIPResponse response = sendStreamAck(request, sendRtpItem, platform);
if (response != null) {
sendRtpItem.setToTag(response.getToTag());
}
redisCatchStorage.updateSendRTPSever(sendRtpItem);
} else {
// 其他平台内容
otherWvpPushStream(sendRtpItemFromRedis, request, platform);
}
});
// 添加回复的拒绝或者错误的通知
// redis消息例如 PUBLISH VM_MSG_STREAM_PUSH_RESPONSE '{"code":1,"msg":"失败","app":"1","stream":"2"}'
redisPushStreamResponseListener.addEvent(sendRtpItem.getApp(), sendRtpItem.getStream(), response -> {
if (response.getCode() != 0) {
dynamicTask.stop(sendRtpItem.getCallId());
redisRpcService.stopWaitePushStreamOnline(sendRtpItem);
redisRpcService.removeCallback(key);
try {
responseAck(request, Response.TEMPORARILY_UNAVAILABLE, response.getMsg());
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 国标级联 点播回复: {}", e.getMessage());
}
}
});
}
public SIPResponse sendStreamAck(MediaServer mediaServerItem, SIPRequest request, SendRtpItem sendRtpItem, ParentPlatform platform, RequestEvent evt) {
String sdpIp = mediaServerItem.getSdpIp();
/**
* 来自其他wvp的推流
*/
private void otherWvpPushStream(SendRtpItem sendRtpItem, SIPRequest request, ParentPlatform platform) {
logger.info("[级联点播] 来自其他wvp的推流 {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream());
sendRtpItem = redisRpcService.getSendRtpItem(sendRtpItem.getRedisKey());
if (sendRtpItem == null) {
return;
}
// 写入redis 超时时回复
sendRtpItem.setStatus(1);
SIPResponse response = sendStreamAck(request, sendRtpItem, platform);
if (response != null) {
sendRtpItem.setToTag(response.getToTag());
}
redisCatchStorage.updateSendRTPSever(sendRtpItem);
}
public SIPResponse sendStreamAck(SIPRequest request, SendRtpItem sendRtpItem, ParentPlatform platform) {
String sdpIp = sendRtpItem.getLocalIp();
if (!ObjectUtils.isEmpty(platform.getSendStreamIp())) {
sdpIp = platform.getSendStreamIp();
}

View File

@ -1,11 +1,11 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
import com.genersoft.iot.vmp.conf.CivilCodeFileConf;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.HandlerCatchData;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
@ -19,7 +19,9 @@ import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.sip.RequestEvent;
import javax.sip.header.FromHeader;
@ -28,6 +30,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@ -46,6 +49,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
private final Map<String, DeviceChannel> addChannelMap = new ConcurrentHashMap<>();
private final List<DeviceChannel> deleteChannelList = new CopyOnWriteArrayList<>();
private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
@Autowired
private UserSetting userSetting;
@ -62,224 +66,195 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
@Autowired
private DynamicTask dynamicTask;
@Autowired
private CivilCodeFileConf civilCodeFileConf;
@Autowired
private SipConfig sipConfig;
private final static String talkKey = "notify-request-for-catalog-task";
@Transactional
public void process(RequestEvent evt) {
try {
long start = System.currentTimeMillis();
FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader);
if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) {
logger.error("[notify-目录订阅] 待处理消息队列已满 {}返回486 BUSY_HERE消息不做处理", userSetting.getMaxNotifyCountQueue());
return;
}
taskQueue.offer(new HandlerCatchData(evt, null, null));
}
Device device = redisCatchStorage.getDevice(deviceId);
if (device == null || !device.isOnLine()) {
logger.warn("[收到目录订阅]{}, 但是设备已经离线", (device != null ? device.getDeviceId():"" ));
return;
@Scheduled(fixedRate = 400) //每400毫秒执行一次
public void executeTaskQueue(){
if (taskQueue.isEmpty()) {
return;
}
for (HandlerCatchData take : taskQueue) {
if (take == null) {
continue;
}
Element rootElement = getRootElement(evt, device.getCharset());
if (rootElement == null) {
logger.warn("[ 收到目录订阅 ] content cannot be null, {}", evt.getRequest());
return;
}
Element deviceListElement = rootElement.element("DeviceList");
if (deviceListElement == null) {
return;
}
Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
if (deviceListIterator != null) {
RequestEvent evt = take.getEvt();
try {
long start = System.currentTimeMillis();
FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader);
// 遍历DeviceList
while (deviceListIterator.hasNext()) {
Element itemDevice = deviceListIterator.next();
Element channelDeviceElement = itemDevice.element("DeviceID");
if (channelDeviceElement == null) {
continue;
}
Element eventElement = itemDevice.element("Event");
String event;
if (eventElement == null) {
logger.warn("[收到目录订阅]{}, 但是Event为空, 设为默认值 ADD", (device != null ? device.getDeviceId():"" ));
event = CatalogEvent.ADD;
}else {
event = eventElement.getText().toUpperCase();
}
DeviceChannel channel = XmlUtil.channelContentHandler(itemDevice, device, event);
if (channel == null) {
logger.info("[收到目录订阅]:但是解析失败 {}", new String(evt.getRequest().getRawContent()));
continue;
}
if (channel.getParentId() != null && channel.getParentId().equals(sipConfig.getId())) {
channel.setParentId(null);
}
channel.setDeviceId(device.getDeviceId());
logger.info("[收到目录订阅]{}/{}", device.getDeviceId(), channel.getChannelId());
switch (event) {
case CatalogEvent.ON:
// 上线
logger.info("[收到通道上线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
updateChannelOnlineList.add(channel);
if (updateChannelOnlineList.size() > 300) {
executeSaveForOnline();
}
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), true);
}
Device device = redisCatchStorage.getDevice(deviceId);
if (device == null || !device.isOnLine()) {
logger.warn("[收到目录订阅]{}, 但是设备已经离线", (device != null ? device.getDeviceId() : ""));
return;
}
Element rootElement = getRootElement(evt, device.getCharset());
if (rootElement == null) {
logger.warn("[ 收到目录订阅 ] content cannot be null, {}", evt.getRequest());
return;
}
Element deviceListElement = rootElement.element("DeviceList");
if (deviceListElement == null) {
return;
}
Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
if (deviceListIterator != null) {
break;
case CatalogEvent.OFF :
// 离线
logger.info("[收到通道离线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
logger.info("[收到通道离线通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
}else {
updateChannelOfflineList.add(channel);
if (updateChannelOfflineList.size() > 300) {
executeSaveForOffline();
}
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false);
}
}
break;
case CatalogEvent.VLOST:
// 视频丢失
logger.info("[收到通道视频丢失通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
logger.info("[收到通道视频丢失通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
}else {
updateChannelOfflineList.add(channel);
if (updateChannelOfflineList.size() > 300) {
executeSaveForOffline();
}
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false);
}
}
break;
case CatalogEvent.DEFECT:
// 故障
logger.info("[收到通道视频故障通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
logger.info("[收到通道视频故障通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
}else {
updateChannelOfflineList.add(channel);
if (updateChannelOfflineList.size() > 300) {
executeSaveForOffline();
}
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false);
}
}
break;
case CatalogEvent.ADD:
// 增加
logger.info("[收到增加通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
// 判断此通道是否存在
DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId, channel.getChannelId());
if (deviceChannel != null) {
logger.info("[增加通道] 已存在,不发送通知只更新,设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
channel.setId(deviceChannel.getId());
updateChannelMap.put(channel.getChannelId(), channel);
if (updateChannelMap.keySet().size() > 300) {
executeSaveForUpdate();
}
}else {
addChannelMap.put(channel.getChannelId(), channel);
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), channel.getChannelId(), true);
}
if (addChannelMap.keySet().size() > 300) {
executeSaveForAdd();
}
}
break;
case CatalogEvent.DEL:
// 删除
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();
}
break;
case CatalogEvent.UPDATE:
// 更新
logger.info("[收到更新通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
// 判断此通道是否存在
DeviceChannel deviceChannelForUpdate = deviceChannelService.getOne(deviceId, channel.getChannelId());
if (deviceChannelForUpdate != null) {
channel.setId(deviceChannelForUpdate.getId());
channel.setUpdateTime(DateUtil.getNow());
updateChannelMap.put(channel.getChannelId(), channel);
if (updateChannelMap.keySet().size() > 300) {
executeSaveForUpdate();
}
}else {
addChannelMap.put(channel.getChannelId(), channel);
if (addChannelMap.keySet().size() > 300) {
executeSaveForAdd();
}
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), channel.getChannelId(), true);
}
}
break;
default:
logger.warn("[ NotifyCatalog ] event not found {}", event );
}
// 转发变化信息
eventPublisher.catalogEventPublish(null, channel, event);
if (!updateChannelMap.keySet().isEmpty()
|| !addChannelMap.keySet().isEmpty()
|| !updateChannelOnlineList.isEmpty()
|| !updateChannelOfflineList.isEmpty()
|| !deleteChannelList.isEmpty()) {
if (!dynamicTask.contains(talkKey)) {
dynamicTask.startDelay(talkKey, this::executeSave, 1000);
// 遍历DeviceList
while (deviceListIterator.hasNext()) {
Element itemDevice = deviceListIterator.next();
Element eventElement = itemDevice.element("Event");
String event;
if (eventElement == null) {
logger.warn("[收到目录订阅]{}, 但是Event为空, 设为默认值 ADD", (device != null ? device.getDeviceId() : ""));
event = CatalogEvent.ADD;
} else {
event = eventElement.getText().toUpperCase();
}
DeviceChannel channel = XmlUtil.channelContentHandler(itemDevice, device, event);
if (channel == null) {
logger.info("[收到目录订阅]:但是解析失败 {}", new String(evt.getRequest().getRawContent()));
continue;
}
if (channel.getParentId() != null && channel.getParentId().equals(sipConfig.getId())) {
channel.setParentId(null);
}
channel.setDeviceId(device.getDeviceId());
logger.info("[收到目录订阅]{}/{}", device.getDeviceId(), channel.getChannelId());
switch (event) {
case CatalogEvent.ON:
// 上线
logger.info("[收到通道上线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
updateChannelOnlineList.add(channel);
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), true);
}
break;
case CatalogEvent.OFF:
// 离线
logger.info("[收到通道离线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
logger.info("[收到通道离线通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
} else {
updateChannelOfflineList.add(channel);
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false);
}
}
break;
case CatalogEvent.VLOST:
// 视频丢失
logger.info("[收到通道视频丢失通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
logger.info("[收到通道视频丢失通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
} else {
updateChannelOfflineList.add(channel);
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false);
}
}
break;
case CatalogEvent.DEFECT:
// 故障
logger.info("[收到通道视频故障通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
if (userSetting.getRefuseChannelStatusChannelFormNotify()) {
logger.info("[收到通道视频故障通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
} else {
updateChannelOfflineList.add(channel);
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false);
}
}
break;
case CatalogEvent.ADD:
// 增加
logger.info("[收到增加通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
// 判断此通道是否存在
DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId, channel.getChannelId());
if (deviceChannel != null) {
logger.info("[增加通道] 已存在,不发送通知只更新,设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
channel.setId(deviceChannel.getId());
channel.setHasAudio(null);
updateChannelMap.put(channel.getChannelId(), channel);
} else {
addChannelMap.put(channel.getChannelId(), channel);
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), channel.getChannelId(), true);
}
}
break;
case CatalogEvent.DEL:
// 删除
logger.info("[收到删除通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
deleteChannelList.add(channel);
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), channel.getChannelId(), false);
}
break;
case CatalogEvent.UPDATE:
// 更新
logger.info("[收到更新通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId());
// 判断此通道是否存在
DeviceChannel deviceChannelForUpdate = deviceChannelService.getOne(deviceId, channel.getChannelId());
if (deviceChannelForUpdate != null) {
channel.setId(deviceChannelForUpdate.getId());
channel.setUpdateTime(DateUtil.getNow());
channel.setHasAudio(null);
updateChannelMap.put(channel.getChannelId(), channel);
} else {
addChannelMap.put(channel.getChannelId(), channel);
if (userSetting.getDeviceStatusNotify()) {
// 发送redis消息
redisCatchStorage.sendChannelAddOrDelete(device.getDeviceId(), channel.getChannelId(), true);
}
}
break;
default:
logger.warn("[ NotifyCatalog ] event not found {}", event);
}
// 转发变化信息
eventPublisher.catalogEventPublish(null, channel, event);
}
}
} catch (DocumentException e) {
logger.error("未处理的异常 ", e);
}
} catch (DocumentException e) {
logger.error("未处理的异常 ", e);
}
taskQueue.clear();
if (!updateChannelMap.keySet().isEmpty()
|| !addChannelMap.keySet().isEmpty()
|| !updateChannelOnlineList.isEmpty()
|| !updateChannelOfflineList.isEmpty()
|| !deleteChannelList.isEmpty()) {
executeSave();
}
}
private void executeSave(){
public void executeSave(){
try {
executeSaveForAdd();
} catch (Exception e) {
logger.error("[存储收到的增加通道] 异常: ", e );
}
try {
executeSaveForUpdate();
} catch (Exception e) {
logger.error("[存储收到的更新通道] 异常: ", e );
}
try {
executeSaveForDelete();
} catch (Exception e) {
logger.error("[存储收到的删除通道] 异常: ", e );
}
try {
executeSaveForOnline();
} catch (Exception e) {
@ -290,16 +265,25 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
} catch (Exception e) {
logger.error("[存储收到的通道离线] 异常: ", e );
}
dynamicTask.stop(talkKey);
try {
executeSaveForUpdate();
} catch (Exception e) {
logger.error("[存储收到的更新通道] 异常: ", e );
}
try {
executeSaveForDelete();
} catch (Exception e) {
logger.error("[存储收到的删除通道] 异常: ", e );
}
}
private void executeSaveForUpdate(){
if (!updateChannelMap.values().isEmpty()) {
logger.info("[存储收到的更新通道], 数量: {}", updateChannelMap.size());
ArrayList<DeviceChannel> deviceChannels = new ArrayList<>(updateChannelMap.values());
updateChannelMap.clear();
deviceChannelService.batchUpdateChannel(deviceChannels);
updateChannelMap.clear();
}
}
private void executeSaveForAdd(){
@ -331,4 +315,8 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent
}
}
@Scheduled(fixedRate = 10000) //每1秒执行一次
public void execute(){
logger.info("[待处理Notify-目录订阅消息数量]: {}", taskQueue.size());
}
}

View File

@ -0,0 +1,195 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.HandlerCatchData;
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
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.service.IMobilePositionService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import javax.sip.RequestEvent;
import javax.sip.header.FromHeader;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* SIP命令类型 NOTIFY请求中的移动位置请求处理
*/
@Component
public class NotifyRequestForMobilePositionProcessor extends SIPRequestProcessorParent {
private final static Logger logger = LoggerFactory.getLogger(NotifyRequestForMobilePositionProcessor.class);
private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
@Autowired
private UserSetting userSetting;
@Autowired
private EventPublisher eventPublisher;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private IDeviceChannelService deviceChannelService;
@Autowired
private IMobilePositionService mobilePositionService;
public void process(RequestEvent evt) {
if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) {
logger.error("[notify-移动位置] 待处理消息队列已满 {}返回486 BUSY_HERE消息不做处理", userSetting.getMaxNotifyCountQueue());
return;
}
taskQueue.offer(new HandlerCatchData(evt, null, null));
}
@Scheduled(fixedRate = 200) //每200毫秒执行一次
public void executeTaskQueue() {
if (taskQueue.isEmpty()) {
return;
}
for (HandlerCatchData take : taskQueue) {
if (take == null) {
continue;
}
RequestEvent evt = take.getEvt();
try {
FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader);
long startTime = System.currentTimeMillis();
// 回复 200 OK
Element rootElement = getRootElement(evt);
if (rootElement == null) {
logger.error("处理MobilePosition移动位置Notify时未获取到消息体,{}", evt.getRequest());
return;
}
Device device = redisCatchStorage.getDevice(deviceId);
if (device == null) {
logger.error("处理MobilePosition移动位置Notify时未获取到device,{}", deviceId);
return;
}
MobilePosition mobilePosition = new MobilePosition();
mobilePosition.setDeviceId(device.getDeviceId());
mobilePosition.setDeviceName(device.getName());
mobilePosition.setCreateTime(DateUtil.getNow());
List<Element> elements = rootElement.elements();
for (Element element : elements) {
switch (element.getName()){
case "DeviceID":
String channelId = element.getStringValue();
if (!deviceId.equals(channelId)) {
mobilePosition.setChannelId(channelId);
}
continue;
case "Time":
String timeVal = element.getStringValue();
if (ObjectUtils.isEmpty(timeVal)) {
mobilePosition.setTime(DateUtil.getNow());
} else {
mobilePosition.setTime(SipUtils.parseTime(timeVal));
}
continue;
case "Longitude":
mobilePosition.setLongitude(Double.parseDouble(element.getStringValue()));
continue;
case "Latitude":
mobilePosition.setLatitude(Double.parseDouble(element.getStringValue()));
continue;
case "Speed":
String speedVal = element.getStringValue();
if (NumericUtil.isDouble(speedVal)) {
mobilePosition.setSpeed(Double.parseDouble(speedVal));
} else {
mobilePosition.setSpeed(0.0);
}
continue;
case "Direction":
String directionVal = element.getStringValue();
if (NumericUtil.isDouble(directionVal)) {
mobilePosition.setDirection(Double.parseDouble(directionVal));
} else {
mobilePosition.setDirection(0.0);
}
continue;
case "Altitude":
String altitudeVal = element.getStringValue();
if (NumericUtil.isDouble(altitudeVal)) {
mobilePosition.setAltitude(Double.parseDouble(altitudeVal));
} else {
mobilePosition.setAltitude(0.0);
}
continue;
}
}
logger.debug("[收到移动位置订阅通知]{}/{}->{}.{}, 时间: {}", mobilePosition.getDeviceId(), mobilePosition.getChannelId(),
mobilePosition.getLongitude(), mobilePosition.getLatitude(), System.currentTimeMillis() - startTime);
mobilePosition.setReportSource("Mobile Position");
mobilePositionService.add(mobilePosition);
// 向关联了该通道并且开启移动位置订阅的上级平台发送移动位置订阅消息
try {
eventPublisher.mobilePositionEventPublish(mobilePosition);
}catch (Exception e) {
logger.error("[向上级转发移动位置失败] ", e);
}
if (mobilePosition.getChannelId() == null || mobilePosition.getChannelId().equals(mobilePosition.getDeviceId())) {
List<DeviceChannel> channels = deviceChannelService.queryChaneListByDeviceId(mobilePosition.getDeviceId());
channels.forEach(channel -> {
// 发送redis消息 通知位置信息的变化
JSONObject jsonObject = new JSONObject();
jsonObject.put("time", DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(mobilePosition.getTime()));
jsonObject.put("serial", channel.getDeviceId());
jsonObject.put("code", channel.getChannelId());
jsonObject.put("longitude", mobilePosition.getLongitude());
jsonObject.put("latitude", mobilePosition.getLatitude());
jsonObject.put("altitude", mobilePosition.getAltitude());
jsonObject.put("direction", mobilePosition.getDirection());
jsonObject.put("speed", mobilePosition.getSpeed());
redisCatchStorage.sendMobilePositionMsg(jsonObject);
});
}else {
// 发送redis消息 通知位置信息的变化
JSONObject jsonObject = new JSONObject();
jsonObject.put("time", DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(mobilePosition.getTime()));
jsonObject.put("serial", mobilePosition.getDeviceId());
jsonObject.put("code", mobilePosition.getChannelId());
jsonObject.put("longitude", mobilePosition.getLongitude());
jsonObject.put("latitude", mobilePosition.getLatitude());
jsonObject.put("altitude", mobilePosition.getAltitude());
jsonObject.put("direction", mobilePosition.getDirection());
jsonObject.put("speed", mobilePosition.getSpeed());
redisCatchStorage.sendMobilePositionMsg(jsonObject);
}
} catch (DocumentException e) {
logger.error("未处理的异常 ", e);
}
}
taskQueue.clear();
}
@Scheduled(fixedRate = 10000)
public void execute(){
logger.info("[待处理Notify-移动位置订阅消息数量]: {}", taskQueue.size());
}
}

View File

@ -1,12 +1,9 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
@ -14,9 +11,7 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
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.redis.RedisUtil;
import gov.nist.javax.sip.message.SIPRequest;
import org.dom4j.DocumentException;
import org.dom4j.Element;
@ -24,10 +19,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
@ -35,8 +27,6 @@ import javax.sip.SipException;
import javax.sip.header.FromHeader;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* SIP命令类型 NOTIFY请求,这是作为上级发送订阅请求后设备才会响应的
@ -47,15 +37,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
private final static Logger logger = LoggerFactory.getLogger(NotifyRequestProcessor.class);
@Autowired
private UserSetting userSetting;
@Autowired
private IVideoManagerStorage storager;
@Autowired
private EventPublisher eventPublisher;
@Autowired
private SipConfig sipConfig;
@ -76,13 +57,8 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
@Autowired
private NotifyRequestForCatalogProcessor notifyRequestForCatalogProcessor;
private ConcurrentLinkedQueue<HandlerCatchData> taskQueue = new ConcurrentLinkedQueue<>();
@Qualifier("taskExecutor")
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
private int maxQueueCount = 30000;
private NotifyRequestForMobilePositionProcessor notifyRequestForMobilePositionProcessor;
@Override
public void afterPropertiesSet() throws Exception {
@ -93,152 +69,33 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
@Override
public void process(RequestEvent evt) {
try {
if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) {
responseAck((SIPRequest) evt.getRequest(), Response.BUSY_HERE, null, null);
logger.error("[notify] 待处理消息队列已满 {}返回486 BUSY_HERE消息不做处理", userSetting.getMaxNotifyCountQueue());
return;
}else {
responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null);
}
}catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("未处理的异常 ", e);
}
boolean runed = !taskQueue.isEmpty();
logger.info("[notify] 待处理消息数量: {}", taskQueue.size());
taskQueue.offer(new HandlerCatchData(evt, null, null));
if (!runed) {
taskExecutor.execute(()-> {
while (!taskQueue.isEmpty()) {
try {
HandlerCatchData take = taskQueue.poll();
if (take == null) {
continue;
}
Element rootElement = getRootElement(take.getEvt());
if (rootElement == null) {
logger.error("处理NOTIFY消息时未获取到消息体,{}", take.getEvt().getRequest());
continue;
}
String cmd = XmlUtil.getText(rootElement, "CmdType");
if (CmdType.CATALOG.equals(cmd)) {
logger.info("接收到Catalog通知");
notifyRequestForCatalogProcessor.process(take.getEvt());
} else if (CmdType.ALARM.equals(cmd)) {
logger.info("接收到Alarm通知");
processNotifyAlarm(take.getEvt());
} else if (CmdType.MOBILE_POSITION.equals(cmd)) {
logger.info("接收到MobilePosition通知");
processNotifyMobilePosition(take.getEvt());
} else {
logger.info("接收到消息:" + cmd);
}
} catch (DocumentException e) {
logger.error("处理NOTIFY消息时错误", e);
}
}
});
}
}
/**
* 处理MobilePosition移动位置Notify
*
* @param evt
*/
private void processNotifyMobilePosition(RequestEvent evt) {
try {
FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME);
String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader);
// 回复 200 OK
responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null);
Element rootElement = getRootElement(evt);
if (rootElement == null) {
logger.error("处理MobilePosition移动位置Notify时未获取到消息体,{}", evt.getRequest());
logger.error("处理NOTIFY消息时未获取到消息体,{}", evt.getRequest());
responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null);
return;
}
String cmd = XmlUtil.getText(rootElement, "CmdType");
MobilePosition mobilePosition = new MobilePosition();
mobilePosition.setCreateTime(DateUtil.getNow());
Element deviceIdElement = rootElement.element("DeviceID");
String channelId = deviceIdElement.getTextTrim().toString();
Device device = redisCatchStorage.getDevice(deviceId);
if (device == null) {
device = redisCatchStorage.getDevice(channelId);
if (device == null) {
// 根据通道id查询设备Id
List<Device> deviceList = deviceChannelService.getDeviceByChannelId(channelId);
if (deviceList.size() > 0) {
device = deviceList.get(0);
}
}
}
if (device == null) {
logger.warn("[mobilePosition移动位置Notify] 未找到通道{}所属的设备", channelId);
return;
}
if (!ObjectUtils.isEmpty(device.getName())) {
mobilePosition.setDeviceName(device.getName());
}
mobilePosition.setDeviceId(device.getDeviceId());
mobilePosition.setChannelId(channelId);
String time = XmlUtil.getText(rootElement, "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"))) {
mobilePosition.setSpeed(Double.parseDouble(XmlUtil.getText(rootElement, "Speed")));
if (CmdType.CATALOG.equals(cmd)) {
notifyRequestForCatalogProcessor.process(evt);
} else if (CmdType.ALARM.equals(cmd)) {
processNotifyAlarm(evt);
} else if (CmdType.MOBILE_POSITION.equals(cmd)) {
notifyRequestForMobilePositionProcessor.process(evt);
} else {
mobilePosition.setSpeed(0.0);
logger.info("接收到消息:" + cmd);
}
if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Direction"))) {
mobilePosition.setDirection(Double.parseDouble(XmlUtil.getText(rootElement, "Direction")));
} else {
mobilePosition.setDirection(0.0);
}
if (NumericUtil.isDouble(XmlUtil.getText(rootElement, "Altitude"))) {
mobilePosition.setAltitude(Double.parseDouble(XmlUtil.getText(rootElement, "Altitude")));
} else {
mobilePosition.setAltitude(0.0);
}
logger.info("[收到移动位置订阅通知]{}/{}->{}.{}", mobilePosition.getDeviceId(), mobilePosition.getChannelId(),
mobilePosition.getLongitude(), mobilePosition.getLatitude());
mobilePosition.setReportSource("Mobile Position");
// 更新device channel 的经纬度
DeviceChannel deviceChannel = new DeviceChannel();
deviceChannel.setDeviceId(device.getDeviceId());
deviceChannel.setChannelId(channelId);
deviceChannel.setLongitude(mobilePosition.getLongitude());
deviceChannel.setLatitude(mobilePosition.getLatitude());
deviceChannel.setGpsTime(mobilePosition.getTime());
deviceChannel = deviceChannelService.updateGps(deviceChannel, device);
mobilePosition.setLongitudeWgs84(deviceChannel.getLongitudeWgs84());
mobilePosition.setLatitudeWgs84(deviceChannel.getLatitudeWgs84());
mobilePosition.setLongitudeGcj02(deviceChannel.getLongitudeGcj02());
mobilePosition.setLatitudeGcj02(deviceChannel.getLatitudeGcj02());
deviceChannelService.updateChannelGPS(device, deviceChannel, mobilePosition);
} catch (DocumentException e) {
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("未处理的异常 ", e);
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
}
/***
* 处理alarm设备报警Notify
*
* @param evt
*/
private void processNotifyAlarm(RequestEvent evt) {
if (!sipConfig.isAlarm()) {
@ -329,28 +186,5 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
}
}
public void setCmder(SIPCommander cmder) {
}
public void setStorager(IVideoManagerStorage storager) {
this.storager = storager;
}
public void setPublisher(EventPublisher publisher) {
this.publisher = publisher;
}
public void setRedis(RedisUtil redis) {
}
public void setDeferredResultHolder(DeferredResultHolder deferredResultHolder) {
}
public IRedisCatchStorage getRedisCatchStorage() {
return redisCatchStorage;
}
public void setRedisCatchStorage(IRedisCatchStorage redisCatchStorage) {
this.redisCatchStorage = redisCatchStorage;
}
}

View File

@ -532,16 +532,17 @@ public class XmlUtil {
String status = getText(itemDevice, "Status");
if (status != null) {
// ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理
if (status.equals("ON") || status.equals("On") || status.equals("ONLINE") || status.equals("OK")) {
if (status.equalsIgnoreCase("ON") || status.equalsIgnoreCase("On") || status.equalsIgnoreCase("ONLINE") || status.equalsIgnoreCase("OK")) {
deviceChannel.setStatus(true);
}
if (status.equals("OFF") || status.equals("Off") || status.equals("OFFLINE")) {
if (status.equalsIgnoreCase("OFF") || status.equalsIgnoreCase("Off") || status.equalsIgnoreCase("OFFLINE")) {
deviceChannel.setStatus(false);
}
}else {
deviceChannel.setStatus(true);
}
// logger.info("状态字符串: {}", status);
// logger.info("状态结果: {}", deviceChannel.isStatus());
// 经度
String longitude = getText(itemDevice, "Longitude");
if (NumericUtil.isDouble(longitude)) {

View File

@ -101,6 +101,9 @@ public class MediaServer {
@Schema(description = "类型: zlm/abl")
private String type;
@Schema(description = "转码的前缀")
private String transcodeSuffix;
public MediaServer() {
}
@ -127,6 +130,7 @@ public class MediaServer {
rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口
rtpPortRange = zlmServerConfig.getPortRange().replace("_",","); // 默认使用30000,30500作为级联时发送流的端口号
recordAssistPort = 0; // 默认关闭
transcodeSuffix = zlmServerConfig.getTranscodeSuffix();
}
@ -403,4 +407,12 @@ public class MediaServer {
public void setWsFlvSSLPort(int wsFlvSSLPort) {
this.wsFlvSSLPort = wsFlvSSLPort;
}
public String getTranscodeSuffix() {
return transcodeSuffix;
}
public void setTranscodeSuffix(String transcodeSuffix) {
this.transcodeSuffix = transcodeSuffix;
}
}

View File

@ -11,6 +11,7 @@ public class RecordInfo {
private String url;
private long startTime;
private double timeLen;
private String params;
public static RecordInfo getInstance(OnRecordMp4HookParam hookParam) {
RecordInfo recordInfo = new RecordInfo();
@ -86,6 +87,14 @@ public class RecordInfo {
this.timeLen = timeLen;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
@Override
public String toString() {
return "RecordInfo{" +
@ -94,6 +103,7 @@ public class RecordInfo {
", 文件大小=" + fileSize +
", 开始时间=" + startTime +
", 时长=" + timeLen +
", params=" + params +
'}';
}
}

View File

@ -29,6 +29,8 @@ public interface IMediaNodeServerService {
boolean stopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc);
boolean initStopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc);
boolean deleteRecordDirectory(MediaServer mediaServer, String app, String stream, String date, String fileName);
List<StreamInfo> getMediaList(MediaServer mediaServer, String app, String stream, String callId);

View File

@ -76,6 +76,8 @@ public interface IMediaServerService {
boolean stopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc);
boolean initStopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc);
boolean deleteRecordDirectory(MediaServer mediaServerItem, String app, String stream, String date, String fileName);
List<StreamInfo> getMediaList(MediaServer mediaInfo, String app, String stream, String callId);
@ -147,4 +149,6 @@ public interface IMediaServerService {
SendRtpItem createSendRtpItem(MediaServer serverItem, String ip, int port, String ssrc, String platformId,
String app, String stream, String channelId, boolean tcp, boolean rtcp);
MediaServer getMediaServerByAppAndStream(String app, String stream);
}

View File

@ -115,6 +115,9 @@ public class MediaServerServiceImpl implements IMediaServerService {
removeCount(event.getMediaServer().getId());
MediaInfo mediaInfo = redisCatchStorage.getStreamInfo(
event.getApp(), event.getStream(), event.getMediaServer().getId());
if (mediaInfo == null) {
return;
}
String type = OriginType.values()[mediaInfo.getOriginType()].getType();
redisCatchStorage.removeStream(mediaInfo.getMediaServer().getId(), type, event.getApp(), event.getStream());
}
@ -399,6 +402,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
logger.info("[添加媒体节点] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
return;
}
mediaServerMapper.add(mediaServer);
if (mediaServer.isStatus()) {
mediaNodeServerService.online(mediaServer);
@ -591,6 +595,16 @@ public class MediaServerServiceImpl implements IMediaServerService {
return mediaNodeServerService.stopSendRtp(mediaInfo, app, stream, ssrc);
}
@Override
public boolean initStopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaInfo.getType());
if (mediaNodeServerService == null) {
logger.info("[stopSendRtp] 失败, mediaServer的类型 {},未找到对应的实现类", mediaInfo.getType());
return false;
}
return mediaNodeServerService.initStopSendRtp(mediaInfo, app, stream, ssrc);
}
@Override
public boolean deleteRecordDirectory(MediaServer mediaServer, String app, String stream, String date, String fileName) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
@ -895,4 +909,16 @@ public class MediaServerServiceImpl implements IMediaServerService {
sendRtpItem.setRtcp(rtcp);
return sendRtpItem;
}
@Override
public MediaServer getMediaServerByAppAndStream(String app, String stream) {
List<MediaServer> mediaServerList = getAll();
for (MediaServer mediaServer : mediaServerList) {
MediaInfo mediaInfo = getMediaInfo(mediaServer, app, stream);
if (mediaInfo != null) {
return mediaServer;
}
}
return null;
}
}

View File

@ -104,7 +104,6 @@ public class SendRtpPortManager {
return port;
}
}
}
interface CheckPortCallback{

View File

@ -10,19 +10,21 @@ import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.bean.ResultForOnPublish;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.event.media.*;
import com.genersoft.iot.vmp.media.event.mediaServer.MediaSendRtpStoppedEvent;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.zlm.dto.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.dto.hook.*;
import com.genersoft.iot.vmp.media.zlm.event.HookZlmServerKeepaliveEvent;
import com.genersoft.iot.vmp.media.zlm.event.HookZlmServerStartEvent;
import com.genersoft.iot.vmp.service.*;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.MediaServerUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -34,7 +36,6 @@ import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
@ -66,6 +67,10 @@ public class ZLMHttpHookListener {
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private IRedisRpcService redisRpcService;
@Autowired
private IInviteStreamService inviteStreamService;
@ -87,9 +92,6 @@ public class ZLMHttpHookListener {
@Autowired
private EventPublisher eventPublisher;
@Autowired
private ZLMMediaListManager zlmMediaListManager;
@Autowired
private HookSubscribe subscribe;
@ -118,6 +120,9 @@ public class ZLMHttpHookListener {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Autowired
private IStreamPushService streamPushService;
/**
* 服务器定时上报时间上报间隔可配置默认10s上报一次
*/
@ -144,7 +149,7 @@ public class ZLMHttpHookListener {
@PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8")
public HookResult onPlay(@RequestBody OnPlayHookParam param) {
Map<String, String> paramMap = urlParamToMap(param.getParams());
Map<String, String> paramMap = MediaServerUtils.urlParamToMap(param.getParams());
// 对于播放流进行鉴权
boolean authenticateResult = mediaService.authenticatePlay(param.getApp(), param.getStream(), paramMap.get("callId"));
if (!authenticateResult) {
@ -197,16 +202,23 @@ public class ZLMHttpHookListener {
if (mediaServer == null) {
return HookResult.SUCCESS();
}
if (param.isRegist()) {
logger.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
MediaArrivalEvent mediaArrivalEvent = MediaArrivalEvent.getInstance(this, param, mediaServer);
applicationEventPublisher.publishEvent(mediaArrivalEvent);
} else {
logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
MediaDepartureEvent mediaDepartureEvent = MediaDepartureEvent.getInstance(this, param, mediaServer);
applicationEventPublisher.publishEvent(mediaDepartureEvent);
if (!ObjectUtils.isEmpty(mediaServer.getTranscodeSuffix())
&& !"null".equalsIgnoreCase(mediaServer.getTranscodeSuffix())
&& param.getStream().endsWith(mediaServer.getTranscodeSuffix()) ) {
return HookResult.SUCCESS();
}
if (param.getSchema().equalsIgnoreCase("rtsp")) {
if (param.isRegist()) {
logger.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
MediaArrivalEvent mediaArrivalEvent = MediaArrivalEvent.getInstance(this, param, mediaServer);
applicationEventPublisher.publishEvent(mediaArrivalEvent);
} else {
logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
MediaDepartureEvent mediaDepartureEvent = MediaDepartureEvent.getInstance(this, param, mediaServer);
applicationEventPublisher.publishEvent(mediaDepartureEvent);
}
}
return HookResult.SUCCESS();
}
@ -219,10 +231,23 @@ public class ZLMHttpHookListener {
logger.info("[ZLM HOOK]流无人观看:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(),
param.getApp(), param.getStream());
JSONObject ret = new JSONObject();
MediaServer mediaInfo = mediaServerService.getOne(param.getMediaServerId());
if (mediaInfo == null) {
JSONObject ret = new JSONObject();
ret.put("code", 0);
return ret;
}
if (!ObjectUtils.isEmpty(mediaInfo.getTranscodeSuffix())
&& !"null".equalsIgnoreCase(mediaInfo.getTranscodeSuffix())
&& param.getStream().endsWith(mediaInfo.getTranscodeSuffix()) ) {
param.setStream(param.getStream().substring(0, param.getStream().lastIndexOf(mediaInfo.getTranscodeSuffix()) -1 ));
}
JSONObject ret = new JSONObject();
boolean close = mediaService.closeStreamOnNoneReader(param.getMediaServerId(), param.getApp(), param.getStream(), param.getSchema());
ret.put("code", close);
ret.put("code", 0);
ret.put("close", close);
return ret;
}
@ -341,22 +366,4 @@ public class ZLMHttpHookListener {
return HookResult.SUCCESS();
}
private Map<String, String> urlParamToMap(String params) {
HashMap<String, String> map = new HashMap<>();
if (ObjectUtils.isEmpty(params)) {
return map;
}
String[] paramsArray = params.split("&");
if (paramsArray.length == 0) {
return map;
}
for (String param : paramsArray) {
String[] paramArray = param.split("=");
if (paramArray.length == 2) {
map.put(paramArray[0], paramArray[1]);
}
}
return map;
}
}

View File

@ -1,121 +0,0 @@
package com.genersoft.iot.vmp.media.zlm;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.dto.ChannelOnlineEvent;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
import com.genersoft.iot.vmp.service.IStreamPushService;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
import com.genersoft.iot.vmp.storager.dao.StreamPushMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author lin
*/
@Component
public class ZLMMediaListManager {
private Logger logger = LoggerFactory.getLogger("ZLMMediaListManager");
@Autowired
private IVideoManagerStorage storager;
@Autowired
private GbStreamMapper gbStreamMapper;
@Autowired
private IStreamPushService streamPushService;
@Autowired
private StreamPushMapper streamPushMapper;
@Autowired
private UserSetting userSetting;
@Autowired
private IMediaServerService mediaServerService;
private Map<String, ChannelOnlineEvent> channelOnPublishEvents = new ConcurrentHashMap<>();
public StreamPushItem addPush(OnStreamChangedHookParam onStreamChangedHookParam) {
StreamPushItem transform = streamPushService.transform(onStreamChangedHookParam);
StreamPushItem pushInDb = streamPushService.getPush(onStreamChangedHookParam.getApp(), onStreamChangedHookParam.getStream());
transform.setPushIng(onStreamChangedHookParam.isRegist());
transform.setUpdateTime(DateUtil.getNow());
transform.setPushTime(DateUtil.getNow());
transform.setSelf(userSetting.getServerId().equals(onStreamChangedHookParam.getSeverId()));
if (pushInDb == null) {
transform.setCreateTime(DateUtil.getNow());
streamPushMapper.add(transform);
}else {
streamPushMapper.update(transform);
gbStreamMapper.updateMediaServer(onStreamChangedHookParam.getApp(), onStreamChangedHookParam.getStream(), onStreamChangedHookParam.getMediaServerId());
}
ChannelOnlineEvent channelOnlineEventLister = getChannelOnlineEventLister(transform.getApp(), transform.getStream());
if ( channelOnlineEventLister != null) {
try {
channelOnlineEventLister.run(transform.getApp(), transform.getStream(), transform.getServerId());;
} catch (ParseException e) {
logger.error("addPush: ", e);
}
removedChannelOnlineEventLister(transform.getApp(), transform.getStream());
}
return transform;
}
public void sendStreamEvent(String app, String stream, String mediaServerId) {
MediaServer mediaServerItem = mediaServerService.getOne(mediaServerId);
// 查看推流状态
Boolean streamReady = mediaServerService.isStreamReady(mediaServerItem, app, stream);
if (streamReady != null && streamReady) {
ChannelOnlineEvent channelOnlineEventLister = getChannelOnlineEventLister(app, stream);
if (channelOnlineEventLister != null) {
try {
channelOnlineEventLister.run(app, stream, mediaServerId);
} catch (ParseException e) {
logger.error("sendStreamEvent: ", e);
}
removedChannelOnlineEventLister(app, stream);
}
}
}
public int removeMedia(String app, String streamId) {
// 查找是否关联了国标 关联了不删除 置为离线
GbStream gbStream = gbStreamMapper.selectOne(app, streamId);
int result;
if (gbStream == null) {
result = storager.removeMedia(app, streamId);
}else {
result =storager.mediaOffline(app, streamId);
}
return result;
}
public void addChannelOnlineEventLister(String app, String stream, ChannelOnlineEvent callback) {
this.channelOnPublishEvents.put(app + "_" + stream, callback);
}
public void removedChannelOnlineEventLister(String app, String stream) {
this.channelOnPublishEvents.remove(app + "_" + stream);
}
public ChannelOnlineEvent getChannelOnlineEventLister(String app, String stream) {
return this.channelOnPublishEvents.get(app + "_" + stream);
}
}

View File

@ -94,6 +94,8 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
MediaServer mediaServer = new MediaServer();
mediaServer.setIp(ip);
mediaServer.setHttpPort(port);
mediaServer.setFlvPort(port);
mediaServer.setWsFlvPort(port);
mediaServer.setSecret(secret);
JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServer);
if (responseJSON == null) {
@ -109,12 +111,15 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
}
mediaServer.setId(zlmServerConfig.getGeneralMediaServerId());
mediaServer.setHttpSSlPort(zlmServerConfig.getHttpPort());
mediaServer.setFlvSSLPort(zlmServerConfig.getHttpPort());
mediaServer.setWsFlvSSLPort(zlmServerConfig.getHttpPort());
mediaServer.setRtmpPort(zlmServerConfig.getRtmpPort());
mediaServer.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());
mediaServer.setRtspPort(zlmServerConfig.getRtspPort());
mediaServer.setRtspSSLPort(zlmServerConfig.getRtspSSlport());
mediaServer.setRtpProxyPort(zlmServerConfig.getRtpProxyPort());
mediaServer.setStreamIp(ip);
mediaServer.setHookIp(sipIp.split(",")[0]);
mediaServer.setSdpIp(ip);
mediaServer.setType("zlm");
@ -131,10 +136,31 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
param.put("ssrc", ssrc);
}
JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaInfo, param);
return (jsonObject != null && jsonObject.getInteger("code") == 0);
if (jsonObject == null || jsonObject.getInteger("code") != 0 ) {
logger.error("停止发流失败: {}, 参数:{}", jsonObject.getString("msg"), JSON.toJSONString(param));
throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg"));
}
return true;
}
@Override
public boolean initStopSendRtp(MediaServer mediaInfo, String app, String stream, String ssrc) {
Map<String, Object> param = new HashMap<>();
param.put("vhost", "__defaultVhost__");
param.put("app", app);
param.put("stream", stream);
if (!ObjectUtils.isEmpty(ssrc)) {
param.put("ssrc", ssrc);
}
JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaInfo, param);
if (jsonObject == null || jsonObject.getInteger("code") != 0 ) {
logger.error("停止发流失败: {}, 参数:{}", jsonObject.getString("msg"), JSON.toJSONString(param));
return false;
}
return true;
}
@Override
public boolean deleteRecordDirectory(MediaServer mediaServer, String app, String stream, String date, String fileName) {
logger.info("[zlm-deleteRecordDirectory] 删除磁盘文件, server: {} {}:{}->{}/{}", mediaServer.getId(), app, stream, date, fileName);

View File

@ -264,4 +264,13 @@ public class ZLMServerFactory {
}
return result;
}
public JSONObject stopSendRtpStream(MediaServer mediaServerItem, SendRtpItem sendRtpItem) {
Map<String, Object> param = new HashMap<>();
param.put("vhost", "__defaultVhost__");
param.put("app", sendRtpItem.getApp());
param.put("stream", sendRtpItem.getStream());
param.put("ssrc", sendRtpItem.getSsrc());
return zlmresTfulUtils.stopSendRtp(mediaServerItem, param);
}
}

View File

@ -1,5 +1,7 @@
package com.genersoft.iot.vmp.media.zlm.dto;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import java.text.ParseException;
/**
@ -7,5 +9,5 @@ import java.text.ParseException;
*/
public interface ChannelOnlineEvent {
void run(String app, String stream, String serverId) throws ParseException;
void run(SendRtpItem sendRtpItem) throws ParseException;
}

View File

@ -35,7 +35,7 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte
* 观看总人数包括hls/rtsp/rtmp/http-flv/ws-flv
*/
@Schema(description = "观看总人数")
private String totalReaderCount;
private Integer totalReaderCount;
/**
* 协议 包括hls/rtsp/rtmp/http-flv/ws-flv
@ -159,7 +159,7 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte
streamPushItem.setStream(streamInfo.getStream());
streamPushItem.setAliveSecond(streamInfo.getMediaInfo().getAliveSecond());
// streamPushItem.setOriginSock(streamInfo.getMediaInfo().getOriginSock());
streamPushItem.setTotalReaderCount(streamInfo.getMediaInfo().getReaderCount() + "");
streamPushItem.setTotalReaderCount(streamInfo.getMediaInfo().getReaderCount());
streamPushItem.setOriginType(streamInfo.getOriginType());
// streamPushItem.setOriginTypeStr(streamInfo.getMediaInfo().getOriginTypeStr());
// streamPushItem.setOriginUrl(streamInfo.getMediaInfo().getOriginUrl());
@ -180,7 +180,7 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte
streamPushItem.setStream(event.getStream());
streamPushItem.setAliveSecond(event.getMediaInfo().getAliveSecond());
// streamPushItem.setOriginSock(streamInfo.getMediaInfo().getOriginSock());
streamPushItem.setTotalReaderCount(event.getMediaInfo().getReaderCount() + "");
streamPushItem.setTotalReaderCount(event.getMediaInfo().getReaderCount());
streamPushItem.setOriginType(event.getMediaInfo().getOriginType());
// streamPushItem.setOriginTypeStr(streamInfo.getMediaInfo().getOriginTypeStr());
// streamPushItem.setOriginUrl(streamInfo.getMediaInfo().getOriginUrl());
@ -242,11 +242,11 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte
this.stream = stream;
}
public String getTotalReaderCount() {
public Integer getTotalReaderCount() {
return totalReaderCount;
}
public void setTotalReaderCount(String totalReaderCount) {
public void setTotalReaderCount(Integer totalReaderCount) {
this.totalReaderCount = totalReaderCount;
}

View File

@ -331,6 +331,9 @@ public class ZLMServerConfig extends HookParam {
@JSONField(name = "shell.shell")
private String shellPhell;
@JSONField(name = "transcode.suffix")
private String transcodeSuffix;
public String getHookIp() {
return hookIp;
@ -1211,4 +1214,12 @@ public class ZLMServerConfig extends HookParam {
public void setHookOnRtpServerTimeout(String hookOnRtpServerTimeout) {
this.hookOnRtpServerTimeout = hookOnRtpServerTimeout;
}
public String getTranscodeSuffix() {
return transcodeSuffix;
}
public void setTranscodeSuffix(String transcodeSuffix) {
this.transcodeSuffix = transcodeSuffix;
}
}

View File

@ -15,6 +15,7 @@ public class OnRecordMp4HookParam extends HookParam{
private String vhost;
private long start_time;
private double time_len;
private String params;
public String getApp() {
return app;
@ -96,6 +97,14 @@ public class OnRecordMp4HookParam extends HookParam{
this.time_len = time_len;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
@Override
public String toString() {
return "OnRecordMp4HookParam{" +
@ -109,6 +118,7 @@ public class OnRecordMp4HookParam extends HookParam{
", vhost='" + vhost + '\'' +
", start_time=" + start_time +
", time_len=" + time_len +
", params=" + params +
'}';
}
}

View File

@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.media.zlm.dto.hook;
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
import java.util.List;
import java.util.Map;
/**
* @author lin
@ -98,6 +99,16 @@ public class OnStreamChangedHookParam extends HookParam{
*/
private String vhost;
/**
* 额外的参数字符串
*/
private String params;
/**
* 额外的参数
*/
private Map<String, String> paramMap;
public boolean isRegist() {
return regist;
}
@ -496,6 +507,23 @@ public class OnStreamChangedHookParam extends HookParam{
this.callId = callId;
}
public Map<String, String> getParamMap() {
return paramMap;
}
public void setParamMap(Map<String, String> paramMap) {
this.paramMap = paramMap;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
@Override
public String toString() {
return "OnStreamChangedHookParam{" +

View File

@ -17,7 +17,7 @@ public interface ICloudRecordService {
/**
* 分页回去云端录像列表
*/
PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServer> mediaServerItems);
PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServer> mediaServerItems, String callId);
/**
* 获取所有的日期
@ -50,4 +50,6 @@ public interface ICloudRecordService {
* 获取播放地址
*/
DownloadFileInfo getPlayUrlPath(Integer recordId);
List<CloudRecordItem> getAllList(String query, String app, String stream, String startTime, String endTime, List<MediaServer> mediaServerItems, String callId, List<Integer> ids);
}

View File

@ -99,4 +99,13 @@ public interface IDeviceChannelService {
void updateChannelGPS(Device device, DeviceChannel deviceChannel, MobilePosition mobilePosition);
void stopPlay(String deviceId, String channelId);
void batchUpdateChannelGPS(List<DeviceChannel> channelList);
void batchAddMobilePosition(List<MobilePosition> addMobilePositionList);
void online(DeviceChannel channel);
void offline(DeviceChannel channel);
void delete(DeviceChannel channel);
}

View File

@ -0,0 +1,13 @@
package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
import java.util.List;
public interface IMobilePositionService {
void add(List<MobilePosition> mobilePositionList);
void add(MobilePosition mobilePosition);
}

View File

@ -115,4 +115,5 @@ public interface IStreamPushService {
Map<String, StreamPushItem> getAllAppAndStreamMap();
void updatePush(OnStreamChangedHookParam param);
}

View File

@ -1,7 +1,9 @@
package com.genersoft.iot.vmp.service.bean;
import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
import com.genersoft.iot.vmp.utils.MediaServerUtils;
import java.util.Map;
/**
* 云端录像数据
@ -89,6 +91,10 @@ public class CloudRecordItem {
cloudRecordItem.setMediaServerId(param.getMediaServer().getId());
cloudRecordItem.setTimeLen((long) param.getRecordInfo().getTimeLen() * 1000);
cloudRecordItem.setEndTime((param.getRecordInfo().getStartTime() + (long)param.getRecordInfo().getTimeLen()) * 1000);
Map<String, String> paramsMap = MediaServerUtils.urlParamToMap(param.getRecordInfo().getParams());
if (paramsMap.get("callId") != null) {
cloudRecordItem.setCallId(paramsMap.get("callId"));
}
return cloudRecordItem;
}

View File

@ -61,6 +61,7 @@ public class MessageForPushChannel {
messageForPushChannel.setGbId(gbId);
messageForPushChannel.setApp(app);
messageForPushChannel.setStream(stream);
messageForPushChannel.setServerId(serverId);
messageForPushChannel.setMediaServerId(mediaServerId);
messageForPushChannel.setPlatFormId(platFormId);
messageForPushChannel.setPlatFormName(platFormName);

View File

@ -5,12 +5,12 @@ import com.alibaba.fastjson2.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event;
import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@ -28,13 +28,18 @@ import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.time.*;
import java.util.*;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
@DS("share")
public class CloudRecordServiceImpl implements ICloudRecordService {
private final static Logger logger = LoggerFactory.getLogger(CloudRecordServiceImpl.class);
@Autowired
@ -53,7 +58,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
private VideoStreamSessionManager streamSession;
@Override
public PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServer> mediaServerItems) {
public PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServer> mediaServerItems, String callId) {
// 开始时间和结束时间在数据库中都是以秒为单位的
Long startTimeStamp = null;
Long endTimeStamp = null;
@ -73,7 +78,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
}
PageHelper.startPage(page, count);
List<CloudRecordItem> all = cloudRecordServiceMapper.getList(query, app, stream, startTimeStamp, endTimeStamp,
null, mediaServerItems);
callId, mediaServerItems, null);
return new PageInfo<>(all);
}
@ -86,10 +91,10 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
}else {
endDate = LocalDate.of(year, month + 1, 1);
}
long startTimeStamp = startDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).getEpochSecond();
long endTimeStamp = endDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).getEpochSecond();
long startTimeStamp = startDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).getEpochSecond() * 1000;
long endTimeStamp = endDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).getEpochSecond() * 1000;
List<CloudRecordItem> cloudRecordItemList = cloudRecordServiceMapper.getList(null, app, stream, startTimeStamp,
endTimeStamp, null, mediaServerItems);
endTimeStamp, null, mediaServerItems, null);
if (cloudRecordItemList.isEmpty()) {
return new ArrayList<>();
}
@ -105,11 +110,13 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
@EventListener
public void onApplicationEvent(MediaRecordMp4Event event) {
CloudRecordItem cloudRecordItem = CloudRecordItem.getInstance(event);
StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(event.getApp(), event.getStream());
if (streamAuthorityInfo != null) {
cloudRecordItem.setCallId(streamAuthorityInfo.getCallId());
if (ObjectUtils.isEmpty(cloudRecordItem.getCallId())) {
StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(event.getApp(), event.getStream());
if (streamAuthorityInfo != null) {
cloudRecordItem.setCallId(streamAuthorityInfo.getCallId());
}
}
logger.info("[添加录像记录] {}/{} 内容:{}", event.getApp(), event.getStream(), event.getRecordInfo());
logger.info("[添加录像记录] {}/{}, callId: {}, 内容:{}", event.getApp(), event.getStream(), cloudRecordItem.getCallId(), event.getRecordInfo());
cloudRecordServiceMapper.add(cloudRecordItem);
}
@ -199,7 +206,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
}
List<CloudRecordItem> all = cloudRecordServiceMapper.getList(null, app, stream, startTimeStamp, endTimeStamp,
callId, mediaServerItems);
callId, mediaServerItems, null);
if (all.isEmpty()) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到待收藏的视频");
}
@ -235,4 +242,27 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
MediaServer mediaServerItem = mediaServerService.getOne(recordItem.getMediaServerId());
return CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath);
}
@Override
public List<CloudRecordItem> getAllList(String query, String app, String stream, String startTime, String endTime, List<MediaServer> mediaServerItems, String callId, List<Integer> ids) {
// 开始时间和结束时间在数据库中都是以秒为单位的
Long startTimeStamp = null;
Long endTimeStamp = null;
if (startTime != null ) {
if (!DateUtil.verification(startTime, DateUtil.formatter)) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "开始时间格式错误,正确格式为: " + DateUtil.formatter);
}
startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(startTime);
}
if (endTime != null ) {
if (!DateUtil.verification(endTime, DateUtil.formatter)) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "结束时间格式错误,正确格式为: " + DateUtil.formatter);
}
endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(endTime);
}
return cloudRecordServiceMapper.getList(query, app, stream, startTimeStamp, endTimeStamp,
callId, mediaServerItems, ids);
}
}

View File

@ -23,6 +23,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import java.util.ArrayList;
@ -247,26 +248,50 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
return channelMapper.batchOnline(channels);
}
@Override
public void online(DeviceChannel channel) {
channelMapper.online(channel.getDeviceId(), channel.getChannelId());
}
@Override
public int channelsOffline(List<DeviceChannel> channels) {
return channelMapper.batchOffline(channels);
}
@Override
public void offline(DeviceChannel channel) {
channelMapper.offline(channel.getDeviceId(), channel.getChannelId());
}
@Override
public void delete(DeviceChannel channel) {
channelMapper.del(channel.getDeviceId(), channel.getChannelId());
}
@Override
public DeviceChannel getOne(String deviceId, String channelId){
return channelMapper.queryChannel(deviceId, channelId);
}
@Override
public void batchUpdateChannel(List<DeviceChannel> channels) {
public synchronized void batchUpdateChannel(List<DeviceChannel> channels) {
String now = DateUtil.getNow();
for (DeviceChannel channel : channels) {
channel.setUpdateTime(now);
}
channelMapper.batchUpdate(channels);
for (DeviceChannel channel : channels) {
if (channel.getParentId() != null) {
channelMapper.updateChannelSubCount(channel.getDeviceId(), channel.getParentId());
int limitCount = 1000;
if (!channels.isEmpty()) {
if (channels.size() > limitCount) {
for (int i = 0; i < channels.size(); i += limitCount) {
int toIndex = i + limitCount;
if (i + limitCount > channels.size()) {
toIndex = channels.size();
}
channelMapper.batchUpdate(channels.subList(i, toIndex));
}
}else {
channelMapper.batchUpdate(channels);
}
}
}
@ -358,4 +383,47 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
public void stopPlay(String deviceId, String channelId) {
channelMapper.stopPlay(deviceId, channelId);
}
@Override
@Transactional
public void batchUpdateChannelGPS(List<DeviceChannel> channelList) {
for (DeviceChannel deviceChannel : channelList) {
deviceChannel.setUpdateTime(DateUtil.getNow());
if (deviceChannel.getGpsTime() == null) {
deviceChannel.setGpsTime(DateUtil.getNow());
}
}
int count = 1000;
if (channelList.size() > count) {
for (int i = 0; i < channelList.size(); i+=count) {
int toIndex = i+count;
if ( i + count > channelList.size()) {
toIndex = channelList.size();
}
List<DeviceChannel> channels = channelList.subList(i, toIndex);
channelMapper.batchUpdatePosition(channels);
}
}else {
channelMapper.batchUpdatePosition(channelList);
}
}
@Override
@Transactional
public void batchAddMobilePosition(List<MobilePosition> mobilePositions) {
// int count = 500;
// if (mobilePositions.size() > count) {
// for (int i = 0; i < mobilePositions.size(); i+=count) {
// int toIndex = i+count;
// if ( i + count > mobilePositions.size()) {
// toIndex = mobilePositions.size();
// }
// List<MobilePosition> mobilePositionsSub = mobilePositions.subList(i, toIndex);
// deviceMobilePositionMapper.batchadd(mobilePositionsSub);
// }
// }else {
// deviceMobilePositionMapper.batchadd(mobilePositions);
// }
deviceMobilePositionMapper.batchadd(mobilePositions);
}
}

View File

@ -558,6 +558,7 @@ public class DeviceServiceImpl implements IDeviceService {
removeMobilePositionSubscribe(deviceInStore, result->{
// 开启订阅
deviceInStore.setSubscribeCycleForMobilePosition(device.getSubscribeCycleForMobilePosition());
deviceInStore.setMobilePositionSubmissionInterval(device.getMobilePositionSubmissionInterval());
addMobilePositionSubscribe(deviceInStore);
// 因为是异步执行需要在这里更新下数据
deviceMapper.updateCustom(deviceInStore);
@ -566,12 +567,14 @@ public class DeviceServiceImpl implements IDeviceService {
}else {
// 开启订阅
deviceInStore.setSubscribeCycleForMobilePosition(device.getSubscribeCycleForMobilePosition());
deviceInStore.setMobilePositionSubmissionInterval(device.getMobilePositionSubmissionInterval());
addMobilePositionSubscribe(deviceInStore);
}
}else if (device.getSubscribeCycleForMobilePosition() == 0) {
// 取消订阅
deviceInStore.setSubscribeCycleForMobilePosition(0);
deviceInStore.setMobilePositionSubmissionInterval(0);
removeMobilePositionSubscribe(deviceInStore, null);
}
}

View File

@ -10,16 +10,15 @@ import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.media.bean.ResultForOnPublish;
import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.bean.ResultForOnPublish;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.service.*;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
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.MediaServerUtils;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.OtherPsSendInfo;
import com.genersoft.iot.vmp.vmanager.bean.OtherRtpSendInfo;
@ -28,12 +27,10 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -66,9 +63,6 @@ public class MediaServiceImpl implements IMediaService {
@Autowired
private IVideoManagerStorage storager;
@Autowired
private ZLMMediaListManager zlmMediaListManager;
@Autowired
private IDeviceService deviceService;
@ -106,7 +100,7 @@ public class MediaServiceImpl implements IMediaService {
}
if (userSetting.getPushAuthority()) {
// 对于推流进行鉴权
Map<String, String> paramMap = urlParamToMap(params);
Map<String, String> paramMap = MediaServerUtils.urlParamToMap(params);
// 推流鉴权
if (params == null) {
logger.info("推流鉴权失败: 缺少必要参数sign=md5(user表的pushKey)");
@ -132,8 +126,6 @@ public class MediaServiceImpl implements IMediaService {
// 鉴权通过
redisCatchStorage.updateStreamAuthorityInfo(app, stream, streamAuthorityInfo);
}
} else {
zlmMediaListManager.sendStreamEvent(app, stream, mediaServer.getId());
}
@ -178,7 +170,7 @@ public class MediaServiceImpl implements IMediaService {
String channelId = ssrcTransactionForAll.get(0).getChannelId();
DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
if (deviceChannel != null) {
result.setEnable_audio(deviceChannel.isHasAudio());
result.setEnable_audio(deviceChannel.getHasAudio());
}
// 如果是录像下载就设置视频间隔十秒
if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) {
@ -217,24 +209,6 @@ public class MediaServiceImpl implements IMediaService {
return result;
}
private Map<String, String> urlParamToMap(String params) {
HashMap<String, String> map = new HashMap<>();
if (ObjectUtils.isEmpty(params)) {
return map;
}
String[] paramsArray = params.split("&");
if (paramsArray.length == 0) {
return map;
}
for (String param : paramsArray) {
String[] paramArray = param.split("=");
if (paramArray.length == 2) {
map.put(paramArray[0], paramArray[1]);
}
}
return map;
}
@Override
public boolean closeStreamOnNoneReader(String mediaServerId, String app, String stream, String schema) {
boolean result = false;
@ -264,11 +238,7 @@ public class MediaServiceImpl implements IMediaService {
redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
sendRtpItem.getCallId(), sendRtpItem.getStream());
if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
redisCatchStorage.sendPlatformStopPlayMsg(sendRtpItem,parentPlatform);
}
}
}

View File

@ -0,0 +1,95 @@
package com.genersoft.iot.vmp.service.impl;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.MobilePosition;
import com.genersoft.iot.vmp.service.IMobilePositionService;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.storager.dao.DeviceMobilePositionMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class MobilePositionServiceImpl implements IMobilePositionService {
@Autowired
private DeviceChannelMapper channelMapper;
@Autowired
private DeviceMobilePositionMapper mobilePositionMapper;
@Autowired
private UserSetting userSetting;
@Autowired
private RedisTemplate<String, MobilePosition> redisTemplate;
private final static Logger logger = LoggerFactory.getLogger(MobilePositionServiceImpl.class);
private final String REDIS_MOBILE_POSITION_LIST = "redis_mobile_position_list";
@Override
public void add(MobilePosition mobilePosition) {
List<MobilePosition> list = new ArrayList<>();
list.add(mobilePosition);
add(list);
}
@Override
public void add(List<MobilePosition> mobilePositionList) {
redisTemplate.opsForList().leftPushAll(REDIS_MOBILE_POSITION_LIST, mobilePositionList);
}
private List<MobilePosition> get(int length) {
Long size = redisTemplate.opsForList().size(REDIS_MOBILE_POSITION_LIST);
if (size == null || size == 0) {
return new ArrayList<>();
}
List<MobilePosition> mobilePositions;
if (size > length) {
mobilePositions = redisTemplate.opsForList().rightPop(REDIS_MOBILE_POSITION_LIST, length);
}else {
mobilePositions = redisTemplate.opsForList().rightPop(REDIS_MOBILE_POSITION_LIST, size);
}
return mobilePositions;
}
@Scheduled(fixedRate = 1000)
@Transactional
public void executeTaskQueue() {
int countLimit = 3000;
List<MobilePosition> mobilePositions = get(countLimit);
if (mobilePositions == null || mobilePositions.isEmpty()) {
return;
}
if (userSetting.getSavePositionHistory()) {
mobilePositionMapper.batchadd(mobilePositions);
}
logger.info("[移动位置订阅]更新通道位置: {}", mobilePositions.size());
Map<String, DeviceChannel> updateChannelMap = new HashMap<>();
for (MobilePosition mobilePosition : mobilePositions) {
DeviceChannel deviceChannel = new DeviceChannel();
deviceChannel.setDeviceId(mobilePosition.getDeviceId());
deviceChannel.setLongitude(mobilePosition.getLongitude());
deviceChannel.setLatitude(mobilePosition.getLatitude());
deviceChannel.setGpsTime(mobilePosition.getTime());
updateChannelMap.put(mobilePosition.getDeviceId() + mobilePosition.getChannelId(), deviceChannel);
}
List<DeviceChannel> channels = new ArrayList<>(updateChannelMap.values());
channelMapper.batchUpdatePosition(channels);
}
}

View File

@ -34,7 +34,6 @@ import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.*;
import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.CloudRecordUtils;
@ -118,9 +117,6 @@ public class PlayServiceImpl implements IPlayService {
@Autowired
private ISIPCommanderForPlatform commanderForPlatform;
@Autowired
private RedisGbPlayMsgListener redisGbPlayMsgListener;
@Autowired
private SSRCFactory ssrcFactory;
@ -263,7 +259,7 @@ public class PlayServiceImpl implements IPlayService {
);
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(event.getMediaServer(), event.getStream(), null,
device.isSsrcCheck(), true, 0, false, !deviceChannel.isHasAudio(), false, device.getStreamModeForParam());
device.isSsrcCheck(), true, 0, false, !deviceChannel.getHasAudio(), false, device.getStreamModeForParam());
playBack(event.getMediaServer(), ssrcInfo, deviceId, channelId, startTime, endTime, null);
}
}
@ -275,7 +271,6 @@ public class PlayServiceImpl implements IPlayService {
logger.warn("[点播] 未找到可用的zlm deviceId: {},channelId:{}", deviceId, channelId);
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm");
}
Device device = redisCatchStorage.getDevice(deviceId);
if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE") && !mediaServerItem.isRtpEnable()) {
logger.warn("[点播] 单端口收流时不支持TCP主动方式收流 deviceId: {},channelId:{}", deviceId, channelId);
@ -289,6 +284,8 @@ public class PlayServiceImpl implements IPlayService {
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
if (inviteInfo != null ) {
if (inviteInfo.getStreamInfo() == null) {
// 释放生成的ssrc使用上一次申请的
ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrc);
// 点播发起了但是尚未成功, 仅注册回调等待结果即可
inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback);
logger.info("[点播开始] 已经请求中,等待结果, deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
@ -324,7 +321,7 @@ public class PlayServiceImpl implements IPlayService {
}
}
String streamId = String.format("%s_%s", device.getDeviceId(), channelId);
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(), false, 0, false, !channel.isHasAudio(), false, device.getStreamModeForParam());
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(), false, 0, false, !channel.getHasAudio(), false, device.getStreamModeForParam());
if (ssrcInfo == null) {
callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null);
inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
@ -763,7 +760,7 @@ public class PlayServiceImpl implements IPlayService {
.replace(":", "")
.replace(" ", "");
String stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr;
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, !channel.isHasAudio(), false, device.getStreamModeForParam());
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, !channel.getHasAudio(), false, device.getStreamModeForParam());
playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, callback);
}
@ -960,7 +957,7 @@ public class PlayServiceImpl implements IPlayService {
return;
}
// 录像下载不使用固定流地址固定流地址会导致如果开始时间与结束时间一致时文件错误的叠加在一起
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(), true, 0, false,!channel.isHasAudio(), false, device.getStreamModeForParam());
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(), true, 0, false,!channel.getHasAudio(), false, device.getStreamModeForParam());
download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed, callback);
}
@ -1419,12 +1416,7 @@ public class PlayServiceImpl implements IPlayService {
// 开始发流
MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
if (mediaInfo == null) {
RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(sendRtpItem);
redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, () -> {
startSendRtpStreamFailHand(sendRtpItem, platform, callIdHeader);
});
} else {
if (mediaInfo != null) {
try {
if (sendRtpItem.isTcpActive()) {
mediaServerService.startSendRtpPassive(mediaInfo, platform, sendRtpItem, null);

View File

@ -349,6 +349,9 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
}
String msgResult;
if ("ffmpeg".equalsIgnoreCase(param.getType())){
if (param.getTimeoutMs() == 0) {
param.setTimeoutMs(15);
}
result = mediaServerService.addFFmpegSource(mediaServer, param.getSrcUrl().trim(), param.getDstUrl(),
param.getTimeoutMs(), param.isEnableAudio(), param.isEnableMp4(),
param.getFfmpegCmdKey());
@ -406,6 +409,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
gbStreamMapper.del(app, stream);
videoManagerStorager.deleteStreamProxy(app, stream);
redisCatchStorage.removeStream(streamProxyItem.getMediaServerId(), "PULL", app, stream);
redisCatchStorage.removeStream(streamProxyItem.getMediaServerId(), "PUSH", app, stream);
Boolean result = removeStreamProxyFromZlm(streamProxyItem);
if (result != null && result) {
logger.info("[移除代理] 代理: {}/{}, 从zlm移除成功", app, stream);

View File

@ -217,7 +217,7 @@ public class StreamPushServiceImpl implements IStreamPushService {
streamPushItem.setStream(item.getStream());
streamPushItem.setAliveSecond(item.getAliveSecond());
streamPushItem.setOriginSock(item.getOriginSock());
streamPushItem.setTotalReaderCount(item.getTotalReaderCount() + "");
streamPushItem.setTotalReaderCount(item.getTotalReaderCount());
streamPushItem.setOriginType(item.getOriginType());
streamPushItem.setOriginTypeStr(item.getOriginTypeStr());
streamPushItem.setOriginUrl(item.getOriginUrl());
@ -633,4 +633,21 @@ public class StreamPushServiceImpl implements IStreamPushService {
public Map<String, StreamPushItem> getAllAppAndStreamMap() {
return streamPushMapper.getAllAppAndStreamMap();
}
@Override
public void updatePush(OnStreamChangedHookParam param) {
StreamPushItem transform = transform(param);
StreamPushItem pushInDb = getPush(param.getApp(), param.getStream());
transform.setPushIng(param.isRegist());
transform.setUpdateTime(DateUtil.getNow());
transform.setPushTime(DateUtil.getNow());
transform.setSelf(userSetting.getServerId().equals(param.getSeverId()));
if (pushInDb == null) {
transform.setCreateTime(DateUtil.getNow());
streamPushMapper.add(transform);
}else {
streamPushMapper.update(transform);
gbStreamMapper.updateMediaServer(param.getApp(), param.getStream(), param.getMediaServerId());
}
}
}

View File

@ -137,7 +137,7 @@ public class StreamPushUploadFileHandler extends AnalysisEventListener<StreamPus
streamPushItem.setName(streamPushExcelDto.getName());
streamPushItem.setOriginType(2);
streamPushItem.setOriginTypeStr("rtsp_push");
streamPushItem.setTotalReaderCount("0");
streamPushItem.setTotalReaderCount(0);
streamPushItem.setPlatformId(streamPushExcelDto.getPlatformId());
streamPushItem.setCatalogId(streamPushExcelDto.getCatalogId());

View File

@ -0,0 +1,22 @@
package com.genersoft.iot.vmp.service.redisMsg;
import com.genersoft.iot.vmp.common.CommonCallback;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
public interface IRedisRpcService {
SendRtpItem getSendRtpItem(String sendRtpItemKey);
WVPResult startSendRtp(String sendRtpItemKey, SendRtpItem sendRtpItem);
WVPResult stopSendRtp(String sendRtpItemKey);
long waitePushStreamOnline(SendRtpItem sendRtpItem, CommonCallback<String> callback);
void stopWaitePushStreamOnline(SendRtpItem sendRtpItem);
void rtpSendStopped(String sendRtpItemKey);
void removeCallback(long key);
}

View File

@ -1,451 +0,0 @@
package com.genersoft.iot.vmp.service.redisMsg;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.event.hook.Hook;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* 监听下级发送推送信息并发送国标推流消息上级
* @author lin
*/
@Component
public class RedisGbPlayMsgListener implements MessageListener {
private final static Logger logger = LoggerFactory.getLogger(RedisGbPlayMsgListener.class);
public static final String WVP_PUSH_STREAM_KEY = "WVP_PUSH_STREAM";
/**
* 流媒体不存在的错误玛
*/
public static final int ERROR_CODE_MEDIA_SERVER_NOT_FOUND = -1;
/**
* 离线的错误玛
*/
public static final int ERROR_CODE_OFFLINE = -2;
/**
* 超时的错误玛
*/
public static final int ERROR_CODE_TIMEOUT = -3;
private final Map<String, PlayMsgCallback> callbacks = new ConcurrentHashMap<>();
private final Map<String, PlayMsgCallbackForStartSendRtpStream> callbacksForStartSendRtpStream = new ConcurrentHashMap<>();
private final Map<String, PlayMsgErrorCallback> callbacksForError = new ConcurrentHashMap<>();
@Autowired
private UserSetting userSetting;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private IMediaServerService mediaServerService;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private DynamicTask dynamicTask;
@Autowired
private HookSubscribe subscribe;
private final ConcurrentLinkedQueue<Message> taskQueue = new ConcurrentLinkedQueue<>();
@Qualifier("taskExecutor")
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
public interface PlayMsgCallback{
void handler(ResponseSendItemMsg responseSendItemMsg) throws ParseException;
}
public interface PlayMsgCallbackForStartSendRtpStream{
void handler();
}
public interface PlayMsgErrorCallback{
void handler(WVPResult wvpResult);
}
@Override
public void onMessage(Message message, byte[] bytes) {
boolean isEmpty = taskQueue.isEmpty();
taskQueue.offer(message);
if (isEmpty) {
taskExecutor.execute(() -> {
while (!taskQueue.isEmpty()) {
Message msg = taskQueue.poll();
try {
WvpRedisMsg wvpRedisMsg = JSON.parseObject(msg.getBody(), WvpRedisMsg.class);
logger.info("[收到REDIS通知] 消息: {}", JSON.toJSONString(wvpRedisMsg));
if (!userSetting.getServerId().equals(wvpRedisMsg.getToId())) {
continue;
}
if (WvpRedisMsg.isRequest(wvpRedisMsg)) {
logger.info("[收到REDIS通知] 请求: {}", new String(msg.getBody()));
switch (wvpRedisMsg.getCmd()){
case WvpRedisMsgCmd.GET_SEND_ITEM:
RequestSendItemMsg content = JSON.parseObject(wvpRedisMsg.getContent(), RequestSendItemMsg.class);
requestSendItemMsgHand(content, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial());
break;
case WvpRedisMsgCmd.REQUEST_PUSH_STREAM:
RequestPushStreamMsg param = JSON.to(RequestPushStreamMsg.class, wvpRedisMsg.getContent());
requestPushStreamMsgHand(param, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial());
break;
case WvpRedisMsgCmd.REQUEST_STOP_PUSH_STREAM:
RequestStopPushStreamMsg streamMsg = JSON.to(RequestStopPushStreamMsg.class, wvpRedisMsg.getContent());
requestStopPushStreamMsgHand(streamMsg, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial());
break;
default:
break;
}
}else {
logger.info("[收到REDIS通知] 回复: {}", new String(msg.getBody()));
switch (wvpRedisMsg.getCmd()){
case WvpRedisMsgCmd.GET_SEND_ITEM:
WVPResult content = JSON.to(WVPResult.class, wvpRedisMsg.getContent());
String key = wvpRedisMsg.getSerial();
switch (content.getCode()) {
case 0:
ResponseSendItemMsg responseSendItemMsg =JSON.to(ResponseSendItemMsg.class, content.getData());
PlayMsgCallback playMsgCallback = callbacks.get(key);
if (playMsgCallback != null) {
callbacksForError.remove(key);
try {
playMsgCallback.handler(responseSendItemMsg);
} catch (ParseException e) {
logger.error("[REDIS消息处理异常] ", e);
}
}
break;
case ERROR_CODE_MEDIA_SERVER_NOT_FOUND:
case ERROR_CODE_OFFLINE:
case ERROR_CODE_TIMEOUT:
PlayMsgErrorCallback errorCallback = callbacksForError.get(key);
if (errorCallback != null) {
callbacks.remove(key);
errorCallback.handler(content);
}
break;
default:
break;
}
break;
case WvpRedisMsgCmd.REQUEST_PUSH_STREAM:
WVPResult wvpResult = JSON.to(WVPResult.class, wvpRedisMsg.getContent());
String serial = wvpRedisMsg.getSerial();
switch (wvpResult.getCode()) {
case 0:
PlayMsgCallbackForStartSendRtpStream playMsgCallback = callbacksForStartSendRtpStream.get(serial);
if (playMsgCallback != null) {
callbacksForError.remove(serial);
playMsgCallback.handler();
}
break;
case ERROR_CODE_MEDIA_SERVER_NOT_FOUND:
case ERROR_CODE_OFFLINE:
case ERROR_CODE_TIMEOUT:
PlayMsgErrorCallback errorCallback = callbacksForError.get(serial);
if (errorCallback != null) {
callbacks.remove(serial);
errorCallback.handler(wvpResult);
}
break;
default:
break;
}
break;
default:
break;
}
}
}catch (Exception e) {
logger.warn("[RedisGbPlayMsg] 发现未处理的异常, \r\n{}", JSON.toJSONString(message));
logger.error("[RedisGbPlayMsg] 异常内容: ", e);
}
}
});
}
}
/**
* 处理收到的请求推流的请求
*/
private void requestPushStreamMsgHand(RequestPushStreamMsg requestPushStreamMsg, String fromId, String serial) {
MediaServer mediaServer = mediaServerService.getOne(requestPushStreamMsg.getMediaServerId());
if (mediaServer == null) {
// TODO 回复错误
return;
}
SendRtpItem sendRtpItem = SendRtpItem.getInstance(requestPushStreamMsg);
try {
mediaServerService.startSendRtp(mediaServer, null, sendRtpItem);
}catch (ControllerException e) {
return;
}
// 回复消息
WVPResult<JSONObject> result = new WVPResult<>();
result.setCode(0);
WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), fromId,
WvpRedisMsgCmd.REQUEST_PUSH_STREAM, serial, JSON.toJSONString(result));
JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
}
/**
* 处理收到的请求sendItem的请求
*/
private void requestSendItemMsgHand(RequestSendItemMsg content, String toId, String serial) {
MediaServer mediaServerItem = mediaServerService.getOne(content.getMediaServerId());
if (mediaServerItem == null) {
logger.info("[回复推流信息] 流媒体{}不存在 ", content.getMediaServerId());
WVPResult<SendRtpItem> result = new WVPResult<>();
result.setCode(ERROR_CODE_MEDIA_SERVER_NOT_FOUND);
result.setMsg("流媒体不存在");
WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), toId,
WvpRedisMsgCmd.GET_SEND_ITEM, serial, JSON.toJSONString(result));
JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
return;
}
// 确定流是否在线
Boolean streamReady = mediaServerService.isStreamReady(mediaServerItem, content.getApp(), content.getStream());
if (streamReady != null && streamReady) {
logger.info("[回复推流信息] {}/{}", content.getApp(), content.getStream());
responseSendItem(mediaServerItem, content, toId, serial);
}else {
// 流已经离线
// 发送redis消息以使设备上线
logger.info("[ app={}, stream={} ]通道离线发送redis信息控制设备开始推流",content.getApp(), content.getStream());
String taskKey = UUID.randomUUID().toString();
// 设置超时
dynamicTask.startDelay(taskKey, ()->{
logger.info("[ app={}, stream={} ] 等待设备开始推流超时", content.getApp(), content.getStream());
WVPResult<SendRtpItem> result = new WVPResult<>();
result.setCode(ERROR_CODE_TIMEOUT);
WvpRedisMsg response = WvpRedisMsg.getResponseInstance(
userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, JSON.toJSONString(result)
);
JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
}, userSetting.getPlatformPlayTimeout());
// 添加订阅
Hook hook = Hook.getInstance(HookType.on_media_arrival, content.getApp(), content.getStream(), content.getMediaServerId());
subscribe.addSubscribe(hook, (hookData)->{
dynamicTask.stop(taskKey);
responseSendItem(mediaServerItem, content, toId, serial);
});
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(1, content.getApp(), content.getStream(),
content.getChannelId(), content.getPlatformId(), content.getPlatformName(), content.getServerId(),
content.getMediaServerId());
String key = VideoManagerConstants.VM_MSG_STREAM_PUSH_REQUESTED;
logger.info("[redis发送通知] 推流被请求 {}: {}/{}", key, messageForPushChannel.getApp(), messageForPushChannel.getStream());
redisTemplate.convertAndSend(key, JSON.toJSON(messageForPushChannel));
}
}
/**
* 将获取到的sendItem发送出去
*/
private void responseSendItem(MediaServer mediaServerItem, RequestSendItemMsg content, String toId, String serial) {
SendRtpItem sendRtpItem = mediaServerService.createSendRtpItem(mediaServerItem, content.getIp(),
content.getPort(), content.getSsrc(), content.getPlatformId(),
content.getApp(), content.getStream(), content.getChannelId(),
content.getTcp(), content.getRtcp());
WVPResult<ResponseSendItemMsg> result = new WVPResult<>();
result.setCode(0);
ResponseSendItemMsg responseSendItemMsg = new ResponseSendItemMsg();
responseSendItemMsg.setSendRtpItem(sendRtpItem);
responseSendItemMsg.setMediaServerItem(mediaServerItem);
result.setData(responseSendItemMsg);
redisCatchStorage.updateSendRTPSever(sendRtpItem);
WvpRedisMsg response = WvpRedisMsg.getResponseInstance(
userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, JSON.toJSONString(result)
);
JSONObject jsonObject = (JSONObject)JSON.toJSON(response);
redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
}
/**
* 发送消息要求下级生成推流信息
* @param serverId 下级服务ID
* @param app 应用名
* @param stream 流ID
* @param ip 目标IP
* @param port 目标端口
* @param ssrc ssrc
* @param platformId 平台国标编号
* @param channelId 通道ID
* @param isTcp 是否使用TCP
* @param callback 得到信息的回调
*/
public void sendMsg(String serverId, String mediaServerId, String app, String stream, String ip, int port, String ssrc,
String platformId, String channelId, boolean isTcp, boolean rtcp, String platformName, PlayMsgCallback callback, PlayMsgErrorCallback errorCallback) {
RequestSendItemMsg requestSendItemMsg = RequestSendItemMsg.getInstance(
serverId, mediaServerId, app, stream, ip, port, ssrc, platformId, channelId, isTcp, rtcp, platformName);
requestSendItemMsg.setServerId(serverId);
String key = UUID.randomUUID().toString();
WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId, WvpRedisMsgCmd.GET_SEND_ITEM,
key, JSON.toJSONString(requestSendItemMsg));
JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg);
logger.info("[请求推流SendItem] {}: {}", serverId, jsonObject);
callbacks.put(key, callback);
callbacksForError.put(key, errorCallback);
dynamicTask.startDelay(key, ()->{
callbacks.remove(key);
callbacksForError.remove(key);
WVPResult<Object> wvpResult = new WVPResult<>();
wvpResult.setCode(ERROR_CODE_TIMEOUT);
wvpResult.setMsg("timeout");
errorCallback.handler(wvpResult);
}, userSetting.getPlatformPlayTimeout());
redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
}
/**
* 发送请求推流的消息
* @param param 推流参数
* @param callback 回调
*/
public void sendMsgForStartSendRtpStream(String serverId, RequestPushStreamMsg param, PlayMsgCallbackForStartSendRtpStream callback) {
String key = UUID.randomUUID().toString();
WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId,
WvpRedisMsgCmd.REQUEST_PUSH_STREAM, key, JSON.toJSONString(param));
JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg);
logger.info("[REDIS 请求其他平台推流] {}: {}", serverId, jsonObject);
dynamicTask.startDelay(key, ()->{
callbacksForStartSendRtpStream.remove(key);
callbacksForError.remove(key);
}, userSetting.getPlatformPlayTimeout());
callbacksForStartSendRtpStream.put(key, callback);
callbacksForError.put(key, (wvpResult)->{
logger.info("[REDIS 请求其他平台推流] 失败: {}", wvpResult.getMsg());
callbacksForStartSendRtpStream.remove(key);
callbacksForError.remove(key);
});
redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
}
/**
* 发送请求推流的消息
*/
public void sendMsgForStopSendRtpStream(String serverId, RequestStopPushStreamMsg streamMsg) {
String key = UUID.randomUUID().toString();
WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId,
WvpRedisMsgCmd.REQUEST_STOP_PUSH_STREAM, key, JSON.toJSONString(streamMsg));
JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg);
logger.info("[REDIS 请求其他平台停止推流] {}: {}", serverId, jsonObject);
redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject);
}
private SendRtpItem querySendRTPServer(String platformGbId, String channelId, String streamId, String callId) {
if (platformGbId == null) {
platformGbId = "*";
}
if (channelId == null) {
channelId = "*";
}
if (streamId == null) {
streamId = "*";
}
if (callId == null) {
callId = "*";
}
String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX
+ userSetting.getServerId() + "_*_"
+ platformGbId + "_"
+ channelId + "_"
+ streamId + "_"
+ callId;
List<Object> scan = RedisUtil.scan(redisTemplate, key);
if (scan.size() > 0) {
return (SendRtpItem)redisTemplate.opsForValue().get(scan.get(0));
}else {
return null;
}
}
/**
* 处理收到的请求推流的请求
*/
private void requestStopPushStreamMsgHand(RequestStopPushStreamMsg streamMsg, String fromId, String serial) {
SendRtpItem sendRtpItem = streamMsg.getSendRtpItem();
if (sendRtpItem == null) {
logger.info("[REDIS 执行其他平台的请求停止推流] 失败: sendRtpItem为NULL");
return;
}
MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
if (mediaInfo == null) {
// TODO 回复错误
return;
}
if (mediaServerService.stopSendRtp(mediaInfo, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc())) {
logger.info("[REDIS 执行其他平台的请求停止推流] 成功: {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream());
// 发送redis消息
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
sendRtpItem.getPlatformId(), streamMsg.getPlatformName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
messageForPushChannel.setPlatFormIndex(streamMsg.getPlatFormIndex());
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
}
}
}

View File

@ -1,111 +0,0 @@
package com.genersoft.iot.vmp.service.redisMsg;
import com.alibaba.fastjson2.JSON;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.service.IStreamPushService;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannelResponse;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import java.text.ParseException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 接收redis发送的结束推流请求
* @author lin
*/
@Component
public class RedisPushStreamCloseResponseListener implements MessageListener {
private final static Logger logger = LoggerFactory.getLogger(RedisPushStreamCloseResponseListener.class);
@Autowired
private IStreamPushService streamPushService;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private IVideoManagerStorage storager;
@Autowired
private ISIPCommanderForPlatform commanderFroPlatform;
@Autowired
private UserSetting userSetting;
@Autowired
private IMediaServerService mediaServerService;
private Map<String, PushStreamResponseEvent> responseEvents = new ConcurrentHashMap<>();
public interface PushStreamResponseEvent{
void run(MessageForPushChannelResponse response);
}
@Override
public void onMessage(Message message, byte[] bytes) {
logger.info("[REDIS消息-推流结束] {}", new String(message.getBody()));
MessageForPushChannel pushChannel = JSON.parseObject(message.getBody(), MessageForPushChannel.class);
StreamPushItem push = streamPushService.getPush(pushChannel.getApp(), pushChannel.getStream());
if (push != null) {
List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChannelId(
push.getGbId());
if (!sendRtpItems.isEmpty()) {
for (SendRtpItem sendRtpItem : sendRtpItems) {
ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
if (parentPlatform != null) {
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStream());
try {
commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem);
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
}
}
if (push.isSelf()) {
// 停止向上级推流
logger.info("[REDIS消息-推流结束] 停止向上级推流:{}", sendRtpItem.getStream());
MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStream());
mediaServerService.stopSendRtp(mediaInfo, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc());
if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
}
}
}
}
}
}
public void addEvent(String app, String stream, PushStreamResponseEvent callback) {
responseEvents.put(app + stream, callback);
}
public void removeEvent(String app, String stream) {
responseEvents.remove(app + stream);
}
}

View File

@ -84,7 +84,7 @@ public class RedisPushStreamStatusListMsgListener implements MessageListener {
streamPushItem.setMediaServerId(mediaServerService.getDefaultMediaServer().getId());
streamPushItem.setOriginType(2);
streamPushItem.setOriginTypeStr("rtsp_push");
streamPushItem.setTotalReaderCount("0");
streamPushItem.setTotalReaderCount(0);
streamPushItemForSave.add(streamPushItem);
allGBId.put(streamPushItem.getGbId(), streamPushItem);
} else {

View File

@ -1,97 +0,0 @@
package com.genersoft.iot.vmp.service.redisMsg;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
import com.genersoft.iot.vmp.media.zlm.dto.ChannelOnlineEvent;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
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.text.ParseException;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* 接收其他wvp发送流变化通知
* @author lin
*/
@Component
public class RedisStreamMsgListener implements MessageListener {
private final static Logger logger = LoggerFactory.getLogger(RedisStreamMsgListener.class);
@Autowired
private UserSetting userSetting;
@Autowired
private ZLMMediaListManager zlmMediaListManager;
private ConcurrentLinkedQueue<Message> taskQueue = new ConcurrentLinkedQueue<>();
@Qualifier("taskExecutor")
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Override
public void onMessage(Message message, byte[] bytes) {
boolean isEmpty = taskQueue.isEmpty();
taskQueue.offer(message);
if (isEmpty) {
taskExecutor.execute(() -> {
while (!taskQueue.isEmpty()) {
Message msg = taskQueue.poll();
try {
JSONObject steamMsgJson = JSON.parseObject(msg.getBody(), JSONObject.class);
if (steamMsgJson == null) {
logger.warn("[收到redis 流变化]消息解析失败");
continue;
}
String serverId = steamMsgJson.getString("serverId");
if (userSetting.getServerId().equals(serverId)) {
// 自己发送的消息忽略即可
continue;
}
logger.info("[收到redis 流变化] {}", new String(message.getBody()));
String app = steamMsgJson.getString("app");
String stream = steamMsgJson.getString("stream");
boolean register = steamMsgJson.getBoolean("register");
String mediaServerId = steamMsgJson.getString("mediaServerId");
OnStreamChangedHookParam onStreamChangedHookParam = new OnStreamChangedHookParam();
onStreamChangedHookParam.setSeverId(serverId);
onStreamChangedHookParam.setApp(app);
onStreamChangedHookParam.setStream(stream);
onStreamChangedHookParam.setRegist(register);
onStreamChangedHookParam.setMediaServerId(mediaServerId);
onStreamChangedHookParam.setCreateStamp(System.currentTimeMillis()/1000);
onStreamChangedHookParam.setAliveSecond(0L);
onStreamChangedHookParam.setTotalReaderCount(0);
onStreamChangedHookParam.setOriginType(0);
onStreamChangedHookParam.setOriginTypeStr("0");
onStreamChangedHookParam.setOriginTypeStr("unknown");
ChannelOnlineEvent channelOnlineEventLister = zlmMediaListManager.getChannelOnlineEventLister(app, stream);
if ( channelOnlineEventLister != null) {
try {
channelOnlineEventLister.run(app, stream, serverId);;
} catch (ParseException e) {
logger.error("addPush: ", e);
}
zlmMediaListManager.removedChannelOnlineEventLister(app, stream);
}
}catch (Exception e) {
logger.warn("[REDIS消息-流变化] 发现未处理的异常, \r\n{}", JSON.toJSONString(message));
logger.error("[REDIS消息-流变化] 异常内容: ", e);
}
}
});
}
}
}

View File

@ -0,0 +1,296 @@
package com.genersoft.iot.vmp.service.redisMsg.control;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.event.hook.Hook;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.SendRtpPortManager;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import java.text.ParseException;
/**
* 其他wvp发起的rpc调用这里的方法被 RedisRpcConfig 通过反射寻找对应的方法名称调用
*/
@Component
public class RedisRpcController {
private final static Logger logger = LoggerFactory.getLogger(RedisRpcController.class);
@Autowired
private SSRCFactory ssrcFactory;
@Autowired
private IMediaServerService mediaServerService;
@Autowired
private SendRtpPortManager sendRtpPortManager;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private UserSetting userSetting;
@Autowired
private HookSubscribe hookSubscribe;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private ISIPCommanderForPlatform commanderFroPlatform;
@Autowired
private IVideoManagerStorage storager;
/**
* 获取发流的信息
*/
public RedisRpcResponse getSendRtpItem(RedisRpcRequest request) {
String sendRtpItemKey = request.getParam().toString();
SendRtpItem sendRtpItem = (SendRtpItem) redisTemplate.opsForValue().get(sendRtpItemKey);
if (sendRtpItem == null) {
logger.info("[redis-rpc] 获取发流的信息, 未找到redis中的发流信息 key{}", sendRtpItemKey);
RedisRpcResponse response = request.getResponse();
response.setStatusCode(200);
return response;
}
logger.info("[redis-rpc] 获取发流的信息: {}/{}, 目标地址: {}{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort());
// 查询本级是否有这个流
MediaServer mediaServerItem = mediaServerService.getMediaServerByAppAndStream(sendRtpItem.getApp(), sendRtpItem.getStream());
if (mediaServerItem == null) {
RedisRpcResponse response = request.getResponse();
response.setStatusCode(200);
}
// 自平台内容
int localPort = sendRtpPortManager.getNextPort(mediaServerItem);
if (localPort == 0) {
logger.info("[redis-rpc] getSendRtpItem->服务器端口资源不足" );
RedisRpcResponse response = request.getResponse();
response.setStatusCode(200);
}
// 写入redis 超时时回复
sendRtpItem.setStatus(1);
sendRtpItem.setServerId(userSetting.getServerId());
sendRtpItem.setLocalIp(mediaServerItem.getSdpIp());
if (sendRtpItem.getSsrc() == null) {
// 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
String ssrc = "Play".equalsIgnoreCase(sendRtpItem.getSessionName()) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
sendRtpItem.setSsrc(ssrc);
}
redisCatchStorage.updateSendRTPSever(sendRtpItem);
redisTemplate.opsForValue().set(sendRtpItemKey, sendRtpItem);
RedisRpcResponse response = request.getResponse();
response.setStatusCode(200);
response.setBody(sendRtpItemKey);
return response;
}
/**
* 监听流上线
*/
public RedisRpcResponse waitePushStreamOnline(RedisRpcRequest request) {
SendRtpItem sendRtpItem = JSONObject.parseObject(request.getParam().toString(), SendRtpItem.class);
logger.info("[redis-rpc] 监听流上线: {}/{}, 目标地址: {}{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort());
// 查询本级是否有这个流
MediaServer mediaServer = mediaServerService.getMediaServerByAppAndStream(sendRtpItem.getApp(), sendRtpItem.getStream());
if (mediaServer != null) {
logger.info("[redis-rpc] 监听流上线时发现流已存在直接返回: {}/{}, 目标地址: {}{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort() );
// 读取redis中的上级点播信息生成sendRtpItm发送出去
if (sendRtpItem.getSsrc() == null) {
// 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
String ssrc = "Play".equalsIgnoreCase(sendRtpItem.getSessionName()) ? ssrcFactory.getPlaySsrc(mediaServer.getId()) : ssrcFactory.getPlayBackSsrc(mediaServer.getId());
sendRtpItem.setSsrc(ssrc);
}
sendRtpItem.setMediaServerId(mediaServer.getId());
sendRtpItem.setLocalIp(mediaServer.getSdpIp());
sendRtpItem.setServerId(userSetting.getServerId());
redisTemplate.opsForValue().set(sendRtpItem.getRedisKey(), sendRtpItem);
RedisRpcResponse response = request.getResponse();
response.setBody(sendRtpItem.getRedisKey());
response.setStatusCode(200);
}
// 监听流上线 流上线直接发送sendRtpItem消息给实际的信令处理者
Hook hook = Hook.getInstance(HookType.on_media_arrival, sendRtpItem.getApp(), sendRtpItem.getStream(), null);
hookSubscribe.addSubscribe(hook, (hookData) -> {
logger.info("[redis-rpc] 监听流上线,流已上线: {}/{}, 目标地址: {}{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort());
// 读取redis中的上级点播信息生成sendRtpItm发送出去
if (sendRtpItem.getSsrc() == null) {
// 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
String ssrc = "Play".equalsIgnoreCase(sendRtpItem.getSessionName()) ? ssrcFactory.getPlaySsrc(hookData.getMediaServer().getId()) : ssrcFactory.getPlayBackSsrc(hookData.getMediaServer().getId());
sendRtpItem.setSsrc(ssrc);
}
sendRtpItem.setMediaServerId(hookData.getMediaServer().getId());
sendRtpItem.setLocalIp(hookData.getMediaServer().getSdpIp());
sendRtpItem.setServerId(userSetting.getServerId());
redisTemplate.opsForValue().set(sendRtpItem.getRedisKey(), sendRtpItem);
RedisRpcResponse response = request.getResponse();
response.setBody(sendRtpItem.getRedisKey());
response.setStatusCode(200);
// 手动发送结果
sendResponse(response);
hookSubscribe.removeSubscribe(hook);
});
return null;
}
/**
* 停止监听流上线
*/
public RedisRpcResponse stopWaitePushStreamOnline(RedisRpcRequest request) {
SendRtpItem sendRtpItem = JSONObject.parseObject(request.getParam().toString(), SendRtpItem.class);
logger.info("[redis-rpc] 停止监听流上线: {}/{}, 目标地址: {}{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort() );
// 监听流上线 流上线直接发送sendRtpItem消息给实际的信令处理者
Hook hook = Hook.getInstance(HookType.on_media_arrival, sendRtpItem.getApp(), sendRtpItem.getStream(), null);
hookSubscribe.removeSubscribe(hook);
RedisRpcResponse response = request.getResponse();
response.setStatusCode(200);
return response;
}
/**
* 开始发流
*/
public RedisRpcResponse startSendRtp(RedisRpcRequest request) {
String sendRtpItemKey = request.getParam().toString();
SendRtpItem sendRtpItem = (SendRtpItem) redisTemplate.opsForValue().get(sendRtpItemKey);
RedisRpcResponse response = request.getResponse();
response.setStatusCode(200);
if (sendRtpItem == null) {
logger.info("[redis-rpc] 开始发流, 未找到redis中的发流信息 key{}", sendRtpItemKey);
WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "未找到redis中的发流信息");
response.setBody(wvpResult);
return response;
}
logger.info("[redis-rpc] 开始发流: {}/{}, 目标地址: {}{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort());
MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
if (mediaServer == null) {
logger.info("[redis-rpc] startSendRtp->未找到MediaServer {}", sendRtpItem.getMediaServerId() );
WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "未找到MediaServer");
response.setBody(wvpResult);
return response;
}
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream());
if (mediaInfo != null) {
logger.info("[redis-rpc] startSendRtp->流不在线: {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream() );
WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "流不在线");
response.setBody(wvpResult);
return response;
}
try {
mediaServerService.startSendRtp(mediaServer, null, sendRtpItem);
}catch (ControllerException exception) {
logger.info("[redis-rpc] 发流失败: {}/{}, 目标地址: {}{} {}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort(), exception.getMsg());
WVPResult wvpResult = WVPResult.fail(exception.getCode(), exception.getMsg());
response.setBody(wvpResult);
return response;
}
logger.info("[redis-rpc] 发流成功: {}/{}, 目标地址: {}{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort());
WVPResult wvpResult = WVPResult.success();
response.setBody(wvpResult);
return response;
}
/**
* 停止发流
*/
public RedisRpcResponse stopSendRtp(RedisRpcRequest request) {
String sendRtpItemKey = request.getParam().toString();
SendRtpItem sendRtpItem = (SendRtpItem) redisTemplate.opsForValue().get(sendRtpItemKey);
RedisRpcResponse response = request.getResponse();
response.setStatusCode(200);
if (sendRtpItem == null) {
logger.info("[redis-rpc] 停止推流, 未找到redis中的发流信息 key{}", sendRtpItemKey);
WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "未找到redis中的发流信息");
response.setBody(wvpResult);
return response;
}
logger.info("[redis-rpc] 停止推流: {}/{}, 目标地址: {}{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort() );
MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
if (mediaServer == null) {
logger.info("[redis-rpc] stopSendRtp->未找到MediaServer {}", sendRtpItem.getMediaServerId() );
WVPResult wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "未找到MediaServer");
response.setBody(wvpResult);
return response;
}
try {
mediaServerService.stopSendRtp(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc());
}catch (ControllerException exception) {
logger.info("[redis-rpc] 停止推流失败: {}/{}, 目标地址: {}{} code {}, msg: {}", sendRtpItem.getApp(),
sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort(), exception.getCode(), exception.getMsg() );
response.setBody(WVPResult.fail(exception.getCode(), exception.getMsg()));
return response;
}
logger.info("[redis-rpc] 停止推流成功: {}/{}, 目标地址: {}{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort() );
response.setBody(WVPResult.success());
return response;
}
/**
* 其他wvp通知推流已经停止了
*/
public RedisRpcResponse rtpSendStopped(RedisRpcRequest request) {
String sendRtpItemKey = request.getParam().toString();
SendRtpItem sendRtpItem = (SendRtpItem) redisTemplate.opsForValue().get(sendRtpItemKey);
RedisRpcResponse response = request.getResponse();
response.setStatusCode(200);
if (sendRtpItem == null) {
logger.info("[redis-rpc] 推流已经停止, 未找到redis中的发流信息 key{}", sendRtpItemKey);
return response;
}
logger.info("[redis-rpc] 推流已经停止: {}/{}, 目标地址: {}{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort() );
String platformId = sendRtpItem.getPlatformId();
ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
if (platform == null) {
return response;
}
try {
commanderFroPlatform.streamByeCmd(platform, sendRtpItem);
redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(),
sendRtpItem.getCallId(), sendRtpItem.getStream());
redisCatchStorage.sendPlatformStopPlayMsg(sendRtpItem, platform);
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 发送BYE: {}", e.getMessage());
}
return response;
}
private void sendResponse(RedisRpcResponse response){
logger.info("[redis-rpc] >> {}", response);
response.setToId(userSetting.getServerId());
RedisRpcMessage message = new RedisRpcMessage();
message.setResponse(response);
redisTemplate.convertAndSend(RedisRpcConfig.REDIS_REQUEST_CHANNEL_KEY, message);
}
}

View File

@ -0,0 +1,151 @@
package com.genersoft.iot.vmp.service.redisMsg.service;
import com.alibaba.fastjson2.JSON;
import com.genersoft.iot.vmp.common.CommonCallback;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
import com.genersoft.iot.vmp.media.event.hook.Hook;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcService;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class RedisRpcServiceImpl implements IRedisRpcService {
private final static Logger logger = LoggerFactory.getLogger(RedisRpcServiceImpl.class);
@Autowired
private RedisRpcConfig redisRpcConfig;
@Autowired
private UserSetting userSetting;
@Autowired
private HookSubscribe hookSubscribe;
@Autowired
private SSRCFactory ssrcFactory;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
private RedisRpcRequest buildRequest(String uri, Object param) {
RedisRpcRequest request = new RedisRpcRequest();
request.setFromId(userSetting.getServerId());
request.setParam(param);
request.setUri(uri);
return request;
}
@Override
public SendRtpItem getSendRtpItem(String sendRtpItemKey) {
RedisRpcRequest request = buildRequest("getSendRtpItem", sendRtpItemKey);
RedisRpcResponse response = redisRpcConfig.request(request, 10);
if (response.getBody() == null) {
return null;
}
return (SendRtpItem)redisTemplate.opsForValue().get(response.getBody().toString());
}
@Override
public WVPResult startSendRtp(String sendRtpItemKey, SendRtpItem sendRtpItem) {
logger.info("[请求其他WVP] 开始推流wvp{} {}/{}", sendRtpItem.getServerId(), sendRtpItem.getApp(), sendRtpItem.getStream());
RedisRpcRequest request = buildRequest("startSendRtp", sendRtpItemKey);
request.setToId(sendRtpItem.getServerId());
RedisRpcResponse response = redisRpcConfig.request(request, 10);
return JSON.parseObject(response.getBody().toString(), WVPResult.class);
}
@Override
public WVPResult stopSendRtp(String sendRtpItemKey) {
SendRtpItem sendRtpItem = (SendRtpItem)redisTemplate.opsForValue().get(sendRtpItemKey);
if (sendRtpItem == null) {
logger.info("[请求其他WVP] 停止推流, 未找到redis中的发流信息 key{}", sendRtpItemKey);
return WVPResult.fail(ErrorCode.ERROR100.getCode(), "未找到发流信息");
}
logger.info("[请求其他WVP] 停止推流wvp{} {}/{}", sendRtpItem.getServerId(), sendRtpItem.getApp(), sendRtpItem.getStream());
RedisRpcRequest request = buildRequest("stopSendRtp", sendRtpItemKey);
request.setToId(sendRtpItem.getServerId());
RedisRpcResponse response = redisRpcConfig.request(request, 10);
return JSON.parseObject(response.getBody().toString(), WVPResult.class);
}
@Override
public long waitePushStreamOnline(SendRtpItem sendRtpItem, CommonCallback<String> callback) {
logger.info("[请求所有WVP监听流上线] {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream());
// 监听流上线 流上线直接发送sendRtpItem消息给实际的信令处理者
Hook hook = Hook.getInstance(HookType.on_media_arrival, sendRtpItem.getApp(), sendRtpItem.getStream(), null);
RedisRpcRequest request = buildRequest("waitePushStreamOnline", sendRtpItem);
request.setToId(sendRtpItem.getServerId());
hookSubscribe.addSubscribe(hook, (hookData) -> {
// 读取redis中的上级点播信息生成sendRtpItm发送出去
if (sendRtpItem.getSsrc() == null) {
// 上级平台点播时不使用上级平台指定的ssrc使用自定义的ssrc参考国标文档-点播外域设备媒体流SSRC处理方式
String ssrc = "Play".equalsIgnoreCase(sendRtpItem.getSessionName()) ? ssrcFactory.getPlaySsrc(hookData.getMediaServer().getId()) : ssrcFactory.getPlayBackSsrc(hookData.getMediaServer().getId());
sendRtpItem.setSsrc(ssrc);
}
sendRtpItem.setMediaServerId(hookData.getMediaServer().getId());
sendRtpItem.setLocalIp(hookData.getMediaServer().getSdpIp());
sendRtpItem.setServerId(userSetting.getServerId());
redisTemplate.opsForValue().set(sendRtpItem.getRedisKey(), sendRtpItem);
if (callback != null) {
callback.run(sendRtpItem.getRedisKey());
}
hookSubscribe.removeSubscribe(hook);
redisRpcConfig.removeCallback(request.getSn());
});
redisRpcConfig.request(request, response -> {
if (response.getBody() == null) {
logger.info("[请求所有WVP监听流上线] 流上线,但是未找到发流信息:{}/{}", sendRtpItem.getApp(), sendRtpItem.getStream());
return;
}
logger.info("[请求所有WVP监听流上线] 流上线 {}/{}->{}", sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.toString());
if (callback != null) {
callback.run(response.getBody().toString());
}
hookSubscribe.removeSubscribe(hook);
});
return request.getSn();
}
@Override
public void stopWaitePushStreamOnline(SendRtpItem sendRtpItem) {
logger.info("[停止WVP监听流上线] {}/{}", sendRtpItem.getApp(), sendRtpItem.getStream());
Hook hook = Hook.getInstance(HookType.on_media_arrival, sendRtpItem.getApp(), sendRtpItem.getStream(), null);
hookSubscribe.removeSubscribe(hook);
RedisRpcRequest request = buildRequest("stopWaitePushStreamOnline", sendRtpItem);
request.setToId(sendRtpItem.getServerId());
redisRpcConfig.request(request, 10);
}
@Override
public void rtpSendStopped(String sendRtpItemKey) {
SendRtpItem sendRtpItem = (SendRtpItem)redisTemplate.opsForValue().get(sendRtpItemKey);
if (sendRtpItem == null) {
logger.info("[停止WVP监听流上线] 未找到redis中的发流信息 key{}", sendRtpItemKey);
return;
}
RedisRpcRequest request = buildRequest("rtpSendStopped", sendRtpItemKey);
request.setToId(sendRtpItem.getServerId());
redisRpcConfig.request(request, 10);
}
@Override
public void removeCallback(long key) {
redisRpcConfig.removeCallback(key);
}
}

View File

@ -2,10 +2,7 @@ package com.genersoft.iot.vmp.storager;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.SystemAllInfo;
import com.genersoft.iot.vmp.gb28181.bean.AlarmChannelMessage;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent;
@ -45,6 +42,8 @@ public interface IRedisCatchStorage {
void updateSendRTPSever(SendRtpItem sendRtpItem);
List<SendRtpItem> querySendRTPServer(String platformGbId, String channelId, String streamId);
/**
* 查询RTP推送信息缓存
* @param platformGbId
@ -197,6 +196,8 @@ public interface IRedisCatchStorage {
void addDiskInfo(List<Map<String, Object>> diskInfo);
void deleteSendRTPServer(SendRtpItem sendRtpItem);
List<SendRtpItem> queryAllSendRTPServer();
List<Device> getAllDevices();
@ -209,7 +210,7 @@ public interface IRedisCatchStorage {
void sendPlatformStartPlayMsg(MessageForPushChannel messageForPushChannel);
void sendPlatformStopPlayMsg(MessageForPushChannel messageForPushChannel);
void sendPlatformStopPlayMsg(SendRtpItem sendRtpItem, ParentPlatform platform);
void addPushListItem(String app, String stream, MediaArrivalEvent param);
@ -219,4 +220,11 @@ public interface IRedisCatchStorage {
void sendPushStreamClose(MessageForPushChannel messageForPushChannel);
void addWaiteSendRtpItem(SendRtpItem sendRtpItem, int platformPlayTimeout);
SendRtpItem getWaiteSendRtpItem(String app, String stream);
void sendStartSendRtp(SendRtpItem sendRtpItem);
void sendPushStreamOnline(SendRtpItem sendRtpItem);
}

View File

@ -50,12 +50,15 @@ public interface CloudRecordServiceMapper {
" <if test= 'mediaServerItemList != null ' > and media_server_id in " +
" <foreach collection='mediaServerItemList' item='item' open='(' separator=',' close=')' > #{item.id}</foreach>" +
" </if>" +
" <if test= 'ids != null ' > and id in " +
" <foreach collection='ids' item='item' open='(' separator=',' close=')' > #{item}</foreach>" +
" </if>" +
" order by start_time DESC" +
" </script>")
List<CloudRecordItem> getList(@Param("query") String query, @Param("app") String app, @Param("stream") String stream,
@Param("startTimeStamp")Long startTimeStamp, @Param("endTimeStamp")Long endTimeStamp,
@Param("callId")String callId, List<MediaServer> mediaServerItemList);
@Param("callId")String callId, List<MediaServer> mediaServerItemList,
List<Integer> ids);
@Select(" <script>" +

View File

@ -52,8 +52,8 @@ public interface DeviceChannelMapper {
"<if test='status != null'>, status=#{status}</if>" +
"<if test='streamId != null'>, stream_id=#{streamId}</if>" +
"<if test='hasAudio != null'>, has_audio=#{hasAudio}</if>" +
", custom_longitude=#{longitude}" +
", custom_latitude=#{latitude}" +
"<if test='customLongitude != null'>, custom_longitude=#{customLongitude}</if>" +
"<if test='customLatitude != null'>, custom_latitude=#{customLatitude}</if>" +
"<if test='longitudeGcj02 != null'>, longitude_gcj02=#{longitudeGcj02}</if>" +
"<if test='latitudeGcj02 != null'>, latitude_gcj02=#{latitudeGcj02}</if>" +
"<if test='longitudeWgs84 != null'>, longitude_wgs84=#{longitudeWgs84}</if>" +
@ -89,8 +89,10 @@ public interface DeviceChannelMapper {
"dc.password, " +
"COALESCE(dc.custom_ptz_type, dc.ptz_type) AS ptz_type, " +
"dc.status, " +
"COALESCE(dc.custom_longitude, dc.longitude) AS longitude, " +
"COALESCE(dc.custom_latitude, dc.latitude) AS latitude, " +
"dc.longitude, " +
"dc.latitude, " +
"dc.custom_longitude, " +
"dc.custom_latitude, " +
"dc.stream_id, " +
"dc.device_id, " +
"dc.parental, " +
@ -345,6 +347,8 @@ public interface DeviceChannelMapper {
"<if test='item.hasAudio != null'>, has_audio=#{item.hasAudio}</if>" +
"<if test='item.longitude != null'>, longitude=#{item.longitude}</if>" +
"<if test='item.latitude != null'>, latitude=#{item.latitude}</if>" +
"<if test='item.customLongitude != null'>, custom_longitude=#{item.customLongitude}</if>" +
"<if test='item.customLatitude != null'>, custom_latitude=#{item.customLatitude}</if>" +
"<if test='item.longitudeGcj02 != null'>, longitude_gcj02=#{item.longitudeGcj02}</if>" +
"<if test='item.latitudeGcj02 != null'>, latitude_gcj02=#{item.latitudeGcj02}</if>" +
"<if test='item.longitudeWgs84 != null'>, longitude_wgs84=#{item.longitudeWgs84}</if>" +
@ -353,7 +357,8 @@ public interface DeviceChannelMapper {
"<if test='item.gpsTime != null'>, gps_time=#{item.gpsTime}</if>" +
"<if test='item.streamIdentification != null'>, stream_identification=#{item.streamIdentification}</if>" +
"<if test='item.id > 0'>WHERE id=#{item.id}</if>" +
"<if test='item.id == 0'>WHERE device_id=#{item.deviceId} AND channel_id=#{item.channelId}</if>" +
"<if test='item.id == 0 and item.channelId != null '>WHERE device_id=#{item.deviceId} AND channel_id=#{item.channelId}</if>" +
"<if test='item.id == 0 and item.channelId == null '>WHERE device_id=#{item.deviceId}</if>" +
"</foreach>" +
"</script>"})
int batchUpdate(List<DeviceChannel> updateChannels);
@ -550,4 +555,24 @@ public interface DeviceChannelMapper {
" <if test='channelId != null'> and channel_id = #{channelId} </if>" +
"</script>")
void updateChannelStreamIdentification(DeviceChannel channel);
@Update({"<script>" +
"<foreach collection='channelList' item='item' separator=';'>" +
" UPDATE" +
" wvp_device_channel" +
" SET update_time=#{item.updateTime}" +
"<if test='item.longitude != null'>, longitude=#{item.longitude}</if>" +
"<if test='item.latitude != null'>, latitude=#{item.latitude}</if>" +
"<if test='item.longitudeGcj02 != null'>, longitude_gcj02=#{item.longitudeGcj02}</if>" +
"<if test='item.latitudeGcj02 != null'>, latitude_gcj02=#{item.latitudeGcj02}</if>" +
"<if test='item.longitudeWgs84 != null'>, longitude_wgs84=#{item.longitudeWgs84}</if>" +
"<if test='item.latitudeWgs84 != null'>, latitude_wgs84=#{item.latitudeWgs84}</if>" +
"<if test='item.gpsTime != null'>, gps_time=#{item.gpsTime}</if>" +
"<if test='item.id > 0'>WHERE id=#{item.id}</if>" +
"<if test='item.id == 0'>WHERE device_id=#{item.deviceId} AND channel_id=#{item.channelId}</if>" +
"</foreach>" +
"</script>"})
void batchUpdatePosition(List<DeviceChannel> channelList);
}

View File

@ -33,4 +33,33 @@ public interface DeviceMobilePositionMapper {
@Delete("DELETE FROM wvp_device_mobile_position WHERE device_id = #{deviceId}")
int clearMobilePositionsByDeviceId(String deviceId);
@Insert("<script> " +
"insert into wvp_device_mobile_position " +
"(device_id,channel_id, device_name,time,longitude,latitude,altitude,speed,direction,report_source," +
"longitude_gcj02,latitude_gcj02,longitude_wgs84,latitude_wgs84,create_time)"+
"values " +
"<foreach collection='mobilePositions' index='index' item='item' separator=','> " +
"(#{item.deviceId}, #{item.channelId}, #{item.deviceName}, #{item.time}, #{item.longitude}, " +
"#{item.latitude}, #{item.altitude}, #{item.speed},#{item.direction}," +
"#{item.reportSource}, #{item.longitudeGcj02}, #{item.latitudeGcj02}, #{item.longitudeWgs84}, #{item.latitudeWgs84}, " +
"#{item.createTime}) " +
"</foreach> " +
"</script>")
void batchadd2(List<MobilePosition> mobilePositions);
@Insert("<script> " +
"<foreach collection='mobilePositions' index='index' item='item' separator=';'> " +
"insert into wvp_device_mobile_position " +
"(device_id,channel_id, device_name,time,longitude,latitude,altitude,speed,direction,report_source," +
"longitude_gcj02,latitude_gcj02,longitude_wgs84,latitude_wgs84,create_time)"+
"values " +
"(#{item.deviceId}, #{item.channelId}, #{item.deviceName}, #{item.time}, #{item.longitude}, " +
"#{item.latitude}, #{item.altitude}, #{item.speed},#{item.direction}," +
"#{item.reportSource}, #{item.longitudeGcj02}, #{item.latitudeGcj02}, #{item.longitudeWgs84}, #{item.latitudeWgs84}, " +
"#{item.createTime}) " +
"</foreach> " +
"</script>")
void batchadd(List<MobilePosition> mobilePositions);
}

View File

@ -41,6 +41,7 @@ public interface MediaServerMapper {
"type,"+
"create_time,"+
"update_time,"+
"transcode_suffix,"+
"hook_alive_interval"+
") VALUES " +
"(" +
@ -72,6 +73,7 @@ public interface MediaServerMapper {
"#{type}, " +
"#{createTime}, " +
"#{updateTime}, " +
"#{transcodeSuffix}, " +
"#{hookAliveInterval})")
int add(MediaServer mediaServerItem);
@ -102,6 +104,7 @@ public interface MediaServerMapper {
"<if test=\"hookAliveInterval != null\">, hook_alive_interval=#{hookAliveInterval}</if>" +
"<if test=\"recordDay != null\">, record_day=#{recordDay}</if>" +
"<if test=\"recordPath != null\">, record_path=#{recordPath}</if>" +
"<if test=\"transcodeSuffix != null\">, transcode_suffix=#{transcodeSuffix}</if>" +
"<if test=\"type != null\">, type=#{type}</if>" +
"WHERE id=#{id}"+
" </script>"})
@ -133,6 +136,7 @@ public interface MediaServerMapper {
"<if test=\"recordDay != null\">, record_day=#{recordDay}</if>" +
"<if test=\"recordPath != null\">, record_path=#{recordPath}</if>" +
"<if test=\"type != null\">, type=#{type}</if>" +
"<if test=\"transcodeSuffix != null\">, transcode_suffix=#{transcodeSuffix}</if>" +
"<if test=\"hookAliveInterval != null\">, hook_alive_interval=#{hookAliveInterval}</if>" +
"WHERE ip=#{ip} and http_port=#{httpPort}"+
" </script>"})

View File

@ -12,6 +12,7 @@ import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
@ -81,12 +82,8 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
@Override
public void resetAllCSEQ() {
String scanKey = VideoManagerConstants.SIP_CSEQ_PREFIX + userSetting.getServerId() + "_*";
List<Object> keys = RedisUtil.scan(redisTemplate, scanKey);
for (Object o : keys) {
String key = (String) o;
redisTemplate.opsForValue().set(key, 1);
}
String key = VideoManagerConstants.SIP_CSEQ_PREFIX + userSetting.getServerId();
redisTemplate.opsForValue().set(key, 1);
}
@Override
@ -146,15 +143,26 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
@Override
public void updateSendRTPSever(SendRtpItem sendRtpItem) {
redisTemplate.opsForValue().set(sendRtpItem.getRedisKey(), sendRtpItem);
}
String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX +
userSetting.getServerId() + "_"
+ sendRtpItem.getMediaServerId() + "_"
+ sendRtpItem.getPlatformId() + "_"
+ sendRtpItem.getChannelId() + "_"
+ sendRtpItem.getStream() + "_"
+ sendRtpItem.getCallId();
redisTemplate.opsForValue().set(key, sendRtpItem);
@Override
public List<SendRtpItem> querySendRTPServer(String platformGbId, String channelId, String streamId) {
String scanKey = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX
+ userSetting.getServerId() + "_*_"
+ platformGbId + "_"
+ channelId + "_"
+ streamId + "_"
+ "*";
List<SendRtpItem> result = new ArrayList<>();
List<Object> scan = RedisUtil.scan(redisTemplate, scanKey);
if (!scan.isEmpty()) {
for (Object o : scan) {
String key = (String) o;
result.add(JsonUtil.redisJsonToObject(redisTemplate, key, SendRtpItem.class));
}
}
return result;
}
@Override
@ -172,7 +180,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
callId = "*";
}
String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX
+ userSetting.getServerId() + "_*_"
+ "*_*_"
+ platformGbId + "_"
+ channelId + "_"
+ streamId + "_"
@ -268,11 +276,20 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
List<Object> scan = RedisUtil.scan(redisTemplate, key);
if (scan.size() > 0) {
for (Object keyStr : scan) {
logger.info("[删除 redis的SendRTP] {}", keyStr.toString());
redisTemplate.delete(keyStr);
}
}
}
/**
* 删除RTP推送信息缓存
*/
@Override
public void deleteSendRTPServer(SendRtpItem sendRtpItem) {
deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(),sendRtpItem.getCallId(), sendRtpItem.getStream());
}
@Override
public List<SendRtpItem> queryAllSendRTPServer() {
String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX
@ -555,7 +572,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
@Override
public void sendMobilePositionMsg(JSONObject jsonObject) {
String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_MOBILE_POSITION;
logger.info("[redis发送通知] 发送 移动位置 {}: {}", key, jsonObject.toString());
logger.debug("[redis发送通知] 发送 移动位置 {}: {}", key, jsonObject.toString());
redisTemplate.convertAndSend(key, jsonObject);
}
@ -646,9 +663,15 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
}
@Override
public void sendPlatformStopPlayMsg(MessageForPushChannel msg) {
public void sendPlatformStopPlayMsg(SendRtpItem sendRtpItem, ParentPlatform platform) {
MessageForPushChannel msg = MessageForPushChannel.getInstance(0,
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
sendRtpItem.getPlatformId(), platform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
msg.setPlatFormIndex(platform.getId());
String key = VideoManagerConstants.VM_MSG_STREAM_STOP_PLAY_NOTIFY;
logger.info("[redis发送通知] 发送 上级平台停止观看 {}: {}/{}->{}", key, msg.getApp(), msg.getStream(), msg.getPlatFormId());
logger.info("[redis发送通知] 发送 上级平台停止观看 {}: {}/{}->{}", key, sendRtpItem.getApp(), sendRtpItem.getStream(), platform.getServerGBId());
redisTemplate.convertAndSend(key, JSON.toJSON(msg));
}
@ -681,4 +704,30 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
logger.info("[redis发送通知] 发送 停止向上级推流 {}: {}/{}->{}", key, msg.getApp(), msg.getStream(), msg.getPlatFormId());
redisTemplate.convertAndSend(key, JSON.toJSON(msg));
}
@Override
public void addWaiteSendRtpItem(SendRtpItem sendRtpItem, int platformPlayTimeout) {
String key = VideoManagerConstants.WAITE_SEND_PUSH_STREAM + sendRtpItem.getApp() + "_" + sendRtpItem.getStream();
redisTemplate.opsForValue().set(key, sendRtpItem);
}
@Override
public SendRtpItem getWaiteSendRtpItem(String app, String stream) {
String key = VideoManagerConstants.WAITE_SEND_PUSH_STREAM + app + "_" + stream;
return JsonUtil.redisJsonToObject(redisTemplate, key, SendRtpItem.class);
}
@Override
public void sendStartSendRtp(SendRtpItem sendRtpItem) {
String key = VideoManagerConstants.START_SEND_PUSH_STREAM + sendRtpItem.getApp() + "_" + sendRtpItem.getStream();
logger.info("[redis发送通知] 通知其他WVP推流 {}: {}/{}->{}", key, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getPlatformId());
redisTemplate.convertAndSend(key, JSON.toJSON(sendRtpItem));
}
@Override
public void sendPushStreamOnline(SendRtpItem sendRtpItem) {
String key = VideoManagerConstants.VM_MSG_STREAM_PUSH_CLOSE_REQUESTED;
logger.info("[redis发送通知] 流上线 {}: {}/{}->{}", key, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getPlatformId());
redisTemplate.convertAndSend(key, JSON.toJSON(sendRtpItem));
}
}

View File

@ -7,7 +7,6 @@ import com.genersoft.iot.vmp.gb28181.bean.*;
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.dto.StreamProxyItem;
import com.genersoft.iot.vmp.service.IGbStreamService;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
@ -139,7 +138,7 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
gbIdSet.add(deviceChannel.getChannelId());
if (allChannelMap.containsKey(deviceChannel.getChannelId())) {
deviceChannel.setStreamId(allChannelMap.get(deviceChannel.getChannelId()).getStreamId());
deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).isHasAudio());
deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).getHasAudio());
if (allChannelMap.get(deviceChannel.getChannelId()).isStatus() !=deviceChannel.isStatus()){
List<String> strings = platformChannelMapper.queryParentPlatformByChannelId(deviceChannel.getChannelId());
if (!CollectionUtils.isEmpty(strings)){
@ -275,7 +274,7 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
deviceChannel.setUpdateTime(DateUtil.getNow());
if (allChannelMap.containsKey(deviceChannel.getChannelId())) {
deviceChannel.setStreamId(allChannelMap.get(deviceChannel.getChannelId()).getStreamId());
deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).isHasAudio());
deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).getHasAudio());
if (allChannelMap.get(deviceChannel.getChannelId()).isStatus() !=deviceChannel.isStatus()){
List<String> strings = platformChannelMapper.queryParentPlatformByChannelId(deviceChannel.getChannelId());
if (!CollectionUtils.isEmpty(strings)){

View File

@ -106,6 +106,14 @@ public class DateUtil {
return formatter.format(LocalDateTime.ofInstant(instant, ZoneId.of(zoneStr)));
}
/**
* 时间戳 yyyy_MM_dd_HH_mm_ss
*/
public static String timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(long timestamp) {
Instant instant = Instant.ofEpochMilli(timestamp);
return urlFormatter.format(LocalDateTime.ofInstant(instant, ZoneId.of(zoneStr)));
}
/**
* yyyy_MM_dd_HH_mm_ss 转时间戳毫秒
*

View File

@ -0,0 +1,26 @@
package com.genersoft.iot.vmp.utils;
import org.springframework.util.ObjectUtils;
import java.util.HashMap;
import java.util.Map;
public class MediaServerUtils {
public static Map<String, String> urlParamToMap(String params) {
HashMap<String, String> map = new HashMap<>();
if (ObjectUtils.isEmpty(params)) {
return map;
}
String[] paramsArray = params.split("&");
if (paramsArray.length == 0) {
return map;
}
for (String param : paramsArray) {
String[] paramArray = param.split("=");
if (paramArray.length == 2) {
map.put(paramArray[0], paramArray[1]);
}
}
return map;
}
}

View File

@ -8,7 +8,9 @@ import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.cloudRecord.bean.CloudRecordUrl;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -21,9 +23,15 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@SuppressWarnings("rawtypes")
@Tag(name = "云端录像接口")
@ -67,22 +75,22 @@ public class CloudRecordController {
if (ObjectUtils.isEmpty(month)) {
month = calendar.get(Calendar.MONTH) + 1;
}
List<MediaServer> mediaServerItems;
List<MediaServer> mediaServers;
if (!ObjectUtils.isEmpty(mediaServerId)) {
mediaServerItems = new ArrayList<>();
MediaServer mediaServerItem = mediaServerService.getOne(mediaServerId);
if (mediaServerItem == null) {
mediaServers = new ArrayList<>();
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId);
}
mediaServerItems.add(mediaServerItem);
mediaServers.add(mediaServer);
} else {
mediaServerItems = mediaServerService.getAllOnlineList();
mediaServers = mediaServerService.getAllOnlineList();
}
if (mediaServerItems.isEmpty()) {
if (mediaServers.isEmpty()) {
return new ArrayList<>();
}
return cloudRecordService.getDateList(app, stream, year, month, mediaServerItems);
return cloudRecordService.getDateList(app, stream, year, month, mediaServers);
}
@ResponseBody
@ -96,6 +104,7 @@ public class CloudRecordController {
@Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = false)
@Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = false)
@Parameter(name = "mediaServerId", description = "流媒体ID置空则查询全部流媒体", required = false)
@Parameter(name = "callId", description = "每次录像的唯一标识,置空则查询全部流媒体", required = false)
public PageInfo<CloudRecordItem> openRtpServer(
@RequestParam(required = false) String query,
@RequestParam(required = false) String app,
@ -104,24 +113,25 @@ public class CloudRecordController {
@RequestParam int count,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime,
@RequestParam(required = false) String mediaServerId
@RequestParam(required = false) String mediaServerId,
@RequestParam(required = false) String callId
) {
logger.info("[云端录像] 查询 app->{}, stream->{}, mediaServerId->{}, page->{}, count->{}, startTime->{}, endTime->{}",
app, stream, mediaServerId, page, count, startTime, endTime);
logger.info("[云端录像] 查询 app->{}, stream->{}, mediaServerId->{}, page->{}, count->{}, startTime->{}, endTime->{}, callId->{}",
app, stream, mediaServerId, page, count, startTime, endTime, callId);
List<MediaServer> mediaServerItems;
List<MediaServer> mediaServers;
if (!ObjectUtils.isEmpty(mediaServerId)) {
mediaServerItems = new ArrayList<>();
MediaServer mediaServerItem = mediaServerService.getOne(mediaServerId);
if (mediaServerItem == null) {
mediaServers = new ArrayList<>();
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId);
}
mediaServerItems.add(mediaServerItem);
mediaServers.add(mediaServer);
} else {
mediaServerItems = mediaServerService.getAllOnlineList();
mediaServers = mediaServerService.getAllOnlineList();
}
if (mediaServerItems.isEmpty()) {
if (mediaServers.isEmpty()) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "当前无流媒体");
}
if (query != null && ObjectUtils.isEmpty(query.trim())) {
@ -139,7 +149,10 @@ public class CloudRecordController {
if (endTime != null && ObjectUtils.isEmpty(endTime.trim())) {
endTime = null;
}
return cloudRecordService.getList(page, count, query, app, stream, startTime, endTime, mediaServerItems);
if (callId != null && ObjectUtils.isEmpty(callId.trim())) {
callId = null;
}
return cloudRecordService.getList(page, count, query, app, stream, startTime, endTime, mediaServers, callId);
}
@ResponseBody
@ -162,20 +175,20 @@ public class CloudRecordController {
@RequestParam(required = false) String callId,
@RequestParam(required = false) String remoteHost
){
MediaServer mediaServerItem;
MediaServer mediaServer;
if (mediaServerId == null) {
mediaServerItem = mediaServerService.getDefaultMediaServer();
mediaServer = mediaServerService.getDefaultMediaServer();
}else {
mediaServerItem = mediaServerService.getOne(mediaServerId);
mediaServer = mediaServerService.getOne(mediaServerId);
}
if (mediaServerItem == null) {
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的流媒体");
}else {
if (remoteHost == null) {
remoteHost = request.getScheme() + "://" + mediaServerItem.getIp() + ":" + mediaServerItem.getRecordAssistPort();
remoteHost = request.getScheme() + "://" + mediaServer.getIp() + ":" + mediaServer.getRecordAssistPort();
}
}
return cloudRecordService.addTask(app, stream, mediaServerItem, startTime, endTime, callId, remoteHost, mediaServerId != null);
return cloudRecordService.addTask(app, stream, mediaServer, startTime, endTime, callId, remoteHost, mediaServerId != null);
}
@ResponseBody
@ -265,4 +278,212 @@ public class CloudRecordController {
){
return cloudRecordService.getPlayUrlPath(recordId);
}
/************************* 以下这些接口只适合wvp和zlm部署在同一台服务器的情况且wvp只有一个zlm节点的情况 ***************************************/
/**
* 下载指定录像文件的压缩包
* @param query 检索内容
* @param app 应用名
* @param stream 流ID
* @param startTime 开始时间(yyyy-MM-dd HH:mm:ss)
* @param endTime 结束时间(yyyy-MM-dd HH:mm:ss)
* @param mediaServerId 流媒体ID置空则查询全部流媒体
* @param callId 每次录像的唯一标识置空则查询全部流媒体
* @param ids 指定的Id
*/
@ResponseBody
@GetMapping("/zip")
public void downloadZipFile(
HttpServletResponse response,
@RequestParam(required = false) String query,
@RequestParam(required = false) String app,
@RequestParam(required = false) String stream,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime,
@RequestParam(required = false) String mediaServerId,
@RequestParam(required = false) String callId,
@RequestParam(required = false) List<Integer> ids
) {
logger.info("[下载指定录像文件的压缩包] 查询 app->{}, stream->{}, mediaServerId->{}, startTime->{}, endTime->{}, callId->{}",
app, stream, mediaServerId, startTime, endTime, callId);
List<MediaServer> mediaServers;
if (!ObjectUtils.isEmpty(mediaServerId)) {
mediaServers = new ArrayList<>();
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId);
}
mediaServers.add(mediaServer);
} else {
mediaServers = mediaServerService.getAll();
}
if (mediaServers.isEmpty()) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "当前无流媒体");
}
if (query != null && ObjectUtils.isEmpty(query.trim())) {
query = null;
}
if (app != null && ObjectUtils.isEmpty(app.trim())) {
app = null;
}
if (stream != null && ObjectUtils.isEmpty(stream.trim())) {
stream = null;
}
if (startTime != null && ObjectUtils.isEmpty(startTime.trim())) {
startTime = null;
}
if (endTime != null && ObjectUtils.isEmpty(endTime.trim())) {
endTime = null;
}
if (callId != null && ObjectUtils.isEmpty(callId.trim())) {
callId = null;
}
if (stream != null && callId != null) {
response.addHeader( "Content-Disposition", "attachment;filename=" + stream + "_" + callId + ".zip" );
}
List<CloudRecordItem> cloudRecordItemList = cloudRecordService.getAllList(query, app, stream, startTime, endTime, mediaServers, callId, ids);
if (ObjectUtils.isEmpty(cloudRecordItemList)) {
return;
}
try {
ZipOutputStream zos = new ZipOutputStream(response.getOutputStream());
for (CloudRecordItem cloudRecordItem : cloudRecordItemList) {
zos.putNextEntry(new ZipEntry(DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(cloudRecordItem.getStartTime()) + ".mp4"));
File file = new File(cloudRecordItem.getFilePath());
if (!file.exists() || file.isDirectory()) {
continue;
}
FileInputStream fis = new FileInputStream(cloudRecordItem.getFilePath());
byte[] buf = new byte[2*1024];
int len;
while ((len = fis.read(buf)) != -1){
zos.write(buf, 0, len);
}
zos.closeEntry();
fis.close();
}
zos.close();
} catch (IOException e) {
logger.error("[下载指定录像文件的压缩包] 失败: 查询 app->{}, stream->{}, mediaServerId->{}, startTime->{}, endTime->{}, callId->{}",
app, stream, mediaServerId, startTime, endTime, callId, e);
}
}
/**
*
* @param query 检索内容
* @param app 应用名
* @param stream 流ID
* @param startTime 开始时间(yyyy-MM-dd HH:mm:ss)
* @param endTime 结束时间(yyyy-MM-dd HH:mm:ss)
* @param mediaServerId 流媒体ID置空则查询全部流媒体
* @param callId 每次录像的唯一标识置空则查询全部流媒体
* @param remoteHost 拼接播放地址时使用的远程地址
*/
@ResponseBody
@GetMapping("/list-url")
@Operation(summary = "分页查询云端录像", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "query", description = "检索内容", required = false)
@Parameter(name = "app", description = "应用名", required = false)
@Parameter(name = "stream", description = "流ID", required = false)
@Parameter(name = "page", description = "当前页", required = true)
@Parameter(name = "count", description = "每页查询数量", required = true)
@Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = false)
@Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = false)
@Parameter(name = "mediaServerId", description = "流媒体ID置空则查询全部流媒体", required = false)
@Parameter(name = "callId", description = "每次录像的唯一标识,置空则查询全部流媒体", required = false)
public PageInfo<CloudRecordUrl> getListWithUrl(
HttpServletRequest request,
@RequestParam(required = false) String query,
@RequestParam(required = false) String app,
@RequestParam(required = false) String stream,
@RequestParam int page,
@RequestParam int count,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime,
@RequestParam(required = false) String mediaServerId,
@RequestParam(required = false) String callId,
@RequestParam(required = false) String remoteHost
) {
logger.info("[云端录像] 查询URL app->{}, stream->{}, mediaServerId->{}, page->{}, count->{}, startTime->{}, endTime->{}, callId->{}",
app, stream, mediaServerId, page, count, startTime, endTime, callId);
List<MediaServer> mediaServers;
if (!ObjectUtils.isEmpty(mediaServerId)) {
mediaServers = new ArrayList<>();
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId);
}
mediaServers.add(mediaServer);
} else {
mediaServers = mediaServerService.getAll();
}
if (mediaServers.isEmpty()) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "当前无流媒体");
}
if (query != null && ObjectUtils.isEmpty(query.trim())) {
query = null;
}
if (app != null && ObjectUtils.isEmpty(app.trim())) {
app = null;
}
if (stream != null && ObjectUtils.isEmpty(stream.trim())) {
stream = null;
}
if (startTime != null && ObjectUtils.isEmpty(startTime.trim())) {
startTime = null;
}
if (endTime != null && ObjectUtils.isEmpty(endTime.trim())) {
endTime = null;
}
if (callId != null && ObjectUtils.isEmpty(callId.trim())) {
callId = null;
}
MediaServer mediaServer = mediaServerService.getDefaultMediaServer();
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体节点");
}
if (remoteHost == null) {
remoteHost = request.getScheme() + "://" + request.getLocalAddr() + ":" +
(request.getScheme().equals("https")? mediaServer.getHttpSSlPort() : mediaServer.getHttpPort());
}
PageInfo<CloudRecordItem> cloudRecordItemPageInfo = cloudRecordService.getList(page, count, query, app, stream, startTime, endTime, mediaServers, callId);
PageInfo<CloudRecordUrl> cloudRecordUrlPageInfo = new PageInfo<>();
if (!ObjectUtils.isEmpty(cloudRecordItemPageInfo)) {
cloudRecordUrlPageInfo.setPageNum(cloudRecordItemPageInfo.getPageNum());
cloudRecordUrlPageInfo.setPageSize(cloudRecordItemPageInfo.getPageSize());
cloudRecordUrlPageInfo.setSize(cloudRecordItemPageInfo.getSize());
cloudRecordUrlPageInfo.setEndRow(cloudRecordItemPageInfo.getEndRow());
cloudRecordUrlPageInfo.setStartRow(cloudRecordItemPageInfo.getStartRow());
cloudRecordUrlPageInfo.setPages(cloudRecordItemPageInfo.getPages());
cloudRecordUrlPageInfo.setPrePage(cloudRecordItemPageInfo.getPrePage());
cloudRecordUrlPageInfo.setNextPage(cloudRecordItemPageInfo.getNextPage());
cloudRecordUrlPageInfo.setIsFirstPage(cloudRecordItemPageInfo.isIsFirstPage());
cloudRecordUrlPageInfo.setIsLastPage(cloudRecordItemPageInfo.isIsLastPage());
cloudRecordUrlPageInfo.setHasPreviousPage(cloudRecordItemPageInfo.isHasPreviousPage());
cloudRecordUrlPageInfo.setHasNextPage(cloudRecordItemPageInfo.isHasNextPage());
cloudRecordUrlPageInfo.setNavigatePages(cloudRecordItemPageInfo.getNavigatePages());
cloudRecordUrlPageInfo.setNavigateFirstPage(cloudRecordItemPageInfo.getNavigateFirstPage());
cloudRecordUrlPageInfo.setNavigateLastPage(cloudRecordItemPageInfo.getNavigateLastPage());
cloudRecordUrlPageInfo.setNavigatepageNums(cloudRecordItemPageInfo.getNavigatepageNums());
cloudRecordUrlPageInfo.setTotal(cloudRecordItemPageInfo.getTotal());
List<CloudRecordUrl> cloudRecordUrlList = new ArrayList<>(cloudRecordItemPageInfo.getList().size());
List<CloudRecordItem> cloudRecordItemList = cloudRecordItemPageInfo.getList();
for (CloudRecordItem cloudRecordItem : cloudRecordItemList) {
CloudRecordUrl cloudRecordUrl = new CloudRecordUrl();
cloudRecordUrl.setId(cloudRecordItem.getId());
cloudRecordUrl.setDownloadUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath()
+ "&save_name=" + cloudRecordItem.getStream() + "_" + cloudRecordItem.getCallId() + "_" + DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(cloudRecordItem.getStartTime()) );
cloudRecordUrl.setPlayUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath());
cloudRecordUrlList.add(cloudRecordUrl);
}
cloudRecordUrlPageInfo.setList(cloudRecordUrlList);
}
return cloudRecordUrlPageInfo;
}
}

View File

@ -0,0 +1,32 @@
package com.genersoft.iot.vmp.vmanager.cloudRecord.bean;
public class CloudRecordUrl {
private String playUrl;
private String downloadUrl;
private int id;
public String getPlayUrl() {
return playUrl;
}
public void setPlayUrl(String playUrl) {
this.playUrl = playUrl;
}
public String getDownloadUrl() {
return downloadUrl;
}
public void setDownloadUrl(String downloadUrl) {
this.downloadUrl = downloadUrl;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}

View File

@ -31,6 +31,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@ -129,6 +130,9 @@ public class PlayController {
}
streamInfo.channgeStreamIp(host);
}
if (!ObjectUtils.isEmpty(newMediaServerItem.getTranscodeSuffix()) && !"null".equalsIgnoreCase(newMediaServerItem.getTranscodeSuffix())) {
streamInfo.setStream(streamInfo.getStream() + "_" + newMediaServerItem.getTranscodeSuffix());
}
wvpResult.setData(new StreamContent(streamInfo));
}else {
wvpResult.setCode(code);

View File

@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.vmanager.log;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig;
import com.genersoft.iot.vmp.conf.security.JwtUtils;
import com.genersoft.iot.vmp.service.ILogService;
import com.genersoft.iot.vmp.storager.dao.dto.LogDto;
@ -92,4 +93,14 @@ public class LogController {
logService.clear();
}
@Autowired
private RedisRpcConfig redisRpcConfig;
@GetMapping("/test/count")
public Object count() {
return redisRpcConfig.getCallbackCount();
}
}

View File

@ -274,7 +274,7 @@ public class StreamPushController {
stream.setStatus(false);
stream.setPushIng(false);
stream.setAliveSecond(0L);
stream.setTotalReaderCount("0");
stream.setTotalReaderCount(0);
if (!streamPushService.add(stream)) {
throw new ControllerException(ErrorCode.ERROR100);
}

View File

@ -241,6 +241,8 @@ user-settings:
register-again-after-time: 60
# 国标续订方式true为续订每次注册在同一个会话里false为重新注册每次使用新的会话
register-keep-int-dialog: false
# 开启接口文档页面。 默认开启生产环境建议关闭遇到swagger相关的漏洞时也可以关闭
doc-enable: true
# 跨域配置,不配置此项则允许所有跨域请求,配置后则只允许配置的页面的地址请求, 可以配置多个
allowed-origins:
- http://localhost:8008

View File

@ -112,7 +112,4 @@ user-settings:
auto-apply-play: true
# 设备/通道状态变化时发送消息
device-status-notify: true
# [可选] 日志配置, 一般不需要改
logging:
config: classpath:logback-spring.xml

View File

@ -74,6 +74,8 @@
<template slot-scope="scope">
<el-button size="medium" icon="el-icon-video-play" type="text" @click="play(scope.row)">播放
</el-button>
<el-button size="medium" icon="el-icon-download" type="text" @click="downloadFile(scope.row)">下载
</el-button>
<!-- <el-button size="medium" icon="el-icon-delete" type="text" style="color: #f56c6c"-->
<!-- @click="deleteRecord(scope.row)">删除-->
<!-- </el-button>-->
@ -234,15 +236,30 @@ export default {
console.log(error);
});
},
getFileBasePath(item) {
let basePath = ""
if (axios.defaults.baseURL.startsWith("http")) {
basePath = `${axios.defaults.baseURL}/record_proxy/${item.mediaServerId}`
downloadFile(file){
console.log(file)
this.$axios({
method: 'get',
url: `/api/cloud/record/play/path`,
params: {
recordId: file.id,
}
}).then((res) => {
console.log(res)
const link = document.createElement('a');
link.target = "_blank";
if (res.data.code === 0) {
if (location.protocol === "https:") {
link.href = res.data.data.httpsPath + "&save_name=" + file.fileName;
}else {
basePath = `${window.location.origin}${axios.defaults.baseURL}/record_proxy/${item.mediaServerId}`
link.href = res.data.data.httpPath + "&save_name=" + file.fileName;
}
return basePath;
},
link.click();
}
}).catch((error) => {
console.log(error);
});
},
deleteRecord() {
// TODO
let that = this;

View File

@ -45,8 +45,7 @@
<i class="el-icon-video-camera" ></i>
{{ getFileShowName(item) }}
</el-tag>
<a class="el-icon-download" style="color: #409EFF;font-weight: 600;margin-left: 10px;"
:href="`${getFileBasePath(item)}/download.html?url=download/${app}/${stream}/${chooseDate}/${item.fileName}`"
<a class="el-icon-download" @click="downloadFile(item)" style="color: #409EFF;font-weight: 600;margin-left: 10px;"
target="_blank"/>
</li>
</ul>
@ -230,9 +229,6 @@
mounted() {
this.recordListStyle.height = this.winHeight + "px";
this.playerStyle["height"] = this.winHeight + "px";
console.log(this.app)
console.log(this.stream)
console.log(this.mediaServerId)
//
this.getDateInYear(()=>{
if (Object.values(this.dateFilesObj).length > 0){
@ -314,21 +310,61 @@
});
},
chooseFile(file){
console.log(file)
if (file == null) {
this.videoUrl = "";
this.choosedFile = "";
}else {
this.choosedFile = file.fileName;
this.videoUrl = `${this.getFileBasePath(file)}/download/${this.app}/${this.stream}/${this.chooseDate}/${file.fileName}`
console.log(this.videoUrl)
this.$axios({
method: 'get',
url: `/api/cloud/record/play/path`,
params: {
recordId: file.id,
}
}).then((res) => {
console.log(res)
if (res.data.code === 0) {
if (location.protocol === "https:") {
this.videoUrl = res.data.data.httpsPath;
}else {
this.videoUrl = res.data.data.httpPath;
}
}
}).catch((error) => {
console.log(error);
});
}
},
downloadFile(file){
console.log(file)
this.$axios({
method: 'get',
url: `/api/cloud/record/play/path`,
params: {
recordId: file.id,
}
}).then((res) => {
console.log(res)
const link = document.createElement('a');
link.target = "_blank";
if (res.data.code === 0) {
if (location.protocol === "https:") {
link.href = res.data.data.httpsPath + "&save_name=" + file.fileName;
}else {
link.href = res.data.data.httpPath + "&save_name=" + file.fileName;
}
link.click();
}
}).catch((error) => {
console.log(error);
});
},
backToList() {
this.$router.back()
},
getFileShowName(item) {
return moment.unix(item.startTime).format('HH:mm:ss') + "-" + moment.unix(item.endTime).format('HH:mm:ss')
return moment(item.startTime).format('HH:mm:ss') + "-" + moment(item.endTime).format('HH:mm:ss')
},
chooseMediaChange() {

View File

@ -326,7 +326,9 @@ export default {
e.ptzType = e.ptzType + "";
that.$set(e, "edit", false);
that.$set(e, "location", "");
if (e.longitude && e.latitude) {
if (e.customLongitude && e.customLatitude) {
that.$set(e, "location", e.customLongitude + "," + e.customLatitude);
}else if (e.longitude && e.latitude) {
that.$set(e, "location", e.longitude + "," + e.latitude);
}
});
@ -481,7 +483,9 @@ export default {
e.ptzType = e.ptzType + "";
this.$set(e, "edit", false);
this.$set(e, "location", "");
if (e.longitude && e.latitude) {
if (e.customLongitude && e.customLatitude) {
this.$set(e, "location", e.customLongitude + "," + e.customLatitude);
}else if (e.longitude && e.latitude) {
this.$set(e, "location", e.longitude + "," + e.latitude);
}
});
@ -603,8 +607,8 @@ export default {
this.$message.warning("位置信息格式有误117.234,36.378");
return;
} else {
row.longitude = parseFloat(segements[0]);
row.latitude = parseFloat(segements[1]);
row.customLongitude = parseFloat(segements[0]);
row.custom_latitude = parseFloat(segements[1]);
if (!(row.longitude && row.latitude)) {
this.$message.warning("位置信息格式有误117.234,36.378");
return;

View File

@ -1,6 +1,7 @@
<template>
<div ref="container" @dblclick="fullscreenSwich"
style="width:100%; height: 100%; background-color: #000000;margin:0 auto;position: relative;">
<div style="width:100%; padding-top: 56.25%; position: relative;"></div>
<div class="buttons-box" id="buttonsBox">
<div class="buttons-box-left">
<i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick"></i>
@ -66,6 +67,9 @@ export default {
// });
// ro.observe(this.$refs.container);
// },
mounted(){
this.updatePlayerDomSize();
},
watch: {
videoUrl: {
handler(val, _) {
@ -91,6 +95,7 @@ export default {
if (width > 0 && height > 0) {
dom.style.width = width + 'px';
dom.style.height = height + "px";
dom.style.paddingTop = 0;
console.log(width)
console.log(height)
}

View File

@ -7,7 +7,7 @@
v-if="Object.keys(this.player).length > 1">
<el-tab-pane label="Jessibuca" name="jessibuca">
<jessibucaPlayer v-if="activePlayer === 'jessibuca'" ref="jessibuca" :visible.sync="showVideoDialog"
:videoUrl="videoUrl" :error="videoError" :message="videoError" style="height: 515px"
:videoUrl="videoUrl" :error="videoError" :message="videoError"
:hasAudio="hasAudio" fluent autoplay live></jessibucaPlayer>
</el-tab-pane>
<el-tab-pane label="WebRTC" name="webRTC">
@ -19,7 +19,7 @@
</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>
: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>

View File

@ -32,7 +32,14 @@
<el-input v-model="platform.deviceGBId" clearable @input="deviceGBIdChange"></el-input>
</el-form-item>
<el-form-item label="本地IP" prop="deviceIp">
<el-input v-model="platform.deviceIp" :disabled="true"></el-input>
<el-select v-model="platform.deviceIp" placeholder="请选择与上级相通的网卡" style="width: 100%">
<el-option
v-for="ip in deviceIps"
:key="ip"
:label="ip"
:value="ip">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="本地端口" prop="devicePort">
<el-input v-model="platform.devicePort" :disabled="true" type="number"></el-input>
@ -51,7 +58,7 @@
<el-input v-model="platform.username"></el-input>
</el-form-item>
<el-form-item label="SIP认证密码" prop="password">
<el-input v-model="platform.password" ></el-input>
<el-input v-model="platform.password"></el-input>
</el-form-item>
<el-form-item label="注册周期(秒)" prop="expires">
<el-input v-model="platform.expires"></el-input>
@ -93,16 +100,17 @@
</el-form-item>
<el-form-item label="其他选项">
<el-checkbox label="启用" v-model="platform.enable" @change="checkExpires"></el-checkbox>
<!-- <el-checkbox label="云台控制" v-model="platform.ptz"></el-checkbox>-->
<!-- <el-checkbox label="云台控制" v-model="platform.ptz"></el-checkbox>-->
<el-checkbox label="拉起推流" v-model="platform.startOfflinePush"></el-checkbox>
<el-checkbox label="RTCP保活" v-model="platform.rtcp" @change="rtcpCheckBoxChange"></el-checkbox>
<el-checkbox label="消息通道" v-model="platform.asMessageChannel" ></el-checkbox>
<el-checkbox label="推送通道" v-model="platform.autoPushChannel" ></el-checkbox>
<el-checkbox label="消息通道" v-model="platform.asMessageChannel"></el-checkbox>
<el-checkbox label="推送通道" v-model="platform.autoPushChannel"></el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">{{
onSubmit_text
}}</el-button>
onSubmit_text
}}
</el-button>
<el-button @click="close">取消</el-button>
</el-form-item>
</el-form>
@ -165,51 +173,56 @@ export default {
administrativeDivision: "",
sendStreamIp: null,
},
deviceIps: [], // IP
rules: {
name: [{ required: true, message: "请输入平台名称", trigger: "blur" }],
name: [{required: true, message: "请输入平台名称", trigger: "blur"}],
serverGBId: [
{ required: true, message: "请输入SIP服务国标编码", trigger: "blur" },
{required: true, message: "请输入SIP服务国标编码", trigger: "blur"},
],
serverGBDomain: [
{ required: true, message: "请输入SIP服务国标域", trigger: "blur" },
{required: true, message: "请输入SIP服务国标域", trigger: "blur"},
],
serverIP: [{ required: true, message: "请输入SIP服务IP", trigger: "blur" }],
serverPort: [{ required: true, message: "请输入SIP服务端口", trigger: "blur" }],
deviceGBId: [{ validator: deviceGBIdRules, trigger: "blur" }],
username: [{ required: false, message: "请输入SIP认证用户名", trigger: "blur" }],
password: [{ required: false, message: "请输入SIP认证密码", trigger: "blur" }],
expires: [{ required: true, message: "请输入注册周期", trigger: "blur" }],
keepTimeout: [{ required: true, message: "请输入心跳周期", trigger: "blur" }],
transport: [{ required: true, message: "请选择信令传输", trigger: "blur" }],
characterSet: [{ required: true, message: "请选择编码字符集", trigger: "blur" }],
serverIP: [{required: true, message: "请输入SIP服务IP", trigger: "blur"}],
serverPort: [{required: true, message: "请输入SIP服务端口", trigger: "blur"}],
deviceGBId: [{validator: deviceGBIdRules, trigger: "blur"}],
username: [{required: false, message: "请输入SIP认证用户名", trigger: "blur"}],
password: [{required: false, message: "请输入SIP认证密码", trigger: "blur"}],
expires: [{required: true, message: "请输入注册周期", trigger: "blur"}],
keepTimeout: [{required: true, message: "请输入心跳周期", trigger: "blur"}],
transport: [{required: true, message: "请选择信令传输", trigger: "blur"}],
characterSet: [{required: true, message: "请选择编码字符集", trigger: "blur"}],
deviceIp: [{required: true, message: "请选择本地IP", trigger: "blur"}],
},
};
},
methods: {
openDialog: function (platform, callback) {
var that = this;
if (platform == null) {
this.onSubmit_text = "立即创建";
this.saveUrl = "/api/platform/add";
this.$axios({
method: 'get',
url:`/api/platform/server_config`
}).then(function (res) {
console.log(res);
this.$axios({
method: 'get',
url: `/api/platform/server_config`
}).then(function (res) {
if (platform == null) {
if (res.data.code === 0) {
that.platform.deviceGBId = res.data.data.username;
that.platform.deviceIp = res.data.data.deviceIp;
that.deviceIps = res.data.data.deviceIp.split(',');
that.platform.deviceIp = that.deviceIps[0];
that.platform.devicePort = res.data.data.devicePort;
that.platform.username = res.data.data.username;
that.platform.password = res.data.data.password;
that.platform.sendStreamIp = res.data.data.sendStreamIp;
that.platform.administrativeDivision = res.data.data.username.substr(0, 6);
}
}).catch(function (error) {
console.log(error);
});
}else {
} else {
that.deviceIps = res.data.data.deviceIp.split(',');
}
}).catch(function (error) {
console.log(error);
});
if (platform == null) {
this.onSubmit_text = "立即创建";
this.saveUrl = "/api/platform/add";
} else {
this.platform.id = platform.id;
this.platform.enable = platform.enable;
this.platform.ptz = platform.ptz;
@ -248,7 +261,7 @@ export default {
},
deviceGBIdChange: function () {
this.platform.username = this.platform.deviceGBId ;
this.platform.username = this.platform.deviceGBId;
if (this.platform.administrativeDivision == null) {
this.platform.administrativeDivision = this.platform.deviceGBId.substr(0, 6);
}
@ -256,12 +269,12 @@ export default {
onSubmit: function () {
this.saveForm()
},
saveForm: function (){
saveForm: function () {
this.$axios({
method: 'post',
url: this.saveUrl,
data: this.platform
}).then((res) =>{
}).then((res) => {
if (res.data.code === 0) {
this.$message({
showClose: true,
@ -272,14 +285,14 @@ export default {
if (this.listChangeCallback != null) {
this.listChangeCallback();
}
}else {
} else {
this.$message({
showClose: true,
message: res.data.msg,
type: "error",
});
}
}).catch((error)=> {
}).catch((error) => {
console.log(error);
});
},
@ -317,24 +330,25 @@ export default {
var result = false;
var that = this;
await that.$axios({
method: 'get',
url:`/api/platform/exit/${deviceGbId}`})
method: 'get',
url: `/api/platform/exit/${deviceGbId}`
})
.then(function (res) {
if (res.data.code === 0) {
result = res.data.data;
}
if (res.data.code === 0) {
result = res.data.data;
}
})
.catch(function (error) {
console.log(error);
});
return result;
},
checkExpires: function() {
checkExpires: function () {
if (this.platform.enable && this.platform.expires === "0") {
this.platform.expires = "3600";
}
},
rtcpCheckBoxChange: function (result){
rtcpCheckBoxChange: function (result) {
if (result) {
this.$message({
showClose: true,
@ -355,10 +369,12 @@ input::-webkit-inner-spin-button {
appearance: none;
margin: 0;
}
/* 火狐 */
input{
-moz-appearance:textfield;
input {
-moz-appearance: textfield;
}
.control-wrapper-not-used {
position: relative;
width: 6.25rem;

View File

@ -97,9 +97,9 @@ enable_ts=1
fmp4_demand=0
hls_demand=0
hls_save_path=./www
modify_stamp=0
modify_stamp=2
mp4_as_player=0
mp4_max_second=3600
mp4_max_second=300
mp4_save_path=./www
rtmp_demand=0
rtsp_demand=0
@ -163,4 +163,16 @@ pktBufSize=8192
port=9000
timeoutSec=5
[transcode]
acodec=pcma
decoder_h264=h264_qsv,h264_videotoolbox,h264_bm,libopenh264
decoder_h265=hevc_qsv,hevc_videotoolbox,hevc_bm
enable_ffmpeg_log=0
encoder_h264=h264_qsv,h264_videotoolbox,h264_bm,libx264,libopenh264
encoder_h265=hevc_qsv,hevc_videotoolbox,hevc_bm,libx265
filter=
suffix=transport
vcodec=H264
; } ---

View File

@ -31,7 +31,6 @@ create table wvp_device (
password character varying(255),
as_message_channel bool default false,
keepalive_interval_time integer,
switch_primary_sub_stream bool default false,
broadcast_push_after_ack bool default false,
constraint uk_device_device unique (device_id)
);

View File

@ -31,7 +31,6 @@ create table wvp_device (
password character varying(255),
as_message_channel bool default false,
keepalive_interval_time integer,
switch_primary_sub_stream bool default false,
broadcast_push_after_ack bool default false,
constraint uk_device_device unique (device_id)
);

View File

@ -4,5 +4,15 @@ alter table wvp_device_channel
alter table wvp_device
drop switch_primary_sub_stream;
# 第一个补丁包
alter table wvp_platform
add send_stream_ip character varying(50);
alter table wvp_device
change on_line on_line bool default false;
alter table wvp_device
change id id serial primary key;
alter table wvp_device
change ssrc_check ssrc_check bool default false;

View File

@ -4,5 +4,15 @@ alter table wvp_device_channel
alter table wvp_device
drop switch_primary_sub_stream;
# 第一个补丁包
alter table wvp_platform
add send_stream_ip character varying(50);
alter table wvp_device
change on_line on_line bool default false;
alter table wvp_device
change id id serial primary key;
alter table wvp_device
change ssrc_check ssrc_check bool default false;

View File

@ -31,7 +31,6 @@ create table wvp_device (
password character varying(255),
as_message_channel bool default false,
keepalive_interval_time integer,
switch_primary_sub_stream bool default false,
broadcast_push_after_ack bool default false,
constraint uk_device_device unique (device_id)
);
@ -172,6 +171,7 @@ create table wvp_media_server (
hook_alive_interval integer,
record_path character varying(255),
record_day integer default 7,
transcode_suffix character varying(255),
constraint uk_media_server_unique_ip_http_port unique (ip, http_port)
);
@ -320,6 +320,7 @@ create table wvp_resources_tree (
parentId integer,
path character varying(255)
);
create table wvp_user_api_key (
id serial primary key ,
user_id bigint,
@ -332,6 +333,7 @@ create table wvp_user_api_key (
update_time character varying(50)
);
/*初始数据*/
INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3');
INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57');

View File

@ -31,7 +31,6 @@ create table wvp_device (
password character varying(255),
as_message_channel bool default false,
keepalive_interval_time integer,
switch_primary_sub_stream bool default false,
broadcast_push_after_ack bool default false,
constraint uk_device_device unique (device_id)
);
@ -172,6 +171,7 @@ create table wvp_media_server (
hook_alive_interval integer,
record_path character varying(255),
record_day integer default 7,
transcode_suffix character varying(255),
constraint uk_media_server_unique_ip_http_port unique (ip, http_port)
);
@ -320,6 +320,7 @@ create table wvp_resources_tree (
parentId integer,
path character varying(255)
);
create table wvp_user_api_key (
id serial primary key ,
user_id bigint,
@ -332,6 +333,7 @@ create table wvp_user_api_key (
update_time character varying(50)
);
/*初始数据*/
INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3');
INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57');

View File

@ -1,3 +1,6 @@
alter table wvp_media_server
add transcode_suffix character varying(255);
alter table wvp_media_server
add type character varying(50) default 'zlm';

View File

@ -1,3 +1,6 @@
alter table wvp_media_server
add transcode_suffix character varying(255);
alter table wvp_media_server
add type character varying(50) default 'zlm';

View File

@ -0,0 +1,342 @@
/*建表*/
create table wvp_device (
id serial primary key ,
device_id character varying(50) not null ,
name character varying(255),
manufacturer character varying(255),
model character varying(255),
firmware character varying(255),
transport character varying(50),
stream_mode character varying(50),
on_line bool default false,
register_time character varying(50),
keepalive_time character varying(50),
ip character varying(50),
create_time character varying(50),
update_time character varying(50),
port integer,
expires integer,
subscribe_cycle_for_catalog integer DEFAULT 0,
subscribe_cycle_for_mobile_position integer DEFAULT 0,
mobile_position_submission_interval integer DEFAULT 5,
subscribe_cycle_for_alarm integer DEFAULT 0,
host_address character varying(50),
charset character varying(50),
ssrc_check bool default false,
geo_coord_sys character varying(50),
media_server_id character varying(50),
custom_name character varying(255),
sdp_ip character varying(50),
local_ip character varying(50),
password character varying(255),
as_message_channel bool default false,
keepalive_interval_time integer,
broadcast_push_after_ack bool default false,
constraint uk_device_device unique (device_id)
);
create table wvp_device_alarm (
id serial primary key ,
device_id character varying(50) not null,
channel_id character varying(50) not null,
alarm_priority character varying(50),
alarm_method character varying(50),
alarm_time character varying(50),
alarm_description character varying(255),
longitude double precision,
latitude double precision,
alarm_type character varying(50),
create_time character varying(50) not null
);
create table wvp_device_channel (
id serial primary key ,
channel_id character varying(50) not null,
name character varying(255),
custom_name character varying(255),
manufacture character varying(50),
model character varying(50),
owner character varying(50),
civil_code character varying(50),
block character varying(50),
address character varying(50),
parent_id character varying(50),
safety_way integer,
register_way integer,
cert_num character varying(50),
certifiable integer,
err_code integer,
end_time character varying(50),
secrecy character varying(50),
ip_address character varying(50),
port integer,
password character varying(255),
ptz_type integer,
custom_ptz_type integer,
status bool default false,
longitude double precision,
custom_longitude double precision,
latitude double precision,
custom_latitude double precision,
stream_id character varying(255),
device_id character varying(50) not null,
parental character varying(50),
has_audio bool default false,
create_time character varying(50) not null,
update_time character varying(50) not null,
sub_count integer,
longitude_gcj02 double precision,
latitude_gcj02 double precision,
longitude_wgs84 double precision,
latitude_wgs84 double precision,
business_group_id character varying(50),
gps_time character varying(50),
stream_identification character varying(50),
constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id)
);
create table wvp_device_mobile_position (
id serial primary key,
device_id character varying(50) not null,
channel_id character varying(50) not null,
device_name character varying(255),
time character varying(50),
longitude double precision,
latitude double precision,
altitude double precision,
speed double precision,
direction double precision,
report_source character varying(50),
longitude_gcj02 double precision,
latitude_gcj02 double precision,
longitude_wgs84 double precision,
latitude_wgs84 double precision,
create_time character varying(50)
);
create table wvp_gb_stream (
gb_stream_id serial primary key,
app character varying(255) not null,
stream character varying(255) not null,
gb_id character varying(50) not null,
name character varying(255),
longitude double precision,
latitude double precision,
stream_type character varying(50),
media_server_id character varying(50),
create_time character varying(50),
constraint uk_gb_stream_unique_gb_id unique (gb_id),
constraint uk_gb_stream_unique_app_stream unique (app, stream)
);
create table wvp_log (
id serial primary key ,
name character varying(50),
type character varying(50),
uri character varying(200),
address character varying(50),
result character varying(50),
timing bigint,
username character varying(50),
create_time character varying(50)
);
create table wvp_media_server (
id character varying(255) primary key ,
ip character varying(50),
hook_ip character varying(50),
sdp_ip character varying(50),
stream_ip character varying(50),
http_port integer,
http_ssl_port integer,
rtmp_port integer,
rtmp_ssl_port integer,
rtp_proxy_port integer,
rtsp_port integer,
rtsp_ssl_port integer,
flv_port integer,
flv_ssl_port integer,
ws_flv_port integer,
ws_flv_ssl_port integer,
auto_config bool default false,
secret character varying(50),
type character varying(50) default 'zlm',
rtp_enable bool default false,
rtp_port_range character varying(50),
send_rtp_port_range character varying(50),
record_assist_port integer,
default_server bool default false,
create_time character varying(50),
update_time character varying(50),
hook_alive_interval integer,
record_path character varying(255),
record_day integer default 7,
transcode_suffix character varying(255),
constraint uk_media_server_unique_ip_http_port unique (ip, http_port)
);
create table wvp_platform (
id serial primary key ,
enable bool default false,
name character varying(255),
server_gb_id character varying(50),
server_gb_domain character varying(50),
server_ip character varying(50),
server_port integer,
device_gb_id character varying(50),
device_ip character varying(50),
device_port character varying(50),
username character varying(255),
password character varying(50),
expires character varying(50),
keep_timeout character varying(50),
transport character varying(50),
character_set character varying(50),
catalog_id character varying(50),
ptz bool default false,
rtcp bool default false,
status bool default false,
start_offline_push bool default false,
administrative_division character varying(50),
catalog_group integer,
create_time character varying(50),
update_time character varying(50),
as_message_channel bool default false,
auto_push_channel bool default false,
send_stream_ip character varying(50),
constraint uk_platform_unique_server_gb_id unique (server_gb_id)
);
create table wvp_platform_catalog (
id character varying(50),
platform_id character varying(50),
name character varying(255),
parent_id character varying(50),
civil_code character varying(50),
business_group_id character varying(50),
constraint uk_platform_catalog_id_platform_id unique (id, platform_id)
);
create table wvp_platform_gb_channel (
id serial primary key ,
platform_id character varying(50),
catalog_id character varying(50),
device_channel_id integer,
constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, catalog_id, device_channel_id)
);
create table wvp_platform_gb_stream (
id serial primary key,
platform_id character varying(50),
catalog_id character varying(50),
gb_stream_id integer,
constraint uk_platform_gb_stream_platform_id_catalog_id_gb_stream_id unique (platform_id, catalog_id, gb_stream_id)
);
create table wvp_stream_proxy (
id serial primary key,
type character varying(50),
app character varying(255),
stream character varying(255),
url character varying(255),
src_url character varying(255),
dst_url character varying(255),
timeout_ms integer,
ffmpeg_cmd_key character varying(255),
rtp_type character varying(50),
media_server_id character varying(50),
enable_audio bool default false,
enable_mp4 bool default false,
enable bool default false,
status boolean,
enable_remove_none_reader bool default false,
create_time character varying(50),
name character varying(255),
update_time character varying(50),
stream_key character varying(255),
enable_disable_none_reader bool default false,
constraint uk_stream_proxy_app_stream unique (app, stream)
);
create table wvp_stream_push (
id serial primary key,
app character varying(255),
stream character varying(255),
total_reader_count character varying(50),
origin_type integer,
origin_type_str character varying(50),
create_time character varying(50),
alive_second integer,
media_server_id character varying(50),
server_id character varying(50),
push_time character varying(50),
status bool default false,
update_time character varying(50),
push_ing bool default false,
self bool default false,
constraint uk_stream_push_app_stream unique (app, stream)
);
create table wvp_cloud_record (
id serial primary key,
app character varying(255),
stream character varying(255),
call_id character varying(255),
start_time bigint,
end_time bigint,
media_server_id character varying(50),
file_name character varying(255),
folder character varying(255),
file_path character varying(255),
collect bool default false,
file_size bigint,
time_len bigint,
constraint uk_stream_push_app_stream_path unique (app, stream, file_path)
);
create table wvp_user (
id serial primary key,
username character varying(255),
password character varying(255),
role_id integer,
create_time character varying(50),
update_time character varying(50),
push_key character varying(50),
constraint uk_user_username unique (username)
);
create table wvp_user_role (
id serial primary key,
name character varying(50),
authority character varying(50),
create_time character varying(50),
update_time character varying(50)
);
create table wvp_resources_tree (
id serial primary key ,
is_catalog bool default true,
device_channel_id integer ,
gb_stream_id integer,
name character varying(255),
parentId integer,
path character varying(255)
);
create table wvp_user_api_key (
id serial primary key ,
user_id bigint,
app character varying(255) ,
api_key text,
expired_at bigint,
remark character varying(255),
enable bool default true,
create_time character varying(50),
update_time character varying(50)
);
/*初始数据*/
INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3');
INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57');

View File

@ -0,0 +1,342 @@
/*建表*/
create table wvp_device (
id serial primary key ,
device_id character varying(50) not null ,
name character varying(255),
manufacturer character varying(255),
model character varying(255),
firmware character varying(255),
transport character varying(50),
stream_mode character varying(50),
on_line bool default false,
register_time character varying(50),
keepalive_time character varying(50),
ip character varying(50),
create_time character varying(50),
update_time character varying(50),
port integer,
expires integer,
subscribe_cycle_for_catalog integer DEFAULT 0,
subscribe_cycle_for_mobile_position integer DEFAULT 0,
mobile_position_submission_interval integer DEFAULT 5,
subscribe_cycle_for_alarm integer DEFAULT 0,
host_address character varying(50),
charset character varying(50),
ssrc_check bool default false,
geo_coord_sys character varying(50),
media_server_id character varying(50),
custom_name character varying(255),
sdp_ip character varying(50),
local_ip character varying(50),
password character varying(255),
as_message_channel bool default false,
keepalive_interval_time integer,
broadcast_push_after_ack bool default false,
constraint uk_device_device unique (device_id)
);
create table wvp_device_alarm (
id serial primary key ,
device_id character varying(50) not null,
channel_id character varying(50) not null,
alarm_priority character varying(50),
alarm_method character varying(50),
alarm_time character varying(50),
alarm_description character varying(255),
longitude double precision,
latitude double precision,
alarm_type character varying(50),
create_time character varying(50) not null
);
create table wvp_device_channel (
id serial primary key ,
channel_id character varying(50) not null,
name character varying(255),
custom_name character varying(255),
manufacture character varying(50),
model character varying(50),
owner character varying(50),
civil_code character varying(50),
block character varying(50),
address character varying(50),
parent_id character varying(50),
safety_way integer,
register_way integer,
cert_num character varying(50),
certifiable integer,
err_code integer,
end_time character varying(50),
secrecy character varying(50),
ip_address character varying(50),
port integer,
password character varying(255),
ptz_type integer,
custom_ptz_type integer,
status bool default false,
longitude double precision,
custom_longitude double precision,
latitude double precision,
custom_latitude double precision,
stream_id character varying(255),
device_id character varying(50) not null,
parental character varying(50),
has_audio bool default false,
create_time character varying(50) not null,
update_time character varying(50) not null,
sub_count integer,
longitude_gcj02 double precision,
latitude_gcj02 double precision,
longitude_wgs84 double precision,
latitude_wgs84 double precision,
business_group_id character varying(50),
gps_time character varying(50),
stream_identification character varying(50),
constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id)
);
create table wvp_device_mobile_position (
id serial primary key,
device_id character varying(50) not null,
channel_id character varying(50) not null,
device_name character varying(255),
time character varying(50),
longitude double precision,
latitude double precision,
altitude double precision,
speed double precision,
direction double precision,
report_source character varying(50),
longitude_gcj02 double precision,
latitude_gcj02 double precision,
longitude_wgs84 double precision,
latitude_wgs84 double precision,
create_time character varying(50)
);
create table wvp_gb_stream (
gb_stream_id serial primary key,
app character varying(255) not null,
stream character varying(255) not null,
gb_id character varying(50) not null,
name character varying(255),
longitude double precision,
latitude double precision,
stream_type character varying(50),
media_server_id character varying(50),
create_time character varying(50),
constraint uk_gb_stream_unique_gb_id unique (gb_id),
constraint uk_gb_stream_unique_app_stream unique (app, stream)
);
create table wvp_log (
id serial primary key ,
name character varying(50),
type character varying(50),
uri character varying(200),
address character varying(50),
result character varying(50),
timing bigint,
username character varying(50),
create_time character varying(50)
);
create table wvp_media_server (
id character varying(255) primary key ,
ip character varying(50),
hook_ip character varying(50),
sdp_ip character varying(50),
stream_ip character varying(50),
http_port integer,
http_ssl_port integer,
rtmp_port integer,
rtmp_ssl_port integer,
rtp_proxy_port integer,
rtsp_port integer,
rtsp_ssl_port integer,
flv_port integer,
flv_ssl_port integer,
ws_flv_port integer,
ws_flv_ssl_port integer,
auto_config bool default false,
secret character varying(50),
type character varying(50) default 'zlm',
rtp_enable bool default false,
rtp_port_range character varying(50),
send_rtp_port_range character varying(50),
record_assist_port integer,
default_server bool default false,
create_time character varying(50),
update_time character varying(50),
hook_alive_interval integer,
record_path character varying(255),
record_day integer default 7,
transcode_suffix character varying(255),
constraint uk_media_server_unique_ip_http_port unique (ip, http_port)
);
create table wvp_platform (
id serial primary key ,
enable bool default false,
name character varying(255),
server_gb_id character varying(50),
server_gb_domain character varying(50),
server_ip character varying(50),
server_port integer,
device_gb_id character varying(50),
device_ip character varying(50),
device_port character varying(50),
username character varying(255),
password character varying(50),
expires character varying(50),
keep_timeout character varying(50),
transport character varying(50),
character_set character varying(50),
catalog_id character varying(50),
ptz bool default false,
rtcp bool default false,
status bool default false,
start_offline_push bool default false,
administrative_division character varying(50),
catalog_group integer,
create_time character varying(50),
update_time character varying(50),
as_message_channel bool default false,
auto_push_channel bool default false,
send_stream_ip character varying(50),
constraint uk_platform_unique_server_gb_id unique (server_gb_id)
);
create table wvp_platform_catalog (
id character varying(50),
platform_id character varying(50),
name character varying(255),
parent_id character varying(50),
civil_code character varying(50),
business_group_id character varying(50),
constraint uk_platform_catalog_id_platform_id unique (id, platform_id)
);
create table wvp_platform_gb_channel (
id serial primary key ,
platform_id character varying(50),
catalog_id character varying(50),
device_channel_id integer,
constraint uk_platform_gb_channel_platform_id_catalog_id_device_channel_id unique (platform_id, catalog_id, device_channel_id)
);
create table wvp_platform_gb_stream (
id serial primary key,
platform_id character varying(50),
catalog_id character varying(50),
gb_stream_id integer,
constraint uk_platform_gb_stream_platform_id_catalog_id_gb_stream_id unique (platform_id, catalog_id, gb_stream_id)
);
create table wvp_stream_proxy (
id serial primary key,
type character varying(50),
app character varying(255),
stream character varying(255),
url character varying(255),
src_url character varying(255),
dst_url character varying(255),
timeout_ms integer,
ffmpeg_cmd_key character varying(255),
rtp_type character varying(50),
media_server_id character varying(50),
enable_audio bool default false,
enable_mp4 bool default false,
enable bool default false,
status boolean,
enable_remove_none_reader bool default false,
create_time character varying(50),
name character varying(255),
update_time character varying(50),
stream_key character varying(255),
enable_disable_none_reader bool default false,
constraint uk_stream_proxy_app_stream unique (app, stream)
);
create table wvp_stream_push (
id serial primary key,
app character varying(255),
stream character varying(255),
total_reader_count character varying(50),
origin_type integer,
origin_type_str character varying(50),
create_time character varying(50),
alive_second integer,
media_server_id character varying(50),
server_id character varying(50),
push_time character varying(50),
status bool default false,
update_time character varying(50),
push_ing bool default false,
self bool default false,
constraint uk_stream_push_app_stream unique (app, stream)
);
create table wvp_cloud_record (
id serial primary key,
app character varying(255),
stream character varying(255),
call_id character varying(255),
start_time int8,
end_time int8,
media_server_id character varying(50),
file_name character varying(255),
folder character varying(255),
file_path character varying(255),
collect bool default false,
file_size int8,
time_len int8,
constraint uk_stream_push_app_stream_path unique (app, stream, file_path)
);
create table wvp_user (
id serial primary key,
username character varying(255),
password character varying(255),
role_id integer,
create_time character varying(50),
update_time character varying(50),
push_key character varying(50),
constraint uk_user_username unique (username)
);
create table wvp_user_role (
id serial primary key,
name character varying(50),
authority character varying(50),
create_time character varying(50),
update_time character varying(50)
);
create table wvp_resources_tree (
id serial primary key ,
is_catalog bool default true,
device_channel_id integer ,
gb_stream_id integer,
name character varying(255),
parentId integer,
path character varying(255)
);
create table wvp_user_api_key (
id serial primary key ,
user_id bigint,
app character varying(255) ,
api_key text,
expired_at bigint,
remark character varying(255),
enable bool default true,
create_time character varying(50),
update_time character varying(50)
);
/*初始数据*/
INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3');
INSERT INTO wvp_user_role VALUES (1, 'admin','0','2021-04-13 14:14:57','2021-04-13 14:14:57');

View File

@ -0,0 +1,26 @@
alter table wvp_media_server
add transcode_suffix character varying(255);
alter table wvp_media_server
add type character varying(50) default 'zlm';
alter table wvp_media_server
add flv_port integer;
alter table wvp_media_server
add flv_ssl_port integer;
alter table wvp_media_server
add ws_flv_port integer;
alter table wvp_media_server
add ws_flv_ssl_port integer;
create table wvp_user_api_key (
id serial primary key ,
user_id bigint,
app character varying(255) ,
api_key text,
expired_at bigint,
remark character varying(255),
enable bool default true,
create_time character varying(50),
update_time character varying(50)
);

View File

@ -0,0 +1,26 @@
alter table wvp_media_server
add transcode_suffix character varying(255);
alter table wvp_media_server
add type character varying(50) default 'zlm';
alter table wvp_media_server
add flv_port integer;
alter table wvp_media_server
add flv_ssl_port integer;
alter table wvp_media_server
add ws_flv_port integer;
alter table wvp_media_server
add ws_flv_ssl_port integer;
create table wvp_user_api_key (
id serial primary key ,
user_id bigint,
app character varying(255) ,
api_key text,
expired_at bigint,
remark character varying(255),
enable bool default true,
create_time character varying(50),
update_time character varying(50)
);