Procházet zdrojové kódy

Merge branch 'wvp-28181-2.0'

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
648540858 před 3 roky
rodič
revize
2f76fa98bb
33 změnil soubory, kde provedl 332 přidání a 116 odebrání
  1. 1 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
  2. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  3. 7 6
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  4. 4 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
  5. 2 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
  6. 14 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
  7. 0 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java
  8. 6 1
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
  9. 1 1
      src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java
  10. 49 10
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  11. 4 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
  12. 10 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java
  13. 2 0
      src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
  14. 13 1
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
  15. 0 1
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
  16. 108 40
      src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
  17. 0 1
      src/main/java/com/genersoft/iot/vmp/service/impl/RedisPushStreamListMsgListener.java
  18. 1 1
      src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushUploadFileHandler.java
  19. 2 0
      src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
  20. 9 5
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
  21. 2 2
      src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformGbStreamMapper.java
  22. 1 1
      src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java
  23. 18 0
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  24. 10 10
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  25. 15 0
      src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamPushExcelDto.java
  26. 1 0
      src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java
  27. 5 0
      src/main/resources/logback-spring-local.xml
  28. 2 1
      web_src/src/components/ParentPlatformList.vue
  29. 5 2
      web_src/src/components/dialog/chooseChannel.vue
  30. 5 2
      web_src/src/components/dialog/chooseChannelForCatalog.vue
  31. 12 4
      web_src/src/components/dialog/getCatalog.vue
  32. 22 19
      web_src/src/components/dialog/platformEdit.vue
  33. binární
      web_src/static/file/推流通道导入.zip

+ 1 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java

@@ -53,7 +53,7 @@ public class ParentPlatform {
     /**
     /**
      * 设备国标编号
      * 设备国标编号
      */
      */
-    @Schema(description = "11111")
+    @Schema(description = "设备国标编号")
     private String deviceGBId;
     private String deviceGBId;
 
 
     /**
     /**
@@ -113,7 +113,6 @@ public class ParentPlatform {
 
 
     /**
     /**
      * RTCP流保活
      * RTCP流保活
-     * TODO 预留, 暂不实现
      */
      */
     @Schema(description = "RTCP流保活")
     @Schema(description = "RTCP流保活")
     private boolean rtcp;
     private boolean rtcp;

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java

@@ -108,7 +108,7 @@ public interface ISIPCommander {
 	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 */
 	 */
-	void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event errorEvent);
+	void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent);
 
 
 	/**
 	/**
 	 * 请求历史媒体下载
 	 * 请求历史媒体下载

+ 7 - 6
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java

@@ -358,7 +358,7 @@ public class SIPCommander implements ISIPCommander {
 //			String streamMode = device.getStreamMode().toUpperCase();
 //			String streamMode = device.getStreamMode().toUpperCase();
 
 
 			logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
 			logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
-			HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtmp", mediaServerItem.getId());
+			HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
 			subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{
 			subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{
 				if (event != null) {
 				if (event != null) {
 					event.response(mediaServerItemInUse, json);
 					event.response(mediaServerItemInUse, json);
@@ -458,7 +458,7 @@ public class SIPCommander implements ISIPCommander {
 	@Override
 	@Override
 	public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
 	public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
 								  String startTime, String endTime, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
 								  String startTime, String endTime, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
-								  SipSubscribe.Event errorEvent) {
+								  SipSubscribe.Event okEvent,SipSubscribe.Event errorEvent) {
 		try {
 		try {
 
 
 			logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
 			logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
@@ -526,7 +526,7 @@ public class SIPCommander implements ISIPCommander {
 
 
 			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
 			CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
 					: udpSipProvider.getNewCallId();
 					: udpSipProvider.getNewCallId();
-			HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtmp", mediaServerItem.getId());
+			HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
 			// 添加订阅
 			// 添加订阅
 			subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{
 			subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json)->{
 						if (hookEvent != null) {
 						if (hookEvent != null) {
@@ -537,10 +537,11 @@ public class SIPCommander implements ISIPCommander {
 					});
 					});
 	        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader, ssrcInfo.getSsrc());
 	        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader, ssrcInfo.getSsrc());
 
 
-	        transmitRequest(device, request, errorEvent, okEvent -> {
-				ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
+	        transmitRequest(device, request, errorEvent, event -> {
+				ResponseEvent responseEvent = (ResponseEvent) event.event;
 	        	streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.playback);
 	        	streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.playback);
-				streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog);
+				streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), event.dialog);
+				okEvent.response(event);
 			});
 			});
 			if (inviteStreamCallback != null) {
 			if (inviteStreamCallback != null) {
 				inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
 				inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));

+ 4 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java

@@ -121,6 +121,10 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
 			param.put("pt", sendRtpItem.getPt());
 			param.put("pt", sendRtpItem.getPt());
 			param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
 			param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
 			param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
 			param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
+			if (!sendRtpItem.isTcp() && parentPlatform.isRtcp()) {
+				// 开启rtcp保活
+				param.put("udp_rtcp_timeout", "1");
+			}
 			JSONObject jsonObject;
 			JSONObject jsonObject;
 			if (sendRtpItem.isTcpActive()) {
 			if (sendRtpItem.isTcpActive()) {
 				jsonObject = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param);
 				jsonObject = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param);

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java

@@ -104,6 +104,8 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
 					MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
 					MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
 					zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
 					zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
 					redisCatchStorage.deleteSendRTPServer(platformGbId, sendRtpItem.getChannelId(), callIdHeader.getCallId(), null);
 					redisCatchStorage.deleteSendRTPServer(platformGbId, sendRtpItem.getChannelId(), callIdHeader.getCallId(), null);
+					redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null);
+					zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
 					int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
 					int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
 					if (totalReaderCount <= 0) {
 					if (totalReaderCount <= 0) {
 						logger.info("收到bye: {} 无其它观看者,通知设备停止推流", streamId);
 						logger.info("收到bye: {} 无其它观看者,通知设备停止推流", streamId);

+ 14 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java

@@ -110,6 +110,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
 	@Autowired
 	@Autowired
 	private ZLMRESTfulUtils zlmresTfulUtils;
 	private ZLMRESTfulUtils zlmresTfulUtils;
 
 
+    @Autowired
+    private ZlmHttpHookSubscribe zlmHttpHookSubscribe;
+
     @Autowired
     @Autowired
     private SIPProcessorObserver sipProcessorObserver;
     private SIPProcessorObserver sipProcessorObserver;
 
 
@@ -430,7 +433,14 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                         if (playTransaction != null) {
                         if (playTransaction != null) {
                             Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream());
                             Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream());
                             if (!streamReady) {
                             if (!streamReady) {
-                                playTransaction = null;
+                                boolean hasRtpServer = mediaServerService.checkRtpServer(mediaServerItem, "rtp", playTransaction.getStream());
+                                if (hasRtpServer) {
+                                    logger.info("[上级点播]已经开启rtpServer但是尚未收到流,开启监听流的到来");
+                                    HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", playTransaction.getStream(), true, "rtsp", mediaServerItem.getId());
+                                    zlmHttpHookSubscribe.addSubscribe(hookSubscribe, hookEvent);
+                                }else {
+                                    playTransaction = null;
+                                }
                             }
                             }
                         }
                         }
                         if (playTransaction == null) {
                         if (playTransaction == null) {
@@ -593,7 +603,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
             responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline");
             responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline");
         } else if ("push".equals(gbStream.getStreamType())) {
         } else if ("push".equals(gbStream.getStreamType())) {
             if (!platform.isStartOfflinePush()) {
             if (!platform.isStartOfflinePush()) {
-                responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel unavailable");
+                // 平台设置中关闭了拉起离线的推流则直接回复
+                responseAck(evt, Response.TEMPORARILY_UNAVAILABLE, "channel stream not pushing");
                 return;
                 return;
             }
             }
             // 发送redis消息以使设备上线
             // 发送redis消息以使设备上线
@@ -629,7 +640,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
                             app, stream, channelId, mediaTransmissionTCP);
                             app, stream, channelId, mediaTransmissionTCP);
 
 
                     if (sendRtpItem == null) {
                     if (sendRtpItem == null) {
-                        logger.warn("服务器端口资源不足");
+                        logger.warn("上级点时创建sendRTPItem失败,可能是服务器端口资源不足");
                         try {
                         try {
                             responseAck(evt, Response.BUSY_HERE);
                             responseAck(evt, Response.BUSY_HERE);
                         } catch (SipException e) {
                         } catch (SipException e) {

+ 0 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java

@@ -211,7 +211,6 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 		}else if (subscribeInfo.getExpires() == 0) {
 		}else if (subscribeInfo.getExpires() == 0) {
 			subscribeHolder.removeCatalogSubscribe(platformId);
 			subscribeHolder.removeCatalogSubscribe(platformId);
 		}
 		}
-
 		try {
 		try {
 			ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformId);
 			ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformId);
 			responseXmlAck(evt, resultXml.toString(), parentPlatform);
 			responseXmlAck(evt, resultXml.toString(), parentPlatform);
@@ -219,5 +218,4 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme
 			e.printStackTrace();
 			e.printStackTrace();
 		}
 		}
 	}
 	}
-
 }
 }

+ 6 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java

@@ -203,6 +203,12 @@ public class XmlUtil {
             return null;
             return null;
         }
         }
         deviceChannel.setChannelId(channelId);
         deviceChannel.setChannelId(channelId);
+        int channelTypeCode = Integer.parseInt(channelId.substring(10, 13));
+        if (channelTypeCode == 136 || channelTypeCode == 137 || channelTypeCode == 138) {
+            deviceChannel.setHasAudio(true);
+        }else {
+            deviceChannel.setHasAudio(false);
+        }
         if (event != null && !event.equals(CatalogEvent.ADD) && !event.equals(CatalogEvent.UPDATE)) {
         if (event != null && !event.equals(CatalogEvent.ADD) && !event.equals(CatalogEvent.UPDATE)) {
             // 除了ADD和update情况下需要识别全部内容,
             // 除了ADD和update情况下需要识别全部内容,
             return deviceChannel;
             return deviceChannel;
@@ -396,7 +402,6 @@ public class XmlUtil {
         } else {
         } else {
             deviceChannel.setPTZType(Integer.parseInt(XmlUtil.getText(itemDevice, "PTZType")));
             deviceChannel.setPTZType(Integer.parseInt(XmlUtil.getText(itemDevice, "PTZType")));
         }
         }
-        deviceChannel.setHasAudio(true); // 默认含有音频,播放时再检查是否有音频及是否AAC
         return deviceChannel;
         return deviceChannel;
     }
     }
 }
 }

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java

@@ -50,7 +50,7 @@ public class AssistRESTfulUtils {
         if (mediaServerItem == null) {
         if (mediaServerItem == null) {
             return null;
             return null;
         }
         }
-        if (mediaServerItem.getRecordAssistPort() > 0) {
+        if (mediaServerItem.getRecordAssistPort() <= 0) {
             logger.warn("未启用Assist服务");
             logger.warn("未启用Assist服务");
             return null;
             return null;
         }
         }

+ 49 - 10
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@@ -19,8 +19,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.util.ObjectUtils;
 import org.springframework.util.ObjectUtils;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -544,6 +542,8 @@ public class ZLMHttpHookListener {
 						for (SendRtpItem sendRtpItem : sendRtpItems) {
 						for (SendRtpItem sendRtpItem : sendRtpItems) {
 							ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
 							ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
 							commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
 							commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
+							redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
+									sendRtpItem.getCallId(), sendRtpItem.getStreamId());
 						}
 						}
 					}
 					}
 				}
 				}
@@ -573,13 +573,19 @@ public class ZLMHttpHookListener {
 			return ret;
 			return ret;
 		}else {
 		}else {
 			StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, streamId);
 			StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, streamId);
-			if (streamProxyItem != null && streamProxyItem.isEnable_remove_none_reader()) {
-				ret.put("close", true);
-				streamProxyService.del(app, streamId);
-				String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url();
-				logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除",  app, streamId, url);
-			}else {
-				ret.put("close", false);
+			if (streamProxyItem != null ) {
+				if (streamProxyItem.isEnable_remove_none_reader()) {
+					// 无人观看自动移除
+					ret.put("close", true);
+					streamProxyService.del(app, streamId);
+					String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url();
+					logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除",  app, streamId, url);
+				}else if (streamProxyItem.isEnable_disable_none_reader()) {
+					// 无人观看停用
+					ret.put("close", true);
+				}else {
+					ret.put("close", false);
+				}
 			}
 			}
 			return ret;
 			return ret;
 		}
 		}
@@ -626,7 +632,7 @@ public class ZLMHttpHookListener {
 	@ResponseBody
 	@ResponseBody
 	@PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
 	@PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
 	public JSONObject onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){
 	public JSONObject onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){
-		
+
 		if (logger.isDebugEnabled()) {
 		if (logger.isDebugEnabled()) {
 			logger.debug("[ ZLM HOOK ]on_server_started API调用,参数:" + jsonObject.toString());
 			logger.debug("[ ZLM HOOK ]on_server_started API调用,参数:" + jsonObject.toString());
 		}
 		}
@@ -649,6 +655,39 @@ public class ZLMHttpHookListener {
 		return ret;
 		return ret;
 	}
 	}
 
 
+	/**
+	 * 发送rtp(startSendRtp)被动关闭时回调
+	 */
+	@ResponseBody
+	@PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8")
+	public JSONObject onSendRtpStopped(HttpServletRequest request, @RequestBody JSONObject jsonObject){
+
+		logger.info("[ ZLM HOOK ]on_send_rtp_stopped API调用,参数:" + jsonObject);
+
+		JSONObject ret = new JSONObject();
+		ret.put("code", 0);
+		ret.put("msg", "success");
+
+		// 查找对应的上级推流,发送停止
+		String app = jsonObject.getString("app");
+		if (!"rtp".equals(app)) {
+			return ret;
+		}
+		String stream = jsonObject.getString("stream");
+		List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(stream);
+		if (sendRtpItems.size() > 0) {
+			for (SendRtpItem sendRtpItem : sendRtpItems) {
+				ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
+				commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
+				redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
+						sendRtpItem.getCallId(), sendRtpItem.getStreamId());
+			}
+		}
+
+
+		return ret;
+	}
+
 	private Map<String, String> urlParamToMap(String params) {
 	private Map<String, String> urlParamToMap(String params) {
 		HashMap<String, String> map = new HashMap<>();
 		HashMap<String, String> map = new HashMap<>();
 		if (ObjectUtils.isEmpty(params)) {
 		if (ObjectUtils.isEmpty(params)) {

+ 4 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java

@@ -96,6 +96,10 @@ public class ZLMRTPServerFactory {
         if(rtpInfo.getInteger("code") == 0){
         if(rtpInfo.getInteger("code") == 0){
             if (rtpInfo.getBoolean("exist")) {
             if (rtpInfo.getBoolean("exist")) {
                 result = rtpInfo.getInteger("local_port");
                 result = rtpInfo.getInteger("local_port");
+                if (result == 0) {
+                    // 此时说明rtpServer已经创建但是流还没有推上来
+
+                }
                 return result;
                 return result;
             }
             }
         }else if(rtpInfo.getInteger("code") == -2){
         }else if(rtpInfo.getInteger("code") == -2){

+ 10 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java

@@ -37,6 +37,9 @@ public class StreamProxyItem extends GbStream {
     private boolean enable_mp4;
     private boolean enable_mp4;
     @Schema(description = "是否 无人观看时删除")
     @Schema(description = "是否 无人观看时删除")
     private boolean enable_remove_none_reader;
     private boolean enable_remove_none_reader;
+
+    @Schema(description = "是否 无人观看时不启用")
+    private boolean enable_disable_none_reader;
     @Schema(description = "上级平台国标ID")
     @Schema(description = "上级平台国标ID")
     private String platformGbId;
     private String platformGbId;
     @Schema(description = "创建时间")
     @Schema(description = "创建时间")
@@ -177,4 +180,11 @@ public class StreamProxyItem extends GbStream {
         this.enable_remove_none_reader = enable_remove_none_reader;
         this.enable_remove_none_reader = enable_remove_none_reader;
     }
     }
 
 
+    public boolean isEnable_disable_none_reader() {
+        return enable_disable_none_reader;
+    }
+
+    public void setEnable_disable_none_reader(boolean enable_disable_none_reader) {
+        this.enable_disable_none_reader = enable_disable_none_reader;
+    }
 }
 }

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java

@@ -82,4 +82,6 @@ public interface IMediaServerService {
     MediaServerItem getDefaultMediaServer();
     MediaServerItem getDefaultMediaServer();
 
 
     void updateMediaServerKeepalive(String mediaServerId, JSONObject data);
     void updateMediaServerKeepalive(String mediaServerId, JSONObject data);
+
+    boolean checkRtpServer(MediaServerItem mediaServerItem, String rtp, String stream);
 }
 }

+ 13 - 1
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java

@@ -152,9 +152,11 @@ public class MediaServerServiceImpl implements IMediaServerService {
             if (streamId == null) {
             if (streamId == null) {
                 streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
                 streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
             }
             }
-            int rtpServerPort = mediaServerItem.getRtpProxyPort();
+            int rtpServerPort;
             if (mediaServerItem.isRtpEnable()) {
             if (mediaServerItem.isRtpEnable()) {
                 rtpServerPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(ssrc):0, port);
                 rtpServerPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(ssrc):0, port);
+            } else {
+                rtpServerPort = mediaServerItem.getRtpProxyPort();
             }
             }
             RedisUtil.set(key, mediaServerItem);
             RedisUtil.set(key, mediaServerItem);
             return new SSRCInfo(rtpServerPort, ssrc, streamId);
             return new SSRCInfo(rtpServerPort, ssrc, streamId);
@@ -537,6 +539,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
         param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex));
         param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex));
         param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex));
         param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex));
         param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrex));
         param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrex));
+        param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrex));
         if (mediaServerItem.getRecordAssistPort() > 0) {
         if (mediaServerItem.getRecordAssistPort() > 0) {
             param.put("hook.on_record_mp4",String.format("http://127.0.0.1:%s/api/record/on_record_mp4", mediaServerItem.getRecordAssistPort()));
             param.put("hook.on_record_mp4",String.format("http://127.0.0.1:%s/api/record/on_record_mp4", mediaServerItem.getRecordAssistPort()));
         }else {
         }else {
@@ -686,4 +689,13 @@ public class MediaServerServiceImpl implements IMediaServerService {
             }
             }
         }
         }
     }
     }
+
+    @Override
+    public boolean checkRtpServer(MediaServerItem mediaServerItem, String app, String stream) {
+        JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, stream);
+        if(rtpInfo.getInteger("code") == 0){
+            return rtpInfo.getBoolean("exist");
+        }
+        return false;
+    }
 }
 }

+ 0 - 1
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java

@@ -73,7 +73,6 @@ public class MediaServiceImpl implements IMediaService {
                 }else {
                 }else {
                     streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null, true);
                     streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null, true);
                 }
                 }
-
             }
             }
         }
         }
         return streamInfo;
         return streamInfo;

+ 108 - 40
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java

@@ -193,17 +193,30 @@ public class PlayServiceImpl implements IPlayService {
             JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
             JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
             if(rtpInfo.getInteger("code") == 0){
             if(rtpInfo.getInteger("code") == 0){
                 if (rtpInfo.getBoolean("exist")) {
                 if (rtpInfo.getBoolean("exist")) {
-
-                    WVPResult wvpResult = new WVPResult();
-                    wvpResult.setCode(ErrorCode.SUCCESS.getCode());
-                    wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
-                    wvpResult.setData(streamInfo);
-                    msg.setData(wvpResult);
-
-                    resultHolder.invokeAllResult(msg);
-                    if (hookEvent != null) {
-                        hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo)));
+                    int localPort = rtpInfo.getInteger("local_port");
+                    if (localPort == 0) {
+                        logger.warn("[点播],点播时发现rtpServerC存在,但是尚未开始推流");
+                        // 此时说明rtpServer已经创建但是流还没有推上来
+                        WVPResult wvpResult = new WVPResult();
+                        wvpResult.setCode(ErrorCode.ERROR100.getCode());
+                        wvpResult.setMsg("点播已经在进行中,请稍候重试");
+                        msg.setData(wvpResult);
+
+                        resultHolder.invokeAllResult(msg);
+                        return playResult;
+                    }else {
+                        WVPResult wvpResult = new WVPResult();
+                        wvpResult.setCode(ErrorCode.SUCCESS.getCode());
+                        wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
+                        wvpResult.setData(streamInfo);
+                        msg.setData(wvpResult);
+
+                        resultHolder.invokeAllResult(msg);
+                        if (hookEvent != null) {
+                            hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo)));
+                        }
                     }
                     }
+
                 }else {
                 }else {
                     redisCatchStorage.stopPlay(streamInfo);
                     redisCatchStorage.stopPlay(streamInfo);
                     storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
                     storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
@@ -318,7 +331,7 @@ public class PlayServiceImpl implements IPlayService {
                 }
                 }
                 logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse );
                 logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse );
                 if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
                 if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
-                    logger.info("[SIP 消息] SSRC修正 {}->{}", ssrc, ssrcInResponse);
+                    logger.info("[点播消息] SSRC修正 {}->{}", ssrc, ssrcInResponse);
 
 
                     if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
                     if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
                         // ssrc 不可用
                         // ssrc 不可用
@@ -468,37 +481,92 @@ public class PlayServiceImpl implements IPlayService {
             resultHolder.exist(DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId, uuid);
             resultHolder.exist(DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId, uuid);
         }, userSetting.getPlayTimeout());
         }, userSetting.getPlayTimeout());
 
 
+        SipSubscribe.Event errorEvent = event -> {
+            dynamicTask.stop(playBackTimeOutTaskKey);
+            requestMessage.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)));
+            playBackResult.setCode(ErrorCode.ERROR100.getCode());
+            playBackResult.setMsg(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg));
+            playBackResult.setData(requestMessage);
+            playBackResult.setEvent(event);
+            playBackCallback.call(playBackResult);
+            streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+        };
+
+        InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> {
+            logger.info("收到回放订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
+            dynamicTask.stop(playBackTimeOutTaskKey);
+            StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
+            if (streamInfo == null) {
+                logger.warn("设备回放API调用失败!");
+                playBackResult.setCode(ErrorCode.ERROR100.getCode());
+                playBackResult.setMsg("设备回放API调用失败!");
+                playBackCallback.call(playBackResult);
+                return;
+            }
+            redisCatchStorage.startPlayback(streamInfo, inviteStreamInfo.getCallId());
+            WVPResult<StreamInfo> success = WVPResult.success(streamInfo);
+            requestMessage.setData(success);
+            playBackResult.setCode(ErrorCode.SUCCESS.getCode());
+            playBackResult.setMsg(ErrorCode.SUCCESS.getMsg());
+            playBackResult.setData(requestMessage);
+            playBackResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
+            playBackResult.setResponse(inviteStreamInfo.getResponse());
+            playBackCallback.call(playBackResult);
+        };
+
         cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack,
         cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack,
-                (InviteStreamInfo inviteStreamInfo) -> {
-                    logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
-                    dynamicTask.stop(playBackTimeOutTaskKey);
-                    StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
-                    if (streamInfo == null) {
-                        logger.warn("设备回放API调用失败!");
-                        playBackResult.setCode(ErrorCode.ERROR100.getCode());
-                        playBackResult.setMsg("设备回放API调用失败!");
-                        playBackCallback.call(playBackResult);
-                        return;
+                hookEvent, eventResult -> {
+                    if (eventResult.type == SipSubscribe.EventResultType.response) {
+                        ResponseEvent responseEvent = (ResponseEvent)eventResult.event;
+                        String contentString = new String(responseEvent.getResponse().getRawContent());
+                        // 获取ssrc
+                        int ssrcIndex = contentString.indexOf("y=");
+                        // 检查是否有y字段
+                        if (ssrcIndex >= 0) {
+                            //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
+                            String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+                            // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
+                            if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
+                                return;
+                            }
+                            logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse );
+                            if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
+                                logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
+
+                                if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
+                                    // ssrc 不可用
+                                    // 释放ssrc
+                                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                                    streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+                                    eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用";
+                                    eventResult.statusCode = 400;
+                                    errorEvent.response(eventResult);
+                                    return;
+                                }
+
+                                // 单端口模式streamId也有变化,需要重新设置监听
+                                if (!mediaServerItem.isRtpEnable()) {
+                                    // 添加订阅
+                                    HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
+                                    subscribe.removeSubscribe(hookSubscribe);
+                                    hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
+                                    subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response)->{
+                                        logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
+                                        dynamicTask.stop(playBackTimeOutTaskKey);
+                                        // hook响应
+                                        onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId, uuid);
+                                        hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
+                                    });
+                                }
+                                // 关闭rtp server
+                                mediaServerService.closeRTPServer(device.getDeviceId(), channelId, ssrcInfo.getStream());
+                                // 重新开启ssrc server
+                                mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort());
+                            }
+                        }
                     }
                     }
-                    redisCatchStorage.startPlayback(streamInfo, inviteStreamInfo.getCallId());
-                    WVPResult<StreamInfo> success = WVPResult.success(streamInfo);
-                    requestMessage.setData(success);
-                    playBackResult.setCode(ErrorCode.SUCCESS.getCode());
-                    playBackResult.setMsg(ErrorCode.SUCCESS.getMsg());
-                    playBackResult.setData(requestMessage);
-                    playBackResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
-                    playBackResult.setResponse(inviteStreamInfo.getResponse());
-                    playBackCallback.call(playBackResult);
-                }, event -> {
-                    dynamicTask.stop(playBackTimeOutTaskKey);
-                    requestMessage.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)));
-                    playBackResult.setCode(ErrorCode.ERROR100.getCode());
-                    playBackResult.setMsg(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg));
-                    playBackResult.setData(requestMessage);
-                    playBackResult.setEvent(event);
-                    playBackCallback.call(playBackResult);
-                    streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
-                });
+
+                }, errorEvent);
         return result;
         return result;
     }
     }
 
 

+ 0 - 1
src/main/java/com/genersoft/iot/vmp/service/impl/RedisPushStreamListMsgListener.java

@@ -53,7 +53,6 @@ public class RedisPushStreamListMsgListener implements MessageListener {
             boolean contains = allAppAndStream.contains(app + stream);
             boolean contains = allAppAndStream.contains(app + stream);
             //不存在就添加
             //不存在就添加
             if (!contains) {
             if (!contains) {
-                streamPushItem.setStatus(false);
                 streamPushItem.setStreamType("push");
                 streamPushItem.setStreamType("push");
                 streamPushItem.setCreateTime(DateUtil.getNow());
                 streamPushItem.setCreateTime(DateUtil.getNow());
                 streamPushItem.setMediaServerId(mediaServerService.getDefaultMediaServer().getId());
                 streamPushItem.setMediaServerId(mediaServerService.getDefaultMediaServer().getId());

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushUploadFileHandler.java

@@ -116,7 +116,7 @@ public class StreamPushUploadFileHandler extends AnalysisEventListener<StreamPus
         streamPushItem.setApp(streamPushExcelDto.getApp());
         streamPushItem.setApp(streamPushExcelDto.getApp());
         streamPushItem.setStream(streamPushExcelDto.getStream());
         streamPushItem.setStream(streamPushExcelDto.getStream());
         streamPushItem.setGbId(streamPushExcelDto.getGbId());
         streamPushItem.setGbId(streamPushExcelDto.getGbId());
-        streamPushItem.setStatus(false);
+        streamPushItem.setStatus(streamPushExcelDto.getStatus());
         streamPushItem.setStreamType("push");
         streamPushItem.setStreamType("push");
         streamPushItem.setCreateTime(DateUtil.getNow());
         streamPushItem.setCreateTime(DateUtil.getNow());
         streamPushItem.setMediaServerId(defaultMediaServerId);
         streamPushItem.setMediaServerId(defaultMediaServerId);

+ 2 - 0
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java

@@ -236,4 +236,6 @@ public interface IRedisCatchStorage {
     void sendStreamPushRequestedMsgForStatus();
     void sendStreamPushRequestedMsgForStatus();
 
 
     List<SendRtpItem> querySendRTPServerByChnnelId(String channelId);
     List<SendRtpItem> querySendRTPServerByChnnelId(String channelId);
+
+    List<SendRtpItem> querySendRTPServerByStream(String stream);
 }
 }

+ 9 - 5
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java

@@ -143,15 +143,12 @@ public interface DeviceChannelMapper {
     @Update(value = {"UPDATE device_channel SET status=0 WHERE deviceId=#{deviceId}"})
     @Update(value = {"UPDATE device_channel SET status=0 WHERE deviceId=#{deviceId}"})
     void offlineByDeviceId(String deviceId);
     void offlineByDeviceId(String deviceId);
 
 
-    @Update(value = {"UPDATE device_channel SET status=1 WHERE deviceId=#{deviceId} AND channelId=#{channelId}"})
-    void online(String deviceId,  String channelId);
-
     @Insert("<script> " +
     @Insert("<script> " +
             "insert into device_channel " +
             "insert into device_channel " +
             "(channelId, deviceId, name, manufacture, model, owner, civilCode, block, subCount, " +
             "(channelId, deviceId, name, manufacture, model, owner, civilCode, block, subCount, " +
             "  address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " +
             "  address, parental, parentId, safetyWay, registerWay, certNum, certifiable, errCode, secrecy, " +
             "  ipAddress, port, password, PTZType, status, streamId, longitude, latitude, longitudeGcj02, latitudeGcj02, " +
             "  ipAddress, port, password, PTZType, status, streamId, longitude, latitude, longitudeGcj02, latitudeGcj02, " +
-            "  longitudeWgs84, latitudeWgs84, createTime, updateTime, businessGroupId, gpsTime) " +
+            "  longitudeWgs84, latitudeWgs84, hasAudio, createTime, updateTime, businessGroupId, gpsTime) " +
             "values " +
             "values " +
             "<foreach collection='addChannels' index='index' item='item' separator=','> " +
             "<foreach collection='addChannels' index='index' item='item' separator=','> " +
             "('${item.channelId}', '${item.deviceId}', '${item.name}', '${item.manufacture}', '${item.model}', " +
             "('${item.channelId}', '${item.deviceId}', '${item.name}', '${item.manufacture}', '${item.model}', " +
@@ -160,7 +157,7 @@ public interface DeviceChannelMapper {
             "'${item.certNum}', ${item.certifiable}, ${item.errCode}, '${item.secrecy}', " +
             "'${item.certNum}', ${item.certifiable}, ${item.errCode}, '${item.secrecy}', " +
             "'${item.ipAddress}', ${item.port}, '${item.password}', ${item.PTZType}, ${item.status}, " +
             "'${item.ipAddress}', ${item.port}, '${item.password}', ${item.PTZType}, ${item.status}, " +
             "'${item.streamId}', ${item.longitude}, ${item.latitude},${item.longitudeGcj02}, " +
             "'${item.streamId}', ${item.longitude}, ${item.latitude},${item.longitudeGcj02}, " +
-            "${item.latitudeGcj02},${item.longitudeWgs84}, ${item.latitudeWgs84},'${item.createTime}', '${item.updateTime}', " +
+            "${item.latitudeGcj02},${item.longitudeWgs84}, ${item.latitudeWgs84}, ${item.hasAudio},'${item.createTime}', '${item.updateTime}', " +
             "'${item.businessGroupId}', '${item.gpsTime}') " +
             "'${item.businessGroupId}', '${item.gpsTime}') " +
             "</foreach> " +
             "</foreach> " +
             "ON DUPLICATE KEY UPDATE " +
             "ON DUPLICATE KEY UPDATE " +
@@ -193,11 +190,15 @@ public interface DeviceChannelMapper {
             "latitudeGcj02=VALUES(latitudeGcj02), " +
             "latitudeGcj02=VALUES(latitudeGcj02), " +
             "longitudeWgs84=VALUES(longitudeWgs84), " +
             "longitudeWgs84=VALUES(longitudeWgs84), " +
             "latitudeWgs84=VALUES(latitudeWgs84), " +
             "latitudeWgs84=VALUES(latitudeWgs84), " +
+            "hasAudio=VALUES(hasAudio), " +
             "businessGroupId=VALUES(businessGroupId), " +
             "businessGroupId=VALUES(businessGroupId), " +
             "gpsTime=VALUES(gpsTime)" +
             "gpsTime=VALUES(gpsTime)" +
             "</script>")
             "</script>")
     int batchAdd(List<DeviceChannel> addChannels);
     int batchAdd(List<DeviceChannel> addChannels);
 
 
+    @Update(value = {"UPDATE device_channel SET status=1 WHERE deviceId=#{deviceId} AND channelId=#{channelId}"})
+    void online(String deviceId,  String channelId);
+
     @Update({"<script>" +
     @Update({"<script>" +
             "<foreach collection='updateChannels' item='item' separator=';'>" +
             "<foreach collection='updateChannels' item='item' separator=';'>" +
             " UPDATE" +
             " UPDATE" +
@@ -341,4 +342,7 @@ public interface DeviceChannelMapper {
             " left join platform_catalog pc on pgc.catalogId = pc.id and pgc.platformId = pc.platformId" +
             " left join platform_catalog pc on pgc.catalogId = pc.id and pgc.platformId = pc.platformId" +
             " where pgc.platformId=#{serverGBId}")
             " where pgc.platformId=#{serverGBId}")
     List<DeviceChannel> queryChannelWithCatalog(String serverGBId);
     List<DeviceChannel> queryChannelWithCatalog(String serverGBId);
+
+    @Select("select * from device_channel where deviceId = #{deviceId}")
+    List<DeviceChannel> queryAllChannels(String deviceId);
 }
 }

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformGbStreamMapper.java

@@ -23,10 +23,10 @@ public interface PlatformGbStreamMapper {
 
 
     @Insert("<script> " +
     @Insert("<script> " +
             "INSERT into platform_gb_stream " +
             "INSERT into platform_gb_stream " +
-            "(gbStreamId, platformId, catalogId) " +
+            "(gbStreamId, platformId, catalogId,status) " +
             "values " +
             "values " +
             "<foreach collection='streamPushItems' index='index' item='item' separator=','> " +
             "<foreach collection='streamPushItems' index='index' item='item' separator=','> " +
-            "(${item.gbStreamId}, '${item.platformId}', '${item.catalogId}')" +
+            "(${item.gbStreamId}, '${item.platformId}', '${item.catalogId}'), '${item.status}')" +
             "</foreach> " +
             "</foreach> " +
             "</script>")
             "</script>")
     int batchAdd(List<StreamPushItem> streamPushItems);
     int batchAdd(List<StreamPushItem> streamPushItems);

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java

@@ -77,7 +77,7 @@ public interface StreamPushMapper {
             "1=1 " +
             "1=1 " +
             " <if test='query != null'> AND (st.app LIKE '%${query}%' OR st.stream LIKE '%${query}%' OR gs.gbId LIKE '%${query}%' OR gs.name LIKE '%${query}%')</if> " +
             " <if test='query != null'> AND (st.app LIKE '%${query}%' OR st.stream LIKE '%${query}%' OR gs.gbId LIKE '%${query}%' OR gs.name LIKE '%${query}%')</if> " +
             " <if test='pushing == true' > AND (gs.gbId is null OR st.pushIng=1)</if>" +
             " <if test='pushing == true' > AND (gs.gbId is null OR st.pushIng=1)</if>" +
-            " <if test='pushing == false' > AND st.pushIng=0</if>" +
+            " <if test='pushing == false' > AND (gs.pushIng is null OR st.pushIng=0) </if>" +
             " <if test='mediaServerId != null' > AND st.mediaServerId=#{mediaServerId} </if>" +
             " <if test='mediaServerId != null' > AND st.mediaServerId=#{mediaServerId} </if>" +
             "order by st.createTime desc" +
             "order by st.createTime desc" +
             " </script>"})
             " </script>"})

+ 18 - 0
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@@ -387,6 +387,24 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
         return result;
         return result;
     }
     }
 
 
+    @Override
+    public List<SendRtpItem> querySendRTPServerByStream(String stream) {
+        if (stream == null) {
+            return null;
+        }
+        String platformGbId = "*";
+        String callId = "*";
+        String channelId = "*";
+        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + userSetting.getServerId() + "_" + platformGbId
+                + "_" + channelId + "_" + stream + "_" + callId;
+        List<Object> scan = RedisUtil.scan(key);
+        List<SendRtpItem> result = new ArrayList<>();
+        for (Object o : scan) {
+            result.add((SendRtpItem) RedisUtil.get((String) o));
+        }
+        return result;
+    }
+
     @Override
     @Override
     public List<SendRtpItem> querySendRTPServer(String platformGbId) {
     public List<SendRtpItem> querySendRTPServer(String platformGbId) {
         if (platformGbId == null) {
         if (platformGbId == null) {

+ 10 - 10
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java

@@ -111,11 +111,11 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		if (CollectionUtils.isEmpty(deviceChannelList)) {
 		if (CollectionUtils.isEmpty(deviceChannelList)) {
 			return false;
 			return false;
 		}
 		}
-		List<DeviceChannel> allChannelInPlay = deviceChannelMapper.getAllChannelInPlay();
-		Map<String,DeviceChannel> allChannelMapInPlay = new ConcurrentHashMap<>();
-		if (allChannelInPlay.size() > 0) {
-			for (DeviceChannel deviceChannel : allChannelInPlay) {
-				allChannelMapInPlay.put(deviceChannel.getChannelId(), deviceChannel);
+		List<DeviceChannel> allChannels = deviceChannelMapper.queryAllChannels(deviceId);
+		Map<String,DeviceChannel> allChannelMap = new ConcurrentHashMap<>();
+		if (allChannels.size() > 0) {
+			for (DeviceChannel deviceChannel : allChannels) {
+				allChannelMap.put(deviceChannel.getChannelId(), deviceChannel);
 			}
 			}
 		}
 		}
 		TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
 		TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
@@ -123,15 +123,17 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		List<DeviceChannel> channels = new ArrayList<>();
 		List<DeviceChannel> channels = new ArrayList<>();
 		StringBuilder stringBuilder = new StringBuilder();
 		StringBuilder stringBuilder = new StringBuilder();
 		Map<String, Integer> subContMap = new HashMap<>();
 		Map<String, Integer> subContMap = new HashMap<>();
-		if (deviceChannelList.size() > 1) {
+		if (deviceChannelList.size() > 0) {
 			// 数据去重
 			// 数据去重
 			Set<String> gbIdSet = new HashSet<>();
 			Set<String> gbIdSet = new HashSet<>();
 			for (DeviceChannel deviceChannel : deviceChannelList) {
 			for (DeviceChannel deviceChannel : deviceChannelList) {
 				if (!gbIdSet.contains(deviceChannel.getChannelId())) {
 				if (!gbIdSet.contains(deviceChannel.getChannelId())) {
 					gbIdSet.add(deviceChannel.getChannelId());
 					gbIdSet.add(deviceChannel.getChannelId());
-					if (allChannelMapInPlay.containsKey(deviceChannel.getChannelId())) {
-						deviceChannel.setStreamId(allChannelMapInPlay.get(deviceChannel.getChannelId()).getStreamId());
+					if (allChannelMap.containsKey(deviceChannel.getChannelId())) {
+						deviceChannel.setStreamId(allChannelMap.get(deviceChannel.getChannelId()).getStreamId());
+						deviceChannel.setHasAudio(allChannelMap.get(deviceChannel.getChannelId()).isHasAudio());
 					}
 					}
+
 					channels.add(deviceChannel);
 					channels.add(deviceChannel);
 					if (!ObjectUtils.isEmpty(deviceChannel.getParentId())) {
 					if (!ObjectUtils.isEmpty(deviceChannel.getParentId())) {
 						if (subContMap.get(deviceChannel.getParentId()) == null) {
 						if (subContMap.get(deviceChannel.getParentId()) == null) {
@@ -153,8 +155,6 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 				}
 				}
 			}
 			}
 
 
-		}else {
-			channels = deviceChannelList;
 		}
 		}
 		if (stringBuilder.length() > 0) {
 		if (stringBuilder.length() > 0) {
 			logger.info("[目录查询]收到的数据存在重复: {}" , stringBuilder);
 			logger.info("[目录查询]收到的数据存在重复: {}" , stringBuilder);

+ 15 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamPushExcelDto.java

@@ -22,6 +22,9 @@ public class StreamPushExcelDto {
     @ExcelProperty("目录ID")
     @ExcelProperty("目录ID")
     private String catalogId;
     private String catalogId;
 
 
+    @ExcelProperty("在线状态")
+    private boolean status;
+
     public String getName() {
     public String getName() {
         return name;
         return name;
     }
     }
@@ -70,4 +73,16 @@ public class StreamPushExcelDto {
     public void setCatalogId(String catalogId) {
     public void setCatalogId(String catalogId) {
         this.catalogId = catalogId;
         this.catalogId = catalogId;
     }
     }
+
+    public boolean isStatus() {
+        return status;
+    }
+
+    public boolean getStatus() {
+        return status;
+    }
+
+    public void setStatus(boolean status) {
+        this.status = status;
+    }
 }
 }

+ 1 - 0
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java

@@ -43,6 +43,7 @@ public class UserController {
     private IRoleService roleService;
     private IRoleService roleService;
 
 
     @GetMapping("/login")
     @GetMapping("/login")
+    @PostMapping("/login")
     @Operation(summary = "登录")
     @Operation(summary = "登录")
     @Parameter(name = "username", description = "用户名", required = true)
     @Parameter(name = "username", description = "用户名", required = true)
     @Parameter(name = "password", description = "密码(32位md5加密)", required = true)
     @Parameter(name = "password", description = "密码(32位md5加密)", required = true)

+ 5 - 0
src/main/resources/logback-spring-local.xml

@@ -98,6 +98,11 @@
 		<appender-ref ref="STDOUT" />
 		<appender-ref ref="STDOUT" />
 	</root>
 	</root>
 
 
+	<logger name="wvp" level="debug" additivity="true">
+		<appender-ref ref="RollingFileError"/>
+		<appender-ref ref="RollingFile"/>
+	</logger>
+
 	<logger name="GB28181_SIP" level="debug" additivity="true">
 	<logger name="GB28181_SIP" level="debug" additivity="true">
 		<appender-ref ref="RollingFileError"/>
 		<appender-ref ref="RollingFileError"/>
 		<appender-ref ref="sipRollingFile"/>
 		<appender-ref ref="sipRollingFile"/>

+ 2 - 1
web_src/src/components/ParentPlatformList.vue

@@ -143,7 +143,8 @@ export default {
         });
         });
     },
     },
     chooseChannel: function(platform) {
     chooseChannel: function(platform) {
-       this.$refs.chooseChannelDialog.openDialog(platform.serverGBId, platform.name, platform.catalogId, platform.treeType, this.initData)
+        console.log("platform.name: " + platform.name)
+       this.$refs.chooseChannelDialog.openDialog(platform.serverGBId,platform.deviceGBId, platform.name, platform.catalogId, platform.treeType, this.initData)
     },
     },
     initData: function() {
     initData: function() {
       this.getPlatformList();
       this.getPlatformList();

+ 5 - 2
web_src/src/components/dialog/chooseChannel.vue

@@ -8,7 +8,7 @@
             <el-tab-pane label="目录结构" name="catalog">
             <el-tab-pane label="目录结构" name="catalog">
               <el-container>
               <el-container>
                 <el-main v-bind:style="{backgroundColor: '#FFF', maxHeight:  winHeight + 'px'}">
                 <el-main v-bind:style="{backgroundColor: '#FFF', maxHeight:  winHeight + 'px'}">
-                  <chooseChannelForCatalog ref="chooseChannelForCatalog" :platformId=platformId :platformName=platformName :defaultCatalogId=defaultCatalogId :catalogIdChange="catalogIdChange" :treeType=treeType ></chooseChannelForCatalog>
+                  <chooseChannelForCatalog ref="chooseChannelForCatalog" :platformId=platformId :platformDeviceId=platformDeviceId :platformName=platformName :defaultCatalogId=defaultCatalogId :catalogIdChange="catalogIdChange" :treeType=treeType ></chooseChannelForCatalog>
                 </el-main>
                 </el-main>
               </el-container>
               </el-container>
             </el-tab-pane>
             </el-tab-pane>
@@ -60,6 +60,7 @@ export default {
             tabActiveName: "gbChannel",
             tabActiveName: "gbChannel",
             catalogTabActiveName: "catalog",
             catalogTabActiveName: "catalog",
             platformId: "",
             platformId: "",
+            platformDeviceId: "",
             catalogId: "",
             catalogId: "",
             catalogName: "",
             catalogName: "",
             currentCatalogId: "",
             currentCatalogId: "",
@@ -73,8 +74,10 @@ export default {
         };
         };
     },
     },
     methods: {
     methods: {
-        openDialog(platformId, platformName, defaultCatalogId, treeType, closeCallback) {
+        openDialog(platformId, platformDeviceId, platformName, defaultCatalogId, treeType, closeCallback) {
+            console.log("defaultCatalogId: " + defaultCatalogId)
             this.platformId = platformId
             this.platformId = platformId
+            this.platformDeviceId = platformDeviceId
             this.platformName = platformName
             this.platformName = platformName
             this.defaultCatalogId = defaultCatalogId
             this.defaultCatalogId = defaultCatalogId
             this.showDialog = true
             this.showDialog = true

+ 5 - 2
web_src/src/components/dialog/chooseChannelForCatalog.vue

@@ -38,7 +38,7 @@
 import catalogEdit from './catalogEdit.vue'
 import catalogEdit from './catalogEdit.vue'
 export default {
 export default {
     name: 'chooseChannelForCatalog',
     name: 'chooseChannelForCatalog',
-    props: ['platformId', 'platformName', 'defaultCatalogId', 'catalogIdChange', 'treeType'],
+    props: ['platformId', 'platformDeviceId', 'platformName', 'defaultCatalogId', 'catalogIdChange', 'treeType'],
     created() {
     created() {
         this.chooseId = this.defaultCatalogId;
         this.chooseId = this.defaultCatalogId;
         this.defaultCatalogIdSign = this.defaultCatalogId;
         this.defaultCatalogIdSign = this.defaultCatalogId;
@@ -171,6 +171,7 @@ export default {
             });
             });
         },
         },
         loadNode: function(node, resolve){
         loadNode: function(node, resolve){
+          console.log("this.platformDeviceId: " + this.platformDeviceId)
           if (node.level === 0) {
           if (node.level === 0) {
             resolve([
             resolve([
               {
               {
@@ -179,7 +180,7 @@ export default {
               type:  -1
               type:  -1
               },{
               },{
                 name: this.platformName,
                 name: this.platformName,
-                id:  this.platformId,
+                id:   this.platformDeviceId,
                 type:  0
                 type:  0
               }
               }
             ]);
             ]);
@@ -298,6 +299,8 @@ export default {
         return false;
         return false;
       },
       },
       nodeClickHandler: function (data, node, tree){
       nodeClickHandler: function (data, node, tree){
+          console.log(data)
+          console.log(node)
        this.chooseId = data.id;
        this.chooseId = data.id;
        this.chooseName = data.name;
        this.chooseName = data.name;
        if (this.catalogIdChange)this.catalogIdChange(this.chooseId, this.chooseName);
        if (this.catalogIdChange)this.catalogIdChange(this.chooseId, this.chooseName);

+ 12 - 4
web_src/src/components/dialog/getCatalog.vue

@@ -77,6 +77,7 @@ export default {
     },
     },
     methods: {
     methods: {
         openDialog(catalogIdResult) {
         openDialog(catalogIdResult) {
+          console.log(this.chooseId)
           this.showDialog = true
           this.showDialog = true
           this.catalogIdResult = catalogIdResult
           this.catalogIdResult = catalogIdResult
         },
         },
@@ -107,9 +108,6 @@ export default {
 
 
         },
         },
         loadNode: function(node, resolve){
         loadNode: function(node, resolve){
-
-
-
           if (node.level === 0) {
           if (node.level === 0) {
             this.$axios({
             this.$axios({
               method:"get",
               method:"get",
@@ -124,7 +122,7 @@ export default {
                   resolve([
                   resolve([
                    {
                    {
                       name: this.platformName,
                       name: this.platformName,
-                      id:  this.platformId,
+                      id:   res.data.data.deviceGBId,
                       type:  0
                       type:  0
                     }
                     }
                   ]);
                   ]);
@@ -142,9 +140,19 @@ export default {
          this.chooseId = data.id;
          this.chooseId = data.id;
         },
         },
         close: function() {
         close: function() {
+          this.chooseId = null;
           this.showDialog = false;
           this.showDialog = false;
         },
         },
         submit: function() {
         submit: function() {
+          console.log(this.chooseId)
+          if (this.chooseId === null) {
+            this.$message({
+              showClose: true,
+              message: '未选择任何节点,',
+              type: 'warning'
+            });
+            return;
+          }
           if (this.catalogIdResult)this.catalogIdResult(this.chooseId)
           if (this.catalogIdResult)this.catalogIdResult(this.chooseId)
           this.showDialog = false;
           this.showDialog = false;
         },
         },

+ 22 - 19
web_src/src/components/dialog/platformEdit.vue

@@ -37,13 +37,13 @@
               <el-form-item label="本地端口" prop="devicePort">
               <el-form-item label="本地端口" prop="devicePort">
                 <el-input v-model="platform.devicePort" :disabled="true" type="number"></el-input>
                 <el-input v-model="platform.devicePort" :disabled="true" type="number"></el-input>
               </el-form-item>
               </el-form-item>
+              <el-form-item label="SIP认证用户名" prop="username">
+                <el-input v-model="platform.username"></el-input>
+              </el-form-item>
             </el-form>
             </el-form>
           </el-col>
           </el-col>
           <el-col :span="12">
           <el-col :span="12">
             <el-form ref="platform2" :rules="rules" :model="platform" label-width="160px">
             <el-form ref="platform2" :rules="rules" :model="platform" label-width="160px">
-              <el-form-item label="SIP认证用户名" prop="username">
-                <el-input v-model="platform.username"></el-input>
-              </el-form-item>
               <el-form-item label="行政区划" prop="administrativeDivision">
               <el-form-item label="行政区划" prop="administrativeDivision">
                 <el-input v-model="platform.administrativeDivision" clearable></el-input>
                 <el-input v-model="platform.administrativeDivision" clearable></el-input>
               </el-form-item>
               </el-form-item>
@@ -79,7 +79,7 @@
                 </el-select>
                 </el-select>
               </el-form-item>
               </el-form-item>
               <el-form-item label="目录结构" prop="treeType" >
               <el-form-item label="目录结构" prop="treeType" >
-                <el-select v-model="platform.treeType" style="width: 100%" >
+                <el-select v-model="platform.treeType" style="width: 100%" @change="treeTypeChange">
                   <el-option key="WGS84" label="行政区划" value="CivilCode"></el-option>
                   <el-option key="WGS84" label="行政区划" value="CivilCode"></el-option>
                   <el-option key="GCJ02" label="业务分组" value="BusinessGroup"></el-option>
                   <el-option key="GCJ02" label="业务分组" value="BusinessGroup"></el-option>
                 </el-select>
                 </el-select>
@@ -98,6 +98,7 @@
                 <el-checkbox label="启用" v-model="platform.enable" @change="checkExpires"></el-checkbox>
                 <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="拉起离线推流" v-model="platform.startOfflinePush"></el-checkbox>
+                <el-checkbox label="RTCP保活" v-model="platform.rtcp" @change="rtcpCheckBoxChange"></el-checkbox>
               </el-form-item>
               </el-form-item>
               <el-form-item>
               <el-form-item>
                 <el-button type="primary" @click="onSubmit">{{
                 <el-button type="primary" @click="onSubmit">{{
@@ -251,21 +252,7 @@ export default {
 
 
     },
     },
     onSubmit: function () {
     onSubmit: function () {
-      if (this.onSubmit_text === "保存") {
-        this.$confirm("修改目录结构会导致关联目录与通道数据被清空", '提示', {
-          dangerouslyUseHTMLString: true,
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          center: true,
-          type: 'warning'
-        }).then(() => {
-          this.saveForm()
-        }).catch(() => {
-
-        });
-      }else {
-        this.saveForm()
-      }
+      this.saveForm()
     },
     },
     saveForm: function (){
     saveForm: function (){
       this.$axios({
       this.$axios({
@@ -343,6 +330,22 @@ export default {
       if (this.platform.enable && this.platform.expires == "0") {
       if (this.platform.enable && this.platform.expires == "0") {
         this.platform.expires = "300";
         this.platform.expires = "300";
       }
       }
+    },
+    rtcpCheckBoxChange: function (result){
+      if (result) {
+        this.$message({
+          showClose: true,
+          message: "开启RTCP保活需要上级平台支持,可以避免无效推流",
+          type: "warning",
+        });
+      }
+    },
+    treeTypeChange: function (){
+      this.$message({
+        showClose: true,
+        message: "修改目录结构会导致关联目录与通道数据被清空,保存后生效",
+        type: "warning",
+      });
     }
     }
   },
   },
 };
 };

binární
web_src/static/file/推流通道导入.zip