Bladeren bron

添加zlm集群支持

64850858 4 jaren geleden
bovenliggende
commit
89a9ab4534
75 gewijzigde bestanden met toevoegingen van 2415 en 898 verwijderingen
  1. 10 0
      pom.xml
  2. 26 1
      sql/mysql.sql
  3. 9 0
      src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
  4. 12 14
      src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
  5. 174 30
      src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java
  6. 79 12
      src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java
  7. 31 8
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  8. 13 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  9. 9 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStream.java
  10. 13 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java
  11. 16 9
      src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformNotRegisterEventLister.java
  12. 7 0
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java
  13. 4 10
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  14. 103 110
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  15. 6 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/SIPRequestAbstractProcessor.java
  16. 22 5
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java
  17. 15 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/ByeRequestProcessor.java
  18. 42 15
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java
  19. 5 5
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
  20. 4 10
      src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java
  21. 0 54
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java
  22. 61 31
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  23. 3 1
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookSubscribe.java
  24. 45 40
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
  25. 46 49
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java
  26. 40 36
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
  27. 93 99
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
  28. 36 24
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerConfig.java
  29. 0 45
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerManger.java
  30. 92 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/IMediaServerItem.java
  31. 12 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java
  32. 254 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java
  33. 20 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java
  34. 14 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java
  35. 33 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMRunInfo.java
  36. 44 0
      src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
  37. 10 7
      src/main/java/com/genersoft/iot/vmp/service/IMediaService.java
  38. 8 3
      src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
  39. 3 1
      src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java
  40. 2 1
      src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java
  41. 340 0
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
  42. 32 21
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
  43. 56 20
      src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
  44. 48 18
      src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java
  45. 14 2
      src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java
  46. 9 14
      src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java
  47. 10 0
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java
  48. 7 2
      src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java
  49. 99 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java
  50. 13 7
      src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java
  51. 5 4
      src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java
  52. 4 21
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  53. 24 0
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java
  54. 7 6
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceConfig.java
  55. 7 6
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java
  56. 4 2
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java
  57. 45 23
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
  58. 13 2
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java
  59. 4 3
      src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java
  60. 10 1
      src/main/java/com/genersoft/iot/vmp/vmanager/record/RecoderProxyController.java
  61. 21 6
      src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java
  62. 21 6
      src/main/java/com/genersoft/iot/vmp/vmanager/streamProxy/StreamProxyController.java
  63. 1 1
      src/main/java/com/genersoft/iot/vmp/web/ApiControlController.java
  64. 1 1
      src/main/java/com/genersoft/iot/vmp/web/ApiController.java
  65. 1 1
      src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java
  66. 1 1
      src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java
  67. 4 0
      src/main/resources/all-application.yml
  68. BIN
      src/main/resources/wvp.sqlite
  69. 15 19
      web_src/src/components/CloudRecord.vue
  70. 6 3
      web_src/src/components/PushVideoList.vue
  71. 11 4
      web_src/src/components/StreamProxyList.vue
  72. 84 40
      web_src/src/components/control.vue
  73. 43 32
      web_src/src/components/dialog/StreamProxyEdit.vue
  74. 7 8
      web_src/src/components/dialog/devicePlayer.vue
  75. 32 0
      web_src/src/components/service/MediaServer.js

+ 10 - 0
pom.xml

@@ -196,6 +196,16 @@
 			<version>1.12</version>
 		</dependency>
 
+<!--		&lt;!&ndash; 检测文件编码 &ndash;&gt;-->
+<!--		&lt;!&ndash; https://mvnrepository.com/artifact/cpdetector/cpdetector &ndash;&gt;-->
+<!--		<dependency>-->
+<!--			<groupId>cpdetector</groupId>-->
+<!--			<artifactId>cpdetector</artifactId>-->
+<!--			<version>1.0.8</version>-->
+<!--		</dependency>-->
+
+
+
 		<!-- onvif协议栈 -->
 		<dependency>
 			<groupId>be.teletask</groupId>

+ 26 - 1
sql/mysql.sql

@@ -138,6 +138,7 @@ create table stream_proxy
     timeout_ms     int          null,
     ffmpeg_cmd_key varchar(255) null,
     rtp_type       varchar(50) null,
+    mediaServerId       varchar(50) null,
     enable_hls     bit(1)   null,
     enable_mp4     bit(1)   null,
     enable         bit(1)   not null,
@@ -166,4 +167,28 @@ create table user
     create_time varchar(50) not null
 );
 
-insert into user (username, password, roleId, create_time) values ('admin', '21232f297a57a5a743894a0e4a801fc3', '0', '2021-04-13 14:14:57');
+insert into user (username, password, roleId, create_time) values ('admin', '21232f297a57a5a743894a0e4a801fc3', '0', '2021-04-13 14:14:57');
+
+create table media_server (
+      id          varchar(255)
+          primary key,
+      ip varchar(50) NOT NULL,
+      hookIp varchar(50) NOT NULL,
+      sdpIp varchar(50) NOT NULL,
+      streamIp varchar(50) NOT NULL,
+      httpPort int NOT NULL,
+      httpSSlPort int NOT NULL,
+      rtmpPort int NOT NULL,
+      rtmpSSlPort int NOT NULL,
+      rtpProxyPort int NOT NULL,
+      rtspPort int NOT NULL,
+      rtspSSLPort int NOT NULL,
+      autoConfig int NOT NULL,
+      secret varchar(50) NOT NULL,
+      streamNoneReaderDelayMS int NOT NULL,
+      rtpEnable int NOT NULL,
+      rtpPortRange varchar(50) NOT NULL,
+      recordAssistPort int NOT NULL,
+      createTime  varchar(50) not null,
+      updateTime  varchar(50) not null
+);

+ 9 - 0
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java

@@ -19,6 +19,7 @@ public class StreamInfo {
     private String rtmp;
     private String rtsp;
     private String rtc;
+    private String mediaServerId;
     private JSONArray tracks;
 
     public static class TransactionInfo{
@@ -165,4 +166,12 @@ public class StreamInfo {
     public void setTransactionInfo(TransactionInfo transactionInfo) {
         this.transactionInfo = transactionInfo;
     }
+
+    public String getMediaServerId() {
+        return mediaServerId;
+    }
+
+    public void setMediaServerId(String mediaServerId) {
+        this.mediaServerId = mediaServerId;
+    }
 }

+ 12 - 14
src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java

@@ -10,33 +10,31 @@ public class VideoManagerConstants {
 	
 	public static final String WVP_SERVER_PREFIX = "VMP_wvp_server";
 
-	public static final String MEDIA_SERVER_PREFIX = "VMP_media_server";
+	public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_";
 
-	public static final String MEDIA_STREAM_PREFIX = "VMP_media_stream";
+	public static final String MEDIA_STREAM_PREFIX = "VMP_MEDIA_STREAM";
 
-	public static final String DEVICE_PREFIX = "VMP_device_";
+	public static final String DEVICE_PREFIX = "VMP_DEVICE_";
 
-	public static final String CACHEKEY_PREFIX = "VMP_channel_";
+	public static final String CACHEKEY_PREFIX = "VMP_CHANNEL_";
 
 	public static final String KEEPLIVEKEY_PREFIX = "VMP_keeplive_";
 
-	public static final String PLAYER_PREFIX = "VMP_player_";
+	public static final String PLAYER_PREFIX = "VMP_PLAYER_";
 
-	public static final String PLAY_BLACK_PREFIX = "VMP_playback_";
+	public static final String PLAY_BLACK_PREFIX = "VMP_PLAYBACK_";
 
-	public static final String PLATFORM_PREFIX = "VMP_platform";
+	public static final String PLATFORM_KEEPLIVEKEY_PREFIX = "VMP_PLATFORM_KEEPLIVE_";
 
-	public static final String PLATFORM_KEEPLIVEKEY_PREFIX = "VMP_platform_keeplive_";
+	public static final String PLATFORM_CATCH_PREFIX = "VMP_PLATFORM_CATCH_";
 
-	public static final String PLATFORM_CATCH_PREFIX = "VMP_platform_catch_";
+	public static final String PLATFORM_REGISTER_PREFIX = "VMP_PLATFORM_REGISTER_";
 
-	public static final String PLATFORM_REGISTER_PREFIX = "VMP_platform_register_";
+	public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_PLATFORM_REGISTER_INFO_";
 
-	public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_platform_register_info_";
+	public static final String PLATFORM_SEND_RTP_INFO_PREFIX = "VMP_PLATFORM_SEND_RTP_INFO_";
 
-	public static final String PLATFORM_SEND_RTP_INFO_PREFIX = "VMP_platform_send_rtp_info_";
-
-	public static final String Pattern_Topic = "VMP_keeplive_platform_";
+	public static final String Pattern_Topic = "VMP_KEEPLIVE_PLATFORM_";
 
 	public static final String EVENT_ONLINE_REGISTER = "1";
 	

+ 174 - 30
src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java

@@ -1,11 +1,16 @@
 package com.genersoft.iot.vmp.conf;
 
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.util.StringUtils;
 
 @Configuration("mediaConfig")
-public class MediaConfig {
+public class MediaConfig implements IMediaServerItem {
+
+    @Value("${media.id:}")
+    private String id;
 
     @Value("${media.ip}")
     private String ip;
@@ -25,22 +30,22 @@ public class MediaConfig {
     @Value("${media.http-port}")
     private Integer httpPort;
 
-    @Value("${media.http-ssl-port:}")
+    @Value("${media.http-ssl-port:0}")
     private Integer httpSSlPort;
 
-    @Value("${media.rtmp-port:}")
+    @Value("${media.rtmp-port:0}")
     private Integer rtmpPort;
 
-    @Value("${media.rtmp-ssl-port:}")
+    @Value("${media.rtmp-ssl-port:0}")
     private Integer rtmpSSlPort;
 
-    @Value("${media.rtp-proxy-port:}")
+    @Value("${media.rtp-proxy-port:0}")
     private Integer rtpProxyPort;
 
-    @Value("${media.rtsp-port:}")
+    @Value("${media.rtsp-port:0}")
     private Integer rtspPort;
 
-    @Value("${media.rtsp-ssl-port:}")
+    @Value("${media.rtsp-ssl-port:0}")
     private Integer rtspSSLPort;
 
     @Value("${media.auto-config:true}")
@@ -61,6 +66,23 @@ public class MediaConfig {
     @Value("${media.record-assist-port:0}")
     private Integer recordAssistPort;
 
+    private String updateTime;
+
+    private String createTime;
+
+    private boolean docker = false;
+
+    private int count;
+
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+    
     public String getIp() {
         return ip;
     }
@@ -82,82 +104,114 @@ public class MediaConfig {
         this.hookIp = hookIp;
     }
 
-    public String getSdpIp() {
-        if (StringUtils.isEmpty(sdpIp)){
-            return ip;
-        }else {
-            return sdpIp;
-        }
+    public String getSipIp() {
+        return sipIp;
     }
 
-    public void setSdpIp(String sdpIp) {
-        this.sdpIp = sdpIp;
+    public void setSipIp(String sipIp) {
+        this.sipIp = sipIp;
     }
 
-    public String getStreamIp() {
-        if (StringUtils.isEmpty(streamIp)){
-            return ip;
-        }else {
-            return streamIp;
-        }
+    public void setSdpIp(String sdpIp) {
+        this.sdpIp = sdpIp;
     }
 
     public void setStreamIp(String streamIp) {
         this.streamIp = streamIp;
     }
 
-    public Integer getHttpPort() {
+    public int getHttpPort() {
         return httpPort;
     }
 
+    @Override
+    public void setHttpPort(int httpPort) {
+
+    }
+
     public void setHttpPort(Integer httpPort) {
         this.httpPort = httpPort;
     }
 
-    public Integer getHttpSSlPort() {
+    public int getHttpSSlPort() {
         return httpSSlPort;
     }
 
+    @Override
+    public void setHttpSSlPort(int httpSSlPort) {
+
+    }
+
     public void setHttpSSlPort(Integer httpSSlPort) {
         this.httpSSlPort = httpSSlPort;
     }
 
-    public Integer getRtmpPort() {
+    public int getRtmpPort() {
         return rtmpPort;
     }
 
+    @Override
+    public void setRtmpPort(int rtmpPort) {
+
+    }
+
     public void setRtmpPort(Integer rtmpPort) {
         this.rtmpPort = rtmpPort;
     }
 
-    public Integer getRtmpSSlPort() {
+    public int getRtmpSSlPort() {
         return rtmpSSlPort;
     }
 
+    @Override
+    public void setRtmpSSlPort(int rtmpSSlPort) {
+
+    }
+
     public void setRtmpSSlPort(Integer rtmpSSlPort) {
         this.rtmpSSlPort = rtmpSSlPort;
     }
 
-    public Integer getRtpProxyPort() {
-        return rtpProxyPort;
+    public int getRtpProxyPort() {
+        if (rtpProxyPort == null) {
+            return 0;
+        }else {
+            return rtpProxyPort;
+        }
+
+    }
+
+    @Override
+    public void setRtpProxyPort(int rtpProxyPort) {
+
     }
 
     public void setRtpProxyPort(Integer rtpProxyPort) {
         this.rtpProxyPort = rtpProxyPort;
     }
 
-    public Integer getRtspPort() {
+    public int getRtspPort() {
         return rtspPort;
     }
 
+    @Override
+    public void setRtspPort(int rtspPort) {
+
+    }
+
     public void setRtspPort(Integer rtspPort) {
         this.rtspPort = rtspPort;
     }
 
-    public Integer getRtspSSLPort() {
+    public int getRtspSSLPort() {
         return rtspSSLPort;
     }
 
+    @Override
+    public void setRtspSSLPort(int rtspSSLPort) {
+
+    }
+
     public void setRtspSSLPort(Integer rtspSSLPort) {
         this.rtspSSLPort = rtspSSLPort;
     }
@@ -202,11 +256,101 @@ public class MediaConfig {
         this.rtpPortRange = rtpPortRange;
     }
 
-    public Integer getRecordAssistPort() {
+    public int getRecordAssistPort() {
         return recordAssistPort;
     }
 
+    @Override
+    public void setRecordAssistPort(int recordAssistPort) {
+
+    }
+
     public void setRecordAssistPort(Integer recordAssistPort) {
         this.recordAssistPort = recordAssistPort;
     }
+
+    @Override
+    public boolean isDocker() {
+        return docker;
+    }
+
+    @Override
+    public void setDocker(boolean docker) {
+        this.docker = docker;
+    }
+
+    public String getSdpIp() {
+        if (StringUtils.isEmpty(sdpIp)){
+            return ip;
+        }else {
+            return sdpIp;
+        }
+    }
+
+    public String getStreamIp() {
+        if (StringUtils.isEmpty(streamIp)){
+            return ip;
+        }else {
+            return streamIp;
+        }
+    }
+
+
+
+    public MediaServerItem getMediaSerItem(){
+        MediaServerItem mediaServerItem = new MediaServerItem();
+        mediaServerItem.setId(id);
+        mediaServerItem.setIp(ip);
+        mediaServerItem.setDocker(true);
+        mediaServerItem.setHookIp(hookIp);
+        mediaServerItem.setSdpIp(sdpIp);
+        mediaServerItem.setStreamIp(streamIp);
+        mediaServerItem.setHttpPort(httpPort);
+        mediaServerItem.setHttpSSlPort(httpSSlPort);
+        mediaServerItem.setRtmpPort(rtmpPort);
+        mediaServerItem.setRtmpSSlPort(rtmpSSlPort);
+        mediaServerItem.setRtpProxyPort(rtpProxyPort);
+        mediaServerItem.setRtspPort(rtspPort);
+        mediaServerItem.setRtspSSLPort(rtspSSLPort);
+        mediaServerItem.setAutoConfig(autoConfig);
+        mediaServerItem.setSecret(secret);
+        mediaServerItem.setStreamNoneReaderDelayMS(streamNoneReaderDelayMS);
+        mediaServerItem.setRtpEnable(rtpEnable);
+        mediaServerItem.setRtpPortRange(rtpPortRange);
+        mediaServerItem.setRecordAssistPort(recordAssistPort);
+        mediaServerItem.setCreateTime(createTime);
+        mediaServerItem.setUpdateTime(updateTime);
+        mediaServerItem.setCount(count);
+        return mediaServerItem;
+    }
+
+    @Override
+    public String getUpdateTime() {
+        return updateTime;
+    }
+
+    @Override
+    public void setUpdateTime(String updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    @Override
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    @Override
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    @Override
+    public int getCount() {
+        return count;
+    }
+
+    @Override
+    public void setCount(int count) {
+        this.count = count;
+    }
 }

+ 79 - 12
src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java

@@ -1,12 +1,16 @@
 package com.genersoft.iot.vmp.conf;
 
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
+import org.apache.catalina.connector.ClientAbortException;
+import org.apache.http.HttpHost;
 import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
-import org.apache.catalina.connector.ClientAbortException;
 import org.mitre.dsmiley.httpproxy.ProxyServlet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.web.servlet.ServletRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -24,13 +28,16 @@ public class ProxyServletConfig {
     private final static Logger logger = LoggerFactory.getLogger(ProxyServletConfig.class);
 
     @Autowired
-    private MediaConfig mediaConfig;
+    private IMediaServerService mediaServerService;
+
+    @Value("${server.port}")
+    private int serverPort;
 
     @Bean
     public ServletRegistrationBean zlmServletRegistrationBean(){
         ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new ZLMProxySerlet(),"/zlm/*");
         servletRegistrationBean.setName("zlm_Proxy");
-        servletRegistrationBean.addInitParameter("targetUri", String.format("http://%s:%s", mediaConfig.getIp(), mediaConfig.getHttpPort()));
+        servletRegistrationBean.addInitParameter("targetUri", "http://127.0.0.1:6080");
         servletRegistrationBean.addUrlMappings();
         if (logger.isDebugEnabled()) {
             servletRegistrationBean.addInitParameter("log", "true");
@@ -38,24 +45,26 @@ public class ProxyServletConfig {
         return servletRegistrationBean;
     }
 
-    class  ZLMProxySerlet extends ProxyServlet{
-
-
-
+    class ZLMProxySerlet extends ProxyServlet{
         @Override
         protected String rewriteQueryStringFromRequest(HttpServletRequest servletRequest, String queryString) {
             String queryStr = super.rewriteQueryStringFromRequest(servletRequest, queryString);
-            if (!StringUtils.isEmpty(queryStr)) {
-                queryStr += "&secret=" + mediaConfig.getSecret();
-            }else {
-                queryStr = "secret=" + mediaConfig.getSecret();
+            IMediaServerItem mediaInfo = getMediaInfoByUri(servletRequest.getRequestURI());
+            if (mediaInfo != null) {
+                if (!StringUtils.isEmpty(queryStr)) {
+                    queryStr += "&secret=" + mediaInfo.getSecret();
+                }else {
+                    queryStr = "secret=" + mediaInfo.getSecret();
+                }
             }
             return queryStr;
         }
 
+        /**
+         * 异常处理
+         */
         @Override
         protected void handleRequestException(HttpRequest proxyRequest, HttpResponse proxyResonse, Exception e){
-            //System.out.println(e.getMessage());
             try {
                 super.handleRequestException(proxyRequest, proxyResonse, e);
             } catch (ServletException servletException) {
@@ -72,6 +81,64 @@ public class ProxyServletConfig {
                 logger.error("zlm 代理失败: ", e);
             }
         }
+
+        /**
+         * 对于为按照格式请求的可以直接返回404
+         */
+        @Override
+        protected String getTargetUri(HttpServletRequest servletRequest) {
+            String requestURI = servletRequest.getRequestURI();
+            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
+
+            String uri = null;
+            if (mediaInfo != null) {
+//                String realRequestURI = requestURI.substring(requestURI.indexOf(mediaInfo.getId())+ mediaInfo.getId().length());
+                uri = String.format("http://%s:%s", mediaInfo.getIp(), mediaInfo.getHttpPort());
+            }else {
+                uri = "http://127.0.0.1:" + serverPort +"/index/hook/null"; // 只是一个能返回404的请求而已, 其他的也可以
+            }
+            return uri;
+        }
+
+        /**
+         * 动态替换请求目标
+         */
+        @Override
+        protected HttpHost getTargetHost(HttpServletRequest servletRequest) {
+            String requestURI = servletRequest.getRequestURI();
+            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
+            HttpHost host;
+            if (mediaInfo != null) {
+                host = new HttpHost(mediaInfo.getIp(), mediaInfo.getHttpPort());
+            }else {
+                host = new HttpHost("127.0.0.1", serverPort);
+            }
+            return host;
+
+        }
+
+        /**
+         * 根据uri获取流媒体信息
+         */
+        IMediaServerItem getMediaInfoByUri(String uri){
+            String[] split = uri.split("/");
+            String mediaServerId = split[2];
+            return mediaServerService.getOne(mediaServerId);
+        }
+
+        /**
+         * 去掉url中的标志信息
+         */
+        @Override
+        protected String rewriteUrlFromRequest(HttpServletRequest servletRequest) {
+            String requestURI = servletRequest.getRequestURI();
+            IMediaServerItem mediaInfo = getMediaInfoByUri(requestURI);
+            String url = super.rewriteUrlFromRequest(servletRequest);
+            if (mediaInfo == null) {
+                return  url;
+            }
+            return url.replace(mediaInfo.getId() + "/", "");
+        }
     }
 
 }

+ 31 - 8
src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java

@@ -95,20 +95,43 @@ public class SipLayer implements SipListener {
 			tcpListeningPoint = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getSipPort(), "TCP");
 			tcpSipProvider = sipStack.createSipProvider(tcpListeningPoint);
 			tcpSipProvider.addSipListener(this);
-			logger.info("Sip Server TCP 启动成功 port {" + sipConfig.getSipPort() + "}");
-		} catch (TransportNotSupportedException | InvalidArgumentException | TooManyListenersException | ObjectInUseException e) {
-			logger.error(String.format("创建SIP服务失败: %s", e.getMessage()));
+			logger.info("Sip Server TCP 启动成功 port {" + sipConfig.getMonitorIp() + ":" + sipConfig.getSipPort() + "}");
+//		} catch (TransportNotSupportedException | InvalidArgumentException | TooManyListenersException | ObjectInUseException e) {
+//			logger.error(String.format("创建SIP服务失败: %s", e.getMessage()));
+//		}
+		} catch (TransportNotSupportedException e) {
+			e.printStackTrace();
+		} catch (InvalidArgumentException e) {
+			logger.error("无法使用 [ {}:{} ]作为SIP[ TCP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用"
+					, sipConfig.getMonitorIp(), sipConfig.getSipPort());
+		} catch (TooManyListenersException e) {
+			e.printStackTrace();
+		} catch (ObjectInUseException e) {
+			e.printStackTrace();
 		}
 		return tcpSipProvider;
 	}
 	
 	@Bean("udpSipProvider")
 	@DependsOn("sipStack")
-	private SipProvider startUdpListener() throws Exception {
-		ListeningPoint udpListeningPoint = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getSipPort(), "UDP");
-		SipProvider udpSipProvider = sipStack.createSipProvider(udpListeningPoint);
-		udpSipProvider.addSipListener(this);
-		logger.info("Sip Server UDP 启动成功 port {" + sipConfig.getSipPort() + "}");
+	private SipProvider startUdpListener() {
+		ListeningPoint udpListeningPoint = null;
+		SipProvider udpSipProvider = null;
+		try {
+			udpListeningPoint = sipStack.createListeningPoint(sipConfig.getMonitorIp(), sipConfig.getSipPort(), "UDP");
+			udpSipProvider = sipStack.createSipProvider(udpListeningPoint);
+			udpSipProvider.addSipListener(this);
+		} catch (TransportNotSupportedException e) {
+			e.printStackTrace();
+		} catch (InvalidArgumentException e) {
+			logger.error("无法使用 [ {}:{} ]作为SIP[ UDP ]服务,可排查: 1. sip.monitor-ip 是否为本机网卡IP; 2. sip.port 是否已被占用"
+					, sipConfig.getMonitorIp(), sipConfig.getSipPort());
+		} catch (TooManyListenersException e) {
+			e.printStackTrace();
+		} catch (ObjectInUseException e) {
+			e.printStackTrace();
+		}
+		logger.info("Sip Server UDP 启动成功 port [" + sipConfig.getMonitorIp() + ":" + sipConfig.getSipPort() + "]");
 		return udpSipProvider;
 	}
 

+ 13 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java

@@ -94,6 +94,11 @@ public class Device {
 	 */
 	private String updateTime;
 
+	/**
+	 * 设备使用的媒体id, 默认为null
+	 */
+	private String mediaServerId;
+
 	public String getDeviceId() {
 		return deviceId;
 	}
@@ -229,4 +234,12 @@ public class Device {
 	public void setUpdateTime(String updateTime) {
 		this.updateTime = updateTime;
 	}
+
+	public String getMediaServerId() {
+		return mediaServerId;
+	}
+
+	public void setMediaServerId(String mediaServerId) {
+		this.mediaServerId = mediaServerId;
+	}
 }

+ 9 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStream.java

@@ -9,6 +9,7 @@ public class GbStream extends PlatformGbStream{
     private String stream;
     private String gbId;
     private String name;
+    private String mediaServerId;
     private double longitude;
     private double latitude;
     private String streamType;
@@ -77,4 +78,12 @@ public class GbStream extends PlatformGbStream{
     public void setStatus(boolean status) {
         this.status = status;
     }
+
+    public String getMediaServerId() {
+        return mediaServerId;
+    }
+
+    public void setMediaServerId(String mediaServerId) {
+        this.mediaServerId = mediaServerId;
+    }
 }

+ 13 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java

@@ -66,6 +66,11 @@ public class SendRtpItem {
      */
     private int localPort;
 
+    /**
+     * 使用的流媒体
+     */
+    private String mediaServerId;
+
     public String getIp() {
         return ip;
     }
@@ -161,4 +166,12 @@ public class SendRtpItem {
     public void setTcpActive(boolean tcpActive) {
         this.tcpActive = tcpActive;
     }
+
+    public String getMediaServerId() {
+        return mediaServerId;
+    }
+
+    public void setMediaServerId(String mediaServerId) {
+        this.mediaServerId = mediaServerId;
+    }
 }

+ 16 - 9
src/main/java/com/genersoft/iot/vmp/gb28181/event/platformNotRegister/PlatformNotRegisterEventLister.java

@@ -6,6 +6,9 @@ import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 import org.slf4j.Logger;
@@ -32,6 +35,8 @@ public class PlatformNotRegisterEventLister implements ApplicationListener<Platf
     private IVideoManagerStorager storager;
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
+    @Autowired
+    private IMediaServerService mediaServerService;
 
     @Autowired
     private SIPCommanderFroPlatform sipCommanderFroPlatform;
@@ -62,22 +67,24 @@ public class PlatformNotRegisterEventLister implements ApplicationListener<Platf
             logger.info("停止[ {} ]的所有推流", event.getPlatformGbID());
             StringBuilder app = new StringBuilder();
             StringBuilder stream = new StringBuilder();
-            for (int i = 0; i < sendRtpItems.size(); i++) {
+            for (SendRtpItem sendRtpItem : sendRtpItems) {
                 if (app.length() != 0) {
                     app.append(",");
                 }
-                app.append(sendRtpItems.get(i).getApp());
+                app.append(sendRtpItem.getApp());
                 if (stream.length() != 0) {
                     stream.append(",");
                 }
-                stream.append(sendRtpItems.get(i).getStreamId());
-                redisCatchStorage.deleteSendRTPServer(event.getPlatformGbID(), sendRtpItems.get(i).getChannelId());
+                stream.append(sendRtpItem.getStreamId());
+                redisCatchStorage.deleteSendRTPServer(event.getPlatformGbID(), sendRtpItem.getChannelId());
+                IMediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+                Map<String, Object> param = new HashMap<>();
+                param.put("vhost", "__defaultVhost__");
+                param.put("app", app.toString());
+                param.put("stream", stream.toString());
+                zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
             }
-            Map<String, Object> param = new HashMap<>();
-            param.put("vhost","__defaultVhost__");
-            param.put("app", app.toString());
-            param.put("stream", stream.toString());
-            zlmrtpServerFactory.stopSendRtpStream(param);
+
 
         }
 

+ 7 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPProcessorFactory.java

@@ -9,6 +9,7 @@ import javax.sip.message.Response;
 
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.gb28181.transmit.response.impl.*;
 import com.genersoft.iot.vmp.service.IPlayService;
@@ -104,6 +105,9 @@ public class SIPProcessorFactory {
 	@Autowired
 	private ZLMRTPServerFactory zlmrtpServerFactory;
 
+	@Autowired
+	private IMediaServerService mediaServerService;
+
 
 	// 注:这里使用注解会导致循环依赖注入,暂用springBean
 	private SipProvider tcpSipProvider;
@@ -128,6 +132,7 @@ public class SIPProcessorFactory {
 			processor.setStorager(storager);
 			processor.setRedisCatchStorage(redisCatchStorage);
 			processor.setZlmrtpServerFactory(zlmrtpServerFactory);
+			processor.setMediaServerService(mediaServerService);
 			return processor;
 		} else if (Request.REGISTER.equals(method)) {
 			RegisterRequestProcessor processor = new RegisterRequestProcessor();
@@ -148,6 +153,7 @@ public class SIPProcessorFactory {
 			processor.setRequestEvent(evt);
 			processor.setRedisCatchStorage(redisCatchStorage);
 			processor.setZlmrtpServerFactory(zlmrtpServerFactory);
+			processor.setMediaServerService(mediaServerService);
 			return processor;
 		} else if (Request.BYE.equals(method)) {
 			ByeRequestProcessor processor = new ByeRequestProcessor();
@@ -155,6 +161,7 @@ public class SIPProcessorFactory {
 			processor.setRedisCatchStorage(redisCatchStorage);
 			processor.setZlmrtpServerFactory(zlmrtpServerFactory);
 			processor.setSIPCommander(cmder);
+			processor.setMediaServerService(mediaServerService);
 			return processor;
 		} else if (Request.CANCEL.equals(method)) {
 			CancelRequestProcessor processor = new CancelRequestProcessor();

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

@@ -3,6 +3,8 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 
 /**    
  * @Description:设备能力接口,用于定义设备的控制、查询能力   
@@ -90,7 +92,7 @@ public interface ISIPCommander {
 	 * @param device  视频设备
 	 * @param channelId  预览通道
 	 */
-	void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
+	void playStreamCmd(IMediaServerItem mediaServerItem, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
 	
 	/**
 	 * 请求回放视频流
@@ -100,7 +102,7 @@ public interface ISIPCommander {
 	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 */
-	void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
+	void playbackStreamCmd(IMediaServerItem mediaServerItem,Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
 	
 	/**
 	 * 视频流停止
@@ -288,12 +290,4 @@ public interface ISIPCommander {
 	 * @return				true = 命令发送成功
 	 */
 	boolean alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime);
-
-
-	/**
-	 * 释放rtpserver
-	 * @param device
-	 * @param channelId
-	 */
-    void closeRTPServer(Device device, String channelId);
 }

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

@@ -13,11 +13,10 @@ import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.MediaConfig;
 import com.genersoft.iot.vmp.conf.UserSetup;
-import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
+import com.genersoft.iot.vmp.media.zlm.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
-import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
-import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
-import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 import gov.nist.javax.sip.message.SIPRequest;
@@ -37,6 +36,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
 import com.genersoft.iot.vmp.gb28181.utils.DateUtil;
 import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
 import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
+import org.springframework.util.StringUtils;
 
 /**    
  * @Description:设备能力接口,用于定义设备的控制、查询能力   
@@ -77,12 +77,6 @@ public class SIPCommander implements ISIPCommander {
 	@Autowired
 	private ZLMRTPServerFactory zlmrtpServerFactory;
 
-	@Autowired
-	private ZLMRESTfulUtils zlmresTfulUtils;
-
-	@Autowired
-	private MediaConfig mediaConfig;
-
 	@Autowired
 	private UserSetup userSetup;
 
@@ -340,48 +334,45 @@ public class SIPCommander implements ISIPCommander {
 	  * @param errorEvent sip错误订阅
 	  */
 	@Override
-	public void playStreamCmd(Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
+	public void playStreamCmd(IMediaServerItem mediaServerItem, Device device, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent) {
 		String streamId = null;
 		try {
 			if (device == null) return;
+			String streamMode = device.getStreamMode().toUpperCase();
+
 			String ssrc = streamSession.createPlaySsrc();
-			if (mediaConfig.isRtpEnable()) {
+			if (mediaServerItem.isRtpEnable()) {
 				streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
 			}else {
 				streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
 			}
-			String streamMode = device.getStreamMode().toUpperCase();
-			ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
-			if (mediaInfo == null) {
-				logger.warn("点播时发现ZLM尚未连接...");
-				return;
-			}
 			Integer mediaPort = null;
 			// 使用动态udp端口
-			if (mediaConfig.isRtpEnable()) {
-				mediaPort = zlmrtpServerFactory.createRTPServer(streamId);
+			if (mediaServerItem.isRtpEnable()) {
+				mediaPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId);
 			}else {
-				mediaPort = mediaInfo.getRtpProxyPort();
+				mediaPort = mediaServerItem.getRtpProxyPort();
 			}
-
+			logger.info("{} 分配的ZLM为: {} [{}:{}]", streamId, mediaServerItem.getId(), mediaServerItem.getIp(), mediaPort);
 			// 添加订阅
 			JSONObject subscribeKey = new JSONObject();
 			subscribeKey.put("app", "rtp");
 			subscribeKey.put("stream", streamId);
 			subscribeKey.put("regist", true);
-
-			subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, json->{
+			subscribeKey.put("mediaServerId", mediaServerItem.getId());
+			subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
+					(IMediaServerItem mediaServerItemInUse, JSONObject json)->{
 				if (userSetup.isWaitTrack() && json.getJSONArray("tracks") == null) return;
-				event.response(json);
+				event.response(mediaServerItemInUse, json);
 				subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey);
 			});
 			//
 			StringBuffer content = new StringBuffer(200);
 			content.append("v=0\r\n");
 //			content.append("o=" + sipConfig.getSipId() + " 0 0 IN IP4 "+mediaInfo.getWanIp()+"\r\n");
-			content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
+			content.append("o="+"00000"+" 0 0 IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
 			content.append("s=Play\r\n");
-			content.append("c=IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
+			content.append("c=IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
 			content.append("t=0 0\r\n");
 
 			if (userSetup.isSeniorSdp()) {
@@ -459,21 +450,32 @@ public class SIPCommander implements ISIPCommander {
 	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
 	 */ 
 	@Override
-	public void playbackStreamCmd(Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event
+	public void playbackStreamCmd(IMediaServerItem mediaServerItem,Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event
 			, SipSubscribe.Event errorEvent) {
 		try {
-			ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
 			String ssrc = streamSession.createPlayBackSsrc();
 			String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
+
+			Integer mediaPort = null;
+			// 使用动态udp端口
+			if (mediaServerItem.isRtpEnable()) {
+				mediaPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId);
+			}else {
+				mediaPort = mediaServerItem.getRtpProxyPort();
+			}
+			logger.info("{} 分配的ZLM为: {} [{}:{}]", streamId, mediaServerItem.getId(), mediaServerItem.getIp(), mediaPort);
+
 			// 添加订阅
 			JSONObject subscribeKey = new JSONObject();
 			subscribeKey.put("app", "rtp");
 			subscribeKey.put("stream", streamId);
 			subscribeKey.put("regist", true);
+			subscribeKey.put("mediaServerId", mediaServerItem.getId());
 			logger.debug("录像回放添加订阅,订阅内容:" + subscribeKey.toString());
-			subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, json->{
+			subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey,
+					(IMediaServerItem mediaServerItemInUse, JSONObject json)->{
 				if (userSetup.isWaitTrack() && json.getJSONArray("tracks") == null) return;
-				event.response(json);
+				event.response(mediaServerItemInUse, json);
 				subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey);
 			});
 
@@ -482,16 +484,12 @@ public class SIPCommander implements ISIPCommander {
 	        content.append("o="+sipConfig.getSipId()+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n");
 	        content.append("s=Playback\r\n");
 	        content.append("u="+channelId+":0\r\n");
-	        content.append("c=IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
+	        content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
 	        content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" "
 					+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n");
-			Integer mediaPort = null;
-			// 使用动态udp端口
-			if (mediaConfig.isRtpEnable()) {
-				mediaPort = zlmrtpServerFactory.createRTPServer(streamId);
-			}else {
-				mediaPort = mediaInfo.getRtpProxyPort();
-			}
+
+
+
 			String streamMode = device.getStreamMode().toUpperCase();
 
 			if (userSetup.isSeniorSdp()) {
@@ -560,56 +558,63 @@ public class SIPCommander implements ISIPCommander {
 
 
 	/**
-	 * 视频流停止
-	 * 
+	 * 视频流停止, 不使用回调
 	 */
 	@Override
 	public void streamByeCmd(String deviceId, String channelId) {
 		streamByeCmd(deviceId, channelId, null);
 	}
+
+	/**
+	 * 视频流停止
+	 */
 	@Override
 	public void streamByeCmd(String deviceId, String channelId, SipSubscribe.Event okEvent) {
-		
+		StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
 		try {
 			ClientTransaction transaction = streamSession.getTransaction(deviceId, channelId);
 			// 服务重启后, 无法直接发送bye, 通过手动构建发送
+//			if (transaction == null) {
+//
+//				if (streamInfo != null) {
+//					MediaServerItem mediaServerItem = redisCatchStorage.getMediaInfo(streamInfo.getMediaServerId());
+//					JSONObject mediaList = zlmresTfulUtils.getMediaList(mediaServerItem,streamInfo.getApp(), streamInfo.getStreamId());
+//					if (mediaList != null) { // 仍在推流才发送
+//						if (mediaList.getInteger("code") == 0) {
+//							JSONArray data = mediaList.getJSONArray("data");
+//							if (data != null && data.size() > 0) {
+//								Device device = storager.queryVideoDevice(deviceId);
+//								if (device != null) {
+//									StreamInfo.TransactionInfo transactionInfo = streamInfo.getTransactionInfo();
+//									try {
+//										Request byteRequest = headerProvider.createByteRequest(device, channelId,
+//												transactionInfo.branch,
+//												transactionInfo.localTag,
+//												transactionInfo.remoteTag,
+//												transactionInfo.callId);
+//										transmitRequest(device, byteRequest);
+//									} catch (InvalidArgumentException e) {
+//										e.printStackTrace();
+//									}
+//								}
+//							}
+//						}
+//					}
+//					redisCatchStorage.stopPlay(streamInfo);
+//				}
+//
+//				if (okEvent != null) {
+//					okEvent.response(null);
+//				}
+//				return;
+//			}
 			if (transaction == null) {
-
-				StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
-				if (streamInfo != null) {
-					JSONObject mediaList = zlmresTfulUtils.getMediaList(streamInfo.getApp(), streamInfo.getStreamId());
-					if (mediaList != null) { // 仍在推流才发送
-						if (mediaList.getInteger("code") == 0) {
-							JSONArray data = mediaList.getJSONArray("data");
-							if (data != null && data.size() > 0) {
-								Device device = storager.queryVideoDevice(deviceId);
-								if (device != null) {
-									StreamInfo.TransactionInfo transactionInfo = streamInfo.getTransactionInfo();
-									try {
-										Request byteRequest = headerProvider.createByteRequest(device, channelId,
-												transactionInfo.branch,
-												transactionInfo.localTag,
-												transactionInfo.remoteTag,
-												transactionInfo.callId);
-										transmitRequest(device, byteRequest);
-									} catch (InvalidArgumentException e) {
-										e.printStackTrace();
-									}
-								}
-							}
-						}
-					}
-					redisCatchStorage.stopPlay(streamInfo);
-				}
-
-				if (okEvent != null) {
-					okEvent.response(null);
-				}
+				logger.warn("[ {} -> {}]停止视频流的时候发现事务已丢失", deviceId, channelId);
 				return;
 			}
-			
 			Dialog dialog = transaction.getDialog();
 			if (dialog == null) {
+				logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", deviceId, channelId);
 				return;
 			}
 			Request byeRequest = dialog.createRequest(Request.BYE);
@@ -632,7 +637,7 @@ public class SIPCommander implements ISIPCommander {
 			}
 
 			dialog.sendRequest(clientTransaction);
-			zlmrtpServerFactory.closeRTPServer(streamSession.getStreamId(deviceId, channelId));
+
 			streamSession.remove(deviceId, channelId);
 		} catch (SipException | ParseException e) {
 			e.printStackTrace();
@@ -721,7 +726,7 @@ public class SIPCommander implements ISIPCommander {
 			cmdXml.append("<Control>\r\n");
 			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (XmlUtil.isEmpty(channelId)) {
+			if (StringUtils.isEmpty(channelId)) {
 				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
 			} else {
 				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
@@ -821,16 +826,16 @@ public class SIPCommander implements ISIPCommander {
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
 			cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
 			cmdXml.append("<AlarmCmd>ResetAlarm</AlarmCmd>\r\n");
-			if (!XmlUtil.isEmpty(alarmMethod) || !XmlUtil.isEmpty(alarmType)) {
+			if (!StringUtils.isEmpty(alarmMethod) || !StringUtils.isEmpty(alarmType)) {
 				cmdXml.append("<Info>\r\n");
 			}
-			if (!XmlUtil.isEmpty(alarmMethod)) {
+			if (!StringUtils.isEmpty(alarmMethod)) {
 				cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
 			}
-			if (!XmlUtil.isEmpty(alarmType)) {
+			if (!StringUtils.isEmpty(alarmType)) {
 				cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
 			}
-			if (!XmlUtil.isEmpty(alarmMethod) || !XmlUtil.isEmpty(alarmType)) {
+			if (!StringUtils.isEmpty(alarmMethod) || !StringUtils.isEmpty(alarmType)) {
 				cmdXml.append("</Info>\r\n");
 			}
 			cmdXml.append("</Control>\r\n");
@@ -863,7 +868,7 @@ public class SIPCommander implements ISIPCommander {
 			cmdXml.append("<Control>\r\n");
 			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (XmlUtil.isEmpty(channelId)) {
+			if (StringUtils.isEmpty(channelId)) {
 				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
 			} else {
 				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
@@ -901,7 +906,7 @@ public class SIPCommander implements ISIPCommander {
 			cmdXml.append("<Control>\r\n");
 			cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (XmlUtil.isEmpty(channelId)) {
+			if (StringUtils.isEmpty(channelId)) {
 				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
 			} else {
 				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
@@ -969,13 +974,13 @@ public class SIPCommander implements ISIPCommander {
 			cmdXml.append("<Control>\r\n");
 			cmdXml.append("<CmdType>DeviceConfig</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (XmlUtil.isEmpty(channelId)) {
+			if (StringUtils.isEmpty(channelId)) {
 				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
 			} else {
 				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
 			}
 			cmdXml.append("<BasicParam>\r\n");
-			if (!XmlUtil.isEmpty(name)) {
+			if (!StringUtils.isEmpty(name)) {
 				cmdXml.append("<Name>" + name + "</Name>\r\n");
 			}
 			if (NumericUtil.isInteger(expiration)) {
@@ -1169,22 +1174,22 @@ public class SIPCommander implements ISIPCommander {
 			cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
 			cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			if (!XmlUtil.isEmpty(startPriority)) {
+			if (!StringUtils.isEmpty(startPriority)) {
 				cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
 			}
-			if (!XmlUtil.isEmpty(endPriority)) {
+			if (!StringUtils.isEmpty(endPriority)) {
 				cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
 			}
-			if (!XmlUtil.isEmpty(alarmMethod)) {
+			if (!StringUtils.isEmpty(alarmMethod)) {
 				cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
 			}
-			if (!XmlUtil.isEmpty(alarmType)) {
+			if (!StringUtils.isEmpty(alarmType)) {
 				cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
 			}
-			if (!XmlUtil.isEmpty(startTime)) {
+			if (!StringUtils.isEmpty(startTime)) {
 				cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
 			}
-			if (!XmlUtil.isEmpty(endTime)) {
+			if (!StringUtils.isEmpty(endTime)) {
 				cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
 			}
 			cmdXml.append("</Query>\r\n");
@@ -1218,7 +1223,7 @@ public class SIPCommander implements ISIPCommander {
 			cmdXml.append("<Query>\r\n");
 			cmdXml.append("<CmdType>ConfigDownload</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (XmlUtil.isEmpty(channelId)) {
+			if (StringUtils.isEmpty(channelId)) {
 				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
 			} else {
 				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
@@ -1253,7 +1258,7 @@ public class SIPCommander implements ISIPCommander {
 			cmdXml.append("<Query>\r\n");
 			cmdXml.append("<CmdType>PresetQuery</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
-			if (XmlUtil.isEmpty(channelId)) {
+			if (StringUtils.isEmpty(channelId)) {
 				cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
 			} else {
 				cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
@@ -1365,22 +1370,22 @@ public class SIPCommander implements ISIPCommander {
 			cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
 			cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
 			cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-			if (!XmlUtil.isEmpty(startPriority)) {
+			if (!StringUtils.isEmpty(startPriority)) {
 				cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
 			}
-			if (!XmlUtil.isEmpty(endPriority)) {
+			if (!StringUtils.isEmpty(endPriority)) {
 				cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
 			}
-			if (!XmlUtil.isEmpty(alarmMethod)) {
+			if (!StringUtils.isEmpty(alarmMethod)) {
 				cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
 			}
-			if (!XmlUtil.isEmpty(alarmType)) {
+			if (!StringUtils.isEmpty(alarmType)) {
 				cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
 			}
-			if (!XmlUtil.isEmpty(startTime)) {
+			if (!StringUtils.isEmpty(startTime)) {
 				cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
 			}
-			if (!XmlUtil.isEmpty(endTime)) {
+			if (!StringUtils.isEmpty(endTime)) {
 				cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
 			}
 			cmdXml.append("</Query>\r\n");
@@ -1431,16 +1436,4 @@ public class SIPCommander implements ISIPCommander {
 		clientTransaction.sendRequest();
 		return clientTransaction;
 	}
-
-
-
-
-	@Override
-	public void closeRTPServer(Device device, String channelId) {
-		if (mediaConfig.isRtpEnable()) {
-			String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
-			zlmrtpServerFactory.closeRTPServer(streamId);
-		}
-		streamSession.remove(device.getDeviceId(), channelId);
-	}
 }

+ 6 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/SIPRequestAbstractProcessor.java

@@ -16,6 +16,8 @@ import javax.sip.message.Request;
 import gov.nist.javax.sip.SipStackImpl;
 import gov.nist.javax.sip.message.SIPRequest;
 import gov.nist.javax.sip.stack.SIPServerTransaction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**    
  * @Description:处理接收IPCamera发来的SIP协议请求消息
@@ -24,6 +26,8 @@ import gov.nist.javax.sip.stack.SIPServerTransaction;
  */
 public abstract class SIPRequestAbstractProcessor implements ISIPRequestProcessor {
 
+	private final static Logger logger = LoggerFactory.getLogger(SIPRequestAbstractProcessor.class);
+
 	protected RequestEvent evt;
 	
 	private SipProvider tcpSipProvider;
@@ -64,9 +68,9 @@ public abstract class SIPRequestAbstractProcessor implements ISIPRequestProcesso
 					}
 				}
 			} catch (TransactionAlreadyExistsException e) {
-				e.printStackTrace();
+				logger.error(e.getMessage());
 			} catch (TransactionUnavailableException e) {
-				e.printStackTrace();
+				logger.error(e.getMessage());
 			}
 		}
 		return serverTransaction;

+ 22 - 5
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/AckRequestProcessor.java

@@ -13,6 +13,9 @@ import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
 import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -30,6 +33,8 @@ public class AckRequestProcessor extends SIPRequestAbstractProcessor {
 
 	private ZLMRTPServerFactory zlmrtpServerFactory;
 
+	private IMediaServerService mediaServerService;
+
 	/**   
 	 * 处理  ACK请求
 	 * 
@@ -76,18 +81,22 @@ public class AckRequestProcessor extends SIPRequestAbstractProcessor {
 			while (!rtpPushed) {
 				try {
 					if (System.currentTimeMillis() - startTime < 30 * 1000) {
-						if (zlmrtpServerFactory.isStreamReady(streamInfo.getApp(), streamInfo.getStreamId())) {
+						IMediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+						if (zlmrtpServerFactory.isStreamReady(mediaInfo, streamInfo.getApp(), streamInfo.getStreamId())) {
 							rtpPushed = true;
-							logger.info("已获取设备推流,开始向上级推流");
-							zlmrtpServerFactory.startSendRtpStream(param);
+							logger.info("已获取设备推流[{}/{}],开始向上级推流[{}:{}]",
+									streamInfo.getApp() ,streamInfo.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort());
+							zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
 						} else {
-							logger.info("等待设备推流.......");
+							logger.info("等待设备推流[{}/{}].......",
+									streamInfo.getApp() ,streamInfo.getStreamId());
 							Thread.sleep(1000);
 							continue;
 						}
 					} else {
 						rtpPushed = true;
-						logger.info("设备推流超时,终止向上级推流");
+						logger.info("设备推流[{}/{}]超时,终止向上级推流",
+								streamInfo.getApp() ,streamInfo.getStreamId());
 					}
 				} catch (InterruptedException e) {
 					e.printStackTrace();
@@ -123,4 +132,12 @@ public class AckRequestProcessor extends SIPRequestAbstractProcessor {
 	public void setZlmrtpServerFactory(ZLMRTPServerFactory zlmrtpServerFactory) {
 		this.zlmrtpServerFactory = zlmrtpServerFactory;
 	}
+
+	public IMediaServerService getMediaServerService() {
+		return mediaServerService;
+	}
+
+	public void setMediaServerService(IMediaServerService mediaServerService) {
+		this.mediaServerService = mediaServerService;
+	}
 }

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

@@ -15,6 +15,9 @@ import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -38,6 +41,8 @@ public class ByeRequestProcessor extends SIPRequestAbstractProcessor {
 
 	private ZLMRTPServerFactory zlmrtpServerFactory;
 
+	private IMediaServerService mediaServerService;
+
 	/**
 	 * 处理BYE请求
 	 * @param evt
@@ -60,9 +65,10 @@ public class ByeRequestProcessor extends SIPRequestAbstractProcessor {
 				param.put("stream",streamId);
 				param.put("ssrc",sendRtpItem.getSsrc());
 				logger.info("停止向上级推流:" + streamId);
-				zlmrtpServerFactory.stopSendRtpStream(param);
+				IMediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+				zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param);
 				redisCatchStorage.deleteSendRTPServer(platformGbId, channelId);
-				if (zlmrtpServerFactory.totalReaderCount(sendRtpItem.getApp(), streamId) == 0) {
+				if (zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId) == 0) {
 					logger.info(streamId + "无其它观看者,通知设备停止推流");
 					cmder.streamByeCmd(sendRtpItem.getDeviceId(), channelId);
 				}
@@ -112,4 +118,11 @@ public class ByeRequestProcessor extends SIPRequestAbstractProcessor {
 		this.cmder = cmder;
 	}
 
+	public IMediaServerService getMediaServerService() {
+		return mediaServerService;
+	}
+
+	public void setMediaServerService(IMediaServerService mediaServerService) {
+		this.mediaServerService = mediaServerService;
+	}
 }

+ 42 - 15
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/InviteRequestProcessor.java

@@ -11,12 +11,16 @@ import javax.sip.header.*;
 import javax.sip.message.Request;
 import javax.sip.message.Response;
 
+import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
@@ -51,6 +55,8 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
 
 	private ZLMRTPServerFactory zlmrtpServerFactory;
 
+	private IMediaServerService mediaServerService;
+
 	public ZLMRTPServerFactory getZlmrtpServerFactory() {
 		return zlmrtpServerFactory;
 	}
@@ -91,6 +97,7 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
 				// 查询平台下是否有该通道
 				DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId);
 				GbStream gbStream = storager.queryStreamInParentPlatform(requesterId, channelId);
+				IMediaServerItem mediaServerItem = null;
 				// 不是通道可能是直播流
 				if (channel != null && gbStream == null ) {
 					if (channel.getStatus() == 0) {
@@ -100,8 +107,15 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
 					}
 					responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在,发181,呼叫转接中
 				}else if(channel == null && gbStream != null){
-					Boolean streamReady = zlmrtpServerFactory.isStreamReady(gbStream.getApp(), gbStream.getStream());
-					if (!streamReady) {
+					String mediaServerId = gbStream.getMediaServerId();
+					mediaServerItem = mediaServerService.getOne(mediaServerId);
+					if (mediaServerItem == null) {
+						logger.info("[ app={}, stream={} ]zlm找不到,返回410",gbStream.getApp(), gbStream.getStream());
+						responseAck(evt, Response.GONE, "media server not found");
+						return;
+					}
+					Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
+					if (!streamReady ) {
 						logger.info("[ app={}, stream={} ]通道离线,返回400",gbStream.getApp(), gbStream.getStream());
 						responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline");
 						return;
@@ -130,8 +144,8 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
 				//boolean recvonly = false;
 				boolean mediaTransmissionTCP = false;
 				Boolean tcpActive = null;
-				for (int i = 0; i < mediaDescriptions.size(); i++) {
-					MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i);
+				for (Object description : mediaDescriptions) {
+					MediaDescription mediaDescription = (MediaDescription) description;
 					Media media = mediaDescription.getMedia();
 
 					Vector mediaFormats = media.getMediaFormats(false);
@@ -147,7 +161,7 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
 								mediaTransmissionTCP = true;
 								if ("active".equals(setup)) {
 									tcpActive = true;
-								}else if ("passive".equals(setup)) {
+								} else if ("passive".equals(setup)) {
 									tcpActive = false;
 								}
 							}
@@ -174,7 +188,13 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
 						responseAck(evt, Response.SERVER_INTERNAL_ERROR);
 						return;
 					}
-					SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(addressStr, port, ssrc, requesterId,
+					mediaServerItem = playService.getNewMediaServerItem(device);
+					if (mediaServerItem == null) {
+						logger.warn("未找到可用的zlm");
+						responseAck(evt, Response.BUSY_HERE);
+						return;
+					}
+					SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
 							device.getDeviceId(), channelId,
 							mediaTransmissionTCP);
 					if (tcpActive != null) {
@@ -189,18 +209,18 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
 					// 写入redis, 超时时回复
 					redisCatchStorage.updateSendRTPSever(sendRtpItem);
 					// 通知下级推流,
-					PlayResult playResult = playService.play(device.getDeviceId(), channelId, (responseJSON)->{
+					PlayResult playResult = playService.play(mediaServerItem,device.getDeviceId(), channelId, (mediaServerItemInUSe, responseJSON)->{
 						// 收到推流, 回复200OK, 等待ack
 						// if (sendRtpItem == null) return;
 						sendRtpItem.setStatus(1);
 						redisCatchStorage.updateSendRTPSever(sendRtpItem);
 						// TODO 添加对tcp的支持
-						ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
+
 						StringBuffer content = new StringBuffer(200);
 						content.append("v=0\r\n");
-						content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
+						content.append("o="+"00000"+" 0 0 IN IP4 "+mediaServerItemInUSe.getSdpIp()+"\r\n");
 						content.append("s=Play\r\n");
-						content.append("c=IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
+						content.append("c=IN IP4 "+mediaServerItemInUSe.getSdpIp()+"\r\n");
 						content.append("t=0 0\r\n");
 						content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n");
 						content.append("a=sendonly\r\n");
@@ -217,7 +237,7 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
 						} catch (ParseException e) {
 							e.printStackTrace();
 						}
-					} ,(event -> {
+					} ,((event) -> {
 						// 未知错误。直接转发设备点播的错误
 						Response response = null;
 						try {
@@ -232,7 +252,7 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
 					}
 
 				}else if (gbStream != null) {
-					SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(addressStr, port, ssrc, requesterId,
+					SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
 							gbStream.getApp(), gbStream.getStream(), channelId,
 							mediaTransmissionTCP);
 
@@ -251,12 +271,11 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
 					sendRtpItem.setStatus(1);
 					redisCatchStorage.updateSendRTPSever(sendRtpItem);
 					// TODO 添加对tcp的支持
-					ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
 					StringBuffer content = new StringBuffer(200);
 					content.append("v=0\r\n");
-					content.append("o="+"00000"+" 0 0 IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
+					content.append("o="+"00000"+" 0 0 IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
 					content.append("s=Play\r\n");
-					content.append("c=IN IP4 "+mediaInfo.getSdpIp()+"\r\n");
+					content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n");
 					content.append("t=0 0\r\n");
 					content.append("m=video "+ sendRtpItem.getLocalPort()+" RTP/AVP 96\r\n");
 					content.append("a=sendonly\r\n");
@@ -444,4 +463,12 @@ public class InviteRequestProcessor extends SIPRequestAbstractProcessor {
 	public void setRedisCatchStorage(IRedisCatchStorage redisCatchStorage) {
 		this.redisCatchStorage = redisCatchStorage;
 	}
+
+	public IMediaServerService getMediaServerService() {
+		return mediaServerService;
+	}
+
+	public void setMediaServerService(IMediaServerService mediaServerService) {
+		this.mediaServerService = mediaServerService;
+	}
 }

+ 5 - 5
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java

@@ -282,7 +282,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
 			//String result = XmlUtil.getText(rootElement, "Result");
 			// 回复200 OK
 			responseAck(evt);
-			if (rootElement.getName().equals("Response")) {//} !XmlUtil.isEmpty(result)) {
+			if (rootElement.getName().equals("Response")) {//} !StringUtils.isEmpty(result)) {
 				// 此处是对本平台发出DeviceControl指令的应答
 				JSONObject json = new JSONObject();
 				XmlUtil.node2Json(rootElement, json);
@@ -299,7 +299,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
 				String platformId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
 				String targetGBId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
 				// 远程启动功能
-				if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement, "TeleBoot"))) {
+				if (!StringUtils.isEmpty(XmlUtil.getText(rootElement, "TeleBoot"))) {
 					if (deviceId.equals(targetGBId)) {
 						// 远程启动本平台:需要在重新启动程序后先对SipStack解绑
 						logger.info("执行远程启动本平台命令");
@@ -337,7 +337,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
 					}
 				}
 				// 云台/前端控制命令
-				if (!XmlUtil.isEmpty(XmlUtil.getText(rootElement,"PTZCmd")) && !deviceId.equals(targetGBId)) {
+				if (!StringUtils.isEmpty(XmlUtil.getText(rootElement,"PTZCmd")) && !deviceId.equals(targetGBId)) {
 					String cmdString = XmlUtil.getText(rootElement,"PTZCmd");
 					Device device = storager.queryVideoDeviceByPlatformIdAndChannelId(platformId, deviceId);
 					cmder.fronEndCmd(device, deviceId, cmdString);
@@ -421,7 +421,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
 			String deviceId = XmlUtil.getText(rootElement, "DeviceID");
 			// 回复200 OK
 			responseAck(evt);
-			if (rootElement.getName().equals("Response")) {//   !XmlUtil.isEmpty(result)) {
+			if (rootElement.getName().equals("Response")) {//   !StringUtils.isEmpty(result)) {
 				// 此处是对本平台发出DeviceControl指令的应答
 				JSONObject json = new JSONObject();
 				XmlUtil.node2Json(rootElement, json);
@@ -718,7 +718,7 @@ public class MessageRequestProcessor extends SIPRequestAbstractProcessor {
 					deviceAlarm.setLatitude(0.00);
 				}
 	
-				if (!XmlUtil.isEmpty(deviceAlarm.getAlarmMethod())) {
+				if (!StringUtils.isEmpty(deviceAlarm.getAlarmMethod())) {
 					if ( deviceAlarm.getAlarmMethod().equals("4")) {
 						MobilePosition mobilePosition = new MobilePosition();
 						mobilePosition.setDeviceId(deviceAlarm.getDeviceId());

+ 4 - 10
src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java

@@ -17,6 +17,7 @@ import org.dom4j.Element;
 import org.dom4j.io.SAXReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
 
 /**
  * 基于dom4j的工具包
@@ -114,12 +115,12 @@ public class XmlUtil {
         // 如果是属性
         for (Object o : element.attributes()) {
             Attribute attr = (Attribute) o;
-            if (!isEmpty(attr.getValue())) {
+            if (!StringUtils.isEmpty(attr.getValue())) {
                 json.put("@" + attr.getName(), attr.getValue());
             }
         }
         List<Element> chdEl = element.elements();
-        if (chdEl.isEmpty() && !isEmpty(element.getText())) {// 如果没有子元素,只有一个值
+        if (chdEl.isEmpty() && !StringUtils.isEmpty(element.getText())) {// 如果没有子元素,只有一个值
             json.put(element.getName(), element.getText());
         }
 
@@ -150,7 +151,7 @@ public class XmlUtil {
             } else { // 子元素没有子元素
                 for (Object o : element.attributes()) {
                     Attribute attr = (Attribute) o;
-                    if (!isEmpty(attr.getValue())) {
+                    if (!StringUtils.isEmpty(attr.getValue())) {
                         json.put("@" + attr.getName(), attr.getValue());
                     }
                 }
@@ -160,11 +161,4 @@ public class XmlUtil {
             }
         }
     }
-
-    public static boolean isEmpty(String str) {
-        if (str == null || str.trim().isEmpty() || "null".equals(str)) {
-            return true;
-        }
-        return false;
-    }
 }

+ 0 - 54
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java

@@ -1,54 +0,0 @@
-//package com.genersoft.iot.vmp.media.zlm;
-//
-//import com.genersoft.iot.vmp.conf.MediaConfig;
-//import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-//import org.springframework.beans.factory.annotation.Autowired;
-//import org.springframework.beans.factory.annotation.Value;
-//import org.springframework.web.bind.annotation.*;
-//import org.springframework.web.client.HttpClientErrorException;
-//import org.springframework.web.client.RestTemplate;
-//
-//import javax.servlet.http.HttpServletRequest;
-//import javax.servlet.http.HttpServletResponse;
-//
-//@RestController
-//@RequestMapping("/zlm")
-//public class ZLMHTTPProxyController {
-//
-//
-//    // private final static Logger logger = LoggerFactory.getLogger(ZLMHTTPProxyController.class);
-//
-//    @Autowired
-//    private IRedisCatchStorage redisCatchStorage;
-//
-//    @Autowired
-//    private MediaConfig mediaConfig;
-//
-//    @ResponseBody
-//    @RequestMapping(value = "/**/**/**", produces = "application/json;charset=UTF-8")
-//    public Object proxy(HttpServletRequest request, HttpServletResponse response){
-//
-//        if (redisCatchStorage.getMediaInfo() == null) {
-//            return "未接入流媒体";
-//        }
-//        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
-//        String requestURI = String.format("http://%s:%s%s?%s&%s",
-//                mediaInfo.getLocalIP(),
-//                mediaConfig.getHttpPort(),
-//                request.getRequestURI().replace("/zlm",""),
-//                mediaInfo.getHookAdminParams(),
-//                request.getQueryString()
-//        );
-//        // 发送请求
-//        RestTemplate restTemplate = new RestTemplate();
-//        //将指定的url返回的参数自动封装到自定义好的对应类对象中
-//        Object result = null;
-//        try {
-//            result = restTemplate.getForObject(requestURI,Object.class);
-//
-//        }catch (HttpClientErrorException httpClientErrorException) {
-//            response.setStatus(httpClientErrorException.getStatusCode().value());
-//        }
-//        return result;
-//    }
-//}

+ 61 - 31
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@@ -9,6 +9,9 @@ import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.MediaConfig;
 import com.genersoft.iot.vmp.conf.UserSetup;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 import com.genersoft.iot.vmp.service.IPlayService;
@@ -53,10 +56,10 @@ public class ZLMHttpHookListener {
 	private IRedisCatchStorage redisCatchStorage;
 
 	@Autowired
-	private ZLMRESTfulUtils zlmresTfulUtils;
+	private IMediaServerService mediaServerService;
 
 	@Autowired
-	private ZLMServerManger zlmServerManger;
+	private ZLMRESTfulUtils zlmresTfulUtils;
 
 	 @Autowired
 	 private ZLMMediaListManager zlmMediaListManager;
@@ -81,6 +84,7 @@ public class ZLMHttpHookListener {
 		if (logger.isDebugEnabled()) {
 			logger.debug("ZLM HOOK on_flow_report API调用,参数:" + json.toString());
 		}
+		String mediaServerId = json.getString("mediaServerId");
 		JSONObject ret = new JSONObject();
 		ret.put("code", 0);
 		ret.put("msg", "success");
@@ -98,6 +102,7 @@ public class ZLMHttpHookListener {
 		if (logger.isDebugEnabled()) {
 			logger.debug("ZLM HOOK on_http_access API 调用,参数:" + json.toString());
 		}
+		String mediaServerId = json.getString("mediaServerId");
 		JSONObject ret = new JSONObject();
 		ret.put("code", 0);
 		ret.put("err", "");
@@ -117,9 +122,14 @@ public class ZLMHttpHookListener {
 		if (logger.isDebugEnabled()) {
 			logger.debug("ZLM HOOK on_play API调用,参数:" + json.toString());
 		}
+		String mediaServerId = json.getString("mediaServerId");
 		ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_play, json);
 		if (subscribe != null ) {
-			subscribe.response(json);
+			IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+			if (mediaInfo != null) {
+				subscribe.response(mediaInfo, json);
+			}
+
 		}
 		JSONObject ret = new JSONObject();
 		ret.put("code", 0);
@@ -133,20 +143,25 @@ public class ZLMHttpHookListener {
 	 */
 	@ResponseBody
 	@PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8")
-	public ResponseEntity<String> onPublish(@RequestBody JSONObject json){
+	public ResponseEntity<String> onPublish(@RequestBody JSONObject json) {
 
 		logger.debug("ZLM HOOK on_publish API调用,参数:" + json.toString());
 
+		String mediaServerId = json.getString("mediaServerId");
 		ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_publish, json);
-		if (subscribe != null) subscribe.response(json);
-
+		if (subscribe != null) {
+			IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+			if (mediaInfo != null) {
+				subscribe.response(mediaInfo, json);
+			}
+		}
 		JSONObject ret = new JSONObject();
 		ret.put("code", 0);
 		ret.put("msg", "success");
 		ret.put("enableHls", true);
 		ret.put("enableMP4", userSetup.isRecordPushLive());
 		ret.put("enableRtxp", true);
-		return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
+		return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
 	}
 	
 	/**
@@ -160,6 +175,7 @@ public class ZLMHttpHookListener {
 		if (logger.isDebugEnabled()) {
 			logger.debug("ZLM HOOK on_record_mp4 API调用,参数:" + json.toString());
 		}
+		String mediaServerId = json.getString("mediaServerId");
 		JSONObject ret = new JSONObject();
 		ret.put("code", 0);
 		ret.put("msg", "success");
@@ -177,6 +193,7 @@ public class ZLMHttpHookListener {
 		if (logger.isDebugEnabled()) {
 			logger.debug("ZLM HOOK on_rtsp_realm API调用,参数:" + json.toString());
 		}
+		String mediaServerId = json.getString("mediaServerId");
 		JSONObject ret = new JSONObject();
 		ret.put("code", 0);
 		ret.put("realm", "");
@@ -195,6 +212,7 @@ public class ZLMHttpHookListener {
 		if (logger.isDebugEnabled()) {
 			logger.debug("ZLM HOOK on_rtsp_auth API调用,参数:" + json.toString());
 		}
+		String mediaServerId = json.getString("mediaServerId");
 		JSONObject ret = new JSONObject();
 		ret.put("code", 0);
 		ret.put("encrypted", false);
@@ -216,9 +234,15 @@ public class ZLMHttpHookListener {
 		// TODO 如果是带有rtpstream则开启按需拉流
 		// String app = json.getString("app");
 		// String stream = json.getString("stream");
-
+		String mediaServerId = json.getString("mediaServerId");
 		ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_shell_login, json);
-		if (subscribe != null) subscribe.response(json);
+		if (subscribe != null ) {
+			IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+			if (mediaInfo != null) {
+				subscribe.response(mediaInfo, json);
+			}
+
+		}
 
 		JSONObject ret = new JSONObject();
 		ret.put("code", 0);
@@ -237,9 +261,15 @@ public class ZLMHttpHookListener {
 		if (logger.isDebugEnabled()) {
 			logger.debug("ZLM HOOK on_stream_changed API调用,参数:" + json.toString());
 		}
-
+		String mediaServerId = json.getString("mediaServerId");
 		ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, json);
-		if (subscribe != null) subscribe.response(json);
+		if (subscribe != null ) {
+			IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+			if (mediaInfo != null) {
+				subscribe.response(mediaInfo, json);
+			}
+
+		}
 
 		// 流消失移除redis play
 		String app = json.getString("app");
@@ -251,6 +281,11 @@ public class ZLMHttpHookListener {
 			logger.info("[stream: " + streamId + "] on_stream_changed->>" + schema);
 		}
 		if ("rtmp".equals(schema)){
+			if (regist) {
+				mediaServerService.addCount(mediaServerId);
+			}else {
+				mediaServerService.removeCount(mediaServerId);
+			}
 			if ("rtp".equals(app) && !regist ) {
 				StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
 				if (streamInfo!=null){
@@ -262,10 +297,11 @@ public class ZLMHttpHookListener {
 				}
 			}else {
 				if (!"rtp".equals(app) ){
+					IMediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
 					if (regist) {
-						zlmMediaListManager.addMedia(app, streamId);
+						zlmMediaListManager.addMedia(mediaServerItem, app, streamId);
 					}else {
-						zlmMediaListManager.removeMedia(app, streamId);
+						zlmMediaListManager.removeMedia( app, streamId);
 					}
 				}
 			}
@@ -288,7 +324,7 @@ public class ZLMHttpHookListener {
 		if (logger.isDebugEnabled()) {
 			logger.debug("ZLM HOOK on_stream_none_reader API调用,参数:" + json.toString());
 		}
-		
+		String mediaServerId = json.getString("mediaServerId");
 		String streamId = json.getString("stream");
 		String app = json.getString("app");
 
@@ -329,11 +365,12 @@ public class ZLMHttpHookListener {
 	@ResponseBody
 	@PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8")
 	public ResponseEntity<String> onStreamNotFound(@RequestBody JSONObject json){
-		
 		if (logger.isDebugEnabled()) {
 			logger.debug("ZLM HOOK on_stream_not_found API调用,参数:" + json.toString());
 		}
-		if (userSetup.isAutoApplyPlay()) {
+		String mediaServerId = json.getString("mediaServerId");
+		IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+		if (userSetup.isAutoApplyPlay() && mediaInfo != null) {
 			String app = json.getString("app");
 			String streamId = json.getString("stream");
 			if ("rtp".equals(app) && streamId.contains("gb_play") ) {
@@ -344,9 +381,9 @@ public class ZLMHttpHookListener {
 					Device device = storager.queryVideoDevice(deviceId);
 					if (device != null) {
 						UUID uuid = UUID.randomUUID();
-						cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
+						cmder.playStreamCmd(mediaInfo, device, channelId, (IMediaServerItem mediaServerItemInuse, JSONObject response) -> {
 							logger.info("收到订阅消息: " + response.toJSONString());
-							playService.onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
+							playService.onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, uuid.toString());
 						}, null);
 					}
 
@@ -367,26 +404,19 @@ public class ZLMHttpHookListener {
 	 */
 	@ResponseBody
 	@PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
-	public ResponseEntity<String> onServerStarted(HttpServletRequest request, @RequestBody JSONObject json){
+	public ResponseEntity<String> onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){
 		
 		if (logger.isDebugEnabled()) {
-			logger.debug("ZLM HOOK on_server_started API调用,参数:" + json.toString());
+			logger.debug("ZLM HOOK on_server_started API调用,参数:" + jsonObject.toString());
 		}
-
-//		String data = json.getString("data");
-//		List<MediaServerConfig> mediaServerConfigs = JSON.parseArray(JSON.toJSONString(json), MediaServerConfig.class);
-//		MediaServerConfig mediaServerConfig = mediaServerConfigs.get(0);
-
+		String remoteAddr = request.getRemoteAddr();
+		jsonObject.put("ip", remoteAddr);
 		List<ZLMHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(ZLMHttpHookSubscribe.HookType.on_server_started);
-		if (subscribes != null && subscribes.size() > 0) {
+		if (subscribes != null  && subscribes.size() > 0) {
 			for (ZLMHttpHookSubscribe.Event subscribe : subscribes) {
-				subscribe.response(json);
+				subscribe.response(null, jsonObject);
 			}
 		}
-		ZLMServerConfig ZLMServerConfig = JSON.toJavaObject(json, ZLMServerConfig.class);
-		zlmServerManger.updateServerCatch(ZLMServerConfig);
-		// 重新发起代理
-
 		JSONObject ret = new JSONObject();
 		ret.put("code", 0);
 		ret.put("msg", "success");

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

@@ -1,6 +1,8 @@
 package com.genersoft.iot.vmp.media.zlm;
 
 import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import org.springframework.stereotype.Component;
 
 import java.util.*;
@@ -30,7 +32,7 @@ public class ZLMHttpHookSubscribe {
     }
 
     public interface Event{
-        void response(JSONObject response);
+        void response(IMediaServerItem mediaServerItem, JSONObject response);
     }
 
     private Map<HookType, Map<JSONObject, ZLMHttpHookSubscribe.Event>> allSubscribes = new ConcurrentHashMap<>();

+ 45 - 40
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java

@@ -2,6 +2,8 @@ package com.genersoft.iot.vmp.media.zlm;
 
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
@@ -45,11 +47,11 @@ public class ZLMMediaListManager {
     private ZLMHttpHookSubscribe subscribe;
 
 
-    public void updateMediaList() {
+    public void updateMediaList(MediaServerItem mediaServerItem) {
         storager.clearMediaList();
 
         // 使用异步的当时更新媒体流列表
-        zlmresTfulUtils.getMediaList((mediaList ->{
+        zlmresTfulUtils.getMediaList(mediaServerItem, (mediaList ->{
             if (mediaList == null) return;
             String dataStr = mediaList.getString("data");
 
@@ -57,10 +59,10 @@ public class ZLMMediaListManager {
             Map<String, StreamPushItem> result = new HashMap<>();
             List<StreamPushItem> streamPushItems = null;
             // 获取所有的国标关联
-            List<GbStream> gbStreams = gbStreamMapper.selectAll();
+//            List<GbStream> gbStreams = gbStreamMapper.selectAllByMediaServerId(mediaServerItem.getId());
             if (code == 0 ) {
                 if (dataStr != null) {
-                    streamPushItems = streamPushService.handleJSON(dataStr);
+                    streamPushItems = streamPushService.handleJSON(dataStr, mediaServerItem);
                 }
             }else {
                 logger.warn("更新视频流失败,错误code: " + code);
@@ -72,24 +74,27 @@ public class ZLMMediaListManager {
                     JSONObject jsonObject = new JSONObject();
                     jsonObject.put("app", streamPushItem.getApp());
                     jsonObject.put("stream", streamPushItem.getStream());
-                    subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_play,jsonObject,(response)->{
-                        updateMedia(response.getString("app"), response.getString("stream"));
-                    });
+                    jsonObject.put("mediaServerId", mediaServerItem.getId());
+                    subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_play,jsonObject,
+                            (IMediaServerItem mediaServerItemInuse, JSONObject response)->{
+                                updateMedia(mediaServerItem, response.getString("app"), response.getString("stream"));
+                            }
+                    );
                 }
             }
         }));
 
     }
 
-    public void addMedia(String app, String streamId) {
+    public void addMedia(IMediaServerItem mediaServerItem, String app, String streamId) {
         //使用异步更新推流
-        updateMedia(app, streamId);
+        updateMedia(mediaServerItem, app, streamId);
     }
 
 
-    public void updateMedia(String app, String streamId) {
+    public void updateMedia(IMediaServerItem mediaServerItem, String app, String streamId) {
         //使用异步更新推流
-        zlmresTfulUtils.getMediaList(app, streamId, "rtmp", json->{
+        zlmresTfulUtils.getMediaList(mediaServerItem, app, streamId, "rtmp", json->{
 
             if (json == null) return;
             String dataStr = json.getString("data");
@@ -99,7 +104,7 @@ public class ZLMMediaListManager {
             List<StreamPushItem> streamPushItems = null;
             if (code == 0 ) {
                 if (dataStr != null) {
-                    streamPushItems = streamPushService.handleJSON(dataStr);
+                    streamPushItems = streamPushService.handleJSON(dataStr, mediaServerItem);
                 }
             }else {
                 logger.warn("更新视频流失败,错误code: " + code);
@@ -122,32 +127,32 @@ public class ZLMMediaListManager {
         }
     }
 
-    public void clearAllSessions() {
-        logger.info("清空所有国标相关的session");
-        JSONObject allSessionJSON = zlmresTfulUtils.getAllSession();
-        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
-        HashSet<String> allLocalPorts = new HashSet();
-        if (allSessionJSON.getInteger("code") == 0) {
-            JSONArray data = allSessionJSON.getJSONArray("data");
-            if (data.size() > 0) {
-                for (int i = 0; i < data.size(); i++) {
-                    JSONObject sessionJOSN = data.getJSONObject(i);
-                    Integer local_port = sessionJOSN.getInteger("local_port");
-                    if (!local_port.equals(Integer.valueOf(mediaInfo.getHttpPort())) &&
-                        !local_port.equals(Integer.valueOf(mediaInfo.getHttpSSLport())) &&
-                        !local_port.equals(Integer.valueOf(mediaInfo.getRtmpPort())) &&
-                        !local_port.equals(Integer.valueOf(mediaInfo.getRtspPort())) &&
-                        !local_port.equals(Integer.valueOf(mediaInfo.getRtspSSlport())) &&
-                        !local_port.equals(Integer.valueOf(mediaInfo.getHookOnFlowReport()))){
-                        allLocalPorts.add(sessionJOSN.getInteger("local_port") + "");
-                     }
-                }
-            }
-        }
-        if (allLocalPorts.size() > 0) {
-            List<String> result = new ArrayList<>(allLocalPorts);
-            String localPortSStr = String.join(",", result);
-            zlmresTfulUtils.kickSessions(localPortSStr);
-        }
-    }
+//    public void clearAllSessions() {
+//        logger.info("清空所有国标相关的session");
+//        JSONObject allSessionJSON = zlmresTfulUtils.getAllSession();
+//        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
+//        HashSet<String> allLocalPorts = new HashSet();
+//        if (allSessionJSON.getInteger("code") == 0) {
+//            JSONArray data = allSessionJSON.getJSONArray("data");
+//            if (data.size() > 0) {
+//                for (int i = 0; i < data.size(); i++) {
+//                    JSONObject sessionJOSN = data.getJSONObject(i);
+//                    Integer local_port = sessionJOSN.getInteger("local_port");
+//                    if (!local_port.equals(Integer.valueOf(mediaInfo.getHttpPort())) &&
+//                        !local_port.equals(Integer.valueOf(mediaInfo.getHttpSSLport())) &&
+//                        !local_port.equals(Integer.valueOf(mediaInfo.getRtmpPort())) &&
+//                        !local_port.equals(Integer.valueOf(mediaInfo.getRtspPort())) &&
+//                        !local_port.equals(Integer.valueOf(mediaInfo.getRtspSSlport())) &&
+//                        !local_port.equals(Integer.valueOf(mediaInfo.getHookOnFlowReport()))){
+//                        allLocalPorts.add(sessionJOSN.getInteger("local_port") + "");
+//                     }
+//                }
+//            }
+//        }
+//        if (allLocalPorts.size() > 0) {
+//            List<String> result = new ArrayList<>(allLocalPorts);
+//            String localPortSStr = String.join(",", result);
+//            zlmresTfulUtils.kickSessions(localPortSStr);
+//        }
+//    }
 }

+ 46 - 49
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java

@@ -3,6 +3,8 @@ package com.genersoft.iot.vmp.media.zlm;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.conf.MediaConfig;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import okhttp3.*;
 import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
@@ -21,23 +23,18 @@ public class ZLMRESTfulUtils {
 
     private final static Logger logger = LoggerFactory.getLogger(ZLMRESTfulUtils.class);
 
-    @Autowired
-    private MediaConfig mediaConfig;
-
-
-
     public interface RequestCallback{
         void run(JSONObject response);
     }
 
-    public JSONObject sendPost(String api, Map<String, Object> param, RequestCallback callback) {
+    public JSONObject sendPost(IMediaServerItem mediaServerItem, String api, Map<String, Object> param, RequestCallback callback) {
         OkHttpClient client = new OkHttpClient();
-        String url = String.format("http://%s:%s/index/api/%s",  mediaConfig.getIp(), mediaConfig.getHttpPort(), api);
+        String url = String.format("http://%s:%s/index/api/%s",  mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
         JSONObject responseJSON = null;
         logger.debug(url);
 
         FormBody.Builder builder = new FormBody.Builder();
-        builder.add("secret",mediaConfig.getSecret());
+        builder.add("secret",mediaServerItem.getSecret());
         if (param != null && param.keySet().size() > 0) {
             for (String key : param.keySet()){
                 if (param.get(key) != null) {
@@ -96,14 +93,14 @@ public class ZLMRESTfulUtils {
     }
 
 
-    public void sendPostForImg(String api, Map<String, Object> param, String targetPath, String fileName) {
+    public void sendPostForImg(IMediaServerItem mediaServerItem, String api, Map<String, Object> param, String targetPath, String fileName) {
         OkHttpClient client = new OkHttpClient();
-        String url = String.format("http://%s:%s/index/api/%s",  mediaConfig.getIp(), mediaConfig.getHttpPort(), api);
+        String url = String.format("http://%s:%s/index/api/%s",  mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
         JSONObject responseJSON = null;
         logger.debug(url);
 
         FormBody.Builder builder = new FormBody.Builder();
-        builder.add("secret",mediaConfig.getSecret());
+        builder.add("secret",mediaServerItem.getSecret());
         if (param != null && param.keySet().size() > 0) {
             for (String key : param.keySet()){
                 if (param.get(key) != null) {
@@ -142,39 +139,39 @@ public class ZLMRESTfulUtils {
     }
 
 
-    public JSONObject getMediaList(String app, String stream, String schema, RequestCallback callback){
+    public JSONObject getMediaList(IMediaServerItem mediaServerItem,String app, String stream, String schema, RequestCallback callback){
         Map<String, Object> param = new HashMap<>();
         if (app != null) param.put("app",app);
         if (stream != null) param.put("stream",stream);
         if (schema != null) param.put("schema",schema);
         param.put("vhost","__defaultVhost__");
-        return sendPost("getMediaList",param, callback);
+        return sendPost(mediaServerItem, "getMediaList",param, callback);
     }
 
-    public JSONObject getMediaList(String app, String stream){
-        return getMediaList(app, stream,null,  null);
+    public JSONObject getMediaList(IMediaServerItem mediaServerItem,String app, String stream){
+        return getMediaList(mediaServerItem, app, stream,null,  null);
     }
 
-    public JSONObject getMediaList(RequestCallback callback){
-        return sendPost("getMediaList",null, callback);
+    public JSONObject getMediaList(IMediaServerItem mediaServerItem,RequestCallback callback){
+        return sendPost(mediaServerItem, "getMediaList",null, callback);
     }
 
-    public JSONObject getMediaInfo(String app, String schema, String stream){
+    public JSONObject getMediaInfo(IMediaServerItem mediaServerItem,String app, String schema, String stream){
         Map<String, Object> param = new HashMap<>();
         param.put("app",app);
         param.put("schema",schema);
         param.put("stream",stream);
         param.put("vhost","__defaultVhost__");
-        return sendPost("getMediaInfo",param, null);
+        return sendPost(mediaServerItem, "getMediaInfo",param, null);
     }
 
-    public JSONObject getRtpInfo(String stream_id){
+    public JSONObject getRtpInfo(IMediaServerItem mediaServerItem,String stream_id){
         Map<String, Object> param = new HashMap<>();
         param.put("stream_id",stream_id);
-        return sendPost("getRtpInfo",param, null);
+        return sendPost(mediaServerItem, "getRtpInfo",param, null);
     }
 
-    public JSONObject addFFmpegSource(String src_url, String dst_url, String timeout_ms,
+    public JSONObject addFFmpegSource(IMediaServerItem mediaServerItem,String src_url, String dst_url, String timeout_ms,
                                       boolean enable_hls, boolean enable_mp4, String ffmpeg_cmd_key){
         logger.info(src_url);
         logger.info(dst_url);
@@ -185,44 +182,44 @@ public class ZLMRESTfulUtils {
         param.put("enable_hls", enable_hls);
         param.put("enable_mp4", enable_mp4);
         param.put("ffmpeg_cmd_key", ffmpeg_cmd_key);
-        return sendPost("addFFmpegSource",param, null);
+        return sendPost(mediaServerItem, "addFFmpegSource",param, null);
     }
 
-    public JSONObject delFFmpegSource(String key){
+    public JSONObject delFFmpegSource(IMediaServerItem mediaServerItem,String key){
         Map<String, Object> param = new HashMap<>();
         param.put("key", key);
-        return sendPost("delFFmpegSource",param, null);
+        return sendPost(mediaServerItem, "delFFmpegSource",param, null);
     }
 
-    public JSONObject getMediaServerConfig(){
-        return sendPost("getServerConfig",null, null);
+    public JSONObject getMediaServerConfig(IMediaServerItem mediaServerItem){
+        return sendPost(mediaServerItem, "getServerConfig",null, null);
     }
 
-    public JSONObject setServerConfig(Map<String, Object> param){
-        return sendPost("setServerConfig",param, null);
+    public JSONObject setServerConfig(IMediaServerItem mediaServerItem, Map<String, Object> param){
+        return sendPost(mediaServerItem,"setServerConfig",param, null);
     }
 
-    public JSONObject openRtpServer(Map<String, Object> param){
-        return sendPost("openRtpServer",param, null);
+    public JSONObject openRtpServer(IMediaServerItem mediaServerItem,Map<String, Object> param){
+        return sendPost(mediaServerItem, "openRtpServer",param, null);
     }
 
-    public JSONObject closeRtpServer(Map<String, Object> param) {
-        return sendPost("closeRtpServer",param, null);
+    public JSONObject closeRtpServer(IMediaServerItem mediaServerItem,Map<String, Object> param) {
+        return sendPost(mediaServerItem, "closeRtpServer",param, null);
     }
 
-    public JSONObject listRtpServer() {
-        return sendPost("listRtpServer",null, null);
+    public JSONObject listRtpServer(IMediaServerItem mediaServerItem) {
+        return sendPost(mediaServerItem, "listRtpServer",null, null);
     }
 
-    public JSONObject startSendRtp(Map<String, Object> param) {
-        return sendPost("startSendRtp",param, null);
+    public JSONObject startSendRtp(IMediaServerItem mediaServerItem,Map<String, Object> param) {
+        return sendPost(mediaServerItem, "startSendRtp",param, null);
     }
 
-    public JSONObject stopSendRtp(Map<String, Object> param) {
-        return sendPost("stopSendRtp",param, null);
+    public JSONObject stopSendRtp(IMediaServerItem mediaServerItem,Map<String, Object> param) {
+        return sendPost(mediaServerItem, "stopSendRtp",param, null);
     }
 
-    public JSONObject addStreamProxy(String app, String stream, String url, boolean enable_hls, boolean enable_mp4, String rtp_type) {
+    public JSONObject addStreamProxy(IMediaServerItem mediaServerItem,String app, String stream, String url, boolean enable_hls, boolean enable_mp4, String rtp_type) {
         Map<String, Object> param = new HashMap<>();
         param.put("vhost", "__defaultVhost__");
         param.put("app", app);
@@ -231,33 +228,33 @@ public class ZLMRESTfulUtils {
         param.put("enable_hls", enable_hls?1:0);
         param.put("enable_mp4", enable_mp4?1:0);
         param.put("rtp_type", rtp_type);
-        return sendPost("addStreamProxy",param, null);
+        return sendPost(mediaServerItem, "addStreamProxy",param, null);
     }
 
-    public JSONObject closeStreams(String app, String stream) {
+    public JSONObject closeStreams(IMediaServerItem mediaServerItem,String app, String stream) {
         Map<String, Object> param = new HashMap<>();
         param.put("vhost", "__defaultVhost__");
         param.put("app", app);
         param.put("stream", stream);
         param.put("force", 1);
-        return sendPost("close_streams",param, null);
+        return sendPost(mediaServerItem, "close_streams",param, null);
     }
 
-    public JSONObject getAllSession() {
-        return sendPost("getAllSession",null, null);
+    public JSONObject getAllSession(IMediaServerItem mediaServerItem) {
+        return sendPost(mediaServerItem, "getAllSession",null, null);
     }
 
-    public void kickSessions(String localPortSStr) {
+    public void kickSessions(IMediaServerItem mediaServerItem, String localPortSStr) {
         Map<String, Object> param = new HashMap<>();
         param.put("local_port", localPortSStr);
-        sendPost("kick_sessions",param, null);
+        sendPost(mediaServerItem, "kick_sessions",param, null);
     }
 
-    public void getSnap(String flvUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) {
+    public void getSnap(IMediaServerItem mediaServerItem, String flvUrl, int timeout_sec, int expire_sec, String targetPath, String fileName) {
         Map<String, Object> param = new HashMap<>();
         param.put("url", flvUrl);
         param.put("timeout_sec", timeout_sec);
         param.put("expire_sec", expire_sec);
-        sendPostForImg("getSnap",param, targetPath, fileName);
+        sendPostForImg(mediaServerItem, "getSnap",param, targetPath, fileName);
     }
 }

+ 40 - 36
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java

@@ -5,6 +5,8 @@ import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.conf.MediaConfig;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
 import com.genersoft.iot.vmp.gb28181.session.SsrcUtil;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -30,10 +32,10 @@ public class ZLMRTPServerFactory {
 
     private Map<String, Integer> currentStreams = null;
 
-    public int createRTPServer(String streamId) {
+    public int createRTPServer(IMediaServerItem mediaServerItem, String streamId) {
         if (currentStreams == null) {
             currentStreams = new HashMap<>();
-            JSONObject jsonObject = zlmresTfulUtils.listRtpServer();
+            JSONObject jsonObject = zlmresTfulUtils.listRtpServer(mediaServerItem);
             if (jsonObject != null) {
                 JSONArray data = jsonObject.getJSONArray("data");
                 if (data != null) {
@@ -48,7 +50,7 @@ public class ZLMRTPServerFactory {
         if (currentStreams.get(streamId) != null) {
             Map<String, Object> closeRtpServerParam = new HashMap<>();
             closeRtpServerParam.put("stream_id", streamId);
-            zlmresTfulUtils.closeRtpServer(closeRtpServerParam);
+            zlmresTfulUtils.closeRtpServer(mediaServerItem, closeRtpServerParam);
             currentStreams.remove(streamId);
         }
 
@@ -58,7 +60,7 @@ public class ZLMRTPServerFactory {
         param.put("port", newPort);
         param.put("enable_tcp", 1);
         param.put("stream_id", streamId);
-        JSONObject jsonObject = zlmresTfulUtils.openRtpServer(param);
+        JSONObject jsonObject = zlmresTfulUtils.openRtpServer(mediaServerItem, param);
 
         if (jsonObject != null) {
             switch (jsonObject.getInteger("code")){
@@ -68,11 +70,11 @@ public class ZLMRTPServerFactory {
                 case -300: // id已经存在, 可能已经在其他端口推流
                     Map<String, Object> closeRtpServerParam = new HashMap<>();
                     closeRtpServerParam.put("stream_id", streamId);
-                    zlmresTfulUtils.closeRtpServer(closeRtpServerParam);
+                    zlmresTfulUtils.closeRtpServer(mediaServerItem, closeRtpServerParam);
                     result = newPort;
                     break;
                 case -400: // 端口占用
-                    result= createRTPServer(streamId);
+                    result= createRTPServer(mediaServerItem, streamId);
                     break;
                 default:
                     logger.error("创建RTP Server 失败 {}: " + jsonObject.getString("msg"), newPort);
@@ -85,20 +87,22 @@ public class ZLMRTPServerFactory {
         return result;
     }
 
-    public boolean closeRTPServer(String streamId) {
+    public boolean closeRTPServer(IMediaServerItem serverItem, String streamId) {
         boolean result = false;
-        Map<String, Object> param = new HashMap<>();
-        param.put("stream_id", streamId);
-        JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(param);
-        if (jsonObject != null ) {
-            if (jsonObject.getInteger("code") == 0) {
-                result = jsonObject.getInteger("hit") == 1;
+        if (serverItem !=null){
+            Map<String, Object> param = new HashMap<>();
+            param.put("stream_id", streamId);
+            JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(serverItem, param);
+            if (jsonObject != null ) {
+                if (jsonObject.getInteger("code") == 0) {
+                    result = jsonObject.getInteger("hit") == 1;
+                }else {
+                    logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
+                }
             }else {
-                logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
+                //  检查ZLM状态
+                logger.error("关闭RTP Server 失败: 请检查ZLM服务");
             }
-        }else {
-            //  检查ZLM状态
-            logger.error("关闭RTP Server 失败: 请检查ZLM服务");
         }
         return result;
     }
@@ -131,11 +135,11 @@ public class ZLMRTPServerFactory {
      * @param tcp 是否为tcp
      * @return SendRtpItem
      */
-    public SendRtpItem createSendRtpItem(String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp){
+    public SendRtpItem createSendRtpItem(IMediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp){
         String playSsrc = SsrcUtil.getPlaySsrc();
-        int localPort = createRTPServer(SsrcUtil.getPlaySsrc());
+        int localPort = createRTPServer(serverItem, SsrcUtil.getPlaySsrc());
         if (localPort != -1) {
-            closeRTPServer(playSsrc);
+            closeRTPServer(serverItem, playSsrc);
         }else {
             logger.error("没有可用的端口");
             return null;
@@ -150,6 +154,7 @@ public class ZLMRTPServerFactory {
         sendRtpItem.setTcp(tcp);
         sendRtpItem.setApp("rtp");
         sendRtpItem.setLocalPort(localPort);
+        sendRtpItem.setMediaServerId(serverItem.getId());
         return sendRtpItem;
     }
 
@@ -163,11 +168,11 @@ public class ZLMRTPServerFactory {
      * @param tcp 是否为tcp
      * @return SendRtpItem
      */
-    public SendRtpItem createSendRtpItem(String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp){
+    public SendRtpItem createSendRtpItem(IMediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp){
         String playSsrc = SsrcUtil.getPlaySsrc();
-        int localPort = createRTPServer(SsrcUtil.getPlaySsrc());
+        int localPort = createRTPServer(serverItem, SsrcUtil.getPlaySsrc());
         if (localPort != -1) {
-            closeRTPServer(playSsrc);
+            closeRTPServer(serverItem, playSsrc);
         }else {
             logger.error("没有可用的端口");
             return null;
@@ -182,21 +187,21 @@ public class ZLMRTPServerFactory {
         sendRtpItem.setChannelId(channelId);
         sendRtpItem.setTcp(tcp);
         sendRtpItem.setLocalPort(localPort);
+        sendRtpItem.setMediaServerId(serverItem.getId());
         return sendRtpItem;
     }
 
     /**
      * 调用zlm RESTful API —— startSendRtp
      */
-    public Boolean startSendRtpStream(Map<String, Object>param) {
+    public Boolean startSendRtpStream(IMediaServerItem mediaServerItem, Map<String, Object>param) {
         Boolean result = false;
-        JSONObject jsonObject = zlmresTfulUtils.startSendRtp(param);
-        logger.info(jsonObject.toJSONString());
+        JSONObject jsonObject = zlmresTfulUtils.startSendRtp(mediaServerItem, param);
         if (jsonObject == null) {
             logger.error("RTP推流失败: 请检查ZLM服务");
         } else if (jsonObject.getInteger("code") == 0) {
             result= true;
-            logger.info("RTP推流请求成功,本地推流端口:" + jsonObject.getString("local_port"));
+            logger.info("RTP推流[ {}/{} ]请求成功,本地推流端口:{}" ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"));
         } else {
             logger.error("RTP推流失败: " + jsonObject.getString("msg"));
         }
@@ -206,16 +211,16 @@ public class ZLMRTPServerFactory {
     /**
      * 查询待转推的流是否就绪
      */
-    public Boolean isRtpReady(String streamId) {
-        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo("rtp", "rtmp", streamId);
+    public Boolean isRtpReady(MediaServerItem mediaServerItem, String streamId) {
+        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem,"rtp", "rtmp", streamId);
         return (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online"));
     }
 
     /**
      * 查询待转推的流是否就绪
      */
-    public Boolean isStreamReady(String app, String streamId) {
-        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(app, "rtmp", streamId);
+    public Boolean isStreamReady(IMediaServerItem mediaServerItem, String app, String streamId) {
+        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
         return (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online"));
     }
 
@@ -224,18 +229,17 @@ public class ZLMRTPServerFactory {
      * @param streamId
      * @return
      */
-    public int totalReaderCount(String app, String streamId) {
-        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(app, "rtmp", streamId);
+    public int totalReaderCount(IMediaServerItem mediaServerItem, String app, String streamId) {
+        JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
         return mediaInfo.getInteger("totalReaderCount");
     }
 
     /**
      * 调用zlm RESTful API —— stopSendRtp
      */
-    public Boolean stopSendRtpStream(Map<String, Object>param) {
+    public Boolean stopSendRtpStream(IMediaServerItem mediaServerItem,Map<String, Object>param) {
         Boolean result = false;
-        JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(param);
-        logger.info(jsonObject.toJSONString());
+        JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaServerItem, param);
         if (jsonObject == null) {
             logger.error("停止RTP推流失败: 请检查ZLM服务");
         } else if (jsonObject.getInteger("code") == 0) {

+ 93 - 99
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java

@@ -4,7 +4,10 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.conf.MediaConfig;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 import com.genersoft.iot.vmp.service.IStreamProxyService;
 import org.slf4j.Logger;
@@ -14,10 +17,10 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import javax.print.attribute.standard.Media;
+import java.util.*;
 
 @Component
 @Order(value=1)
@@ -25,140 +28,131 @@ public class ZLMRunner implements CommandLineRunner {
 
     private final static Logger logger = LoggerFactory.getLogger(ZLMRunner.class);
 
-     @Autowired
-     private IVideoManagerStorager storager;
-
-    @Autowired
-    private MediaConfig mediaConfig;
-
-    @Value("${server.port}")
-    private String serverPort;
-
-    @Value("${server.ssl.enabled:false}")
-    private boolean sslEnabled;
-
-    private boolean startGetMedia = false;
+    private Map<String, Boolean> startGetMedia;
 
     @Autowired
     private ZLMRESTfulUtils zlmresTfulUtils;
 
     @Autowired
-    private ZLMMediaListManager zlmMediaListManager;
+    private ZLMHttpHookSubscribe hookSubscribe;
 
     @Autowired
-    private ZLMHttpHookSubscribe hookSubscribe;
+    private IStreamProxyService streamProxyService;
 
     @Autowired
-    private ZLMServerManger zlmServerManger;
+    private IMediaServerService mediaServerService;
 
     @Autowired
-    private IStreamProxyService streamProxyService;
+    private MediaConfig mediaConfig;
 
     @Override
     public void run(String... strings) throws Exception {
-        // 订阅 zlm启动事件
-        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_started,null,(response)->{
-            ZLMServerConfig ZLMServerConfig = JSONObject.toJavaObject(response, ZLMServerConfig.class);
-            zLmRunning(ZLMServerConfig);
+        IMediaServerItem presetMediaServer = mediaServerService.getOneByHostAndPort(
+                mediaConfig.getIp(), mediaConfig.getHttpPort());
+        if (presetMediaServer  != null) {
+            mediaConfig.setId(presetMediaServer.getId());
+            mediaServerService.update(mediaConfig);
+        }
+
+        // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
+        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_started,null,
+                (IMediaServerItem mediaServerItem, JSONObject response)->{
+            ZLMServerConfig zlmServerConfig = JSONObject.toJavaObject(response, ZLMServerConfig.class);
+            if (zlmServerConfig !=null ) {
+                startGetMedia.remove(zlmServerConfig.getGeneralMediaServerId());
+                mediaServerService.handLeZLMServerConfig(zlmServerConfig);
+//                zLmRunning(zlmServerConfig);
+            }
         });
 
         // 获取zlm信息
-        logger.info("等待zlm接入...");
-        startGetMedia = true;
-        ZLMServerConfig ZLMServerConfig = getMediaServerConfig();
+        logger.info("等待默认zlm接入...");
 
-        if (ZLMServerConfig != null) {
-            zLmRunning(ZLMServerConfig);
+        // 获取所有的zlm, 并开启主动连接
+        List<IMediaServerItem> all = mediaServerService.getAll();
+        if (presetMediaServer == null) {
+            all.add(mediaConfig.getMediaSerItem());
+        }
+        for (IMediaServerItem mediaServerItem : all) {
+            if (startGetMedia == null) startGetMedia = new HashMap<>();
+            startGetMedia.put(mediaServerItem.getId(), true);
+            new Thread(() -> {
+                ZLMServerConfig zlmServerConfig = getMediaServerConfig(mediaServerItem);
+                if (zlmServerConfig != null) {
+                    startGetMedia.remove(mediaServerItem.getId());
+                    mediaServerService.handLeZLMServerConfig(zlmServerConfig);
+                }
+            }).start();
         }
+        Timer timer = new Timer();
+        // 1分钟后未连接到则不再去主动连接
+        timer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+            if (startGetMedia != null) {
+                Set<String> allZlmId = startGetMedia.keySet();
+                for (String id : allZlmId) {
+                    logger.error("[ {} ]]主动连接失败,不再主动连接", id);
+                    startGetMedia.put(id, false);
+                }
+            }
+            }
+        }, 60 * 1000 * 2);
     }
 
-    public ZLMServerConfig getMediaServerConfig() {
-        if (!startGetMedia) return null;
-        JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig();
+    public ZLMServerConfig getMediaServerConfig(IMediaServerItem mediaServerItem) {
+        if ( startGetMedia.get(mediaServerItem.getId()) == null || !startGetMedia.get(mediaServerItem.getId())) return null;
+        JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
         ZLMServerConfig ZLMServerConfig = null;
         if (responseJSON != null) {
             JSONArray data = responseJSON.getJSONArray("data");
             if (data != null && data.size() > 0) {
                 ZLMServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class);
-
+                ZLMServerConfig.setIp(mediaServerItem.getIp());
             }
         } else {
-            logger.error("getMediaServerConfig失败, 1s后重试");
+            logger.error("[ {} ]-[ {}:{} ]主动连接失败失败, 2s后重试",
+                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
             try {
-                Thread.sleep(1000);
+                Thread.sleep(2000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
-            ZLMServerConfig = getMediaServerConfig();
+            ZLMServerConfig = getMediaServerConfig(mediaServerItem);
         }
         return ZLMServerConfig;
-    }
 
-    private void saveZLMConfig() {
-        logger.info("设置zlm...");
-        String protocol = sslEnabled ? "https" : "http";
-        String hookPrex = String.format("%s://%s:%s/index/hook", protocol, mediaConfig.getHookIp(), serverPort);
-        String recordHookPrex = null;
-        if (mediaConfig.getRecordAssistPort() != 0) {
-            recordHookPrex = String.format("http://127.0.0.1:%s/api/record", mediaConfig.getRecordAssistPort());
-        }
-        Map<String, Object> param = new HashMap<>();
-        param.put("api.secret",mediaConfig.getSecret()); // -profile:v Baseline
-        param.put("ffmpeg.cmd","%s -fflags nobuffer -rtsp_transport tcp -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264  -f flv %s");
-        param.put("hook.enable","1");
-        param.put("hook.on_flow_report","");
-        param.put("hook.on_play",String.format("%s/on_play", hookPrex));
-        param.put("hook.on_http_access","");
-        param.put("hook.on_publish", String.format("%s/on_publish", hookPrex));
-        param.put("hook.on_record_mp4",recordHookPrex != null? String.format("%s/on_record_mp4", recordHookPrex): "");
-        param.put("hook.on_record_ts","");
-        param.put("hook.on_rtsp_auth","");
-        param.put("hook.on_rtsp_realm","");
-        param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex));
-        param.put("hook.on_shell_login",String.format("%s/on_shell_login", hookPrex));
-        param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex));
-        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.timeoutSec","20");
-        param.put("general.streamNoneReaderDelayMS",mediaConfig.getStreamNoneReaderDelayMS());
-
-        JSONObject responseJSON = zlmresTfulUtils.setServerConfig(param);
-
-        if (responseJSON != null && responseJSON.getInteger("code") == 0) {
-            logger.info("设置zlm成功");
-        }else {
-            logger.info("设置zlm失败");
-        }
     }
 
     /**
      * zlm 连接成功或者zlm重启后
      */
-    private void zLmRunning(ZLMServerConfig zlmServerConfig){
-        logger.info( "[ id: " + zlmServerConfig.getGeneralMediaServerId() + "] zlm接入成功...");
-        // 关闭循环获取zlm配置
-        startGetMedia = false;
-        if (mediaConfig.isAutoConfig()) saveZLMConfig();
-        zlmServerManger.updateServerCatch(zlmServerConfig);
-
-        // 清空所有session
-//        zlmMediaListManager.clearAllSessions();
-
-        // 更新流列表
-        zlmMediaListManager.updateMediaList();
-        // 恢复流代理
-        List<StreamProxyItem> streamProxyListForEnable = storager.getStreamProxyListForEnable(true);
-        for (StreamProxyItem streamProxyDto : streamProxyListForEnable) {
-            logger.info("恢复流代理," + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
-            JSONObject jsonObject = streamProxyService.addStreamProxyToZlm(streamProxyDto);
-            if (jsonObject == null) {
-                // 设置为未启用
-                logger.info("恢复流代理失败,请检查流地址后重新启用" + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
-                streamProxyService.stop(streamProxyDto.getApp(), streamProxyDto.getStream());
-            }else if (jsonObject.getInteger("code") != 0){ // TODO 将错误信息存入数据库, 前端展示
-                logger.info("恢复流代理失败:" + streamProxyDto.getApp() + "/" + streamProxyDto.getStream() + "[ " + JSONObject.toJSONString(jsonObject) + " ]");
-                streamProxyService.stop(streamProxyDto.getApp(), streamProxyDto.getStream());
-            }
-        }
-    }
+//    private void zLmRunning(ZLMServerConfig zlmServerConfig){
+//        logger.info( "[ id: " + zlmServerConfig.getGeneralMediaServerId() + "] zlm接入成功...");
+//        // 关闭循环获取zlm配置
+//        startGetMedia = false;
+//        MediaServerItem mediaServerItem = new MediaServerItem(zlmServerConfig, sipIp);
+//        storager.updateMediaServer(mediaServerItem);
+//
+//        if (mediaServerItem.isAutoConfig()) setZLMConfig(mediaServerItem);
+//        zlmServerManger.updateServerCatchFromHook(zlmServerConfig);
+//
+//        // 清空所有session
+////        zlmMediaListManager.clearAllSessions();
+//
+//        // 更新流列表
+//        zlmMediaListManager.updateMediaList(mediaServerItem);
+//        // 恢复流代理, 只查找这个这个流媒体
+//        List<StreamProxyItem> streamProxyListForEnable = storager.getStreamProxyListForEnableInMediaServer(
+//                mediaServerItem.getId(), true);
+//        for (StreamProxyItem streamProxyDto : streamProxyListForEnable) {
+//            logger.info("恢复流代理," + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
+//            JSONObject jsonObject = streamProxyService.addStreamProxyToZlm(streamProxyDto);
+//            if (jsonObject == null) {
+//                // 设置为未启用
+//                logger.info("恢复流代理失败,请检查流地址后重新启用" + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
+//                streamProxyService.stop(streamProxyDto.getApp(), streamProxyDto.getStream());
+//            }
+//        }
+//    }
 }

+ 36 - 24
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerConfig.java

@@ -34,12 +34,15 @@ public class ZLMServerConfig {
     @JSONField(name = "general.streamNoneReaderDelayMS")
     private String generalStreamNoneReaderDelayMS;
 
+    @JSONField(name = "ip")
     private String ip;
 
     private String sdpIp;
 
     private String streamIp;
 
+    private String hookIp;
+
     private String updateTime;
 
     private String createTime;
@@ -66,7 +69,7 @@ public class ZLMServerConfig {
     private String hookEnable;
 
     @JSONField(name = "hook.on_flow_report")
-    private Integer hookOnFlowReport;
+    private String hookOnFlowReport;
 
     @JSONField(name = "hook.on_http_access")
     private String hookOnHttpAccess;
@@ -117,7 +120,7 @@ public class ZLMServerConfig {
     private String httpNotFound;
 
     @JSONField(name = "http.port")
-    private Integer httpPort;
+    private int httpPort;
 
     @JSONField(name = "http.rootPath")
     private String httpRootPath;
@@ -126,7 +129,7 @@ public class ZLMServerConfig {
     private String httpSendBufSize;
 
     @JSONField(name = "http.sslport")
-    private Integer httpSSLport;
+    private int httpSSLport;
 
     @JSONField(name = "multicast.addrMax")
     private String multicastAddrMax;
@@ -159,10 +162,10 @@ public class ZLMServerConfig {
     private String rtmpModifyStamp;
 
     @JSONField(name = "rtmp.port")
-    private Integer rtmpPort;
+    private int rtmpPort;
 
     @JSONField(name = "rtmp.sslport")
-    private Integer rtmpSslPort;
+    private int rtmpSslPort;
 
     @JSONField(name = "rtp.audioMtuSize")
     private String rtpAudioMtuSize;
@@ -186,7 +189,7 @@ public class ZLMServerConfig {
     private String rtpProxyDumpDir;
 
     @JSONField(name = "rtp_proxy.port")
-    private Integer rtpProxyPort;
+    private int rtpProxyPort;
 
     @JSONField(name = "rtp_proxy.timeoutSec")
     private String rtpProxyTimeoutSec;
@@ -201,10 +204,10 @@ public class ZLMServerConfig {
     private String rtspKeepAliveSecond;
 
     @JSONField(name = "rtsp.port")
-    private Integer rtspPort;
+    private int rtspPort;
 
     @JSONField(name = "rtsp.sslport")
-    private Integer rtspSSlport;
+    private int rtspSSlport;
 
     @JSONField(name = "shell.maxReqSize")
     private String shellMaxReqSize;
@@ -212,6 +215,15 @@ public class ZLMServerConfig {
     @JSONField(name = "shell.shell")
     private String shellPhell;
 
+
+    public String getHookIp() {
+        return hookIp;
+    }
+
+    public void setHookIp(String hookIp) {
+        this.hookIp = hookIp;
+    }
+
     public String getApiDebug() {
         return apiDebug;
     }
@@ -388,11 +400,11 @@ public class ZLMServerConfig {
         this.hookEnable = hookEnable;
     }
 
-    public Integer getHookOnFlowReport() {
+    public String getHookOnFlowReport() {
         return hookOnFlowReport;
     }
 
-    public void setHookOnFlowReport(Integer hookOnFlowReport) {
+    public void setHookOnFlowReport(String hookOnFlowReport) {
         this.hookOnFlowReport = hookOnFlowReport;
     }
 
@@ -524,11 +536,11 @@ public class ZLMServerConfig {
         this.httpNotFound = httpNotFound;
     }
 
-    public Integer getHttpPort() {
+    public int getHttpPort() {
         return httpPort;
     }
 
-    public void setHttpPort(Integer httpPort) {
+    public void setHttpPort(int httpPort) {
         this.httpPort = httpPort;
     }
 
@@ -548,11 +560,11 @@ public class ZLMServerConfig {
         this.httpSendBufSize = httpSendBufSize;
     }
 
-    public Integer getHttpSSLport() {
+    public int getHttpSSLport() {
         return httpSSLport;
     }
 
-    public void setHttpSSLport(Integer httpSSLport) {
+    public void setHttpSSLport(int httpSSLport) {
         this.httpSSLport = httpSSLport;
     }
 
@@ -636,19 +648,19 @@ public class ZLMServerConfig {
         this.rtmpModifyStamp = rtmpModifyStamp;
     }
 
-    public Integer getRtmpPort() {
+    public int getRtmpPort() {
         return rtmpPort;
     }
 
-    public void setRtmpPort(Integer rtmpPort) {
+    public void setRtmpPort(int rtmpPort) {
         this.rtmpPort = rtmpPort;
     }
 
-    public Integer getRtmpSslPort() {
+    public int getRtmpSslPort() {
         return rtmpSslPort;
     }
 
-    public void setRtmpSslPort(Integer rtmpSslPort) {
+    public void setRtmpSslPort(int rtmpSslPort) {
         this.rtmpSslPort = rtmpSslPort;
     }
 
@@ -708,11 +720,11 @@ public class ZLMServerConfig {
         this.rtpProxyDumpDir = rtpProxyDumpDir;
     }
 
-    public Integer getRtpProxyPort() {
+    public int getRtpProxyPort() {
         return rtpProxyPort;
     }
 
-    public void setRtpProxyPort(Integer rtpProxyPort) {
+    public void setRtpProxyPort(int rtpProxyPort) {
         this.rtpProxyPort = rtpProxyPort;
     }
 
@@ -748,19 +760,19 @@ public class ZLMServerConfig {
         this.rtspKeepAliveSecond = rtspKeepAliveSecond;
     }
 
-    public Integer getRtspPort() {
+    public int getRtspPort() {
         return rtspPort;
     }
 
-    public void setRtspPort(Integer rtspPort) {
+    public void setRtspPort(int rtspPort) {
         this.rtspPort = rtspPort;
     }
 
-    public Integer getRtspSSlport() {
+    public int getRtspSSlport() {
         return rtspSSlport;
     }
 
-    public void setRtspSSlport(Integer rtspSSlport) {
+    public void setRtspSSlport(int rtspSSlport) {
         this.rtspSSlport = rtspSSlport;
     }
 

+ 0 - 45
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerManger.java

@@ -1,45 +0,0 @@
-package com.genersoft.iot.vmp.media.zlm;
-
-import com.genersoft.iot.vmp.conf.MediaConfig;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-import org.springframework.util.StringUtils;
-
-@Component
-public class ZLMServerManger {
-
-    @Autowired
-    private IRedisCatchStorage redisCatchStorage;
-
-    @Autowired
-    private MediaConfig mediaConfig;
-
-    public void updateServerCatch(ZLMServerConfig zlmServerConfig) {
-
-        zlmServerConfig.setIp(mediaConfig.getIp());
-        zlmServerConfig.setStreamIp(mediaConfig.getStreamIp());
-        zlmServerConfig.setSdpIp(mediaConfig.getSdpIp());
-        zlmServerConfig.setHttpPort(mediaConfig.getHttpPort());
-
-        if(!StringUtils.isEmpty(mediaConfig.getHttpSSlPort()))
-            zlmServerConfig.setHttpSSLport(mediaConfig.getHttpSSlPort());
-
-        if(!StringUtils.isEmpty(mediaConfig.getRtspPort()))
-            zlmServerConfig.setRtspPort(mediaConfig.getRtspPort());
-
-        if(!StringUtils.isEmpty(mediaConfig.getRtspSSLPort()))
-            zlmServerConfig.setRtspSSlport(mediaConfig.getRtspSSLPort());
-
-        if(!StringUtils.isEmpty(mediaConfig.getRtmpPort()))
-            zlmServerConfig.setRtmpPort(mediaConfig.getRtmpPort());
-
-        if(!StringUtils.isEmpty(mediaConfig.getRtmpSSlPort()))
-            zlmServerConfig.setRtmpSslPort(mediaConfig.getRtmpSSlPort());
-
-        if(!StringUtils.isEmpty(mediaConfig.getRtpProxyPort()))
-            zlmServerConfig.setRtpProxyPort(mediaConfig.getRtpProxyPort());
-
-        redisCatchStorage.updateMediaInfo(zlmServerConfig);
-    }
-}

+ 92 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/IMediaServerItem.java

@@ -0,0 +1,92 @@
+package com.genersoft.iot.vmp.media.zlm.dto;
+
+public interface IMediaServerItem {
+
+    String getId();
+
+    void setId(String id);
+
+    String getIp();
+
+    void setIp(String ip);
+
+    String getHookIp();
+
+    void setHookIp(String hookIp);
+
+    String getSdpIp();
+
+    void setSdpIp(String sdpIp);
+
+    String getStreamIp();
+
+    void setStreamIp(String streamIp);
+
+    int getHttpPort();
+
+    void setHttpPort(int httpPort);
+
+    int getHttpSSlPort();
+
+    void setHttpSSlPort(int httpSSlPort);
+
+    int getRtmpPort();
+
+    void setRtmpPort(int rtmpPort);
+
+    int getRtmpSSlPort();
+
+    void setRtmpSSlPort(int rtmpSSlPort);
+
+    int getRtpProxyPort();
+
+    void setRtpProxyPort(int rtpProxyPort);
+
+    int getRtspPort();
+
+    void setRtspPort(int rtspPort);
+
+    int getRtspSSLPort();
+
+    void setRtspSSLPort(int rtspSSLPort);
+
+    boolean isAutoConfig();
+
+    void setAutoConfig(boolean autoConfig);
+
+    String getSecret();
+
+    void setSecret(String secret);
+
+    String getStreamNoneReaderDelayMS();
+
+    void setStreamNoneReaderDelayMS(String streamNoneReaderDelayMS);
+
+    boolean isRtpEnable();
+
+    void setRtpEnable(boolean rtpEnable);
+
+    String getRtpPortRange();
+
+    void setRtpPortRange(String rtpPortRange);
+
+    int getRecordAssistPort();
+
+    void setRecordAssistPort(int recordAssistPort);
+
+    boolean isDocker();
+
+    void setDocker(boolean docker);
+
+    String getUpdateTime();
+
+    void setUpdateTime(String updateTime);
+
+    String getCreateTime();
+
+    void setCreateTime(String createTime);
+
+    int getCount();
+
+    void setCount(int count);
+}

+ 12 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java

@@ -78,6 +78,10 @@ public class MediaItem {
      */
     private String vhost;
 
+    /**
+     * 是否是docker部署, docker部署不会自动更新zlm使用的端口,需要自己手动修改
+     */
+    private boolean docker;
 
     public static class MediaTrack {
         /**
@@ -364,4 +368,12 @@ public class MediaItem {
     public OriginSock getOriginSock() {
         return originSock;
     }
+
+    public boolean isDocker() {
+        return docker;
+    }
+
+    public void setDocker(boolean docker) {
+        this.docker = docker;
+    }
 }

+ 254 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java

@@ -0,0 +1,254 @@
+package com.genersoft.iot.vmp.media.zlm.dto;
+
+
+import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
+import org.springframework.util.StringUtils;
+
+public class MediaServerItem implements IMediaServerItem{
+
+    private String id;
+
+    private String ip;
+
+    private String hookIp;
+
+    private String sdpIp;
+
+    private String streamIp;
+
+    private int httpPort;
+
+    private int httpSSlPort;
+
+    private int rtmpPort;
+
+    private int rtmpSSlPort;
+
+    private int rtpProxyPort;
+
+    private int rtspPort;
+
+    private int rtspSSLPort;
+
+    private boolean autoConfig;
+
+    private String secret;
+
+    private String streamNoneReaderDelayMS;
+
+    private boolean rtpEnable;
+
+    private String rtpPortRange;
+
+    private int recordAssistPort;
+
+    private String createTime;
+
+    private String updateTime;
+
+    private boolean docker;
+
+    private int count;
+
+    public MediaServerItem() {
+    }
+
+    public MediaServerItem(ZLMServerConfig zlmServerConfig, String sipIp) {
+        id = zlmServerConfig.getGeneralMediaServerId();
+        ip = zlmServerConfig.getIp();
+        hookIp = StringUtils.isEmpty(zlmServerConfig.getHookIp())? sipIp: zlmServerConfig.getHookIp();
+        sdpIp = StringUtils.isEmpty(zlmServerConfig.getSdpIp())? zlmServerConfig.getIp(): zlmServerConfig.getSdpIp();
+        streamIp = StringUtils.isEmpty(zlmServerConfig.getStreamIp())? zlmServerConfig.getIp(): zlmServerConfig.getStreamIp();
+        httpPort = zlmServerConfig.getHttpPort();
+        httpSSlPort = zlmServerConfig.getHttpSSLport();
+        rtmpPort = zlmServerConfig.getRtmpPort();
+        rtmpSSlPort = zlmServerConfig.getRtmpSslPort();
+        rtpProxyPort = zlmServerConfig.getRtpProxyPort();
+        rtspPort = zlmServerConfig.getRtspPort();
+        rtspSSLPort = zlmServerConfig.getRtspSSlport();
+        autoConfig = true; // 默认值true;
+        secret = zlmServerConfig.getApiSecret();
+        streamNoneReaderDelayMS = zlmServerConfig.getGeneralStreamNoneReaderDelayMS();
+        rtpEnable = false; // 默认使用单端口;直到用户自己设置开启多端口
+        recordAssistPort = 0; // 默认关闭
+
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+    public String getHookIp() {
+        return hookIp;
+    }
+
+    public void setHookIp(String hookIp) {
+        this.hookIp = hookIp;
+    }
+
+    public String getSdpIp() {
+        return sdpIp;
+    }
+
+    public void setSdpIp(String sdpIp) {
+        this.sdpIp = sdpIp;
+    }
+
+    public String getStreamIp() {
+        return streamIp;
+    }
+
+    public void setStreamIp(String streamIp) {
+        this.streamIp = streamIp;
+    }
+
+    public int getHttpPort() {
+        return httpPort;
+    }
+
+    public void setHttpPort(int httpPort) {
+        this.httpPort = httpPort;
+    }
+
+    public int getHttpSSlPort() {
+        return httpSSlPort;
+    }
+
+    public void setHttpSSlPort(int httpSSlPort) {
+        this.httpSSlPort = httpSSlPort;
+    }
+
+    public int getRtmpPort() {
+        return rtmpPort;
+    }
+
+    public void setRtmpPort(int rtmpPort) {
+        this.rtmpPort = rtmpPort;
+    }
+
+    public int getRtmpSSlPort() {
+        return rtmpSSlPort;
+    }
+
+    public void setRtmpSSlPort(int rtmpSSlPort) {
+        this.rtmpSSlPort = rtmpSSlPort;
+    }
+
+    public int getRtpProxyPort() {
+        return rtpProxyPort;
+    }
+
+    public void setRtpProxyPort(int rtpProxyPort) {
+        this.rtpProxyPort = rtpProxyPort;
+    }
+
+    public int getRtspPort() {
+        return rtspPort;
+    }
+
+    public void setRtspPort(int rtspPort) {
+        this.rtspPort = rtspPort;
+    }
+
+    public int getRtspSSLPort() {
+        return rtspSSLPort;
+    }
+
+    public void setRtspSSLPort(int rtspSSLPort) {
+        this.rtspSSLPort = rtspSSLPort;
+    }
+
+    public boolean isAutoConfig() {
+        return autoConfig;
+    }
+
+    public void setAutoConfig(boolean autoConfig) {
+        this.autoConfig = autoConfig;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+
+    public String getStreamNoneReaderDelayMS() {
+        return streamNoneReaderDelayMS;
+    }
+
+    public void setStreamNoneReaderDelayMS(String streamNoneReaderDelayMS) {
+        this.streamNoneReaderDelayMS = streamNoneReaderDelayMS;
+    }
+
+    public boolean isRtpEnable() {
+        return rtpEnable;
+    }
+
+    public void setRtpEnable(boolean rtpEnable) {
+        this.rtpEnable = rtpEnable;
+    }
+
+    public String getRtpPortRange() {
+        return rtpPortRange;
+    }
+
+    public void setRtpPortRange(String rtpPortRange) {
+        this.rtpPortRange = rtpPortRange;
+    }
+
+    public int getRecordAssistPort() {
+        return recordAssistPort;
+    }
+
+    public void setRecordAssistPort(int recordAssistPort) {
+        this.recordAssistPort = recordAssistPort;
+    }
+
+    @Override
+    public boolean isDocker() {
+        return docker;
+    }
+
+    @Override
+    public void setDocker(boolean docker) {
+        this.docker = docker;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(String updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public void setCount(int count) {
+        this.count = count;
+    }
+}

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

@@ -7,6 +7,7 @@ public class StreamProxyItem extends GbStream {
     private String type;
     private String app;
     private String stream;
+    private String mediaServerId;
     private String url;
     private String src_url;
     private String dst_url;
@@ -17,6 +18,7 @@ public class StreamProxyItem extends GbStream {
     private boolean enable_hls;
     private boolean enable_mp4;
     private String platformGbId;
+    private String createTime;
 
     public String getType() {
         return type;
@@ -42,6 +44,16 @@ public class StreamProxyItem extends GbStream {
         this.stream = stream;
     }
 
+    @Override
+    public String getMediaServerId() {
+        return mediaServerId;
+    }
+
+    @Override
+    public void setMediaServerId(String mediaServerId) {
+        this.mediaServerId = mediaServerId;
+    }
+
     public String getUrl() {
         return url;
     }
@@ -122,4 +134,12 @@ public class StreamProxyItem extends GbStream {
     public void setPlatformGbId(String platformGbId) {
         this.platformGbId = platformGbId;
     }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
 }

+ 14 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java

@@ -76,6 +76,11 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte
      */
     private String vhost;
 
+    /**
+     * 使用的流媒体ID
+     */
+    private String mediaServerId;
+
     public String getVhost() {
         return vhost;
     }
@@ -202,5 +207,14 @@ public class StreamPushItem extends GbStream implements Comparable<StreamPushIte
     }
 
 
+    @Override
+    public String getMediaServerId() {
+        return mediaServerId;
+    }
+
+    @Override
+    public void setMediaServerId(String mediaServerId) {
+        this.mediaServerId = mediaServerId;
+    }
 }
 

+ 33 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMRunInfo.java

@@ -0,0 +1,33 @@
+package com.genersoft.iot.vmp.media.zlm.dto;
+
+/**
+ * 记录zlm运行中一些参数
+ */
+public class ZLMRunInfo {
+
+    /**
+     * zlm当前流数量
+     */
+    private int mediaCount;
+
+    /**
+     * 在线状态
+     */
+    private boolean online;
+
+    public int getMediaCount() {
+        return mediaCount;
+    }
+
+    public void setMediaCount(int mediaCount) {
+        this.mediaCount = mediaCount;
+    }
+
+    public boolean isOnline() {
+        return online;
+    }
+
+    public void setOnline(boolean online) {
+        this.online = online;
+    }
+}

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

@@ -0,0 +1,44 @@
+package com.genersoft.iot.vmp.service;
+
+import com.genersoft.iot.vmp.conf.MediaConfig;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+
+import java.util.List;
+
+/**
+ * 媒体服务节点
+ */
+public interface IMediaServerService {
+
+    List<IMediaServerItem> getAll();
+
+    IMediaServerItem getOne(String generalMediaServerId);
+
+    IMediaServerItem getOneByHostAndPort(String host, int port);
+
+    /**
+     * 新的节点加入
+     * @param zlmServerConfig
+     * @return
+     */
+    void handLeZLMServerConfig(ZLMServerConfig zlmServerConfig);
+
+    void updateServerCatch(IMediaServerItem mediaServerItem, Integer count, Boolean b);
+
+    IMediaServerItem getMediaServerForMinimumLoad();
+
+    void setZLMConfig(IMediaServerItem mediaServerItem);
+
+    void init();
+
+    void closeRTPServer(Device device, String channelId);
+
+    void update(MediaConfig mediaConfig);
+
+    void addCount(String mediaServerId);
+
+    void removeCount(String mediaServerId);
+}

+ 10 - 7
src/main/java/com/genersoft/iot/vmp/service/IMediaService.java

@@ -2,6 +2,8 @@ package com.genersoft.iot.vmp.service;
 
 import com.alibaba.fastjson.JSONArray;
 import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 
 /**
  * 媒体信息业务
@@ -14,29 +16,30 @@ public interface IMediaService {
      * @param stream
      * @return
      */
-    StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream);
+    StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId,String addr);
+
 
     /**
-     * 根据应用名和流ID获取播放地址, 只是地址拼接
+     * 根据应用名和流ID获取播放地址, 通过zlm接口检查是否存在, 返回的ip使用远程访问ip,适用与zlm与wvp在一台主机的情况
      * @param app
      * @param stream
      * @return
      */
-    StreamInfo getStreamInfoByAppAndStream(String app, String stream, JSONArray tracks);
+    StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId);
 
     /**
-     * 根据应用名和流ID获取播放地址, 只是地址拼接,返回的ip使用远程访问ip,适用与zlm与wvp在一台主机的情况
+     * 根据应用名和流ID获取播放地址, 只是地址拼接
      * @param app
      * @param stream
      * @return
      */
-    StreamInfo getStreamInfoByAppAndStream(String app, String stream, JSONArray tracks, String addr);
+    StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaServerItem, String app, String stream, JSONArray tracks);
 
     /**
-     * 根据应用名和流ID获取播放地址, 通过zlm接口检查是否存在, 返回的ip使用远程访问ip,适用与zlm与wvp在一台主机的情况
+     * 根据应用名和流ID获取播放地址, 只是地址拼接,返回的ip使用远程访问ip,适用与zlm与wvp在一台主机的情况
      * @param app
      * @param stream
      * @return
      */
-    StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String addr);
+    StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaInfo, String app, String stream, JSONArray tracks, String addr);
 }

+ 8 - 3
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java

@@ -1,8 +1,11 @@
 package com.genersoft.iot.vmp.service;
 
 import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
 import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
 
 /**
@@ -10,8 +13,10 @@ import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
  */
 public interface IPlayService {
 
-    void onPublishHandlerForPlayBack(JSONObject resonse, String deviceId, String channelId, String uuid);
-    void onPublishHandlerForPlay(JSONObject resonse, String deviceId, String channelId, String uuid);
+    void onPublishHandlerForPlayBack(IMediaServerItem mediaServerItem,JSONObject resonse, String deviceId, String channelId, String uuid);
+    void onPublishHandlerForPlay(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid);
 
-    PlayResult play(String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
+    PlayResult play(IMediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
+
+    IMediaServerItem getNewMediaServerItem(Device device);
 }

+ 3 - 1
src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java

@@ -1,6 +1,8 @@
 package com.genersoft.iot.vmp.service;
 
 import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
 import com.github.pagehelper.PageInfo;
 
@@ -61,5 +63,5 @@ public interface IStreamProxyService {
      * 获取ffmpeg.cmd模板
      * @return
      */
-    JSONObject getFFmpegCMDs();
+    JSONObject getFFmpegCMDs(IMediaServerItem mediaServerItem);
 }

+ 2 - 1
src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.service;
 
 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
 import com.github.pagehelper.PageInfo;
 
@@ -8,7 +9,7 @@ import java.util.List;
 
 public interface IStreamPushService {
 
-    List<StreamPushItem> handleJSON(String json);
+    List<StreamPushItem> handleJSON(String json, IMediaServerItem mediaServerItem);
 
     /**
      * 将应用名和流ID加入国标关联

+ 340 - 0
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java

@@ -0,0 +1,340 @@
+package com.genersoft.iot.vmp.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.conf.MediaConfig;
+import com.genersoft.iot.vmp.conf.ProxyServletConfig;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
+import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.ZLMRunInfo;
+import com.genersoft.iot.vmp.service.IMediaServerService;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
+import org.mitre.dsmiley.httpproxy.ProxyServlet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * 媒体服务器节点管理
+ */
+@Service
+public class MediaServerServiceImpl implements IMediaServerService {
+
+    private final static Logger logger = LoggerFactory.getLogger(MediaServerServiceImpl.class);
+
+    private Map<String, IMediaServerItem> zlmServers = new HashMap<>(); // 所有数据库的zlm的缓存
+    private Map<String, Integer> zlmServerStatus = new LinkedHashMap<>(); // 所有上线的zlm的缓存以及负载
+
+    @Value("${sip.ip}")
+    private String sipIp;
+
+    @Value("${server.ssl.enabled:false}")
+    private boolean sslEnabled;
+
+    @Value("${server.port}")
+    private String serverPort;
+
+    @Autowired
+    private MediaConfig mediaConfig;
+
+    @Autowired
+    private ZLMRESTfulUtils zlmresTfulUtils;
+
+    @Autowired
+    private MediaServerMapper mediaServerMapper;
+
+
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
+    @Autowired
+    private VideoStreamSessionManager streamSession;
+
+    @Autowired
+    private ZLMRTPServerFactory zlmrtpServerFactory;
+
+    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+    /**
+     * 初始化
+     */
+    @Override
+    public void init() {
+        zlmServers.clear();
+        zlmServerStatus.clear();
+        List<MediaServerItem> mediaServerItemList = mediaServerMapper.queryAll();
+        for (IMediaServerItem mediaServerItem : mediaServerItemList) {
+            zlmServers.put(mediaServerItem.getId(), mediaServerItem);
+        }
+    }
+
+    @Override
+    public void closeRTPServer(Device device, String channelId) {
+        StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(device.getDeviceId(), channelId);
+        IMediaServerItem mediaServerItem = null;
+        if (streamInfo != null) {
+            mediaServerItem = this.getOne (streamInfo.getMediaServerId());
+        }
+        String streamId = String.format("gb_play_%s_%s", device.getDeviceId(), channelId);
+        zlmrtpServerFactory.closeRTPServer(mediaServerItem, streamId);
+        streamSession.remove(device.getDeviceId(), channelId);
+    }
+
+    @Override
+    public void update(MediaConfig mediaConfig) {
+
+    }
+
+    @Override
+    public List<IMediaServerItem> getAll() {
+        if (zlmServers.size() == 0) {
+            init();
+        }
+        List<IMediaServerItem> result = new ArrayList<>();
+        for (String id : zlmServers.keySet()) {
+            IMediaServerItem mediaServerItem = zlmServers.get(id);
+            mediaServerItem.setCount(zlmServerStatus.get(id) == null ? 0 : zlmServerStatus.get(id));
+            result.add(mediaServerItem);
+        }
+        return result;
+
+
+//        return mediaServerMapper.queryAll();
+    }
+
+    /**
+     * 获取单个zlm服务器
+     * @param mediaServerId 服务id
+     * @return MediaServerItem
+     */
+    @Override
+    public IMediaServerItem getOne(String mediaServerId) {
+        if (mediaServerId ==null) return null;
+        IMediaServerItem mediaServerItem = zlmServers.get(mediaServerId);
+        if (mediaServerItem != null) {
+            mediaServerItem.setCount(zlmServerStatus.get(mediaServerId) == null ? 0 : zlmServerStatus.get(mediaServerId));
+            return mediaServerItem;
+        }else {
+            IMediaServerItem item = mediaServerMapper.queryOne(mediaServerId);
+            if (item != null) {
+                zlmServers.put(item.getId(), item);
+            }
+            return item;
+        }
+    }
+
+    @Override
+    public IMediaServerItem getOneByHostAndPort(String host, int port) {
+        return mediaServerMapper.queryOneByHostAndPort(host, port);
+    }
+
+    /**
+     * 处理zlm上线
+     * @param zlmServerConfig zlm上线携带的参数
+     */
+    @Override
+    public void handLeZLMServerConfig(ZLMServerConfig zlmServerConfig) {
+        logger.info("[ {} ]-[ {}:{} ]已连接",
+                zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
+
+        IMediaServerItem serverItem = getOne(zlmServerConfig.getGeneralMediaServerId());
+        String now = this.format.format(new Date(System.currentTimeMillis()));
+        if (serverItem != null) {
+            serverItem.setSecret(zlmServerConfig.getApiSecret());
+            serverItem.setIp(zlmServerConfig.getIp());
+            // 如果是配置文件中的zlm。 也就是默认zlm。 一切以配置文件内容为准
+            // docker部署不会使用zlm配置的端口号;
+            // 直接编译部署的使用配置文件的端口号,如果zlm修改配改了配置,wvp自动修改
+
+            if (serverItem.getId().equals(mediaConfig.getId())
+                    || (serverItem.getIp().equals(mediaConfig.getIp()) && serverItem.getHttpPort() == mediaConfig.getHttpPort())) {
+                // 配置文件的zlm
+                mediaConfig.setId(zlmServerConfig.getGeneralMediaServerId());
+                mediaConfig.setUpdateTime(now);
+                if (mediaConfig.getHttpPort() == 0) mediaConfig.setHttpPort(zlmServerConfig.getHttpPort());
+                if (mediaConfig.getHttpSSlPort() == 0) mediaConfig.setHttpSSlPort(zlmServerConfig.getHttpSSLport());
+                if (mediaConfig.getRtmpPort() == 0) mediaConfig.setRtmpPort(zlmServerConfig.getRtmpPort());
+                if (mediaConfig.getRtmpSSlPort() == 0) mediaConfig.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());
+                if (mediaConfig.getRtspPort() == 0) mediaConfig.setRtspPort(zlmServerConfig.getRtspPort());
+                if (mediaConfig.getRtspSSLPort() == 0) mediaConfig.setRtspSSLPort(zlmServerConfig.getRtspSSlport());
+                if (mediaConfig.getRtpProxyPort() == 0) mediaConfig.setRtpProxyPort(zlmServerConfig.getRtpProxyPort());
+                mediaServerMapper.update(mediaConfig);
+                serverItem = mediaConfig.getMediaSerItem();
+                setZLMConfig(mediaConfig);
+            }else {
+                if (!serverItem.isDocker()) {
+                    serverItem.setHttpPort(zlmServerConfig.getHttpPort());
+                    serverItem.setHttpSSlPort(zlmServerConfig.getHttpSSLport());
+                    serverItem.setRtmpPort(zlmServerConfig.getRtmpPort());
+                    serverItem.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());
+                    serverItem.setRtpProxyPort(zlmServerConfig.getRtpProxyPort());
+                    serverItem.setRtspPort(zlmServerConfig.getRtspPort());
+
+                }
+                serverItem.setUpdateTime(now);
+                mediaServerMapper.update(serverItem);
+                setZLMConfig(serverItem);
+            }
+        }else {
+            if (zlmServerConfig.getGeneralMediaServerId().equals(mediaConfig.getId())
+                    || (zlmServerConfig.getIp().equals(mediaConfig.getIp()) && zlmServerConfig.getHttpPort() == mediaConfig.getHttpPort())) {
+                mediaConfig.setId(zlmServerConfig.getGeneralMediaServerId());
+                mediaConfig.setCreateTime(now);
+                mediaConfig.setUpdateTime(now);
+                serverItem = mediaConfig;
+                mediaServerMapper.add(mediaConfig);
+            }else {
+                // 一个新的zlm接入wvp
+                serverItem = new MediaServerItem(zlmServerConfig, sipIp);
+                serverItem.setCreateTime(now);
+                serverItem.setUpdateTime(now);
+                mediaServerMapper.add(serverItem);
+            }
+        }
+        // 更新缓存
+        if (zlmServerStatus.get(serverItem.getId()) == null) {
+            zlmServers.put(serverItem.getId(), serverItem);
+            zlmServerStatus.put(serverItem.getId(),0);
+        }
+        // 查询服务流数量
+        IMediaServerItem finalServerItem = serverItem;
+        zlmresTfulUtils.getMediaList(serverItem, null, null, "rtmp",(mediaList ->{
+            Integer code = mediaList.getInteger("code");
+            if (code == 0) {
+                JSONArray data = mediaList.getJSONArray("data");
+                if (data != null) {
+                    zlmServerStatus.put(finalServerItem.getId(),data.size());
+                }else {
+                    zlmServerStatus.put(finalServerItem.getId(),0);
+                }
+
+            }
+        }));
+
+    }
+
+    /**
+     * 更新缓存
+     * @param mediaServerItem zlm服务
+     * @param count 在线数
+     * @param online 在线状态
+     */
+    @Override
+    public void updateServerCatch(IMediaServerItem mediaServerItem, Integer count, Boolean online) {
+        if (mediaServerItem != null) {
+            zlmServers.put(mediaServerItem.getId(), mediaServerItem);
+            Collection<Integer> values = zlmServerStatus.values();
+            if (online != null && count != null) {
+                zlmServerStatus.put(mediaServerItem.getId(), count);
+            }
+        }
+    }
+
+    @Override
+    public void addCount(String mediaServerId) {
+        if (zlmServerStatus.get(mediaServerId) != null) {
+            zlmServerStatus.put(mediaServerId, zlmServerStatus.get(mediaServerId) + 1);
+        }
+    }
+
+    @Override
+    public void removeCount(String mediaServerId) {
+        if (zlmServerStatus.get(mediaServerId) != null) {
+            zlmServerStatus.put(mediaServerId, zlmServerStatus.get(mediaServerId) - 1);
+        }
+    }
+
+    /**
+     * 获取负载最低的节点
+     * @return MediaServerItem
+     */
+    @Override
+    public IMediaServerItem getMediaServerForMinimumLoad() {
+        int mediaCount = -1;
+        String key = null;
+        System.out.println(JSON.toJSONString(zlmServerStatus));
+        if (zlmServerStatus.size() == 1) {
+            Map.Entry entry = zlmServerStatus.entrySet().iterator().next();
+            key= (String) entry.getKey();
+        }else {
+            for (String id : zlmServerStatus.keySet()) {
+                if (key == null) {
+                    key = id;
+                    mediaCount = zlmServerStatus.get(id);
+                }
+                if (zlmServerStatus.get(id) == 0) {
+                    key = id;
+                    break;
+                }else if (mediaCount >= zlmServerStatus.get(id)){
+                    mediaCount = zlmServerStatus.get(id);
+                    key = id;
+                }
+            }
+        }
+
+        if (key == null) {
+            logger.info("获取负载最低的节点时无在线节点");
+            return null;
+        }else{
+            return  zlmServers.get(key);
+        }
+    }
+
+    /**
+     * 对zlm服务器进行基础配置
+     * @param mediaServerItem 服务ID
+     */
+    @Override
+    public void setZLMConfig(IMediaServerItem mediaServerItem) {
+        logger.info("[ {} ]-[ {}:{} ]设置zlm",
+                mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
+        String protocol = sslEnabled ? "https" : "http";
+        String hookPrex = String.format("%s://%s:%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort);
+        String recordHookPrex = null;
+        if (mediaServerItem.getRecordAssistPort() != 0) {
+            recordHookPrex = String.format("http://127.0.0.1:%s/api/record", mediaServerItem.getRecordAssistPort());
+        }
+        Map<String, Object> param = new HashMap<>();
+        param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline
+        param.put("ffmpeg.cmd","%s -fflags nobuffer -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264  -f flv %s");
+        param.put("hook.enable","1");
+        param.put("hook.on_flow_report","");
+        param.put("hook.on_play",String.format("%s/on_play", hookPrex));
+        param.put("hook.on_http_access","");
+        param.put("hook.on_publish", String.format("%s/on_publish", hookPrex));
+        param.put("hook.on_record_mp4",recordHookPrex != null? String.format("%s/on_record_mp4", recordHookPrex): "");
+        param.put("hook.on_record_ts","");
+        param.put("hook.on_rtsp_auth","");
+        param.put("hook.on_rtsp_realm","");
+        param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex));
+        param.put("hook.on_shell_login",String.format("%s/on_shell_login", hookPrex));
+        param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex));
+        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.timeoutSec","20");
+        param.put("general.streamNoneReaderDelayMS",mediaServerItem.getStreamNoneReaderDelayMS());
+
+        JSONObject responseJSON = zlmresTfulUtils.setServerConfig(mediaServerItem, param);
+
+        if (responseJSON != null && responseJSON.getInteger("code") == 0) {
+            logger.info("[ {} ]-[ {}:{} ]设置zlm成功",
+                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
+        }else {
+            logger.info("[ {} ]-[ {}:{} ]设置zlm失败" + responseJSON.getString("msg"),
+                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
+        }
+    }
+}

+ 32 - 21
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java

@@ -6,6 +6,9 @@ import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 import com.genersoft.iot.vmp.service.IMediaService;
@@ -21,30 +24,53 @@ public class MediaServiceImpl implements IMediaService {
     @Autowired
     private IVideoManagerStorager storager;
 
+    @Autowired
+    private IMediaServerService mediaServerService;
+
     @Autowired
     private ZLMRESTfulUtils zlmresTfulUtils;
 
 
 
     @Override
-    public StreamInfo getStreamInfoByAppAndStream(String app, String stream, JSONArray tracks) {
-        return getStreamInfoByAppAndStream(app, stream, tracks, null);
+    public StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaInfo, String app, String stream, JSONArray tracks) {
+        return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null);
+    }
+
+    @Override
+    public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, String addr) {
+        StreamInfo streamInfo = null;
+        IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+        if (mediaInfo == null) {
+            return streamInfo;
+        }
+        JSONObject mediaList = zlmresTfulUtils.getMediaList(mediaInfo, app, stream);
+        if (mediaList != null) {
+            if (mediaList.getInteger("code") == 0) {
+                JSONArray data = mediaList.getJSONArray("data");
+                if (data == null) return null;
+                JSONObject mediaJSON = JSON.parseObject(JSON.toJSONString(data.get(0)), JSONObject.class);
+                JSONArray tracks = mediaJSON.getJSONArray("tracks");
+                streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks);
+            }
+        }
+        return streamInfo;
     }
 
     @Override
-    public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream) {
-        return getStreamInfoByAppAndStreamWithCheck(app, stream, null);
+    public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId) {
+        return getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, null);
     }
 
     @Override
-    public StreamInfo getStreamInfoByAppAndStream(String app, String stream, JSONArray tracks, String addr) {
-        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
+    public StreamInfo getStreamInfoByAppAndStream(IMediaServerItem mediaInfo, String app, String stream, JSONArray tracks, String addr) {
         StreamInfo streamInfoResult = new StreamInfo();
         streamInfoResult.setStreamId(stream);
         streamInfoResult.setApp(app);
         if (addr == null) {
             addr = mediaInfo.getStreamIp();
         }
+        streamInfoResult.setMediaServerId(mediaInfo.getId());
         streamInfoResult.setRtmp(String.format("rtmp://%s:%s/%s/%s", addr, mediaInfo.getRtmpPort(), app,  stream));
         streamInfoResult.setRtsp(String.format("rtsp://%s:%s/%s/%s", addr, mediaInfo.getRtspPort(), app,  stream));
         streamInfoResult.setFlv(String.format("http://%s:%s/%s/%s.flv", addr, mediaInfo.getHttpPort(), app,  stream));
@@ -60,19 +86,4 @@ public class MediaServiceImpl implements IMediaService {
         return streamInfoResult;
     }
 
-    @Override
-    public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String addr) {
-        StreamInfo streamInfo = null;
-        JSONObject mediaList = zlmresTfulUtils.getMediaList(app, stream);
-        if (mediaList != null) {
-            if (mediaList.getInteger("code") == 0) {
-                JSONArray data = mediaList.getJSONArray("data");
-                if (data == null) return null;
-                JSONObject mediaJSON = JSON.parseObject(JSON.toJSONString(data.get(0)), JSONObject.class);
-                JSONArray tracks = mediaJSON.getJSONArray("tracks");
-                streamInfo = getStreamInfoByAppAndStream(app, stream, tracks, addr);
-            }
-        }
-        return streamInfo;
-    }
 }

+ 56 - 20
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java

@@ -14,6 +14,9 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@@ -59,6 +62,9 @@ public class PlayServiceImpl implements IPlayService {
     @Autowired
     private IMediaService mediaService;
 
+    @Autowired
+    private IMediaServerService mediaServerService;
+
     @Autowired
     private VideoStreamSessionManager streamSession;
 
@@ -67,8 +73,18 @@ public class PlayServiceImpl implements IPlayService {
 
 
     @Override
-    public PlayResult play(String deviceId, String channelId, ZLMHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent) {
+    public PlayResult play(IMediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent) {
         PlayResult playResult = new PlayResult();
+        if (mediaServerItem == null) {
+            RequestMessage msg = new RequestMessage();
+            msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + playResult.getUuid());
+            WVPResult wvpResult = new WVPResult();
+            wvpResult.setCode(-1);
+            wvpResult.setMsg("未找到可用的zlm");
+            msg.setData(wvpResult);
+            resultHolder.invokeResult(msg);
+            return playResult;
+        }
         Device device = storager.queryVideoDevice(deviceId);
         StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
         playResult.setDevice(device);
@@ -82,7 +98,7 @@ public class PlayServiceImpl implements IPlayService {
         result.onTimeout(()->{
             logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId));
             // 释放rtpserver
-            cmder.closeRTPServer(playResult.getDevice(), channelId);
+            mediaServerService.closeRTPServer(playResult.getDevice(), channelId);
             RequestMessage msg = new RequestMessage();
             msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + playResult.getUuid());
             WVPResult wvpResult = new WVPResult();
@@ -115,9 +131,10 @@ public class PlayServiceImpl implements IPlayService {
                     WVPResult wvpResult = (WVPResult)responseEntity.getBody();
                     if (wvpResult.getCode() == 0) {
                         StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
+                        IMediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId());
                         String streamUrl = streamInfoForSuccess.getFmp4();
                         // 请求截图
-                        zlmresTfulUtils.getSnap(streamUrl, 15, 1, path, fileName);
+                        zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName);
                     }
                 }
             } catch (FileNotFoundException e) {
@@ -126,17 +143,17 @@ public class PlayServiceImpl implements IPlayService {
         });
         if (streamInfo == null) {
             // 发送点播消息
-            cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
+            cmder.playStreamCmd(mediaServerItem, device, channelId, (IMediaServerItem mediaServerItemInUse, JSONObject response) -> {
                 logger.info("收到订阅消息: " + response.toJSONString());
-                onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
+                onPublishHandlerForPlay(mediaServerItemInUse, response, deviceId, channelId, uuid.toString());
                 if (hookEvent != null) {
-                    hookEvent.response(response);
+                    hookEvent.response(mediaServerItem, response);
                 }
-            }, event -> {
+            }, (event) -> {
                 RequestMessage msg = new RequestMessage();
                 msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                 Response response = event.getResponse();
-                cmder.closeRTPServer(playResult.getDevice(), channelId);
+                mediaServerService.closeRTPServer(playResult.getDevice(), channelId);
                 WVPResult wvpResult = new WVPResult();
                 wvpResult.setCode(-1);
                 wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
@@ -158,7 +175,10 @@ public class PlayServiceImpl implements IPlayService {
                 resultHolder.invokeResult(msg);
                 return playResult;
             }
-            JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
+            String mediaServerId = streamInfo.getMediaServerId();
+            IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+
+            JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
             if (rtpInfo != null && rtpInfo.getBoolean("exist")) {
                 RequestMessage msg = new RequestMessage();
                 msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
@@ -171,16 +191,16 @@ public class PlayServiceImpl implements IPlayService {
 
                 resultHolder.invokeResult(msg);
                 if (hookEvent != null) {
-                    hookEvent.response(JSONObject.parseObject(JSON.toJSONString(streamInfo)));
+                    hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo)));
                 }
             } else {
                 redisCatchStorage.stopPlay(streamInfo);
                 storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
-                cmder.playStreamCmd(device, channelId, (JSONObject response) -> {
+                cmder.playStreamCmd(mediaServerItem, device, channelId, (IMediaServerItem mediaServerItemInuse, JSONObject response) -> {
                     logger.info("收到订阅消息: " + response.toJSONString());
-                    onPublishHandlerForPlay(response, deviceId, channelId, uuid.toString());
-                }, event -> {
-                    cmder.closeRTPServer(playResult.getDevice(), channelId);
+                    onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, uuid.toString());
+                }, (event) -> {
+                    mediaServerService.closeRTPServer(playResult.getDevice(), channelId);
                     RequestMessage msg = new RequestMessage();
                     msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
                     Response response = event.getResponse();
@@ -198,10 +218,10 @@ public class PlayServiceImpl implements IPlayService {
     }
 
     @Override
-    public void onPublishHandlerForPlay(JSONObject resonse, String deviceId, String channelId, String uuid) {
+    public void onPublishHandlerForPlay(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
         RequestMessage msg = new RequestMessage();
         msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
-        StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid);
+        StreamInfo streamInfo = onPublishHandler(mediaServerItem, resonse, deviceId, channelId, uuid);
         if (streamInfo != null) {
             DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
             if (deviceChannel != null) {
@@ -234,10 +254,26 @@ public class PlayServiceImpl implements IPlayService {
     }
 
     @Override
-    public void onPublishHandlerForPlayBack(JSONObject resonse, String deviceId, String channelId, String uuid) {
+    public IMediaServerItem getNewMediaServerItem(Device device) {
+        if (device == null) return null;
+        String mediaServerId = device.getMediaServerId();
+        IMediaServerItem mediaServerItem = null;
+        if (mediaServerId == null) {
+            mediaServerItem = mediaServerService.getMediaServerForMinimumLoad();
+        }else {
+            mediaServerItem = mediaServerService.getOne(mediaServerId);
+        }
+        if (mediaServerItem == null) {
+            logger.warn("点播时未找到可使用的ZLM...");
+        }
+        return mediaServerItem;
+    }
+
+    @Override
+    public void onPublishHandlerForPlayBack(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
         RequestMessage msg = new RequestMessage();
         msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
-        StreamInfo streamInfo = onPublishHandler(resonse, deviceId, channelId, uuid);
+        StreamInfo streamInfo = onPublishHandler(mediaServerItem, resonse, deviceId, channelId, uuid);
         if (streamInfo != null) {
             redisCatchStorage.startPlayback(streamInfo);
             msg.setData(JSON.toJSONString(streamInfo));
@@ -249,10 +285,10 @@ public class PlayServiceImpl implements IPlayService {
         }
     }
 
-    public StreamInfo onPublishHandler(JSONObject resonse, String deviceId, String channelId, String uuid) {
+    public StreamInfo onPublishHandler(IMediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
         String streamId = resonse.getString("stream");
         JSONArray tracks = resonse.getJSONArray("tracks");
-        StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream("rtp", streamId, tracks);
+        StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem,"rtp", streamId, tracks);
         streamInfo.setDeviceID(deviceId);
         streamInfo.setChannelId(channelId);
         return streamInfo;

+ 48 - 18
src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java

@@ -2,10 +2,12 @@ package com.genersoft.iot.vmp.service.impl;
 
 import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
-import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
 import com.genersoft.iot.vmp.service.IGbStreamService;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
 import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
@@ -13,6 +15,8 @@ import com.genersoft.iot.vmp.storager.dao.PlatformGbStreamMapper;
 import com.genersoft.iot.vmp.storager.dao.StreamProxyMapper;
 import com.genersoft.iot.vmp.service.IStreamProxyService;
 import com.github.pagehelper.PageInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -25,6 +29,8 @@ import java.util.List;
 @Service
 public class StreamProxyServiceImpl implements IStreamProxyService {
 
+    private final static Logger logger = LoggerFactory.getLogger(StreamProxyServiceImpl.class);
+
     @Autowired
     private IVideoManagerStorager videoManagerStorager;
 
@@ -32,7 +38,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
     private IRedisCatchStorage redisCatchStorage;
 
     @Autowired
-    private ZLMRESTfulUtils zlmresTfulUtils;
+    private ZLMRESTfulUtils zlmresTfulUtils;;
 
     @Autowired
     private StreamProxyMapper streamProxyMapper;
@@ -46,15 +52,28 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
     @Autowired
     private IGbStreamService gbStreamService;
 
+    @Autowired
+    private IMediaServerService mediaServerService;
+
 
     @Override
     public String save(StreamProxyItem param) {
-        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
+        IMediaServerItem mediaInfo;
+        if ("auto".equals(param.getMediaServerId())){
+            mediaInfo = mediaServerService.getMediaServerForMinimumLoad();
+        }else {
+            mediaInfo = mediaServerService.getOne(param.getMediaServerId());
+        }
+        if (mediaInfo == null) {
+            logger.warn("保存代理未找到在线的ZLM...");
+            return "保存失败";
+        }
         String dstUrl = String.format("rtmp://%s:%s/%s/%s", "127.0.0.1", mediaInfo.getRtmpPort(), param.getApp(),
                 param.getStream() );
         param.setDst_url(dstUrl);
         StringBuffer result = new StringBuffer();
         boolean streamLive = false;
+        param.setMediaServerId(mediaInfo.getId());
         // 更新
         if (videoManagerStorager.queryStreamProxy(param.getApp(), param.getStream()) != null) {
             if (videoManagerStorager.updateStreamProxy(param)) {
@@ -81,6 +100,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
                         videoManagerStorager.updateStreamProxy(param);
                     }
                 }
+            }else {
+                result.append("保存失败");
             }
 
         }
@@ -99,11 +120,18 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
     @Override
     public JSONObject addStreamProxyToZlm(StreamProxyItem param) {
         JSONObject result = null;
+        IMediaServerItem mediaServerItem = null;
+        if (param.getMediaServerId() == null) {
+            logger.warn("添加代理时MediaServerId 为null");
+            return null;
+        }else {
+            mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
+        }
         if ("default".equals(param.getType())){
-            result = zlmresTfulUtils.addStreamProxy(param.getApp(), param.getStream(), param.getUrl(),
+            result = zlmresTfulUtils.addStreamProxy(mediaServerItem, param.getApp(), param.getStream(), param.getUrl(),
                     param.isEnable_hls(), param.isEnable_mp4(), param.getRtp_type());
         }else if ("ffmpeg".equals(param.getType())) {
-            result = zlmresTfulUtils.addFFmpegSource(param.getSrc_url(), param.getDst_url(),
+            result = zlmresTfulUtils.addFFmpegSource(mediaServerItem, param.getSrc_url(), param.getDst_url(),
                     param.getTimeout_ms() + "", param.isEnable_hls(), param.isEnable_mp4(),
                     param.getFfmpeg_cmd_key());
         }
@@ -112,8 +140,9 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
 
     @Override
     public JSONObject removeStreamProxyFromZlm(StreamProxyItem param) {
-        JSONObject result = zlmresTfulUtils.closeStreams(param.getApp(), param.getStream());
-
+        if (param ==null) return null;
+        IMediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
+        JSONObject result = zlmresTfulUtils.closeStreams(mediaServerItem, param.getApp(), param.getStream());
         return result;
     }
 
@@ -124,17 +153,18 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
 
     @Override
     public void del(String app, String stream) {
-        StreamProxyItem streamProxyItem = new StreamProxyItem();
-        streamProxyItem.setApp(app);
-        streamProxyItem.setStream(stream);
-        JSONObject jsonObject = removeStreamProxyFromZlm(streamProxyItem);
-        if (jsonObject.getInteger("code") == 0) {
+        StreamProxyItem streamProxyItem = videoManagerStorager.queryStreamProxy(app, stream);
+        if (streamProxyItem != null) {
             videoManagerStorager.deleteStreamProxy(app, stream);
-            // 如果关联了国标那么移除关联
-            gbStreamMapper.del(app, stream);
-            platformGbStreamMapper.delByAppAndStream(app, stream);
-            // TODO 如果关联的推流, 那么状态设置为离线
+            JSONObject jsonObject = removeStreamProxyFromZlm(streamProxyItem);
+            if (jsonObject != null && jsonObject.getInteger("code") == 0) {
+                // 如果关联了国标那么移除关联
+                gbStreamMapper.del(app, stream);
+                platformGbStreamMapper.delByAppAndStream(app, stream);
+                // TODO 如果关联的推流, 那么状态设置为离线
+            }
         }
+
     }
 
     @Override
@@ -168,9 +198,9 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
     }
 
     @Override
-    public JSONObject getFFmpegCMDs() {
+    public JSONObject getFFmpegCMDs(IMediaServerItem mediaServerItem) {
         JSONObject result = new JSONObject();
-        JSONObject mediaServerConfigResuly = zlmresTfulUtils.getMediaServerConfig();
+        JSONObject mediaServerConfigResuly = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
         if (mediaServerConfigResuly != null && mediaServerConfigResuly.getInteger("code") == 0
                 && mediaServerConfigResuly.getJSONArray("data").size() > 0){
             JSONObject mediaServerConfig = mediaServerConfigResuly.getJSONArray("data").getJSONObject(0);

+ 14 - 2
src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java

@@ -5,9 +5,13 @@ import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.TypeReference;
 import com.genersoft.iot.vmp.gb28181.bean.GbStream;
 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.IStreamPushService;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
 import com.genersoft.iot.vmp.storager.dao.StreamPushMapper;
 import com.github.pagehelper.PageHelper;
@@ -32,8 +36,14 @@ public class StreamPushServiceImpl implements IStreamPushService {
     @Autowired
     private ZLMRESTfulUtils zlmresTfulUtils;
 
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
+    @Autowired
+    private IMediaServerService mediaServerService;
+
     @Override
-    public List<StreamPushItem> handleJSON(String jsonData) {
+    public List<StreamPushItem> handleJSON(String jsonData, IMediaServerItem mediaServerItem) {
         if (jsonData == null) return null;
 
         Map<String, StreamPushItem> result = new HashMap<>();
@@ -50,6 +60,7 @@ public class StreamPushServiceImpl implements IStreamPushService {
             if (streamPushItem == null) {
                 streamPushItem = new StreamPushItem();
                 streamPushItem.setApp(item.getApp());
+                streamPushItem.setMediaServerId(mediaServerItem.getId());
                 streamPushItem.setStream(item.getStream());
                 streamPushItem.setAliveSecond(item.getAliveSecond());
                 streamPushItem.setCreateStamp(item.getCreateStamp());
@@ -87,7 +98,8 @@ public class StreamPushServiceImpl implements IStreamPushService {
     @Override
     public boolean removeFromGB(GbStream stream) {
         int del = gbStreamMapper.del(stream.getApp(), stream.getStream());
-        JSONObject mediaList = zlmresTfulUtils.getMediaList(stream.getApp(), stream.getStream());
+        IMediaServerItem mediaInfo = mediaServerService.getOne(stream.getMediaServerId());
+        JSONObject mediaList = zlmresTfulUtils.getMediaList(mediaInfo, stream.getApp(), stream.getStream());
         if (mediaList == null) {
             streamPushMapper.del(stream.getApp(), stream.getStream());
         }

+ 9 - 14
src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java

@@ -2,10 +2,11 @@ package com.genersoft.iot.vmp.storager;
 
 import com.alibaba.fastjson.JSONObject;
 import com.genersoft.iot.vmp.common.StreamInfo;
-import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 
 import java.util.List;
 import java.util.Map;
@@ -40,19 +41,6 @@ public interface IRedisCatchStorage {
 
     StreamInfo queryPlayByDevice(String deviceId, String channelId);
 
-    /**
-     * 更新流媒体信息
-     * @param ZLMServerConfig
-     * @return
-     */
-    boolean updateMediaInfo(ZLMServerConfig ZLMServerConfig);
-
-    /**
-     * 获取流媒体信息
-     * @return
-     */
-    ZLMServerConfig getMediaInfo();
-
     Map<String, StreamInfo> queryPlayByDeviceId(String deviceId);
 
     boolean startPlayback(StreamInfo stream);
@@ -114,6 +102,13 @@ public interface IRedisCatchStorage {
      */
     void clearCatchByDeviceId(String deviceId);
 
+    /**
+     * 获取mediaServer节点
+     * @param mediaServerId
+     * @return
+     */
+//    MediaServerItem getMediaInfo(String mediaServerId);
+
     /**
      * 设置所有设备离线
      */

+ 10 - 0
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java

@@ -3,6 +3,8 @@ package com.genersoft.iot.vmp.storager;
 import java.util.List;
 
 import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
 import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce;
@@ -365,4 +367,12 @@ public interface IVideoManagerStorager {
 	 * @param online
 	 */
 	void updateParentPlatformStatus(String platformGbID, boolean online);
+
+	/**
+	 * 更新媒体节点
+	 * @param mediaServerItem
+	 */
+	void updateMediaServer(MediaServerItem mediaServerItem);
+
+	List<StreamProxyItem> getStreamProxyListForEnableInMediaServer(String id, boolean b);
 }

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

@@ -12,9 +12,10 @@ import java.util.List;
 public interface GbStreamMapper {
 
     @Insert("INSERT INTO gb_stream (app, stream, gbId, name, " +
-            "longitude, latitude, streamType, status) VALUES" +
+            "longitude, latitude, streamType, mediaServerId, status) VALUES" +
             "('${app}', '${stream}', '${gbId}', '${name}', " +
-            "'${longitude}', '${latitude}', '${streamType}', ${status})")
+            "'${longitude}', '${latitude}', '${streamType}', " +
+            "'${mediaServerId}', ${status})")
     int add(GbStream gbStream);
 
     @Update("UPDATE gb_stream " +
@@ -25,6 +26,7 @@ public interface GbStreamMapper {
             "streamType=#{streamType}," +
             "longitude=#{longitude}, " +
             "latitude=#{latitude}," +
+            "mediaServerId=#{mediaServerId}," +
             "status=${status} " +
             "WHERE app=#{app} AND stream=#{stream} AND gbId=#{gbId}")
     int update(GbStream gbStream);
@@ -52,4 +54,7 @@ public interface GbStreamMapper {
             "SET status=${status} " +
             "WHERE app=#{app} AND stream=#{stream}")
     void setStatus(String app, String stream, boolean status);
+
+    @Select("SELECT gs.*, pgs.platformId FROM gb_stream gs LEFT JOIN  platform_gb_stream pgs ON gs.app = pgs.app AND gs.stream = pgs.stream WHERE mediaServerId=#{mediaServerId} ")
+    List<GbStream> selectAllByMediaServerId(String mediaServerId);
 }

+ 99 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java

@@ -0,0 +1,99 @@
+package com.genersoft.iot.vmp.storager.dao;
+
+import com.genersoft.iot.vmp.conf.MediaConfig;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+
+@Mapper
+@Repository
+public interface MediaServerMapper {
+
+    @Insert("INSERT INTO media_server (" +
+            "id, " +
+            "ip, " +
+            "hookIp, " +
+            "sdpIp, " +
+            "streamIp, " +
+            "httpPort, " +
+            "httpSSlPort, " +
+            "rtmpPort, " +
+            "rtmpSSlPort, " +
+            "rtpProxyPort, " +
+            "rtspPort, " +
+            "rtspSSLPort, " +
+            "autoConfig, " +
+            "secret, " +
+            "streamNoneReaderDelayMS, " +
+            "rtpEnable, " +
+            "rtpPortRange, " +
+            "recordAssistPort, " +
+            "createTime, " +
+            "updateTime" +
+            ") VALUES " +
+            "(" +
+            "'${id}', " +
+            "'${ip}', " +
+            "'${hookIp}', " +
+            "'${sdpIp}', " +
+            "'${streamIp}', " +
+            "${httpPort}, " +
+            "${httpSSlPort}, " +
+            "${rtmpPort}, " +
+            "${rtmpSSlPort}, " +
+            "${rtpProxyPort}, " +
+            "${rtspPort}, " +
+            "${rtspSSLPort}, " +
+            "${autoConfig}, " +
+            "'${secret}', " +
+            "${streamNoneReaderDelayMS}, " +
+            "${rtpEnable}, " +
+            "'${rtpPortRange}', " +
+            "${recordAssistPort}, " +
+            "'${createTime}', " +
+            "'${updateTime}')")
+    int add(IMediaServerItem mediaServerItem);
+
+    @Update(value = {" <script>" +
+            "UPDATE media_server " +
+            "SET updateTime='${updateTime}'" +
+            "<if test=\"ip != null\">, ip='${ip}'</if>" +
+            "<if test=\"hookIp != null\">, hookIp='${hookIp}'</if>" +
+            "<if test=\"sdpIp != null\">, sdpIp='${sdpIp}'</if>" +
+            "<if test=\"streamIp != null\">, streamIp='${streamIp}'</if>" +
+            "<if test=\"httpPort != null\">, httpPort=${httpPort}</if>" +
+            "<if test=\"httpSSlPort != null\">, httpSSlPort=${httpSSlPort}</if>" +
+            "<if test=\"rtmpPort != null\">, rtmpPort=${rtmpPort}</if>" +
+            "<if test=\"rtmpSSlPort != null\">, rtmpSSlPort=${rtmpSSlPort}</if>" +
+            "<if test=\"rtpProxyPort != null\">, rtpProxyPort=${rtpProxyPort}</if>" +
+            "<if test=\"rtspPort != null\">, rtspPort=${rtspPort}</if>" +
+            "<if test=\"rtspSSLPort != null\">, rtspSSLPort=${rtspSSLPort}</if>" +
+            "<if test=\"autoConfig != null\">, autoConfig=${autoConfig}</if>" +
+            "<if test=\"streamNoneReaderDelayMS != null\">, streamNoneReaderDelayMS=${streamNoneReaderDelayMS}</if>" +
+            "<if test=\"rtpEnable != null\">, rtpEnable=${rtpEnable}</if>" +
+            "<if test=\"rtpPortRange != null\">, rtpPortRange='${rtpPortRange}'</if>" +
+            "<if test=\"secret != null\">, secret='${secret}'</if>" +
+            "<if test=\"recordAssistPort != null\">, recordAssistPort=${recordAssistPort}</if>" +
+            "WHERE id='${id}'"+
+            " </script>"})
+    int update(IMediaServerItem mediaServerItem);
+
+    @Select("SELECT * FROM media_server WHERE id='${id}'")
+    MediaServerItem queryOne(String id);
+
+    @Select("SELECT * FROM media_server")
+    List<MediaServerItem> queryAll();
+
+    @Select("DELETE FROM media_server WHERE id='${id}'")
+    int delOne(String secret);
+
+    @Select("SELECT * FROM media_server WHERE ip='${host}' and httpPort=${port}")
+    MediaServerItem queryOneByHostAndPort(String host, int port);
+}

+ 13 - 7
src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java

@@ -10,10 +10,10 @@ import java.util.List;
 @Repository
 public interface StreamProxyMapper {
 
-    @Insert("INSERT INTO stream_proxy (type, app, stream, url, src_url, dst_url, " +
-            "timeout_ms, ffmpeg_cmd_key, rtp_type, enable_hls, enable_mp4, enable) VALUES" +
-            "('${type}','${app}', '${stream}', '${url}', '${src_url}', '${dst_url}', " +
-            "'${timeout_ms}', '${ffmpeg_cmd_key}', '${rtp_type}', ${enable_hls}, ${enable_mp4}, ${enable} )")
+    @Insert("INSERT INTO stream_proxy (type, app, stream,mediaServerId, url, src_url, dst_url, " +
+            "timeout_ms, ffmpeg_cmd_key, rtp_type, enable_hls, enable_mp4, enable, createTime) VALUES" +
+            "('${type}','${app}', '${stream}', '${mediaServerId}','${url}', '${src_url}', '${dst_url}', " +
+            "'${timeout_ms}', '${ffmpeg_cmd_key}', '${rtp_type}', ${enable_hls}, ${enable_mp4}, ${enable}, '${createTime}' )")
     int add(StreamProxyItem streamProxyDto);
 
     @Update("UPDATE stream_proxy " +
@@ -21,6 +21,7 @@ public interface StreamProxyMapper {
             "app=#{app}," +
             "stream=#{stream}," +
             "url=#{url}, " +
+            "mediaServerId=#{mediaServerId}, " +
             "src_url=#{src_url}," +
             "dst_url=#{dst_url}, " +
             "timeout_ms=#{timeout_ms}, " +
@@ -35,12 +36,17 @@ public interface StreamProxyMapper {
     @Delete("DELETE FROM stream_proxy WHERE app=#{app} AND stream=#{stream}")
     int del(String app, String stream);
 
-    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream")
+    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream order by st.createTime desc")
     List<StreamProxyItem> selectAll();
 
-    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream WHERE st.enable=${enable}")
+    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream WHERE st.enable=${enable} order by st.createTime desc")
     List<StreamProxyItem> selectForEnable(boolean enable);
 
-    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream WHERE st.app=#{app} AND st.stream=#{stream}")
+    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream WHERE st.app=#{app} AND st.stream=#{stream} order by st.createTime desc")
     StreamProxyItem selectOne(String app, String stream);
+
+    @Select("SELECT st.*, pgs.gbId, pgs.name, pgs.longitude, pgs.latitude FROM stream_proxy st " +
+            "LEFT JOIN gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream " +
+            "WHERE st.enable=${enable} and st.mediaServerId = '${id}' order by st.createTime desc")
+    List<StreamProxyItem> selectForEnableInMediaServer(String id, boolean enable);
 }

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

@@ -11,14 +11,15 @@ import java.util.List;
 public interface StreamPushMapper {
 
     @Insert("INSERT INTO stream_push (app, stream, totalReaderCount, originType, originTypeStr, " +
-            "createStamp, aliveSecond) VALUES" +
+            "createStamp, aliveSecond, mediaServerId) VALUES" +
             "('${app}', '${stream}', '${totalReaderCount}', '${originType}', '${originTypeStr}', " +
-            "'${createStamp}', '${aliveSecond}' )")
+            "'${createStamp}', '${aliveSecond}', '${mediaServerId}' )")
     int add(StreamPushItem streamPushItem);
 
     @Update("UPDATE stream_push " +
             "SET app=#{app}," +
             "stream=#{stream}," +
+            "mediaServerId=#{mediaServerId}," +
             "totalReaderCount=#{totalReaderCount}, " +
             "originType=#{originType}," +
             "originTypeStr=#{originTypeStr}, " +
@@ -41,10 +42,10 @@ public interface StreamPushMapper {
 
     @Insert("<script>"  +
             "INSERT INTO stream_push (app, stream, totalReaderCount, originType, originTypeStr, " +
-            "createStamp, aliveSecond) " +
+            "createStamp, aliveSecond, mediaServerId) " +
             "VALUES <foreach collection='streamPushItems' item='item' index='index' >" +
             "( '${item.app}', '${item.stream}', '${item.totalReaderCount}', '${item.originType}', " +
-            "'${item.originTypeStr}','${item.createStamp}', '${item.aliveSecond}' )" +
+            "'${item.originTypeStr}','${item.createStamp}', '${item.aliveSecond}', '${item.mediaServerId}' )" +
             " </foreach>" +
             "</script>")
     void addAll(List<StreamPushItem> streamPushItems);

+ 4 - 21
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@@ -5,6 +5,8 @@ import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.common.VideoManagerConstants;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
 import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
@@ -87,26 +89,6 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
         return (StreamInfo)redis.get(playLeys.get(0).toString());
     }
 
-    /**
-     * 更新流媒体信息
-     * @param ZLMServerConfig
-     * @return
-     */
-    @Override
-    public boolean updateMediaInfo(ZLMServerConfig ZLMServerConfig) {
-        ZLMServerConfig.setUpdateTime(format.format(new Date(System.currentTimeMillis())));
-        return redis.set(VideoManagerConstants.MEDIA_SERVER_PREFIX, ZLMServerConfig);
-    }
-
-    /**
-     * 获取流媒体信息
-     * @return
-     */
-    @Override
-    public ZLMServerConfig getMediaInfo() {
-        return (ZLMServerConfig)redis.get(VideoManagerConstants.MEDIA_SERVER_PREFIX);
-    }
-
     @Override
     public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) {
         Map<String, StreamInfo> streamInfos = new HashMap<>();
@@ -297,7 +279,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
 
     @Override
     public void outlineForAll() {
-        List<Object> onlineDevices = redis.scan(String.format("%S*", VideoManagerConstants.KEEPLIVEKEY_PREFIX));
+        List<Object> onlineDevices = redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX + "*" );
         for (int i = 0; i < onlineDevices.size(); i++) {
             String key = (String) onlineDevices.get(i);
             redis.del(key);
@@ -308,4 +290,5 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
     public void updateWVPInfo(JSONObject jsonObject) {
 
     }
+
 }

+ 24 - 0
src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java

@@ -5,6 +5,7 @@ import java.util.*;
 
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -70,6 +71,9 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
 	@Autowired
     private VideoStreamSessionManager streamSession;
 
+	@Autowired
+    private MediaServerMapper mediaServerMapper;
+
 	private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
 
@@ -459,6 +463,8 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
 		boolean result = false;
 		streamProxyItem.setStreamType("proxy");
 		streamProxyItem.setStatus(true);
+		String now = this.format.format(new Date(System.currentTimeMillis()));
+		streamProxyItem.setCreateTime(now);
 		try {
 			if (gbStreamMapper.add(streamProxyItem)<0 || streamProxyMapper.add(streamProxyItem) < 0) {
 				//事务回滚
@@ -467,6 +473,7 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
 			result = true;
 			dataSourceTransactionManager.commit(transactionStatus);     //手动提交
 		}catch (Exception e) {
+			logger.error("向数据库添加流代理失败:", e);
 			dataSourceTransactionManager.rollback(transactionStatus);
 		}
 		return result;
@@ -599,4 +606,21 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
 	public void updateParentPlatformStatus(String platformGbID, boolean online) {
 		platformMapper.updateParentPlatformStatus(platformGbID, online);
 	}
+
+	@Override
+	public void updateMediaServer(MediaServerItem mediaServerItem) {
+		String now = this.format.format(new Date(System.currentTimeMillis()));
+		mediaServerItem.setUpdateTime(now);
+		if (mediaServerMapper.queryOne(mediaServerItem.getId()) != null) {
+			mediaServerMapper.update(mediaServerItem);
+		}else {
+			mediaServerItem.setCreateTime(now);
+			mediaServerMapper.add(mediaServerItem);
+		}
+	}
+
+	@Override
+	public List<StreamProxyItem> getStreamProxyListForEnableInMediaServer(String id, boolean enable) {
+		return streamProxyMapper.selectForEnableInMediaServer(id, enable);
+	}
 }

+ 7 - 6
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceConfig.java

@@ -25,6 +25,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.async.DeferredResult;
 
@@ -78,7 +79,7 @@ public class DeviceConfig {
 		cmder.deviceBasicConfigCmd(device, channelId, name, expiration, heartBeatInterval, heartBeatCount, event -> {
 			Response response = event.getResponse();
 			RequestMessage msg = new RequestMessage();
-			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
+			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
 			msg.setData(String.format("设备配置操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
 			resultHolder.invokeResult(msg);
 		});
@@ -87,7 +88,7 @@ public class DeviceConfig {
 			logger.warn(String.format("设备配置操作超时, 设备未返回应答指令"));
 			// 释放rtpserver
 			RequestMessage msg = new RequestMessage();
-			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
+			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
 			JSONObject json = new JSONObject();
 			json.put("DeviceID", deviceId);
 			json.put("Status", "Timeout");
@@ -95,7 +96,7 @@ public class DeviceConfig {
 			msg.setData(json); //("看守位控制操作超时, 设备未返回应答指令");
 			resultHolder.invokeResult(msg);
 		});
-		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
+		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONFIG + (StringUtils.isEmpty(channelId) ? deviceId : channelId), result);
 		return result;
 	}
 
@@ -123,7 +124,7 @@ public class DeviceConfig {
 		cmder.deviceConfigQuery(device, channelId, configType, event -> {
 			Response response = event.getResponse();
 			RequestMessage msg = new RequestMessage();
-			msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
+			msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
 			msg.setData(String.format("获取设备配置失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
 			resultHolder.invokeResult(msg);
 		});
@@ -132,11 +133,11 @@ public class DeviceConfig {
 			logger.warn(String.format("获取设备配置超时"));
 			// 释放rtpserver
 			RequestMessage msg = new RequestMessage();
-			msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
+			msg.setId(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
 			msg.setData("Timeout. Device did not response to this command.");
 			resultHolder.invokeResult(msg);
 		});
-		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
+		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_CONFIGDOWNLOAD + (StringUtils.isEmpty(channelId) ? deviceId : channelId), result);
 		return result;
 	}
 

+ 7 - 6
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java

@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.async.DeferredResult;
 
@@ -97,7 +98,7 @@ public class DeviceControl {
         cmder.recordCmd(device, channelId, recordCmdStr, event -> {
             Response response = event.getResponse();
             RequestMessage msg = new RequestMessage();
-			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
+			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
 			msg.setData(String.format("开始/停止录像操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
 			resultHolder.invokeResult(msg);
 		});
@@ -106,11 +107,11 @@ public class DeviceControl {
 			logger.warn(String.format("开始/停止录像操作超时, 设备未返回应答指令"));
 			// 释放rtpserver
 			RequestMessage msg = new RequestMessage();
-			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
+			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
 			msg.setData("Timeout. Device did not response to this command.");
 			resultHolder.invokeResult(msg);
 		});
-		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
+		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId), result);
 		return result;
 	}
 
@@ -254,7 +255,7 @@ public class DeviceControl {
 		cmder.homePositionCmd(device, channelId, enabled, resetTime, presetIndex, event -> {
 			Response response = event.getResponse();
 			RequestMessage msg = new RequestMessage();
-			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
+			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
 			msg.setData(String.format("看守位控制操作失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
 			resultHolder.invokeResult(msg);
 		});
@@ -263,7 +264,7 @@ public class DeviceControl {
 			logger.warn(String.format("看守位控制操作超时, 设备未返回应答指令"));
 			// 释放rtpserver
 			RequestMessage msg = new RequestMessage();
-			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
+			msg.setId(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
 			JSONObject json = new JSONObject();
 			json.put("DeviceID", deviceId);
 			json.put("Status", "Timeout");
@@ -271,7 +272,7 @@ public class DeviceControl {
 			msg.setData(json); //("看守位控制操作超时, 设备未返回应答指令");
 			resultHolder.invokeResult(msg);
 		});
-		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
+		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_DEVICECONTROL + (StringUtils.isEmpty(channelId) ? deviceId : channelId), result);
 		return result;
 	}
 }

+ 4 - 2
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java

@@ -43,11 +43,13 @@ public class MediaController {
     @ApiImplicitParams({
             @ApiImplicitParam(name = "app", value = "应用名", dataTypeClass = String.class),
             @ApiImplicitParam(name = "stream", value = "流id", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "mediaServerId", value = "媒体服务器id", dataTypeClass = String.class),
     })
     @GetMapping(value = "/stream_info_by_app_and_stream")
     @ResponseBody
-    public StreamInfo getStreamInfoByAppAndStream(String app, String stream){
-        return mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream);
+    public StreamInfo getStreamInfoByAppAndStream(@RequestParam String app, @RequestParam String stream, @RequestParam String mediaServerId){
+
+        return mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream,mediaServerId);
     }
 
 

+ 45 - 23
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java

@@ -8,6 +8,9 @@ import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
@@ -72,6 +75,9 @@ public class PlayController {
 	@Autowired
 	private IMediaService mediaService;
 
+	@Autowired
+	private IMediaServerService mediaServerService;
+
 	@ApiOperation("开始点播")
 	@ApiImplicitParams({
 			@ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
@@ -81,8 +87,10 @@ public class PlayController {
 	public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId,
 													   @PathVariable String channelId) {
 
-		PlayResult playResult = playService.play(deviceId, channelId, null, null);
-
+		// 获取可用的zlm
+		Device device = storager.queryVideoDevice(deviceId);
+		IMediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
+		PlayResult playResult = playService.play(newMediaServerItem, deviceId, channelId, null, null);
 
 		return playResult.getResult();
 	}
@@ -102,8 +110,8 @@ public class PlayController {
 
 		// 录像查询以channelId作为deviceId查询
 		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_STOP + uuid, result);
-
-		cmder.streamByeCmd(deviceId, channelId, event -> {
+		Device device = storager.queryVideoDevice(deviceId);
+		cmder.streamByeCmd(deviceId, channelId, (event) -> {
 			StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
 			if (streamInfo == null) {
 				RequestMessage msg = new RequestMessage();
@@ -120,6 +128,7 @@ public class PlayController {
 				msg.setData(String.format("success"));
 				resultHolder.invokeResult(msg);
 			}
+			mediaServerService.closeRTPServer(device, channelId);
 		});
 
 		if (deviceId != null || channelId != null) {
@@ -165,16 +174,16 @@ public class PlayController {
 			logger.warn("视频转码API调用失败!, 视频流已经停止!");
 			return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK);
 		}
-		JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId);
+		IMediaServerItem mediaInfo = mediaServerService.getOne(streamInfo.getMediaServerId());
+		JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
 		if (!rtpInfo.getBoolean("exist")) {
 			logger.warn("视频转码API调用失败!, 视频流已停止推流!");
 			return new ResponseEntity<String>("推流信息在流媒体中不存在, 视频流可能已停止推流", HttpStatus.OK);
 		} else {
-			ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
 			String dstUrl = String.format("rtmp://%s:%s/convert/%s", "127.0.0.1", mediaInfo.getRtmpPort(),
 					streamId );
 			String srcUrl = String.format("rtsp://%s:%s/rtp/%s", "127.0.0.1", mediaInfo.getRtspPort(), streamId);
-			JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(srcUrl, dstUrl, "1000000", true, false, null);
+			JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(mediaInfo, srcUrl, dstUrl, "1000000", true, false, null);
 			logger.info(jsonObject.toJSONString());
 			JSONObject result = new JSONObject();
 			if (jsonObject != null && jsonObject.getInteger("code") == 0) {
@@ -182,7 +191,7 @@ public class PlayController {
 				JSONObject data = jsonObject.getJSONObject("data");
 				if (data != null) {
 				   	result.put("key", data.getString("key"));
-					StreamInfo streamInfoResult = mediaService.getStreamInfoByAppAndStreamWithCheck("convert", streamId);
+					StreamInfo streamInfoResult = mediaService.getStreamInfoByAppAndStreamWithCheck("convert", streamId, mediaInfo.getId());
 					result.put("data", streamInfoResult);
 				}
 			}else {
@@ -203,25 +212,38 @@ public class PlayController {
 			@ApiImplicitParam(name = "key", value = "视频流key", dataTypeClass = String.class),
 	})
 	@PostMapping("/convertStop/{key}")
-	public ResponseEntity<String> playConvertStop(@PathVariable String key) {
-
-		JSONObject jsonObject = zlmresTfulUtils.delFFmpegSource(key);
-		logger.info(jsonObject.toJSONString());
+	public ResponseEntity<String> playConvertStop(@PathVariable String key, String mediaServerId) {
 		JSONObject result = new JSONObject();
-		if (jsonObject != null && jsonObject.getInteger("code") == 0) {
+		if (mediaServerId == null) {
+			result.put("code", 400);
+			result.put("msg", "mediaServerId is null");
+			return new ResponseEntity<String>( result.toJSONString(), HttpStatus.BAD_REQUEST);
+		}
+		IMediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+		if (mediaInfo == null) {
 			result.put("code", 0);
-			JSONObject data = jsonObject.getJSONObject("data");
-			if (data != null && data.getBoolean("flag")) {
-				result.put("code", "0");
-				result.put("msg", "success");
-			}else {
+			result.put("msg", "使用的流媒体已经停止运行");
+			return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
+		}else {
+			JSONObject jsonObject = zlmresTfulUtils.delFFmpegSource(mediaInfo, key);
+			logger.info(jsonObject.toJSONString());
+			if (jsonObject != null && jsonObject.getInteger("code") == 0) {
+				result.put("code", 0);
+				JSONObject data = jsonObject.getJSONObject("data");
+				if (data != null && data.getBoolean("flag")) {
+					result.put("code", "0");
+					result.put("msg", "success");
+				}else {
 
+				}
+			}else {
+				result.put("code", 1);
+				result.put("msg", "delFFmpegSource fail");
 			}
-		}else {
-			result.put("code", 1);
-			result.put("msg", "delFFmpegSource fail");
+			return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
 		}
-		return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
+
+
 	}
 
 	@ApiOperation("语音广播命令")
@@ -249,7 +271,7 @@ public class PlayController {
 			resultHolder.invokeResult(msg);
 			return result;
 		}
-		cmder.audioBroadcastCmd(device, event -> {
+		cmder.audioBroadcastCmd(device, (event) -> {
 			Response response = event.getResponse();
 			RequestMessage msg = new RequestMessage();
 			msg.setId(DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId);

+ 13 - 2
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java

@@ -4,6 +4,8 @@ import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 //import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.service.IPlayService;
 import io.swagger.annotations.Api;
@@ -87,9 +89,18 @@ public class PlaybackController {
 			cmder.streamByeCmd(deviceId, channelId);
 		}
 		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result);
-		cmder.playbackStreamCmd(device, channelId, startTime, endTime, (JSONObject response) -> {
+		IMediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
+		if (newMediaServerItem == null) {
+			logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId));
+			RequestMessage msg = new RequestMessage();
+			msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid);
+			msg.setData("Timeout");
+			resultHolder.invokeResult(msg);
+			return result;
+		}
+		cmder.playbackStreamCmd(newMediaServerItem, device, channelId, startTime, endTime, (IMediaServerItem mediaServerItem, JSONObject response) -> {
 			logger.info("收到订阅消息: " + response.toJSONString());
-			playService.onPublishHandlerForPlayBack(response, deviceId, channelId, uuid.toString());
+			playService.onPublishHandlerForPlayBack(mediaServerItem, response, deviceId, channelId, uuid.toString());
 		}, event -> {
 			Response response = event.getResponse();
 			RequestMessage msg = new RequestMessage();

+ 4 - 3
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/ptz/PtzController.java

@@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.async.DeferredResult;
 
@@ -104,7 +105,7 @@ public class PtzController {
 		cmder.presetQuery(device, channelId, event -> {
 			Response response = event.getResponse();
 			RequestMessage msg = new RequestMessage();
-			msg.setId(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
+			msg.setId(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
 			msg.setData(String.format("获取设备预置位失败,错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase()));
 			resultHolder.invokeResult(msg);
 		});
@@ -113,11 +114,11 @@ public class PtzController {
 			logger.warn(String.format("获取设备预置位超时"));
 			// 释放rtpserver
 			RequestMessage msg = new RequestMessage();
-			msg.setId(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (XmlUtil.isEmpty(channelId) ? deviceId : channelId));
+			msg.setId(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (StringUtils.isEmpty(channelId) ? deviceId : channelId));
 			msg.setData("获取设备预置位超时");
 			resultHolder.invokeResult(msg);
 		});
-		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (XmlUtil.isEmpty(channelId) ? deviceId : channelId), result);
+		resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PRESETQUERY + (StringUtils.isEmpty(channelId) ? deviceId : channelId), result);
 		return result;
 	}
 }

+ 10 - 1
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecoderProxyController.java

@@ -2,6 +2,9 @@ package com.genersoft.iot.vmp.vmanager.record;
 
 import com.genersoft.iot.vmp.conf.MediaConfig;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -27,6 +30,8 @@ public class RecoderProxyController {
 
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
+    @Autowired
+    private IMediaServerService mediaServerService;
 
     @Autowired
     private MediaConfig mediaConfig;
@@ -48,7 +53,11 @@ public class RecoderProxyController {
             return null;
         }
         // 后续改为根据Id获取对应的ZLM
-        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
+        IMediaServerItem mediaInfo = mediaServerService.getOne(mediaId);
+        if (mediaInfo == null) {
+            response.setStatus(HttpStatus.NOT_FOUND.value());
+            return null;
+        }
         String requestURI = String.format("http://%s:%s%s?%s",
                 mediaInfo.getSdpIp(),
                 mediaConfig.getRecordAssistPort(),

+ 21 - 6
src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java

@@ -2,8 +2,11 @@ package com.genersoft.iot.vmp.vmanager.server;
 
 import com.genersoft.iot.vmp.VManageBootstrap;
 import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.utils.SpringBeanFactory;
+import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import gov.nist.javax.sip.SipStackImpl;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -16,6 +19,7 @@ import javax.sip.ObjectInUseException;
 import javax.sip.SipProvider;
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
 
 @SuppressWarnings("rawtypes")
 @Api(tags = "服务控制")
@@ -28,17 +32,28 @@ public class ServerController {
     private ConfigurableApplicationContext context;
 
     @Autowired
-    private IRedisCatchStorage redisCatchStorage;
+    private IMediaServerService mediaServerService;
 
 
     @ApiOperation("流媒体服务列表")
     @GetMapping(value = "/media_server/list")
     @ResponseBody
-    public Object getMediaServerList(){
-        // TODO 为后续多个zlm支持准备
-        ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
-        ArrayList<ZLMServerConfig> result = new ArrayList<>();
-        result.add(mediaInfo);
+    public WVPResult<List<IMediaServerItem>> getMediaServerList(){
+        WVPResult<List<IMediaServerItem>> result = new WVPResult<>();
+        result.setCode(0);
+        result.setMsg("success");
+        result.setData(mediaServerService.getAll());
+        return result;
+    }
+
+    @ApiOperation("获取流媒体服务")
+    @GetMapping(value = "/media_server/one/{id}")
+    @ResponseBody
+    public WVPResult<IMediaServerItem> getMediaServer(@PathVariable String id){
+        WVPResult<IMediaServerItem> result = new WVPResult<>();
+        result.setCode(0);
+        result.setMsg("success");
+        result.setData(mediaServerService.getOne(id));
         return result;
     }
 

+ 21 - 6
src/main/java/com/genersoft/iot/vmp/vmanager/streamProxy/StreamProxyController.java

@@ -1,11 +1,15 @@
 package com.genersoft.iot.vmp.vmanager.streamProxy;
 
 import com.alibaba.fastjson.JSONObject;
+import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
+import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.service.IStreamProxyService;
 import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
 import com.github.pagehelper.PageInfo;
+import io.netty.util.internal.StringUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiImplicitParams;
@@ -14,6 +18,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
 @SuppressWarnings("rawtypes")
@@ -31,6 +36,10 @@ public class StreamProxyController {
     @Autowired
     private IRedisCatchStorage redisCatchStorage;
 
+
+    @Autowired
+    private IMediaServerService mediaServerService;
+
     @Autowired
     private IStreamProxyService streamProxyService;
 
@@ -60,6 +69,7 @@ public class StreamProxyController {
     @ResponseBody
     public WVPResult save(@RequestBody StreamProxyItem param){
         logger.info("添加代理: " + JSONObject.toJSONString(param));
+        if (StringUtils.isEmpty(param.getMediaServerId())) param.setMediaServerId("auto");
         String msg = streamProxyService.save(param);
         WVPResult<Object> result = new WVPResult<>();
         result.setCode(0);
@@ -69,10 +79,15 @@ public class StreamProxyController {
 
     @ApiOperation("获取ffmpeg.cmd模板")
     @GetMapping(value = "/ffmpeg_cmd/list")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "mediaServerId", value = "流媒体ID", dataTypeClass = String.class),
+    })
     @ResponseBody
-    public WVPResult getFFmpegCMDs(){
-        logger.debug("获取ffmpeg.cmd模板:" );
-        JSONObject data = streamProxyService.getFFmpegCMDs();
+    public WVPResult getFFmpegCMDs(@RequestParam String mediaServerId){
+        logger.debug("获取节点[ {} ]ffmpeg.cmd模板", mediaServerId );
+
+        IMediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
+        JSONObject data = streamProxyService.getFFmpegCMDs(mediaServerItem);
         WVPResult<JSONObject> result = new WVPResult<>();
         result.setCode(0);
         result.setMsg("success");
@@ -82,12 +97,12 @@ public class StreamProxyController {
 
     @ApiOperation("移除代理")
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "app", value = "应用名", dataTypeClass = String.class),
-            @ApiImplicitParam(name = "stream", value = "流ID", dataTypeClass = String.class),
+            @ApiImplicitParam(name = "app", value = "应用名", required = true, dataTypeClass = String.class),
+            @ApiImplicitParam(name = "stream", value = "流ID", required = true, dataTypeClass = String.class),
     })
     @DeleteMapping(value = "/del")
     @ResponseBody
-    public WVPResult del(String app, String stream){
+    public WVPResult del(@RequestParam String app, @RequestParam String stream){
         logger.info("移除代理: " + app + "/" + stream);
         WVPResult<Object> result = new WVPResult<>();
         if (app == null || stream == null) {

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/web/ApiControlController.java

@@ -10,7 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 /**
- * 兼容LiveGBS的API:设备控制
+ * API兼容:设备控制
  */
 @CrossOrigin
 @RestController

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/web/ApiController.java

@@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 
 /**
- * 兼容LiveGBS的API:系统接口
+ * API兼容:系统接口
  */
 @Controller
 @CrossOrigin

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java

@@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.*;
 import java.util.List;
 
 /**
- * 兼容LiveGBS的API:设备信息
+ * API兼容:设备信息
  */
 @SuppressWarnings("unchecked")
 @CrossOrigin

+ 1 - 1
src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java

@@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.context.request.async.DeferredResult;
 
 /**
- * 兼容LiveGBS的API:实时直播
+ * API兼容:实时直播
  */
 @SuppressWarnings(value = {"rawtypes", "unchecked"})
 @CrossOrigin

+ 4 - 0
src/main/resources/all-application.yml

@@ -70,9 +70,13 @@ sip:
     keepalive-timeout: 180
     # [可选] 国标级联注册失败,再次发起注册的时间间隔。 默认60秒
     register-time-interval: 60
+    # TODO [可选] 收到心跳后自动上线, 重启服务后会将所有设备置为离线,默认false,等待注册后上线。设置为true则收到心跳设置为上线。
+    # keepalliveToOnline: false
 
 #zlm 默认服务器配置
 media:
+    # [可选] zlm服务器唯一id,用于触发hook时区别是哪台服务器,general.mediaServerId
+    id:
     # [必须修改] zlm服务器的内网IP
     ip: 192.168.0.100
     # [可选] 返回流地址时的ip,置空使用 media.ip

BIN
src/main/resources/wvp.sqlite


+ 15 - 19
web_src/src/components/CloudRecord.vue

@@ -7,22 +7,23 @@
 			<el-main>
         <div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;">
           <span style="font-size: 1rem; font-weight: bold;">云端录像</span>
-          <div style="position: absolute; right: 1rem; top: 0.3rem;">
-            <el-button v-if="!recordDetail" icon="el-icon-refresh-right" circle size="mini" :loading="loading" @click="getRecordList()"></el-button>
-            <el-button v-if="recordDetail" icon="el-icon-arrow-left" circle size="mini" @click="backToList()"></el-button>
-          </div>
-        </div>
-        <div v-if="!recordDetail">
-          <div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;font-size: 14px;">
+          <div style="position: absolute; right: 5rem; top: 0.3rem;">
             节点选择: <el-select size="mini" @change="chooseMediaChange" style="width: 16rem; margin-right: 1rem;" v-model="mediaServer" placeholder="请选择" default-first-option>
             <el-option
               v-for="item in mediaServerList"
-              :key="item.generalMediaServerId"
-              :label="item.generalMediaServerId + '( ' + item.wanIp + ' )'"
+              :key="item.id"
+              :label="item.id + '( ' + item.streamIp + ' )'"
               :value="item">
             </el-option>
           </el-select>
           </div>
+          <div style="position: absolute; right: 1rem; top: 0.3rem;">
+            <el-button v-if="!recordDetail" icon="el-icon-refresh-right" circle size="mini" :loading="loading" @click="getRecordList()"></el-button>
+            <el-button v-if="recordDetail" icon="el-icon-arrow-left" circle size="mini" @click="backToList()"></el-button>
+          </div>
+        </div>
+        <div v-if="!recordDetail">
+
           <!--设备列表-->
           <el-table :data="recordList" border style="width: 100%" :height="winHeight">
             <el-table-column prop="app" label="应用名" align="center">
@@ -60,6 +61,7 @@
 <script>
 	import uiHeader from './UiHeader.vue'
 	import cloudRecordDetail from './CloudRecordDetail.vue'
+  import MediaServer from './service/MediaServer'
 	export default {
 		name: 'app',
 		components: {
@@ -78,6 +80,7 @@
         count:15,
         total:0,
         loading: false,
+        mediaServerObj : new MediaServer(),
         recordDetail: false
 
 			};
@@ -107,20 +110,13 @@
       },
       getMediaServerList: function (){
         let that = this;
-        this.$axios({
-          method: 'get',
-          url:`/api/server/media_server/list`,
-        }).then(function (res) {
-          console.log(res)
-          that.mediaServerList = res.data;
+        that.mediaServerObj.getMediaServerList((data)=>{
+          that.mediaServerList = data;
           if (that.mediaServerList.length > 0) {
             that.mediaServer = that.mediaServerList[0]
             that.getRecordList();
           }
-
-        }).catch(function (error) {
-          console.log(error);
-        });
+        })
       },
       getRecordList: function (){
         let that = this;

+ 6 - 3
web_src/src/components/PushVideoList.vue

@@ -17,6 +17,8 @@
 					</el-table-column>
 					<el-table-column prop="gbId" label="国标编码" width="150" align="center">
 					</el-table-column>
+					<el-table-column prop="mediaServerId" label="流媒体" width="150" align="center">
+					</el-table-column>
 					<el-table-column label="开始时间" align="center" >
 						<template slot-scope="scope">
 							<el-button-group>
@@ -29,7 +31,7 @@
 							{{(scope.row.status == false && scope.row.gbId == null) || scope.row.status ?'是':'否'}}
 						</template>
 					</el-table-column>
-					
+
 					<el-table-column label="操作" width="360" align="center" fixed="right">
 						<template slot-scope="scope">
 							<el-button-group>
@@ -125,7 +127,7 @@
 					that.getDeviceListLoading = false;
 				});
 			},
-			
+
 			playPuhsh: function(row){
 				let that = this;
 				this.getListLoading = true;
@@ -134,7 +136,8 @@
 					url:`/api/media/stream_info_by_app_and_stream`,
 					params: {
 						app: row.app,
-						stream: row.stream
+						stream: row.stream,
+            mediaServerId: row.mediaServerId
 					}
 				}).then(function (res) {
 					that.getListLoading = false;

+ 11 - 4
web_src/src/components/StreamProxyList.vue

@@ -32,6 +32,15 @@
 						</div>
 						</template>
 					</el-table-column>
+          <el-table-column prop="mediaServerId" label="流媒体" width="150" align="center"></el-table-column>
+          <el-table-column label="类型" width="100" align="center">
+            <template slot-scope="scope">
+              <div slot="reference" class="name-wrapper">
+                <el-tag size="medium">{{scope.row.type}}</el-tag>
+              </div>
+            </template>
+          </el-table-column>
+
 					<el-table-column prop="gbId" label="国标编码" width="180" align="center" show-overflow-tooltip/>
           <el-table-column label="启用" width="120" align="center">
             <template slot-scope="scope">
@@ -147,8 +156,6 @@
 						count: that.count
 					}
 				}).then(function (res) {
-					console.log(res);
-					console.log(res.data.list);
 					that.total = res.data.total;
 					that.streamProxyList = res.data.list;
 					that.getListLoading = false;
@@ -170,7 +177,6 @@
           this.getListLoading = false;
           if (res.data.code == 0 ){
             if (res.data.data.length > 0) {
-              console.log(res.data.data)
               this.$refs.onvifEdit.openDialog(res.data.data, (url)=>{
                   if (url != null) {
                     this.$refs.onvifEdit.close();
@@ -200,7 +206,8 @@
 					url:`/api/media/stream_info_by_app_and_stream`,
 					params: {
 						app: row.app,
-						stream: row.stream
+						stream: row.stream,
+            mediaServerId: row.mediaServerId
 					}
 				}).then(function (res) {
 					that.getListLoading = false;

+ 84 - 40
web_src/src/components/control.vue

@@ -7,6 +7,17 @@
         <el-main>
             <div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;">
                 <span style="font-size: 1rem; font-weight: bold;">控制台</span>
+                <div style="position: absolute; right: 17rem; top: 0.3rem;">
+                  节点选择: <el-select size="mini" @change="chooseMediaChange" style="width: 16rem; margin-right: 1rem;" v-model="mediaServerChoose" placeholder="请选择" default-first-option>
+                  <el-option
+                    v-for="item in mediaServerList"
+                    :key="item.id"
+                    :label="item.id + '( ' + item.streamIp + ' )'"
+                    :value="item.id">
+                  </el-option>
+                  </el-select>
+                  <span >{{loadCount}}</span>
+                </div>
                 <div style="position: absolute; right: 1rem; top: 0.3rem;">
                     <el-popover placement="bottom" width="750" height="300" trigger="click">
                         <div style="height: 600px;overflow:auto;">
@@ -53,6 +64,7 @@
 
 <script>
 import uiHeader from './UiHeader.vue'
+import MediaServer from './service/MediaServer'
 
 import echarts from 'echarts';
 export default {
@@ -87,68 +99,101 @@ export default {
             chartInterval: 0, //更新图表统计图定时任务标识
             allSessionData: [],
             visible: false,
-            serverConfig: {}
+            serverConfig: {},
+            mediaServer : new MediaServer(),
+            mediaServerChoose : null,
+            loadCount : 0,
+            mediaServerList : []
         };
     },
     mounted() {
-        this.getAllSession();
+
         this.initTable();
         this.updateData();
         this.chartInterval = setInterval(this.updateData, 3000);
+        this.mediaServer.getMediaServerList((data)=>{
+          this.mediaServerList = data.data;
+          if (this.mediaServerList && this.mediaServerList.length > 0) {
+            this.mediaServerChoose = this.mediaServerList[0].id
+            this.loadCount = this.mediaServerList[0].count;
+            this.getThreadsLoad();
+            this.getAllSession();
+          }
+        })
     },
     destroyed() {
         clearInterval(this.chartInterval); //释放定时任务
     },
     methods: {
+        chooseMediaChange: function (val) {
+            this.loadCount = 0
+            this.initTable()
+            this.updateData();
+        },
         updateData: function () {
             this.getThreadsLoad();
+            this.getLoadCount();
+            this.getAllSession();
         },
         /**
          * 获取线程状态
          */
         getThreadsLoad: function () {
             let that = this;
-            this.$axios({
+            if (that.mediaServerChoose != null) {
+              this.$axios({
                 method: 'get',
-                url: '/zlm/index/api/getThreadsLoad'
-            }).then(function (res) {
+                url: '/zlm/' + that.mediaServerChoose +'/index/api/getThreadsLoad'
+              }).then(function (res) {
                 if (res.data.code == 0) {
-                    that.tableOption.xAxis.data.push(new Date().toLocaleTimeString('chinese', {
-                        hour12: false
-                    }));
-                    that.table1Option.xAxis.data.push(new Date().toLocaleTimeString('chinese', {
-                        hour12: false
-                    }));
+                  that.tableOption.xAxis.data.push(new Date().toLocaleTimeString('chinese', {
+                    hour12: false
+                  }));
+                  that.table1Option.xAxis.data.push(new Date().toLocaleTimeString('chinese', {
+                    hour12: false
+                  }));
 
-                    for (var i = 0; i < res.data.data.length; i++) {
-                        if (that.tableOption.series[i] === undefined) {
-                            let data = {
-                                data: [],
-                                type: 'line'
-                            };
-                            let data1 = {
-                                data: [],
-                                type: 'line'
-                            };
-                            data.data.push(res.data.data[i].delay);
-                            data1.data.push(res.data.data[i].load);
-                            that.tableOption.series.push(data);
-                            that.table1Option.series.push(data1);
-                        } else {
-                            that.tableOption.series[i].data.push(res.data.data[i].delay);
-                            that.table1Option.series[i].data.push(res.data.data[i].load);
-                        }
+                  for (var i = 0; i < res.data.data.length; i++) {
+                    if (that.tableOption.series[i] === undefined) {
+                      let data = {
+                        data: [],
+                        type: 'line'
+                      };
+                      let data1 = {
+                        data: [],
+                        type: 'line'
+                      };
+                      data.data.push(res.data.data[i].delay);
+                      data1.data.push(res.data.data[i].load);
+                      that.tableOption.series.push(data);
+                      that.table1Option.series.push(data1);
+                    } else {
+                      that.tableOption.series[i].data.push(res.data.data[i].delay);
+                      that.table1Option.series[i].data.push(res.data.data[i].load);
                     }
-                    that.tableOption.dataZoom[0].start = that.charZoomStart;
-                    that.tableOption.dataZoom[0].end = that.charZoomEnd;
-                    that.table1Option.dataZoom[0].start = that.charZoomStart;
-                    that.table1Option.dataZoom[0].end = that.charZoomEnd;
-                    //that.myChart = echarts.init(document.getElementById('ThreadsLoad'));
-                    that.myChart.setOption(that.tableOption, true);
-                    // that.myChart1 = echarts.init(document.getElementById('WorkThreadsLoad'));
-                    that.myChart1.setOption(that.table1Option, true);
+                  }
+                  that.tableOption.dataZoom[0].start = that.charZoomStart;
+                  that.tableOption.dataZoom[0].end = that.charZoomEnd;
+                  that.table1Option.dataZoom[0].start = that.charZoomStart;
+                  that.table1Option.dataZoom[0].end = that.charZoomEnd;
+                  //that.myChart = echarts.init(document.getElementById('ThreadsLoad'));
+                  that.myChart.setOption(that.tableOption, true);
+                  // that.myChart1 = echarts.init(document.getElementById('WorkThreadsLoad'));
+                  that.myChart1.setOption(that.table1Option, true);
                 }
-            });
+              });
+            }
+
+        },
+        getLoadCount: function (){
+          let that = this;
+          if (that.mediaServerChoose != null) {
+            that.mediaServer.getMediaServer(that.mediaServerChoose, (data)=>{
+              if (data.code == 0) {
+                that.loadCount = data.data.count
+              }
+            })
+          }
         },
         initTable: function () {
             let that = this;
@@ -242,10 +287,9 @@ export default {
         getAllSession: function () {
             let that = this;
             that.allSessionData = [];
-            console.log("地址:" + '/zlm/index/api/getAllSession');
             this.$axios({
                 method: 'get',
-                url: '/zlm/index/api/getAllSession'
+                url: '/zlm/' + that.mediaServerChoose +'/index/api/getAllSession'
             }).then(function (res) {
                 res.data.data.forEach(item => {
                     let data = {

+ 43 - 32
web_src/src/components/dialog/StreamProxyEdit.vue

@@ -39,6 +39,22 @@
               <el-form-item label="超时时间:毫秒" prop="timeout_ms" v-if="proxyParam.type=='ffmpeg'">
                 <el-input v-model="proxyParam.timeout_ms" clearable></el-input>
               </el-form-item>
+              <el-form-item label="节点选择" prop="rtp_type">
+                <el-select
+                  v-model="proxyParam.mediaServerId"
+                  @change="mediaServerIdChange"
+                  style="width: 100%"
+                  placeholder="请选择拉流节点"
+                >
+                  <el-option label="自动选择" value="auto"></el-option>
+                  <el-option
+                    v-for="item in mediaServerList"
+                    :key="item.id"
+                    :label="item.id"
+                    :value="item.id">
+                  </el-option>
+                </el-select>
+              </el-form-item>
               <el-form-item label="FFmpeg命令模板" prop="ffmpeg_cmd_key" v-if="proxyParam.type=='ffmpeg'">
 <!--                <el-input v-model="proxyParam.ffmpeg_cmd_key" clearable></el-input>-->
                 <el-select
@@ -68,6 +84,7 @@
                   <el-option label="组播" value="2"></el-option>
                 </el-select>
               </el-form-item>
+
               <el-form-item label="国标平台">
                 <el-select
                   v-model="proxyParam.platformGbId"
@@ -106,6 +123,8 @@
 </template>
 
 <script>
+import MediaServer from './../service/MediaServer'
+
 export default {
   name: "streamProxyEdit",
   props: {},
@@ -134,27 +153,8 @@ export default {
       isLoging: false,
       dialogLoading: false,
       onSubmit_text: "立即创建",
-      platformList: [{
-          id: 1,
-          enable: true,
-          name: "141",
-          serverGBId: "34020000002000000001",
-          serverGBDomain: "3402000000",
-          serverIP: "192.168.1.141",
-          serverPort: 15060,
-          deviceGBId: "34020000002000000001",
-          deviceIp: "192.168.1.20",
-          devicePort: "5060",
-          username: "34020000002000000001",
-          password: "12345678",
-          expires: "300",
-          keepTimeout: "60",
-          transport: "UDP",
-          characterSet: "GB2312",
-          ptz: false,
-          rtcp: false,
-          status: true,
-      }],
+      platformList: [],
+      mediaServer: new MediaServer(),
       proxyParam: {
           name: null,
           type: "default",
@@ -170,7 +170,9 @@ export default {
           enable_hls: true,
           enable_mp4: false,
           platformGbId: null,
+          mediaServerId: "auto",
       },
+      mediaServerList:{},
       ffmpegCmdList:{},
 
       rules: {
@@ -193,7 +195,6 @@ export default {
       }
 
       let that = this;
-
       this.$axios({
         method: 'get',
         url:`/api/platform/query/10000/0`
@@ -202,17 +203,28 @@ export default {
       }).catch(function (error) {
         console.log(error);
       });
-      this.$axios({
-        method: 'get',
-        url:`/api/proxy/ffmpeg_cmd/list`
-      }).then(function (res) {
-        that.ffmpegCmdList = res.data.data;
-      }).catch(function (error) {
-        console.log(error);
-      });
+      this.mediaServer.getMediaServerList((data)=>{
+        this.mediaServerList = data;
+      })
+    },
+    mediaServerIdChange:function (){
+      let that = this;
+      if (that.proxyParam.mediaServerId !== "auto"){
+        that.$axios({
+          method: 'get',
+          url:`/api/proxy/ffmpeg_cmd/list`,
+          params: {
+            mediaServerId: that.proxyParam.mediaServerId
+          }
+        }).then(function (res) {
+          that.ffmpegCmdList = res.data.data;
+        }).catch(function (error) {
+          console.log(error);
+        });
+      }
+
     },
     onSubmit: function () {
-      console.log("onSubmit");
       this.dialogLoading = true;
       var that = this;
       that.$axios({
@@ -239,7 +251,6 @@ export default {
       });
     },
     close: function () {
-      console.log("关闭添加视频平台");
       this.showDialog = false;
       this.dialogLoading = false;
       this.$refs.streamProxy.resetFields();

+ 7 - 8
web_src/src/components/dialog/devicePlayer.vue

@@ -181,6 +181,7 @@ export default {
             showVideoDialog: false,
             streamId: '',
             app : '',
+            mediaServerId : '',
             convertKey: '',
             deviceId: '',
             channelId: '',
@@ -218,7 +219,7 @@ export default {
             if (tab.name == "codec") {
                 this.$axios({
                     method: 'get',
-                    url: '/zlm/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app='+ this.app +'&stream='+ this.streamId
+                    url: '/zlm/' +this.mediaServerId+ '/index/api/getMediaInfo?vhost=__defaultVhost__&schema=rtmp&app='+ this.app +'&stream='+ this.streamId
                 }).then(function (res) {
                     that.tracksLoading = false;
                     if (res.data.code == 0 && res.data.online) {
@@ -235,12 +236,11 @@ export default {
             }
         },
         openDialog: function (tab, deviceId, channelId, param) {
-          console.log("openDialog")
-          console.log(param)
             this.tabActiveName = tab;
             this.channelId = channelId;
             this.deviceId = deviceId;
             this.streamId = "";
+            this.mediaServerId = "";
             this.app = "";
             this.videoUrl = ""
             if (!!this.$refs.videoPlayer) {
@@ -257,8 +257,8 @@ export default {
                     break;
                 case "streamPlay":
                     this.tabActiveName = "media";
-                    this.showRrecord = false,
-                    this.showPtz = false,
+                    this.showRrecord = false;
+                    this.showPtz = false;
                     this.play(param.streamInfo, param.hasAudio)
                     break;
                 case "control":
@@ -269,19 +269,17 @@ export default {
             console.log(val)
         },
         play: function (streamInfo, hasAudio) {
-
             this.hasAudio = hasAudio;
             this.isLoging = false;
             // this.videoUrl = streamInfo.rtc;
             this.videoUrl = this.getUrlByStreamInfo(streamInfo);
             this.streamId = streamInfo.streamId;
             this.app = streamInfo.app;
+            this.mediaServerId = streamInfo.mediaServerId;
             this.playFromStreamInfo(false, streamInfo)
         },
         getUrlByStreamInfo(streamInfo){
             let baseZlmApi = process.env.NODE_ENV === 'development'?`${location.host}/debug/zlm`:`${location.host}/zlm`
-            console.log(12121212)
-            console.log(baseZlmApi)
             // return `${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`;
             // return `http://${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`;
             return streamInfo.ws_flv;
@@ -430,6 +428,7 @@ export default {
                     var streamInfo = res.data;
                     that.app = streamInfo.app;
                     that.streamId = streamInfo.streamId;
+                    that.mediaServerId = streamInfo.mediaServerId;
                     that.videoUrl = that.getUrlByStreamInfo(streamInfo);
                     that.recordPlay = true;
                 });

+ 32 - 0
web_src/src/components/service/MediaServer.js

@@ -0,0 +1,32 @@
+import axios from 'axios';
+
+class MediaServer{
+
+  constructor() {
+    this.$axios = axios;
+  }
+
+  getMediaServerList(callback){
+    this.$axios({
+      method: 'get',
+      url:`/api/server/media_server/list`,
+    }).then(function (res) {
+      if (typeof (callback) == "function") callback(res.data)
+    }).catch(function (error) {
+      console.log(error);
+    });
+  }
+
+  getMediaServer(id, callback){
+    this.$axios({
+      method: 'get',
+      url:`/api/server/media_server/one/` + id,
+    }).then(function (res) {
+      if (typeof (callback) == "function") callback(res.data)
+    }).catch(function (error) {
+      console.log(error);
+    });
+  }
+}
+
+export default MediaServer;