Procházet zdrojové kódy

Merge branch 'master' into dev

# Conflicts:
#	jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/SIPRequestHeaderProvider.java
18339543638 před 3 roky
rodič
revize
8ed84d30ee
67 změnil soubory, kde provedl 3865 přidání a 2093 odebrání
  1. 11 0
      .idea/.gitignore
  2. 10 10
      docker/dev-env/docker-compose.yml
  3. 13 2
      jetlinks-manager/media-manager/pom.xml
  4. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/GbStream.java
  5. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/ParentPlatform.java
  6. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/PlatformCatalog.java
  7. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/PlatformGbStream.java
  8. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/SipServerConfig.java
  9. 3 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/SsrcConfig.java
  10. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/WvpSipDate.java
  11. 219 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/config/MediaConfig.java
  12. 16 7
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/contanst/VideoManagerConstants.java
  13. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/controller/ApiStreamController.java
  14. 332 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/controller/PlayController.java
  15. 0 199
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/DeviceChannel.java
  16. 323 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/MediaDeviceChannel.java
  17. 68 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/bean/SsrcTransaction.java
  18. 140 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/event/SipSubscribe.java
  19. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/result/PlayResult.java
  20. 13 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/result/WVPResult.java
  21. 35 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalMediaDeviceChannelService.java
  22. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalMediaDeviceService.java
  23. 711 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalMediaServerItemService.java
  24. 267 294
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalPlayService.java
  25. 0 23
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/SipService.java
  26. 115 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/session/VideoStreamSessionManager.java
  27. 3 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/AbstractSipProcessor.java
  28. 1 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/SipContext.java
  29. 4 4
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/SipRequestProcessorParent.java
  30. 40 40
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/SipServerHelper.java
  31. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/IMessageHandler.java
  32. 41 6
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/InviteRequestProcessor.java
  33. 3 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/MessageRequestProcessor.java
  34. 3 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/RegisterRequestProcessor.java
  35. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/TimeoutProcessor.java
  36. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/IMessageHandler.java
  37. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/MessageHandlerAbstract.java
  38. 2 8
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/MessageRequestProcessor.java
  39. 3 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/notify/KeepaliveNotifyMessageHandler.java
  40. 3 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/notify/NoSupportMessageHandler.java
  41. 73 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/response/InviteResponseProcessor.java
  42. 5 7
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/response/RegisterResponseProcessor.java
  43. 0 210
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/storage/IRedisCatchStorage.java
  44. 27 27
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/storage/IVideoManagerStorager.java
  45. 461 453
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/storage/impl/RedisCatchStorageImpl.java
  46. 16 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/SIPRequestHeaderProvider.java
  47. 148 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/callback/DeferredResultHolder.java
  48. 32 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/callback/RequestMessage.java
  49. 80 6
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/cmd/SipCommander.java
  50. 137 179
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMHttpHookListener.java
  51. 48 22
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMHttpHookSubscribe.java
  52. 5 5
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMRESTfulUtils.java
  53. 39 18
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMRTPServerFactory.java
  54. 231 195
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMRunner.java
  55. 23 269
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/dto/MediaItem.java
  56. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/dto/StreamProxyItem.java
  57. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/dto/StreamPushItem.java
  58. 46 9
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/entity/MediaServerItem.java
  59. 11 11
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/utils/RedisUtil.java
  60. 4 5
      jetlinks-standalone/pom.xml
  61. 3 1
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/JetLinksApplication.java
  62. 8 0
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/ClusterConfiguration.java
  63. 2 1
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/CroConfiguration.java
  64. 4 0
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/JetLinksConfiguration.java
  65. 35 7
      jetlinks-standalone/src/main/resources/application.yml
  66. 22 32
      jetlinks-standalone/src/test/java/org/jetlinks/community/BridgeTest.java
  67. 6 0
      pom.xml

+ 11 - 0
.idea/.gitignore

@@ -0,0 +1,11 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+.idea/*
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+*.iml
+Maven_*

+ 10 - 10
docker/dev-env/docker-compose.yml

@@ -1,15 +1,15 @@
 version: '2'
 services:
-  redis:
-    image: redis:5.0.4
-    container_name: jetlinks-ce-redis
-    ports:
-      - "6379:6379"
-    volumes:
-      - "redis-volume:/data"
-    command: redis-server --appendonly yes
-    environment:
-      - TZ=Asia/Shanghai
+#  redis:
+#    image: redis:5.0.4
+#    container_name: jetlinks-ce-redis
+#    ports:
+#      - "6379:6379"
+#    volumes:
+#      - "redis-volume:/data"
+#    command: redis-server --appendonly yes
+#    environment:
+#      - TZ=Asia/Shanghai
   elasticsearch:
     image: elasticsearch:6.8.11
     container_name: jetlinks-ce-elasticsearch

+ 13 - 2
jetlinks-manager/media-manager/pom.xml

@@ -2,12 +2,13 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
     <parent>
-        <artifactId>jetlinks-components</artifactId>
         <groupId>org.jetlinks.community</groupId>
+        <artifactId>jetlinks-manager</artifactId>
         <version>1.10.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
     </parent>
-    <modelVersion>4.0.0</modelVersion>
 
     <artifactId>media-manager</artifactId>
 
@@ -34,6 +35,16 @@
             <version>${hsweb.framework.version}</version>
         </dependency>
 
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.boot</groupId>-->
+<!--            <artifactId>spring-boot-starter-web</artifactId>-->
+<!--        </dependency>-->
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-all</artifactId>

+ 2 - 2
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/GbStream.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/GbStream.java

@@ -1,4 +1,4 @@
-package org.jetlinks.community.media.entity;
+package org.jetlinks.community.media.bean;
 
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode;
  */
 @Data
 @EqualsAndHashCode(callSuper = true)
-public class GbStream extends PlatformGbStream{
+public class GbStream extends PlatformGbStream {
 
     private String app;
     private String stream;

+ 1 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/ParentPlatform.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/ParentPlatform.java

@@ -1,4 +1,4 @@
-package org.jetlinks.community.media.entity;
+package org.jetlinks.community.media.bean;
 
 public class ParentPlatform {
 

+ 1 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/PlatformCatalog.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/PlatformCatalog.java

@@ -1,4 +1,4 @@
-package org.jetlinks.community.media.entity;
+package org.jetlinks.community.media.bean;
 
 import lombok.Data;
 

+ 1 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/PlatformGbStream.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/PlatformGbStream.java

@@ -1,4 +1,4 @@
-package org.jetlinks.community.media.entity;
+package org.jetlinks.community.media.bean;
 
 import lombok.Data;
 

+ 1 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/SipServerConfig.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/SipServerConfig.java

@@ -1,4 +1,4 @@
-package org.jetlinks.community.media.entity;
+package org.jetlinks.community.media.bean;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.EqualsAndHashCode;

+ 3 - 2
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/session/SsrcConfig.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/SsrcConfig.java

@@ -1,11 +1,12 @@
-package org.jetlinks.community.media.session;
+package org.jetlinks.community.media.bean;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 import java.util.Set;
 
-public class SsrcConfig {
+public class SsrcConfig implements Serializable {
 
     /**
      * 播流最大并发个数

+ 2 - 2
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/WvpSipDate.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/WvpSipDate.java

@@ -1,4 +1,4 @@
-package org.jetlinks.community.media.entity;
+package org.jetlinks.community.media.bean;
 
 import gov.nist.core.InternalErrorHandler;
 import gov.nist.javax.sip.header.SIPDate;
@@ -14,7 +14,7 @@ public class WvpSipDate extends SIPDate {
      *
      */
     private static final long serialVersionUID = 1L;
-    
+
     private Calendar javaCal;
 
     public WvpSipDate(long timeMillis) {

+ 219 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/config/MediaConfig.java

@@ -0,0 +1,219 @@
+package org.jetlinks.community.media.config;
+
+import lombok.Data;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.util.StringUtils;
+
+import java.text.SimpleDateFormat;
+
+@Data
+@ConfigurationProperties(prefix = "media")
+public class MediaConfig {
+
+//    //@Value("${media.id:}")
+    private String id;
+
+    //@Value("${media.ip}")
+    private String ip;
+
+    //@Value("${media.hook-ip:${sip.ip}}")
+    private String hookIp;
+
+    //@Value("${sip.ip}")
+    private String sipIp;
+
+    //@Value("${sip.domain}")
+    private String sipDomain;
+
+    //@Value("${media.sdp-ip:${media.ip}}")
+    private String sdpIp;
+
+    //@Value("${media.stream-ip:${media.ip}}")
+    private String streamIp;
+
+    //@Value("${media.http-port}")
+    private Integer httpPort;
+
+    //@Value("${media.http-ssl-port:0}")
+    private Integer httpSSlPort = 0;
+
+    //@Value("${media.rtmp-port:0}")
+    private Integer rtmpPort = 0;
+
+    //@Value("${media.rtmp-ssl-port:0}")
+    private Integer rtmpSSlPort = 0;
+
+    //@Value("${media.rtp-proxy-port:0}")
+    private Integer rtpProxyPort = 0;
+
+    //@Value("${media.rtsp-port:0}")
+    private Integer rtspPort = 0;
+
+    //@Value("${media.rtsp-ssl-port:0}")
+    private Integer rtspSSLPort = 0;
+
+    //@Value("${media.auto-config:true}")
+    private boolean autoConfig = true;
+
+    //@Value("${media.secret}")
+    private String secret;
+
+    //@Value("${media.stream-none-reader-delay-ms:18000}")
+    private int streamNoneReaderDelayMS = 18000;
+
+    //@Value("${media.rtp.enable}")
+    private boolean rtpEnable;
+
+    //@Value("${media.rtp.port-range}")
+    private String rtpPortRange;
+
+
+    //@Value("${media.rtp.send-port-range}")
+    private String sendRtpPortRange;
+
+    //@Value("${media.record-assist-port:0}")
+    private Integer recordAssistPort = 0;
+
+    public String getId() {
+        return id;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public String getHookIp() {
+        if (StringUtils.isEmpty(hookIp)){
+            return sipIp;
+        }else {
+            return hookIp;
+        }
+
+    }
+
+    public String getSipIp() {
+        if (sipIp == null) {
+            return this.ip;
+        }else {
+            return sipIp;
+        }
+    }
+
+    public int getHttpPort() {
+        return httpPort;
+    }
+
+    public int getHttpSSlPort() {
+        return httpSSlPort;
+    }
+
+    public int getRtmpPort() {
+        return rtmpPort;
+    }
+
+    public int getRtmpSSlPort() {
+        return rtmpSSlPort;
+    }
+
+    public int getRtpProxyPort() {
+        if (rtpProxyPort == null) {
+            return 0;
+        }else {
+            return rtpProxyPort;
+        }
+
+    }
+
+    public int getRtspPort() {
+        return rtspPort;
+    }
+
+    public int getRtspSSLPort() {
+        return rtspSSLPort;
+    }
+
+    public boolean isAutoConfig() {
+        return autoConfig;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public int getStreamNoneReaderDelayMS() {
+        return streamNoneReaderDelayMS;
+    }
+
+    public boolean isRtpEnable() {
+        return rtpEnable;
+    }
+
+    public String getRtpPortRange() {
+        return rtpPortRange;
+    }
+
+    public int getRecordAssistPort() {
+        return recordAssistPort;
+    }
+
+    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 String getSipDomain() {
+        return sipDomain;
+    }
+
+    public String getSendRtpPortRange() {
+        return sendRtpPortRange;
+    }
+
+    private MediaServerItem mediaServerItem;
+    public MediaServerItem getMediaSerItem(){
+        if(mediaServerItem!=null){
+            return mediaServerItem;
+        }
+        MediaServerItem mediaServerItem = new MediaServerItem();
+        mediaServerItem.setId(id);
+        mediaServerItem.setIp(ip);
+        mediaServerItem.setDefaultServer(true);
+        mediaServerItem.setHookIp(getHookIp());
+        mediaServerItem.setSdpIp(getSdpIp());
+        mediaServerItem.setStreamIp(getStreamIp());
+        mediaServerItem.setHttpPort(httpPort);
+        mediaServerItem.setHttpSSlPort(httpSSlPort);
+        mediaServerItem.setRtmpPort(rtmpPort);
+        mediaServerItem.setRtmpSSlPort(rtmpSSlPort);
+        mediaServerItem.setRtpProxyPort(getRtpProxyPort());
+        mediaServerItem.setRtspPort(rtspPort);
+        mediaServerItem.setRtspSSLPort(rtspSSLPort);
+        mediaServerItem.setAutoConfig(autoConfig);
+        mediaServerItem.setSecret(secret);
+        mediaServerItem.setStreamNoneReaderDelayMS(streamNoneReaderDelayMS);
+        mediaServerItem.setRtpEnable(rtpEnable);
+        mediaServerItem.setRtpPortRange(rtpPortRange);
+        mediaServerItem.setSendRtpPortRange(sendRtpPortRange);
+        mediaServerItem.setRecordAssistPort(recordAssistPort);
+        mediaServerItem.setHookAliveInterval(120);
+
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        mediaServerItem.setCreateTime(format.format(System.currentTimeMillis()));
+        mediaServerItem.setUpdateTime(format.format(System.currentTimeMillis()));
+
+        return mediaServerItem;
+    }
+
+}

+ 16 - 7
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/contanst/VideoManagerConstants.java

@@ -1,21 +1,27 @@
 package org.jetlinks.community.media.contanst;
 
-/**    
- * @description: 定义常量   
+/**
+ * @description: 定义常量
  * @author: swwheihei
- * @date:   2019年5月30日 下午3:04:04   
- *   
+ * @date:   2019年5月30日 下午3:04:04
+ *
  */
 public class VideoManagerConstants {
-	
+
 	public static final String WVP_SERVER_PREFIX = "VMP_SIGNALLING_SERVER_INFO_";
 
 	public static final String WVP_SERVER_STREAM_PREFIX = "VMP_SIGNALLING_STREAM_";
 
+    /**
+     * 存储zlm媒体流服务器信息
+     */
 	public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_";
 
 	public static final String MEDIA_SERVER_KEEPALIVE_PREFIX = "VMP_MEDIA_SERVER_KEEPALIVE_";
 
+    /**
+     * zlm在线媒体服务器信息
+     */
 	public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS_";
 
 	public static final String MEDIA_STREAM_PREFIX = "VMP_MEDIA_STREAM";
@@ -27,6 +33,9 @@ public class VideoManagerConstants {
 	public static final String KEEPLIVEKEY_PREFIX = "VMP_KEEPALIVE_";
 
 	// 此处多了一个_,暂不修改
+    /**
+     * 点播流前缀
+     */
 	public static final String PLAYER_PREFIX = "VMP_PLAYER_";
 	public static final String PLAY_BLACK_PREFIX = "VMP_PLAYBACK_";
 
@@ -43,13 +52,13 @@ public class VideoManagerConstants {
 	public static final String PLATFORM_SEND_RTP_INFO_PREFIX = "VMP_PLATFORM_SEND_RTP_INFO_";
 
 	public static final String EVENT_ONLINE_REGISTER = "1";
-	
+
 	public static final String EVENT_ONLINE_KEEPLIVE = "2";
 
 	public static final String EVENT_ONLINE_MESSAGE = "3";
 
 	public static final String EVENT_OUTLINE_UNREGISTER = "1";
-	
+
 	public static final String EVENT_OUTLINE_TIMEOUT = "2";
 
 	public static final String MEDIA_SSRC_USED_PREFIX = "VMP_MEDIA_USED_SSRC_";

+ 2 - 2
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/controller/ApiStreamController.java

@@ -10,8 +10,8 @@
 //import org.jetlinks.community.media.entity.DeviceChannel;
 //import org.jetlinks.community.media.entity.MediaDevice;
 //import org.jetlinks.community.media.enums.DeviceState;
-//import org.jetlinks.community.media.vmanager.gb28181.play.bean.PlayResult;
-//import org.jetlinks.community.media.zlm.dto.MediaServerItem;
+//import org.jetlinks.community.media.gb28181.result.PlayResult;
+//import org.jetlinks.community.media.zlm.entity.MediaServerItem;
 //import org.jetlinks.community.utils.SipUtils;
 //import org.redisson.api.RedissonClient;
 //import org.springframework.web.bind.annotation.GetMapping;

+ 332 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/controller/PlayController.java

@@ -0,0 +1,332 @@
+package org.jetlinks.community.media.controller;
+
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.web.authorization.annotation.QueryAction;
+import org.hswebframework.web.authorization.annotation.Resource;
+import org.hswebframework.web.exception.BusinessException;
+import org.jetlinks.community.media.service.LocalMediaDeviceService;
+import org.jetlinks.community.media.service.LocalPlayService;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Mono;
+
+
+@RestController
+@RequestMapping("/device/directives")
+@Slf4j
+//@Authorize
+@Resource(id="gb28181-play",name = "国标设备点播")
+@AllArgsConstructor
+@Tag(name = "设备下发指令")
+public class PlayController {
+    //
+//	private final static Logger logger = LoggerFactory.getLogger(com.genersoft.iot.vmp.vmanager.gb28181.play.PlayController.class);
+//
+//	@Autowired
+//	private SIPCommander cmder;
+//
+//	@Autowired
+//	private VideoStreamSessionManager streamSession;
+//
+//	@Autowired
+//	private IVideoManagerStorager storager;
+//
+//	@Autowired
+//	private IRedisCatchStorage redisCatchStorage;
+//
+//	@Autowired
+//	private ZLMRESTfulUtils zlmresTfulUtils;
+//
+//	@Autowired
+//	private DeferredResultHolder resultHolder;
+//
+//	@Autowired
+//	private IPlayService playService;
+//
+//	@Autowired
+//	private IMediaService mediaService;
+//
+//	@Autowired
+//	private IMediaServerService mediaServerService;
+//
+    private final LocalMediaDeviceService mediaDeviceService;
+
+    private final LocalPlayService playService;
+
+    @QueryAction
+    @Operation(summary = "设备点播")
+    @GetMapping("/start/{deviceId}/{channelId}")
+//    public Mono<ResponseEntity<String>> play(@PathVariable String deviceId,
+//                                             @PathVariable String channelId) {
+        public Mono<?> play(@PathVariable String deviceId,
+            @PathVariable String channelId) {
+
+
+        return
+            //获取设备信息
+            mediaDeviceService.findById(deviceId)
+                //获取设备相连的媒体流服务器信息
+                .flatMap(playService::getNewMediaServerItem)
+                .switchIfEmpty(Mono.error(new BusinessException("未找到可用的zlm媒体服务器")))
+                .flatMap(mediaServerItem ->
+                    playService.play(mediaServerItem, deviceId, channelId, null, null)
+                );
+
+//        return playResult.getResult();
+    }
+//
+//	@ApiOperation("停止点播")
+//	@ApiImplicitParams({
+//			@ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class),
+//			@ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class),
+//	})
+//	@GetMapping("/stop/{deviceId}/{channelId}")
+//	public DeferredResult<ResponseEntity<String>> playStop(@PathVariable String deviceId, @PathVariable String channelId) {
+//
+//		logger.debug(String.format("设备预览/回放停止API调用,streamId:%s_%s", deviceId, channelId ));
+//
+//		String uuid = UUID.randomUUID().toString();
+//		DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>();
+//
+//		// 录像查询以channelId作为deviceId查询
+//		String key = DeferredResultHolder.CALLBACK_CMD_STOP + deviceId + channelId;
+//		resultHolder.put(key, uuid, result);
+//		Device device = storager.queryVideoDevice(deviceId);
+//		cmder.streamByeCmd(deviceId, channelId, (event) -> {
+//			StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
+//			if (streamInfo == null) {
+//				RequestMessage msg = new RequestMessage();
+//				msg.setId(uuid);
+//				msg.setKey(key);
+//				msg.setData("点播未找到");
+//				resultHolder.invokeAllResult(msg);
+//				storager.stopPlay(deviceId, channelId);
+//			}else {
+//				redisCatchStorage.stopPlay(streamInfo);
+//				storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
+//				RequestMessage msg = new RequestMessage();
+//				msg.setId(uuid);
+//				msg.setKey(key);
+//				//Response response = event.getResponse();
+//				msg.setData(String.format("success"));
+//				resultHolder.invokeAllResult(msg);
+//			}
+//			mediaServerService.closeRTPServer(device, channelId);
+//		});
+//
+//		if (deviceId != null || channelId != null) {
+//			JSONObject json = new JSONObject();
+//			json.put("deviceId", deviceId);
+//			json.put("channelId", channelId);
+//			RequestMessage msg = new RequestMessage();
+//			msg.setId(uuid);
+//			msg.setKey(key);
+//			msg.setData(json.toString());
+//			resultHolder.invokeAllResult(msg);
+//		} else {
+//			logger.warn("设备预览/回放停止API调用失败!");
+//			RequestMessage msg = new RequestMessage();
+//			msg.setId(uuid);
+//			msg.setKey(key);
+//			msg.setData("streamId null");
+//			resultHolder.invokeAllResult(msg);
+//		}
+//
+//		// 超时处理
+//		result.onTimeout(()->{
+//			logger.warn(String.format("设备预览/回放停止超时,deviceId/channelId:%s_%s ", deviceId, channelId));
+//			RequestMessage msg = new RequestMessage();
+//			msg.setId(uuid);
+//			msg.setKey(key);
+//			msg.setData("Timeout");
+//			resultHolder.invokeAllResult(msg);
+//		});
+//		return result;
+//	}
+//
+//	/**
+//	 * 将不是h264的视频通过ffmpeg 转码为h264 + aac
+//	 * @param streamId 流ID
+//	 * @return
+//	 */
+//	@ApiOperation("将不是h264的视频通过ffmpeg 转码为h264 + aac")
+//	@ApiImplicitParams({
+//			@ApiImplicitParam(name = "streamId", value = "视频流ID", dataTypeClass = String.class),
+//	})
+//	@PostMapping("/convert/{streamId}")
+//	public ResponseEntity<String> playConvert(@PathVariable String streamId) {
+//		StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
+//		if (streamInfo == null) {
+//			streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
+//		}
+//		if (streamInfo == null) {
+//			logger.warn("视频转码API调用失败!, 视频流已经停止!");
+//			return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK);
+//		}
+//		MediaServerItem 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 {
+//			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(mediaInfo, srcUrl, dstUrl, "1000000", true, false, null);
+//			logger.info(jsonObject.toJSONString());
+//			JSONObject result = new JSONObject();
+//			if (jsonObject != null && jsonObject.getInteger("code") == 0) {
+//				   result.put("code", 0);
+//				JSONObject data = jsonObject.getJSONObject("data");
+//				if (data != null) {
+//				   	result.put("key", data.getString("key"));
+//					StreamInfo streamInfoResult = mediaService.getStreamInfoByAppAndStreamWithCheck("convert", streamId, mediaInfo.getId());
+//					result.put("data", streamInfoResult);
+//				}
+//			}else {
+//				result.put("code", 1);
+//				result.put("msg", "cover fail");
+//			}
+//			return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
+//		}
+//	}
+//
+//	/**
+//	 * 结束转码
+//	 * @param key
+//	 * @return
+//	 */
+//	@ApiOperation("结束转码")
+//	@ApiImplicitParams({
+//			@ApiImplicitParam(name = "key", value = "视频流key", dataTypeClass = String.class),
+//	})
+//	@PostMapping("/convertStop/{key}")
+//	public ResponseEntity<String> playConvertStop(@PathVariable String key, String mediaServerId) {
+//		JSONObject result = new JSONObject();
+//		if (mediaServerId == null) {
+//			result.put("code", 400);
+//			result.put("msg", "mediaServerId is null");
+//			return new ResponseEntity<String>( result.toJSONString(), HttpStatus.BAD_REQUEST);
+//		}
+//		MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+//		if (mediaInfo == null) {
+//			result.put("code", 0);
+//			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");
+//			}
+//			return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK);
+//		}
+//
+//
+//	}
+//
+//	@ApiOperation("语音广播命令")
+//	@ApiImplicitParams({
+//			@ApiImplicitParam(name = "deviceId", value = "设备Id", dataTypeClass = String.class),
+//	})
+//    @GetMapping("/broadcast/{deviceId}")
+//    @PostMapping("/broadcast/{deviceId}")
+//    public DeferredResult<ResponseEntity<String>> broadcastApi(@PathVariable String deviceId) {
+//        if (logger.isDebugEnabled()) {
+//            logger.debug("语音广播API调用");
+//        }
+//        Device device = storager.queryVideoDevice(deviceId);
+//		DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(3 * 1000L);
+//		String key  = DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId;
+//		if (resultHolder.exist(key, null)) {
+//			result.setResult(new ResponseEntity<>("设备使用中", HttpStatus.OK));
+//			return result;
+//		}
+//		String uuid  = UUID.randomUUID().toString();
+//        if (device == null) {
+//
+//			resultHolder.put(key, key,  result);
+//			RequestMessage msg = new RequestMessage();
+//			msg.setKey(key);
+//			msg.setId(uuid);
+//			JSONObject json = new JSONObject();
+//			json.put("DeviceID", deviceId);
+//			json.put("CmdType", "Broadcast");
+//			json.put("Result", "Failed");
+//			json.put("Description", "Device 不存在");
+//			msg.setData(json);
+//			resultHolder.invokeResult(msg);
+//			return result;
+//		}
+//		cmder.audioBroadcastCmd(device, (event) -> {
+//			RequestMessage msg = new RequestMessage();
+//			msg.setKey(key);
+//			msg.setId(uuid);
+//			JSONObject json = new JSONObject();
+//			json.put("DeviceID", deviceId);
+//			json.put("CmdType", "Broadcast");
+//			json.put("Result", "Failed");
+//			json.put("Description", String.format("语音广播操作失败,错误码: %s, %s", event.statusCode, event.msg));
+//			msg.setData(json);
+//			resultHolder.invokeResult(msg);
+//		});
+//
+//		result.onTimeout(() -> {
+//			logger.warn(String.format("语音广播操作超时, 设备未返回应答指令"));
+//			RequestMessage msg = new RequestMessage();
+//			msg.setKey(key);
+//			msg.setId(uuid);
+//			JSONObject json = new JSONObject();
+//			json.put("DeviceID", deviceId);
+//			json.put("CmdType", "Broadcast");
+//			json.put("Result", "Failed");
+//			json.put("Error", "Timeout. Device did not response to broadcast command.");
+//			msg.setData(json);
+//			resultHolder.invokeResult(msg);
+//		});
+//		resultHolder.put(key, uuid, result);
+//		return result;
+//	}
+//
+//	@ApiOperation("获取所有的ssrc")
+//	@GetMapping("/ssrc")
+//	public WVPResult<JSONObject> getSSRC() {
+//		if (logger.isDebugEnabled()) {
+//			logger.debug("获取所有的ssrc");
+//		}
+//		JSONArray objects = new JSONArray();
+//		List<SsrcTransaction> allSsrc = streamSession.getAllSsrc();
+//		for (SsrcTransaction transaction : allSsrc) {
+//			JSONObject jsonObject = new JSONObject();
+//			jsonObject.put("deviceId", transaction.getDeviceId());
+//			jsonObject.put("channelId", transaction.getChannelId());
+//			jsonObject.put("ssrc", transaction.getSsrc());
+//			jsonObject.put("streamId", transaction.getStreamId());
+//			objects.add(jsonObject);
+//		}
+//
+//		WVPResult<JSONObject> result = new WVPResult<>();
+//		result.setCode(0);
+//		result.setMsg("success");
+//		JSONObject jsonObject = new JSONObject();
+//		jsonObject.put("data", objects);
+//		jsonObject.put("count", objects.size());
+//		result.setData(jsonObject);
+//		return result;
+//	}
+//
+}
+

+ 0 - 199
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/DeviceChannel.java

@@ -1,199 +0,0 @@
-package org.jetlinks.community.media.entity;
-
-import lombok.Data;
-
-@Data
-public class DeviceChannel {
-
-
-
-	/**
-	 * 通道id
-	 */
-	private String channelId;
-
-	/**
-	 * 设备id
-	 */
-	private String deviceId;
-	
-	/**
-	 * 通道名
-	 */
-	private String name;
-	
-	/**
-	 * 生产厂商
-	 */
-	private String manufacture;
-	
-	/**
-	 * 型号
-	 */
-	private String model;
-	
-	/**
-	 * 设备归属
-	 */
-	private String owner;
-	
-	/**
-	 * 行政区域
-	 */
-	private String civilCode;
-	
-	/**
-	 * 警区
-	 */
-	private String block;
-
-	/**
-	 * 安装地址
-	 */
-	private String address;
-	
-	/**
-	 * 是否有子设备 1有, 0没有
-	 */
-	private int parental;
-	
-	/**
-	 * 父级id
-	 */
-	private String parentId;
-	
-	/**
-	 * 信令安全模式  缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式
-	 */
-	private int safetyWay;
-	
-	/**
-	 * 注册方式 缺省为1;1:符合IETFRFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式
-	 */
-	private int registerWay;
-	
-	/**
-	 * 证书序列号
-	 */
-	private String certNum;
-	
-	/**
-	 * 证书有效标识 缺省为0;证书有效标识:0:无效1: 有效
-	 */
-	private int certifiable;
-	
-	/**
-	 * 证书无效原因码
-	 */
-	private int errCode;
-	
-	/**
-	 * 证书终止有效期
-	 */
-	private String endTime;
-	
-	/**
-	 * 保密属性 缺省为0; 0:不涉密, 1:涉密
-	 */
-	private String secrecy;
-	
-	/**
-	 * IP地址
-	 */
-	private String ipAddress;
-	
-	/**
-	 * 端口号
-	 */
-	private int port;
-	
-	/**
-	 * 密码
-	 */
-	private String password;
-
-	/**
-	 * 云台类型
-	 */
-	private int PTZType;
-
-	/**
-	 * 云台类型描述字符串
-	 */
-	private String PTZTypeText;
-
-	/**
-	 * 创建时间
-	 */
-	private String createTime;
-
-	/**
-	 * 更新时间
-	 */
-	private String updateTime;
-	
-	/**
-	 * 在线/离线
-	 * 1在线,0离线
-	 * 默认在线
-	 * 信令:
-	 * <Status>ON</Status>
-	 * <Status>OFF</Status>
-	 * 遇到过NVR下的IPC下发信令可以推流, 但是 Status 响应 OFF
-	 */
-	private int status;
-
-	/**
-	 * 经度
-	 */
-	private double longitude;
-	
-	/**
-	 * 纬度
-	 */
-	private double latitude;
-
-	/**
-	 * 子设备数
-	 */
-	private int subCount;
-
-	/**
-	 * 流唯一编号,存在表示正在直播
-	 */
-	private String  streamId;
-
-	/**
-	 *  是否含有音频
-	 */
-	private boolean hasAudio;
-
-	public String getDeviceId() {
-		return deviceId;
-	}
-
-	public void setDeviceId(String deviceId) {
-		this.deviceId = deviceId;
-	}
-
-	public void setPTZType(int PTZType) {
-		this.PTZType = PTZType;
-		switch (PTZType) {
-			case 0:
-				this.PTZTypeText = "未知";
-				break;
-			case 1:
-				this.PTZTypeText = "球机";
-				break;
-			case 2:
-				this.PTZTypeText = "半球";
-				break;
-			case 3:
-				this.PTZTypeText = "固定枪机";
-				break;
-			case 4:
-				this.PTZTypeText = "遥控枪机";
-				break;
-		}
-	}
-}

+ 323 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/MediaDeviceChannel.java

@@ -0,0 +1,323 @@
+package org.jetlinks.community.media.entity;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.hswebframework.web.api.crud.entity.GenericEntity;
+
+import javax.persistence.Column;
+import javax.persistence.Table;
+
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Table(name = "media_device_channel", schema = "媒体设备通道")
+public class MediaDeviceChannel extends GenericEntity<String> {
+
+
+
+	/**
+	 * 通道id
+	 */
+    @Column(name = "channel_id")
+    @Schema(
+        description = "通道id"
+    )
+	private String channelId;
+
+	/**
+	 * 设备id
+	 */
+    @Column(name = "device_id")
+    @Schema(
+        description = "设备id"
+    )
+	private String deviceId;
+
+	/**
+	 * 通道名
+	 */
+    @Column(name = "name")
+    @Schema(
+        description = "通道名"
+    )
+	private String name;
+
+	/**
+	 * 生产厂商
+	 */
+    @Column(name = "manufacture")
+    @Schema(
+        description = "生产厂商"
+    )
+	private String manufacture;
+
+	/**
+	 * 型号
+	 */
+    @Column(name = "model")
+    @Schema(
+        description = "型号"
+    )
+	private String model;
+
+	/**
+	 * 设备归属
+	 */
+    @Column(name = "owner")
+    @Schema(
+        description = "设备归属"
+    )
+	private String owner;
+
+	/**
+	 * 行政区域
+	 */
+    @Column(name = "civil_code")
+    @Schema(
+        description = "行政区域"
+    )
+	private String civilCode;
+
+	/**
+	 * 警区
+	 */
+    @Column(name = "block")
+    @Schema(
+        description = "警区"
+    )
+	private String block;
+
+	/**
+	 * 安装地址
+	 */
+    @Column(name = "address")
+    @Schema(
+        description = "安装地址"
+    )
+	private String address;
+
+	/**
+	 * 是否有子设备 1有, 0没有
+	 */
+    @Column(name = "parental")
+    @Schema(
+        description = "是否有子设备"
+    )
+	private int parental;
+
+	/**
+	 * 父级id
+	 */
+    @Column(name = "parent_id")
+    @Schema(
+        description = "父级id"
+    )
+	private String parentId;
+
+	/**
+	 * 信令安全模式  缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式
+	 */
+    @Column(name = "safety_way")
+    @Schema(
+        description = "信令安全模式,缺省为0; 0:不采用; 2: S/MIME签名方式; 3: S/ MIME加密签名同时采用方式; 4:数字摘要方式"
+    )
+	private int safetyWay;
+
+	/**
+	 * 注册方式 缺省为1;1:符合IETFRFC3261标准的认证注册模 式; 2:基于口令的双向认证注册模式; 3:基于数字证书的双向认证注册模式
+	 */
+    @Column(name = "register_way")
+    @Schema(
+        description = "注册方式"
+    )
+	private int registerWay;
+
+	/**
+	 * 证书序列号
+	 */
+    @Column(name = "cert_num")
+    @Schema(
+        description = "证书序列号"
+    )
+	private String certNum;
+
+	/**
+	 * 证书有效标识 缺省为0;证书有效标识:0:无效1: 有效
+	 */
+    @Column(name = "certifiable")
+    @Schema(
+        description = "证书有效标识"
+    )
+	private int certifiable;
+
+	/**
+	 * 证书无效原因码
+	 */
+    @Column(name = "err_code")
+    @Schema(
+        description = "证书无效原因码"
+    )
+	private int errCode;
+
+	/**
+	 * 证书终止有效期
+	 */
+    @Column(name = "end_time")
+    @Schema(
+        description = "证书终止有效期"
+    )
+	private String endTime;
+
+	/**
+	 * 保密属性 缺省为0; 0:不涉密, 1:涉密
+	 */
+    @Column(name = "secrecy")
+    @Schema(
+        description = "保密属性"
+    )
+	private String secrecy;
+
+	/**
+	 * IP地址
+	 */
+    @Column(name = "ip_address")
+    @Schema(
+        description = "IP地址"
+    )
+	private String ipAddress;
+
+	/**
+	 * 端口号
+	 */
+    @Column(name = "port")
+    @Schema(
+        description = "端口号"
+    )
+	private int port;
+
+	/**
+	 * 密码
+	 */
+    @Column(name = "password")
+    @Schema(
+        description = "密码"
+    )
+	private String password;
+
+	/**
+	 * 云台类型
+	 */
+    @Column(name = "PTZ_type")
+    @Schema(
+        description = "云台类型"
+    )
+	private int PTZType;
+
+	/**
+	 * 云台类型描述字符串
+	 */
+    @Column(name = "PTZ_type_text")
+    @Schema(
+        description = "云台类型描述字符串"
+    )
+	private String PTZTypeText;
+
+	/**
+	 * 创建时间
+	 */
+    @Column(name = "create_time")
+    @Schema(
+        description = "创建时间"
+    )
+	private String createTime;
+
+	/**
+	 * 更新时间
+	 */
+    @Column(name = "update_time")
+    @Schema(
+        description = "更新时间"
+    )
+	private String updateTime;
+
+	/**
+	 * 在线/离线
+	 * 1在线,0离线
+	 * 默认在线
+	 * 信令:
+	 * <Status>ON</Status>
+	 * <Status>OFF</Status>
+	 * 遇到过NVR下的IPC下发信令可以推流, 但是 Status 响应 OFF
+	 */
+    @Column(name = "status")
+    @Schema(
+        description = "在线/离线"
+    )
+	private int status;
+
+	/**
+	 * 经度
+	 */
+    @Column(name = "longitude")
+    @Schema(
+        description = "经度"
+    )
+	private double longitude;
+
+	/**
+	 * 纬度
+	 */
+    @Column(name = "latitude")
+    @Schema(
+        description = "纬度"
+    )
+	private double latitude;
+
+	/**
+	 * 子设备数
+	 */
+    @Column(name = "sub_count")
+    @Schema(
+        description = "子设备数"
+    )
+	private int subCount;
+
+	/**
+	 * 流唯一编号,存在表示正在直播
+	 */
+    @Column(name = "stream_id")
+    @Schema(
+        description = "流唯一编号,存在表示正在直播"
+    )
+	private String  streamId;
+
+	/**
+	 *  是否含有音频
+	 */
+    @Column(name = "has_audio")
+    @Schema(
+        description = "是否含有音频"
+    )
+	private boolean hasAudio;
+
+	public void setPTZType(int PTZType) {
+		this.PTZType = PTZType;
+		switch (PTZType) {
+			case 0:
+				this.PTZTypeText = "未知";
+				break;
+			case 1:
+				this.PTZTypeText = "球机";
+				break;
+			case 2:
+				this.PTZTypeText = "半球";
+				break;
+			case 3:
+				this.PTZTypeText = "固定枪机";
+				break;
+			case 4:
+				this.PTZTypeText = "遥控枪机";
+				break;
+		}
+	}
+}

+ 68 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/bean/SsrcTransaction.java

@@ -0,0 +1,68 @@
+package org.jetlinks.community.media.gb28181.bean;
+
+public class SsrcTransaction {
+
+    private String deviceId;
+    private String channelId;
+    private String ssrc;
+    private String streamId;
+    private byte[] transaction;
+    private byte[] dialog;
+    private String mediaServerId;
+
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public String getChannelId() {
+        return channelId;
+    }
+
+    public void setChannelId(String channelId) {
+        this.channelId = channelId;
+    }
+
+    public String getSsrc() {
+        return ssrc;
+    }
+
+    public void setSsrc(String ssrc) {
+        this.ssrc = ssrc;
+    }
+
+    public String getStreamId() {
+        return streamId;
+    }
+
+    public void setStreamId(String streamId) {
+        this.streamId = streamId;
+    }
+
+    public byte[] getTransaction() {
+        return transaction;
+    }
+
+    public void setTransaction(byte[] transaction) {
+        this.transaction = transaction;
+    }
+
+    public byte[] getDialog() {
+        return dialog;
+    }
+
+    public void setDialog(byte[] dialog) {
+        this.dialog = dialog;
+    }
+
+    public String getMediaServerId() {
+        return mediaServerId;
+    }
+
+    public void setMediaServerId(String mediaServerId) {
+        this.mediaServerId = mediaServerId;
+    }
+}

+ 140 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/event/SipSubscribe.java

@@ -0,0 +1,140 @@
+package org.jetlinks.community.media.gb28181.event;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.community.media.bean.DeviceNotFoundEvent;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.sip.*;
+import javax.sip.header.CallIdHeader;
+import javax.sip.message.Response;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+@Component
+@Slf4j
+public class SipSubscribe {
+
+
+    private Map<String, Event> errorSubscribes = new ConcurrentHashMap<>();
+
+    private Map<String, Event> okSubscribes = new ConcurrentHashMap<>();
+
+    private Map<String, Date> timeSubscribes = new ConcurrentHashMap<>();
+
+//    @Scheduled(cron="*/5 * * * * ?")   //每五秒执行一次
+//    @Scheduled(fixedRate= 100 * 60 * 60 )
+    @Scheduled(cron="0 0 * * * ?")   //每小时执行一次, 每个整点
+    public void execute(){
+        log.info("[定时任务] 清理过期的订阅信息");
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(new Date());
+        calendar.set(Calendar.HOUR, calendar.get(Calendar.HOUR) - 1);
+        for (String key : timeSubscribes.keySet()) {
+            if (timeSubscribes.get(key).before(calendar.getTime())){
+                log.info("[定时任务] 清理过期的订阅信息: {}", key);
+                errorSubscribes.remove(key);
+                okSubscribes.remove(key);
+                timeSubscribes.remove(key);
+            }
+        }
+    }
+
+    public interface Event extends Consumer<EventResult> {
+
+    }
+
+    public static class EventResult<EventObject>{
+        public int statusCode;
+        public String type;
+        public String msg;
+        public String callId;
+        public Dialog dialog;
+        public EventObject event;
+
+        public EventResult() {
+        }
+
+        public EventResult(EventObject event) {
+            this.event = event;
+            if (event instanceof ResponseEvent) {
+                ResponseEvent responseEvent = (ResponseEvent)event;
+                Response response = responseEvent.getResponse();
+                this.dialog = responseEvent.getDialog();
+                this.type = "response";
+                if (response != null) {
+                    this.msg = response.getReasonPhrase();
+                    this.statusCode = response.getStatusCode();
+                }
+                this.callId = ((CallIdHeader)response.getHeader(CallIdHeader.NAME)).getCallId();
+
+            }else if (event instanceof TimeoutEvent) {
+                TimeoutEvent timeoutEvent = (TimeoutEvent)event;
+                this.type = "timeout";
+                this.msg = "消息超时未回复";
+                this.statusCode = -1024;
+                this.callId = timeoutEvent.getClientTransaction().getDialog().getCallId().getCallId();
+                this.dialog = timeoutEvent.getClientTransaction().getDialog();
+            }else if (event instanceof TransactionTerminatedEvent) {
+                TransactionTerminatedEvent transactionTerminatedEvent = (TransactionTerminatedEvent)event;
+                this.type = "transactionTerminated";
+                this.msg = "事务已结束";
+                this.statusCode = -1024;
+                this.callId = transactionTerminatedEvent.getClientTransaction().getDialog().getCallId().getCallId();
+                this.dialog = transactionTerminatedEvent.getClientTransaction().getDialog();
+            }else if (event instanceof DialogTerminatedEvent) {
+                DialogTerminatedEvent dialogTerminatedEvent = (DialogTerminatedEvent)event;
+                this.type = "dialogTerminated";
+                this.msg = "会话已结束";
+                this.statusCode = -1024;
+                this.callId = dialogTerminatedEvent.getDialog().getCallId().getCallId();
+                this.dialog = dialogTerminatedEvent.getDialog();
+            }else if (event instanceof DeviceNotFoundEvent) {
+                DeviceNotFoundEvent deviceNotFoundEvent = (DeviceNotFoundEvent)event;
+                this.type = "deviceNotFoundEvent";
+                this.msg = "设备未找到";
+                this.statusCode = -1024;
+                this.callId = deviceNotFoundEvent.getDialog().getCallId().getCallId();
+                this.dialog = deviceNotFoundEvent.getDialog();
+            }
+        }
+    }
+
+    public void addErrorSubscribe(String key, SipSubscribe.Event event) {
+        errorSubscribes.put(key, event);
+        timeSubscribes.put(key, new Date());
+    }
+
+    public void addOkSubscribe(String key, SipSubscribe.Event event) {
+        okSubscribes.put(key, event);
+        timeSubscribes.put(key, new Date());
+    }
+
+    public SipSubscribe.Event getErrorSubscribe(String key) {
+        return errorSubscribes.get(key);
+    }
+
+    public void removeErrorSubscribe(String key) {
+        errorSubscribes.remove(key);
+        timeSubscribes.remove(key);
+    }
+
+    public SipSubscribe.Event getOkSubscribe(String key) {
+        return okSubscribes.get(key);
+    }
+
+    public void removeOkSubscribe(String key) {
+        okSubscribes.remove(key);
+        timeSubscribes.remove(key);
+    }
+    public int getErrorSubscribesSize(){
+        return errorSubscribes.size();
+    }
+    public int getOkSubscribesSize(){
+        return okSubscribes.size();
+    }
+}

+ 1 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/vmanager/gb28181/play/bean/PlayResult.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/result/PlayResult.java

@@ -1,4 +1,4 @@
-package org.jetlinks.community.media.vmanager.gb28181.play.bean;
+package org.jetlinks.community.media.gb28181.result;
 
 import lombok.Data;
 import org.jetlinks.community.media.entity.MediaDevice;

+ 13 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/result/WVPResult.java

@@ -0,0 +1,13 @@
+package org.jetlinks.community.media.gb28181.result;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor(staticName = "of")
+public class WVPResult<T> {
+
+    private int code;
+    private String msg;
+    private T data;
+}

+ 35 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalMediaDeviceChannelService.java

@@ -0,0 +1,35 @@
+package org.jetlinks.community.media.service;
+
+import lombok.AllArgsConstructor;
+import org.hswebframework.web.crud.service.GenericReactiveCrudService;
+import org.jetlinks.community.media.entity.MediaDeviceChannel;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+/**
+ * @author lifang
+ * @projectName jetlinks-community
+ * @description: TODO
+ * @date 2022/2/6  1:56
+ */
+@Service
+@AllArgsConstructor
+public class LocalMediaDeviceChannelService extends GenericReactiveCrudService<MediaDeviceChannel, String> {
+    public Mono<Void> startPlay(String deviceId, String channelId, String streamId) {
+        return this.createUpdate()
+            .set(MediaDeviceChannel::getStreamId,streamId)
+            .where(MediaDeviceChannel::getDeviceId,deviceId)
+            .where(MediaDeviceChannel::getChannelId,channelId)
+            .execute()
+            .then();
+    }
+
+    public Mono<Void> stopPlay(String deviceId, String channelId) {
+        return this.createUpdate()
+            .set(MediaDeviceChannel::getStreamId,null)
+            .where(MediaDeviceChannel::getDeviceId,deviceId)
+            .where(MediaDeviceChannel::getChannelId,channelId)
+            .execute()
+            .then();
+    }
+}

+ 1 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalMediaDeviceService.java

@@ -5,7 +5,7 @@ import lombok.AllArgsConstructor;
 import org.hswebframework.web.crud.service.GenericReactiveCrudService;
 import org.jetlinks.community.gateway.annotation.Subscribe;
 import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.SipServerConfig;
+import org.jetlinks.community.media.bean.SipServerConfig;
 import org.jetlinks.community.media.enums.DeviceState;
 import org.jetlinks.community.media.sip.SipContext;
 import org.jetlinks.community.utils.SipUtils;

+ 711 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalMediaServerItemService.java

@@ -0,0 +1,711 @@
+package org.jetlinks.community.media.service;
+
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.web.crud.service.GenericReactiveCrudService;
+import org.jetlinks.community.media.bean.SSRCInfo;
+import org.jetlinks.community.media.bean.StreamInfo;
+import org.jetlinks.community.media.contanst.VideoManagerConstants;
+import org.jetlinks.community.media.bean.SsrcConfig;
+import org.jetlinks.community.media.entity.MediaDevice;
+import org.jetlinks.community.media.session.VideoStreamSessionManager;
+import org.jetlinks.community.media.zlm.ZLMRESTfulUtils;
+import org.jetlinks.community.media.zlm.ZLMRTPServerFactory;
+import org.jetlinks.community.media.zlm.ZLMServerConfig;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
+import org.jetlinks.community.utils.RedisUtil;
+import org.jetlinks.core.cluster.ClusterManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 媒体服务器节点管理
+ */
+@Service
+@Slf4j
+public class LocalMediaServerItemService extends GenericReactiveCrudService<MediaServerItem, String>  implements CommandLineRunner {
+
+//    private final SipConfig sipConfig;
+
+    @Value("${server.ssl.enabled:false}")
+    private boolean sslEnabled;
+
+    @Value("${server.port}")
+    private Integer serverPort;
+
+
+    private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+    private final RedisUtil redisUtil;
+
+    private final String serverId;
+
+    private final ZLMRESTfulUtils zlmresTfulUtils;
+
+    private final ZLMRTPServerFactory zlmrtpServerFactory;
+
+    private final VideoStreamSessionManager streamSession;
+    @Autowired
+    public LocalMediaServerItemService(
+        ClusterManager clusterManager,
+        RedisUtil redisUtil,
+        ZLMRESTfulUtils zlmresTfulUtils,
+        ZLMRTPServerFactory zlmrtpServerFactory,
+        VideoStreamSessionManager streamSession)  {
+        this.serverId= clusterManager.getCurrentServerId();
+        this.redisUtil=redisUtil;
+        this.zlmresTfulUtils=zlmresTfulUtils;
+        this.zlmrtpServerFactory=zlmrtpServerFactory;
+        this.streamSession=streamSession;
+    }
+
+    //    @Autowired
+//    private final UserSetup userSetup;
+//
+
+//
+//    @Autowired
+//    private  final MediaServerMapper mediaServerMapper;
+//
+//    @Autowired
+//    private  final VideoStreamSessionManager streamSession;
+//
+//
+//    @Autowired
+//    private final  RedisUtil redisUtil;
+//
+//    @Autowired
+//    private  final IVideoManagerStorager storager;
+//
+//    @Autowired
+//    private final  IStreamProxyService streamProxyService;
+//
+//    @Autowired
+//    private  final EventPublisher publisher;
+//
+//    @Autowired
+//    JedisUtil jedisUtil;
+
+    /**
+     * 初始化媒体流服务器信息,将所有信息放入缓存中
+     */
+    @Override
+    public void run(String... args) throws Exception {
+        log.info("[缓存初始化] Media Server ");
+        //查询所有媒体服务器数据
+        this.createQuery()
+            .fetch()
+            .filter(item->StrUtil.isNotEmpty(item.getId()))
+            .doOnNext(mediaServerItem->{
+                if (mediaServerItem.getSsrcConfig() == null) {
+                    //对ssrc进行更新 todo
+                    SsrcConfig ssrcConfig = new SsrcConfig(mediaServerItem.getId(), null,"340200000");
+                    mediaServerItem.setSsrcConfig(ssrcConfig);
+                }
+            })
+            .doOnNext(mediaServerItem->redisUtil.set(VideoManagerConstants.MEDIA_SERVER_PREFIX + serverId + "_" + mediaServerItem.getId(),mediaServerItem))
+            .subscribe();
+    }
+
+
+
+    public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId) {
+        return openRTPServer(mediaServerItem, streamId, false);
+    }
+
+    public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean isPlayback) {
+        if (mediaServerItem == null || mediaServerItem.getId() == null) {
+            return null;
+        }
+        // 获取mediaServer可用的ssrc
+        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + serverId + "_" + mediaServerItem.getId();
+
+        SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig();
+        if (ssrcConfig == null) {
+            log.info("media server [ {} ] ssrcConfig is null", mediaServerItem.getId());
+            return null;
+        }else {
+            String ssrc = null;
+            if (isPlayback) {
+                ssrc = ssrcConfig.getPlayBackSsrc();
+            }else {
+                ssrc = ssrcConfig.getPlaySsrc();
+            }
+
+            if (streamId == null) {
+                streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
+            }
+            int rtpServerPort = mediaServerItem.getRtpProxyPort();
+            if (mediaServerItem.isRtpEnable()) {
+                rtpServerPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId);
+            }
+            redisUtil.set(key, mediaServerItem);
+            return SSRCInfo.of(rtpServerPort, ssrc, streamId);
+        }
+    }
+
+
+    public void closeRTPServer(MediaDevice device, String channelId) {
+        String mediaServerId = streamSession.getMediaServerId(device.getId(), channelId);
+        MediaServerItem mediaServerItem = this.getOne(mediaServerId);
+        if (mediaServerItem != null) {
+            //更新sstc信息
+            String streamId = String.format("%s_%s", device.getId(), channelId);
+            zlmrtpServerFactory.closeRTPServer(mediaServerItem, streamId);
+            releaseSsrc(mediaServerItem, streamSession.getSSRC(device.getId(), channelId));
+        }
+        streamSession.remove(device.getId(), channelId);
+    }
+
+
+    public void releaseSsrc(MediaServerItem mediaServerItem, String ssrc) {
+        if (mediaServerItem == null || ssrc == null) {
+            return;
+        }
+        SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig();
+        ssrcConfig.releaseSsrc(ssrc);
+        mediaServerItem.setSsrcConfig(ssrcConfig);
+        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + serverId+ "_" + mediaServerItem.getId();
+        redisUtil.set(key, mediaServerItem);
+    }
+
+    /**
+     * zlm 重启后重置他的推流信息, TODO 给正在使用的设备发送停止命令
+     */
+    private void clearRTPServer(MediaServerItem mediaServerItem) {
+//        mediaServerItem.setSsrcConfig(new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getDomain()));
+        mediaServerItem.setSsrcConfig(new SsrcConfig(mediaServerItem.getId(), null,"340200000"));
+        redisUtil.zAdd(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + serverId, mediaServerItem.getId(), 0);
+    }
+//
+//
+//    @Override
+//    public void update(MediaServerItem mediaSerItem) {
+//        mediaServerMapper.update(mediaSerItem);
+//        MediaServerItem mediaServerItemInRedis = getOne(mediaSerItem.getId());
+//        MediaServerItem mediaServerItemInDataBase = mediaServerMapper.queryOne(mediaSerItem.getId());
+//        if (mediaServerItemInRedis != null && mediaServerItemInRedis.getSsrcConfig() != null) {
+//            mediaServerItemInDataBase.setSsrcConfig(mediaServerItemInRedis.getSsrcConfig());
+//        }else {
+//            mediaServerItemInDataBase.setSsrcConfig(
+//                    new SsrcConfig(
+//                            mediaServerItemInDataBase.getId(),
+//                            null,
+//                            sipConfig.getDomain()
+//                    )
+//            );
+//        }
+//        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetup.getServerId() + "_" + mediaServerItemInDataBase.getId();
+//        redisUtil.set(key, mediaServerItemInDataBase);
+//    }
+//
+//    @Override
+//    public List<MediaServerItem> getAll() {
+//        List<MediaServerItem> result = new ArrayList<>();
+//        List<Object> mediaServerKeys = redisUtil.scan(String.format("%S*", VideoManagerConstants.MEDIA_SERVER_PREFIX+ userSetup.getServerId() + "_" ));
+//        String onlineKey = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetup.getServerId();
+//        for (Object mediaServerKey : mediaServerKeys) {
+//            String key = (String) mediaServerKey;
+//            MediaServerItem mediaServerItem = (MediaServerItem) redisUtil.get(key);
+//            // 检查状态
+//            if (redisUtil.zScore(onlineKey, mediaServerItem.getId()) != null) {
+//                mediaServerItem.setStatus(true);
+//            }
+//            result.add(mediaServerItem);
+//        }
+//        result.sort((serverItem1, serverItem2)->{
+//            int sortResult = 0;
+//            try {
+//                sortResult = format.parse(serverItem1.getCreateTime()).compareTo(format.parse(serverItem2.getCreateTime()));
+//            } catch (ParseException e) {
+//                e.printStackTrace();
+//            }
+//            return  sortResult;
+//        });
+//        return result;
+//    }
+//
+//
+//    @Override
+//    public List<MediaServerItem> getAllFromDatabase() {
+//        return mediaServerMapper.queryAll();
+//    }
+//
+//    @Override
+//    public List<MediaServerItem> getAllOnline() {
+//        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetup.getServerId();
+//        Set<String> mediaServerIdSet = redisUtil.zRevRange(key, 0, -1);
+//
+//        List<MediaServerItem> result = new ArrayList<>();
+//        if (mediaServerIdSet != null && mediaServerIdSet.size() > 0) {
+//            for (String mediaServerId : mediaServerIdSet) {
+//                String serverKey = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetup.getServerId() + "_" + mediaServerId;
+//                result.add((MediaServerItem) redisUtil.get(serverKey));
+//            }
+//        }
+//        Collections.reverse(result);
+//        return result;
+//    }
+//
+    /**
+     * 获取单个zlm服务器
+     * @param mediaServerId 服务id
+     * @return MediaServerItem
+     */
+    public MediaServerItem getOne(String mediaServerId) {
+        if (mediaServerId == null) {
+            return null;
+        }
+        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + serverId + "_" + mediaServerId;
+        return (MediaServerItem)redisUtil.get(key);
+    }
+//
+//    @Override
+//    public MediaServerItem getOneByHostAndPort(String host, int port) {
+//        return mediaServerMapper.queryOneByHostAndPort(host, port);
+//    }
+//
+
+    //todo
+    public Mono<MediaServerItem> getDefaultMediaServer() {
+//        return mediaServerMapper.queryDefault();
+        return Mono.empty();
+    }
+
+    public Mono<Void> clearMediaServerForOnline() {
+        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + serverId;
+        return Mono.fromRunnable(()->redisUtil.del(key));
+    }
+//
+//    @Override
+//    public WVPResult<String> add(MediaServerItem mediaServerItem) {
+//        WVPResult<String> result = new WVPResult<>();
+//        mediaServerItem.setCreateTime(this.format.format(System.currentTimeMillis()));
+//        mediaServerItem.setUpdateTime(this.format.format(System.currentTimeMillis()));
+//        mediaServerItem.setHookAliveInterval(120);
+//        JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
+//        if (responseJSON != null) {
+//            JSONArray data = responseJSON.getJSONArray("data");
+//            if (data != null && data.size() > 0) {
+//                ZLMServerConfig zlmServerConfig= JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class);
+//                if (mediaServerMapper.queryOne(zlmServerConfig.getGeneralMediaServerId()) != null) {
+//                    result.setCode(-1);
+//                    result.setMsg("保存失败,媒体服务ID [ " + zlmServerConfig.getGeneralMediaServerId() + " ] 已存在,请修改媒体服务器配置");
+//                    return result;
+//                }
+//                mediaServerItem.setId(zlmServerConfig.getGeneralMediaServerId());
+//                zlmServerConfig.setIp(mediaServerItem.getIp());
+//                mediaServerMapper.add(mediaServerItem);
+//                zlmServerOnline(zlmServerConfig);
+//                result.setCode(0);
+//                result.setMsg("success");
+//            }else {
+//                result.setCode(-1);
+//                result.setMsg("连接失败");
+//            }
+//
+//        }else {
+//            result.setCode(-1);
+//            result.setMsg("连接失败");
+//        }
+//       return result;
+//    }
+
+    /**
+     * 处理zlm上线 todo
+     * @param zlmServerConfig zlm上线携带的参数
+     */
+    public Mono<Void> zlmServerOnline(ZLMServerConfig zlmServerConfig) {
+        log.info("[ ZLM:{} ]-[ {}:{} ]已连接",
+            zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
+        //根据id主键查询
+        return this.findById(zlmServerConfig.getGeneralMediaServerId())
+            .switchIfEmpty(this.createQuery()
+                .where(MediaServerItem::getIp,zlmServerConfig.getIp())
+                .where(MediaServerItem::getHttpPort,zlmServerConfig.getHttpPort())
+                .fetchOne())
+            .switchIfEmpty(Mono.fromRunnable(()->
+                log.warn("[未注册的zlm] 拒接接入:来自{}:{}", zlmServerConfig.getIp(),zlmServerConfig.getHttpPort() )).
+                then(Mono.empty()))
+            .doOnNext(serverItem->{
+                serverItem.setHookAliveInterval(zlmServerConfig.getHookAliveInterval());
+                if (serverItem.getHttpPort() == 0) {
+                    serverItem.setHttpPort(zlmServerConfig.getHttpPort());
+                }
+                if (serverItem.getHttpSSlPort() == 0) {
+                    serverItem.setHttpSSlPort(zlmServerConfig.getHttpSSLport());
+                }
+                if (serverItem.getRtmpPort() == 0) {
+                    serverItem.setRtmpPort(zlmServerConfig.getRtmpPort());
+                }
+                if (serverItem.getRtmpSSlPort() == 0) {
+                    serverItem.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());
+                }
+                if (serverItem.getRtspPort() == 0) {
+                    serverItem.setRtspPort(zlmServerConfig.getRtspPort());
+                }
+                if (serverItem.getRtspSSLPort() == 0) {
+                    serverItem.setRtspSSLPort(zlmServerConfig.getRtspSSlport());
+                }
+                if (serverItem.getRtpProxyPort() == 0) {
+                    serverItem.setRtpProxyPort(zlmServerConfig.getRtpProxyPort());
+                }
+                serverItem.setStatus(true);
+            })
+            //更新zlm服务器信息
+            .doOnNext(serverItem->{
+                if (StrUtil.isEmpty(serverItem.getId())) {
+                    serverItem.setId(zlmServerConfig.getGeneralMediaServerId());
+                    this.createUpdate()
+                        .set(serverItem)
+                        .where(MediaServerItem::getIp,zlmServerConfig.getIp())
+                        .where(MediaServerItem::getHttpPort,zlmServerConfig.getHttpPort())
+                        .execute()
+                        .subscribe();
+                }else {
+                    this.createUpdate()
+                        .set(serverItem)
+                        .where(MediaServerItem::getId,serverItem.getId())
+                        .execute()
+                        .subscribe();
+                }
+            })
+            //更新zlm服务器缓存信息
+            .doOnNext(serverItem->{
+                String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + serverId + "_" + zlmServerConfig.getGeneralMediaServerId();
+                if (redisUtil.get(key) == null) {
+                    //todo
+//                    SsrcConfig ssrcConfig = new SsrcConfig(zlmServerConfig.getGeneralMediaServerId(), null, sipconfig.getDomain());
+                    SsrcConfig ssrcConfig = new SsrcConfig(zlmServerConfig.getGeneralMediaServerId(), null," sipconfig.getDomain()");
+                    serverItem.setSsrcConfig(ssrcConfig);
+                }else {
+                    MediaServerItem mediaServerItemInRedis = (MediaServerItem)redisUtil.get(key);
+                    serverItem.setSsrcConfig(mediaServerItemInRedis.getSsrcConfig());
+                }
+                redisUtil.set(key, serverItem);
+            })
+            //更新zlm服务器在线信息
+            .doOnNext(this::resetOnlineServerItem)
+            .doOnNext(serverItem-> setZLMConfig(serverItem, "0".equals(zlmServerConfig.getHookEnable())))
+            //更新zlm服务器存活心跳
+            .flatMap(serverItem -> updateMediaServerKeepalive(serverItem.getId(), null))
+
+            .then()
+
+
+            ;
+        //todo
+//        publisher.zlmOnlineEventPublish(serverItem.getId());
+
+    }
+//
+//
+//    @Override
+//    public void zlmServerOffline(String mediaServerId) {
+//        delete(mediaServerId);
+//    }
+//
+
+    private void resetOnlineServerItem(MediaServerItem serverItem) {
+        // 更新缓存
+        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + serverId;
+        // 使用zset的分数作为当前并发量, 默认值设置为0
+        if (redisUtil.zScore(key, serverItem.getId()) == null) {  // 不存在则设置默认值 已存在则重置
+            redisUtil.zAdd(key, serverItem.getId(), 0L);
+            // 查询服务流数量
+            zlmresTfulUtils.getMediaList(serverItem, null, null, "rtmp",(mediaList ->{
+                Integer code = mediaList.getInt("code");
+                if (code == 0) {
+                    JSONArray data = mediaList.getJSONArray("data");
+                    if (data != null) {
+                        redisUtil.zAdd(key, serverItem.getId(), data.size());
+                    }
+                }
+            }));
+        }else {
+            clearRTPServer(serverItem);
+        }
+
+    }
+
+    /**
+     * 媒体服务器通道在线数
+     * @param mediaServerId
+     */
+    public Mono<Void> addCount(String mediaServerId) {
+        return Mono.fromRunnable(()->{
+            if (mediaServerId == null) {
+                return;
+            }
+            String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + serverId;
+            redisUtil.zIncrScore(key, mediaServerId, 1);
+        });
+    }
+
+    public Mono<Void> removeCount(String mediaServerId) {
+        return Mono.fromRunnable(()->{
+            String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + serverId;
+            redisUtil.zIncrScore(key, mediaServerId, - 1);
+        });
+
+    }
+
+    /**
+     * 获取负载最低的节点
+     * @return MediaServerItem
+     */
+    public Mono<MediaServerItem> getMediaServerForMinimumLoad() {
+        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + serverId;
+
+        if (redisUtil.zSize(key)  == null || redisUtil.zSize(key) == 0) {
+            log.info("获取负载最低的节点时无在线节点");
+            return null;
+        }
+
+        // 获取分数最低的,及并发最低的
+        Set<Object> objects = redisUtil.ZRange(key, 0, -1);
+        ArrayList<Object> mediaServerObjectS = new ArrayList<>(objects);
+
+        String mediaServerId = (String)mediaServerObjectS.get(0);
+        return Mono.just(getOne(mediaServerId));
+    }
+
+    /**
+     * 对zlm服务器进行基础配置
+     * @param mediaServerItem 服务ID
+     * @param restart 是否重启zlm
+     */
+    public void setZLMConfig(MediaServerItem mediaServerItem, boolean restart) {
+        log.info("[ ZLM:{} ]-[ {}:{} ]设置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.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrex));
+        param.put("hook.timeoutSec","20");
+        param.put("general.streamNoneReaderDelayMS","-1".equals(mediaServerItem.getStreamNoneReaderDelayMS())?"3600000":mediaServerItem.getStreamNoneReaderDelayMS() );
+
+        JSONObject responseJSON = zlmresTfulUtils.setServerConfig(mediaServerItem, param);
+
+        if (responseJSON != null && responseJSON.getInt("code") == 0) {
+            if (restart) {
+                log.info("[ ZLM:{} ]-[ {}:{} ]设置zlm成功, 开始重启以保证配置生效",
+                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
+                zlmresTfulUtils.restartServer(mediaServerItem);
+            }else {
+                log.info("[ ZLM:{} ]-[ {}:{} ]设置zlm成功",
+                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
+            }
+
+
+        }else {
+            log.info("[ ZLM:{} ]-[ {}:{} ]设置zlm失败",
+                mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
+        }
+
+
+    }
+//
+//
+//    @Override
+//    public WVPResult<MediaServerItem> checkMediaServer(String ip, int port, String secret) {
+//        WVPResult<MediaServerItem> result = new WVPResult<>();
+//        if (mediaServerMapper.queryOneByHostAndPort(ip, port) != null) {
+//            result.setCode(-1);
+//            result.setMsg("此连接已存在");
+//            return result;
+//        }
+//        MediaServerItem mediaServerItem = new MediaServerItem();
+//        mediaServerItem.setIp(ip);
+//        mediaServerItem.setHttpPort(port);
+//        mediaServerItem.setSecret(secret);
+//        JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
+//        if (responseJSON == null) {
+//            result.setCode(-1);
+//            result.setMsg("连接失败");
+//            return result;
+//        }
+//        JSONArray data = responseJSON.getJSONArray("data");
+//        ZLMServerConfig zlmServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class);
+//        if (zlmServerConfig == null) {
+//            result.setCode(-1);
+//            result.setMsg("读取配置失败");
+//            return result;
+//        }
+//        if (mediaServerMapper.queryOne(zlmServerConfig.getGeneralMediaServerId()) != null) {
+//            result.setCode(-1);
+//            result.setMsg("媒体服务ID [" + zlmServerConfig.getGeneralMediaServerId() + " ] 已存在,请修改媒体服务器配置");
+//            return result;
+//        }
+//        mediaServerItem.setHttpSSlPort(zlmServerConfig.getHttpPort());
+//        mediaServerItem.setRtmpPort(zlmServerConfig.getRtmpPort());
+//        mediaServerItem.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());
+//        mediaServerItem.setRtspPort(zlmServerConfig.getRtspPort());
+//        mediaServerItem.setRtspSSLPort(zlmServerConfig.getRtspSSlport());
+//        mediaServerItem.setRtpProxyPort(zlmServerConfig.getRtpProxyPort());
+//        mediaServerItem.setStreamIp(ip);
+//        mediaServerItem.setHookIp(sipConfig.getIp());
+//        mediaServerItem.setSdpIp(ip);
+//        mediaServerItem.setStreamNoneReaderDelayMS(zlmServerConfig.getGeneralStreamNoneReaderDelayMS());
+//        result.setCode(0);
+//        result.setMsg("成功");
+//        result.setData(mediaServerItem);
+//        return result;
+//    }
+//
+//    @Override
+//    public boolean checkMediaRecordServer(String ip, int port) {
+//        boolean result = false;
+//        OkHttpClient client = new OkHttpClient();
+//        String url = String.format("http://%s:%s/index/api/record",  ip, port);
+//
+//        FormBody.Builder builder = new FormBody.Builder();
+//
+//        Request request = new Request.Builder()
+//                .get()
+//                .url(url)
+//                .build();
+//        try {
+//            Response response = client.newCall(request).execute();
+//            if (response != null) {
+//                result = true;
+//            }
+//        } catch (Exception e) {}
+//
+//        return result;
+//    }
+//
+//    @Override
+//    public void delete(String id) {
+//        redisUtil.zRemove(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetup.getServerId(), id);
+//        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetup.getServerId() + "_" + id;
+//        redisUtil.del(key);
+//    }
+//
+
+    /**
+     * 设置ZML服务器的心跳时间
+     * @param mediaServerId
+     * @param data
+     * @return
+     */
+    public Mono<Void> updateMediaServerKeepalive(String mediaServerId, JSONObject data) {
+        return this.findById(mediaServerId)
+            .doOnNext(mediaServerItem -> {
+                String key = VideoManagerConstants.MEDIA_SERVER_KEEPALIVE_PREFIX +serverId + "_" + mediaServerId;
+                int hookAliveInterval = mediaServerItem.getHookAliveInterval() + 2;
+                redisUtil.set(key, data, hookAliveInterval);
+            })
+            .then();
+    }
+
+    public Mono<StreamInfo> getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks) {
+        return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null);
+    }
+
+
+    public Mono<StreamInfo> getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, String addr) {
+        StreamInfo streamInfo = null;
+        return Mono.just(mediaServerId)
+            .flatMap(this::findById)
+            .switchIfEmpty(this.getDefaultMediaServer())
+            .flatMap(mediaServerItem -> {
+                JSONObject mediaList = zlmresTfulUtils.getMediaList(mediaServerItem, app, stream);
+                if (mediaList != null) {
+                    if (mediaList.getInt("code") == 0) {
+                        JSONArray data = mediaList.getJSONArray("data");
+                        if (data == null) {
+                            return null;
+                        }
+                        JSONObject mediaJSON = JSONUtil.parseObj(JSONUtil.toJsonStr(data.get(0)));
+                        JSONArray tracks = mediaJSON.getJSONArray("tracks");
+                        return getStreamInfoByAppAndStream(mediaServerItem, app, stream, tracks);
+                    }
+                }
+                return Mono.empty();
+            });
+    }
+
+
+
+
+    public  Mono<StreamInfo> getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId) {
+        return getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, null);
+    }
+
+
+    public Mono<StreamInfo> getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object 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));
+        if (mediaInfo.getRtmpSSlPort() != 0) {
+            streamInfoResult.setRtmps(String.format("rtmps://%s:%s/%s/%s", addr, mediaInfo.getRtmpSSlPort(), app,  stream));
+        }
+        streamInfoResult.setRtsp(String.format("rtsp://%s:%s/%s/%s", addr, mediaInfo.getRtspPort(), app,  stream));
+        if (mediaInfo.getRtspSSLPort() != 0) {
+            streamInfoResult.setRtsps(String.format("rtsps://%s:%s/%s/%s", addr, mediaInfo.getRtspSSLPort(), app,  stream));
+        }
+        streamInfoResult.setFlv(String.format("http://%s:%s/%s/%s.flv", addr, mediaInfo.getHttpPort(), app,  stream));
+        streamInfoResult.setWs_flv(String.format("ws://%s:%s/%s/%s.flv", addr, mediaInfo.getHttpPort(), app,  stream));
+        streamInfoResult.setHls(String.format("http://%s:%s/%s/%s/hls.m3u8", addr, mediaInfo.getHttpPort(), app,  stream));
+        streamInfoResult.setWs_hls(String.format("ws://%s:%s/%s/%s/hls.m3u8", addr, mediaInfo.getHttpPort(), app,  stream));
+        streamInfoResult.setFmp4(String.format("http://%s:%s/%s/%s.live.mp4", addr, mediaInfo.getHttpPort(), app,  stream));
+        streamInfoResult.setWs_fmp4(String.format("ws://%s:%s/%s/%s.live.mp4", addr, mediaInfo.getHttpPort(), app,  stream));
+        streamInfoResult.setTs(String.format("http://%s:%s/%s/%s.live.ts", addr, mediaInfo.getHttpPort(), app,  stream));
+        streamInfoResult.setWs_ts(String.format("ws://%s:%s/%s/%s.live.ts", addr, mediaInfo.getHttpPort(), app,  stream));
+        if (mediaInfo.getHttpSSlPort() != 0) {
+            streamInfoResult.setHttps_flv(String.format("https://%s:%s/%s/%s.flv", addr, mediaInfo.getHttpSSlPort(), app,  stream));
+            streamInfoResult.setWss_flv(String.format("wss://%s:%s/%s/%s.flv", addr, mediaInfo.getHttpSSlPort(), app,  stream));
+            streamInfoResult.setHttps_hls(String.format("https://%s:%s/%s/%s/hls.m3u8", addr, mediaInfo.getHttpSSlPort(), app,  stream));
+            streamInfoResult.setWss_hls(String.format("wss://%s:%s/%s/%s/hls.m3u8", addr, mediaInfo.getHttpSSlPort(), app,  stream));
+            streamInfoResult.setHttps_fmp4(String.format("https://%s:%s/%s/%s.live.mp4", addr, mediaInfo.getHttpSSlPort(), app,  stream));
+            streamInfoResult.setWss_fmp4(String.format("wss://%s:%s/%s/%s.live.mp4", addr, mediaInfo.getHttpSSlPort(), app,  stream));
+            streamInfoResult.setHttps_ts(String.format("https://%s:%s/%s/%s.live.ts", addr, mediaInfo.getHttpSSlPort(), app,  stream));
+            streamInfoResult.setWss_ts(String.format("wss://%s:%s/%s/%s.live.ts", addr, mediaInfo.getHttpSSlPort(), app,  stream));
+            streamInfoResult.setWss_ts(String.format("wss://%s:%s/%s/%s.live.ts", addr, mediaInfo.getHttpSSlPort(), app,  stream));
+            streamInfoResult.setRtc(String.format("https://%s:%s/index/api/webrtc?app=%s&stream=%s&type=play", mediaInfo.getStreamIp(), mediaInfo.getHttpSSlPort(), app,  stream));
+        }
+
+        streamInfoResult.setTracks(tracks);
+        return Mono.just(streamInfoResult);
+    }
+
+}

+ 267 - 294
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalPlayService.java

@@ -1,293 +1,265 @@
-//package org.jetlinks.community.media.service;
-//
-//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.UserSetup;
-//import com.genersoft.iot.vmp.gb28181.bean.Device;
-//import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
-//import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
-//import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
-//import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
-//import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
-//import com.genersoft.iot.vmp.gb28181.transmit.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.MediaServerItem;
-//import com.genersoft.iot.vmp.service.IMediaServerService;
-//import com.genersoft.iot.vmp.service.bean.SSRCInfo;
-//import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-//import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
-//import com.genersoft.iot.vmp.utils.redis.RedisUtil;
-//import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
-//import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
-//import com.genersoft.iot.vmp.service.IMediaService;
-//import com.genersoft.iot.vmp.service.IPlayService;
-//import gov.nist.javax.sip.stack.SIPDialog;
-//import lombok.AllArgsConstructor;
-//import lombok.extern.slf4j.Slf4j;
-//import org.jetlinks.community.media.zlm.dto.MediaServerItem;
-//import org.redisson.api.RedissonClient;
-//import org.slf4j.Logger;
-//import org.slf4j.LoggerFactory;
-//import org.springframework.beans.factory.annotation.Autowired;
-//import org.springframework.http.HttpStatus;
-//import org.springframework.http.ResponseEntity;
-//import org.springframework.stereotype.Service;
-//import org.springframework.util.ResourceUtils;
-//import org.springframework.web.context.request.async.DeferredResult;
-//
-//import java.io.FileNotFoundException;
-//import java.util.Objects;
-//import java.util.UUID;
-//
-//@SuppressWarnings(value = {"rawtypes", "unchecked"})
-//@Service
-//@Slf4j
-//@AllArgsConstructor
-//public class LocalPlayService  {
-//
-//
-//    private final RedissonClient redissonClient;
-////    @Autowired
+package org.jetlinks.community.media.service;
+
+import cn.hutool.core.lang.UUID;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import com.alibaba.fastjson.JSON;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.web.exception.BusinessException;
+import org.jetlinks.community.media.bean.SSRCInfo;
+import org.jetlinks.community.media.bean.StreamInfo;
+import org.jetlinks.community.media.entity.MediaDeviceChannel;
+import org.jetlinks.community.media.entity.MediaDevice;
+import org.jetlinks.community.media.gb28181.result.WVPResult;
+import org.jetlinks.community.media.session.VideoStreamSessionManager;
+import org.jetlinks.community.media.storage.impl.RedisCatchStorageImpl;
+import org.jetlinks.community.media.transmit.callback.DeferredResultHolder;
+import org.jetlinks.community.media.transmit.callback.RequestMessage;
+import org.jetlinks.community.media.transmit.cmd.SipCommander;
+import org.jetlinks.community.media.gb28181.result.PlayResult;
+import org.jetlinks.community.media.zlm.ZLMHttpHookSubscribe;
+import org.jetlinks.community.media.zlm.ZLMRESTfulUtils;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
+import org.jetlinks.core.cluster.ClusterEventBus;
+import org.jetlinks.core.event.EventBus;
+import org.jetlinks.core.event.Subscription;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.util.ResourceUtils;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import java.io.FileNotFoundException;
+import java.time.Duration;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@SuppressWarnings(value = {"rawtypes", "unchecked"})
+@Service
+@Slf4j
+@AllArgsConstructor
+public class LocalPlayService  {
+
+    ////    @Autowired
 ////    private IVideoManagerStorager storager;
 //
-////    @Autowired
-////    private SIPCommander cmder;
-//
-////    @Autowired
-////    private IRedisCatchStorage redisCatchStorage;
-////
-////    @Autowired
+    private final SipCommander cmder;
+
+    private final RedisCatchStorageImpl redisCatchStorage;
+
+    private final LocalMediaServerItemService mediaServerItemService;
+
+    private final EventBus eventBus;
+
+    private final ClusterEventBus clusterEventBus;
+
+    private final VideoStreamSessionManager streamSession;
+
+    private final LocalMediaDeviceChannelService deviceChannelService;
+    //    @Autowired
 ////    private RedisUtil redis;
 ////
-////    @Autowired
-////    private DeferredResultHolder resultHolder;
-////
-////    @Autowired
-////    private ZLMRESTfulUtils zlmresTfulUtils;
-////
-////    @Autowired
-////    private IMediaService mediaService;
-////
-////    @Autowired
-////    private IMediaServerService mediaServerService;
-////
-////    @Autowired
-////    private VideoStreamSessionManager streamSession;
-////
-////    @Autowired
-////    private UserSetup userSetup;
+    private DeferredResultHolder resultHolder;
+
+    private final ZLMRESTfulUtils zlmresTfulUtils;
+
 //
-//
-//
-//    public Mono<PlayResult> play(MediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent) {
-//        PlayResult playResult = new PlayResult();
-//        RequestMessage msg = new RequestMessage();
-//        String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
-//        msg.setKey(key);
-//        String uuid = UUID.randomUUID().toString();
-//        msg.setId(uuid);
-//        playResult.setUuid(uuid);
-//        DeferredResult<ResponseEntity<String>> result = new DeferredResult<>(userSetup.getPlayTimeout());
-//        playResult.setResult(result);
-//        // 录像查询以channelId作为deviceId查询
-//        resultHolder.put(key, uuid, result);
-//        if (mediaServerItem == null) {
-//            WVPResult wvpResult = new WVPResult();
-//            wvpResult.setCode(-1);
-//            wvpResult.setMsg("未找到可用的zlm");
-//            msg.setData(wvpResult);
-//            resultHolder.invokeResult(msg);
-//            return playResult;
-//        }
-//        Device device = redisCatchStorage.getDevice(deviceId);
-//        StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
-//        playResult.setDevice(device);
-//        // 超时处理
-//        result.onTimeout(()->{
-//            log.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId));
-//            WVPResult wvpResult = new WVPResult();
-//            wvpResult.setCode(-1);
-//            SIPDialog dialog = streamSession.getDialog(deviceId, channelId);
-//            if (dialog != null) {
-//                wvpResult.setMsg("收流超时,请稍候重试");
-//            }else {
-//                wvpResult.setMsg("点播超时,请稍候重试");
-//            }
-//            msg.setData(wvpResult);
-//            // 点播超时回复BYE
-//            cmder.streamByeCmd(device.getDeviceId(), channelId);
-//            // 释放rtpserver
-//            mediaServerService.closeRrpServer(playResult.getDevice(), channelId);
-//            // 回复之前所有的点播请求
-//            resultHolder.invokeAllResult(msg);
-//        });
-//        result.onCompletion(()->{
-//            // 点播结束时调用截图接口
-//            try {
-//                String classPath = ResourceUtils.getURL("classpath:").getPath();
-//                // System.out.println(classPath);
-//                // 兼容打包为jar的class路径
-//                if(classPath.contains("jar")) {
-//                    classPath = classPath.substring(0, classPath.lastIndexOf("."));
-//                    classPath = classPath.substring(0, classPath.lastIndexOf("/") + 1);
-//                }
-//                if (classPath.startsWith("file:")) {
-//                    classPath = classPath.substring(classPath.indexOf(":") + 1);
-//                }
-//                String path = classPath + "static/static/snap/";
-//                // 兼容Windows系统路径(去除前面的“/”)
-//                if(System.getProperty("os.name").contains("indows")) {
-//                    path = path.substring(1);
-//                }
-//                String fileName =  deviceId + "_" + channelId + ".jpg";
-//                ResponseEntity responseEntity =  (ResponseEntity)result.getResult();
-//                if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
-//                    WVPResult wvpResult = (WVPResult)responseEntity.getBody();
-//                    if (Objects.requireNonNull(wvpResult).getCode() == 0) {
-//                        StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
-//                        MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId());
-//                        String streamUrl = streamInfoForSuccess.getFmp4();
-//                        // 请求截图
-//                        log.info("[请求截图]: " + fileName);
-//                        zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName);
-//                    }
-//                }
-//            } catch (FileNotFoundException e) {
-//                e.printStackTrace();
-//            }
-//        });
-//        if (streamInfo == null) {
-//            SSRCInfo ssrcInfo;
-//            String streamId = null;
-//            if (mediaServerItem.isRtpEnable()) {
-//                streamId = String.format("%s_%s", device.getDeviceId(), channelId);
-//            }
-//
-//            ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId);
-//
-//            // 发送点播消息
-//            cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
-//                log.info("收到订阅消息: " + response.toJSONString());
-//                onPublishHandlerForPlay(mediaServerItemInUse, response, deviceId, channelId, uuid);
-//                if (hookEvent != null) {
-//                    hookEvent.response(mediaServerItem, response);
-//                }
-//            }, (event) -> {
-//                WVPResult wvpResult = new WVPResult();
-//                wvpResult.setCode(-1);
-//                // 点播返回sip错误
-//                mediaServerService.closeRrpServer(playResult.getDevice(), channelId);
-//                wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg));
-//                msg.setData(wvpResult);
-//                resultHolder.invokeAllResult(msg);
-//                if (errorEvent != null) {
+//    @Autowired
+//    private UserSetup userSetup;
+
+    public Mono<ResponseEntity> play(MediaServerItem mediaServerItem, String deviceId, String channelId,
+                                     ZLMHttpHookSubscribe.Event hookEvent, ZLMHttpHookSubscribe.Event errorEvent) {
+        PlayResult playResult = new PlayResult();
+
+        String msgId=UUID.randomUUID().toString();
+
+        RequestMessage msg = RequestMessage.of(deviceId,channelId,msgId,null);
+
+        final AtomicBoolean start=new AtomicBoolean(false);
+
+        return eventBus.subscribe(
+            Subscription
+                .builder()
+                .topics(DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg))
+                .features(Subscription.Feature.local)
+                .build())
+            //超时报错 todo
+            .timeout(Duration.ofSeconds(10), Mono.error(new BusinessException("点播/收流超时,请稍候重试")))
+            //报错时,发送bye指令
+            .flatMap(payload -> Mono.just(payload.bodyToJson(true)))
+
+            .cast(ResponseEntity.class)
+            //判断回复是否有效
+            .flatMap(response->{
+                if(response==null){
+                    return Mono.error(new BusinessException("服务器内部出现问题"));
+                }
+                if(response.getStatusCode()!=HttpStatus.OK){
+                    WVPResult wvpResult = (WVPResult)response.getBody();
+                    return Mono.error(new BusinessException(Optional.ofNullable(wvpResult.getMsg()).orElse("服务器内部出现问题")));
+                }
+                return Mono.just(response);
+            })
+
+            // 点播结束时调用截图接口
+            .doOnNext(response -> {
+                try {
+                    String classPath = ResourceUtils.getURL("classpath:").getPath();
+                    // System.out.println(classPath);
+                    // 兼容打包为jar的class路径
+                    if (classPath.contains("jar")) {
+                        classPath = classPath.substring(0, classPath.lastIndexOf("."));
+                        classPath = classPath.substring(0, classPath.lastIndexOf("/") + 1);
+                    }
+                    if (classPath.startsWith("file:")) {
+                        classPath = classPath.substring(classPath.indexOf(":") + 1);
+                    }
+                    String path = classPath + "static/static/snap/";
+                    // 兼容Windows系统路径(去除前面的“/”)
+                    if (System.getProperty("os.name").contains("indows")) {
+                        path = path.substring(1);
+                    }
+                    String fileName =  deviceId + "_" + channelId + ".jpg";
+                    WVPResult wvpResult = (WVPResult)response.getBody();
+                    if (Objects.requireNonNull(wvpResult).getCode() == 0) {
+                        StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
+                        MediaServerItem mediaInfo = mediaServerItemService.getOne(streamInfoForSuccess.getMediaServerId());
+                        String streamUrl = streamInfoForSuccess.getFmp4();
+                        // 请求截图
+                        log.info("[请求截图]: " + fileName);
+                        zlmresTfulUtils.getSnap(mediaInfo, streamUrl, 15, 1, path, fileName);
+                    }
+
+                } catch (FileNotFoundException e) {
+                    throw new BusinessException("设备上传视频截图文件找不到");
+                }
+            })
+            //保证接下来的操作流仅被触发一次
+            .filter(ignore->!start.get())
+            .doOnNext(ignore->start.set(true))
+
+            .concatWith(
+                Mono.fromRunnable(()->
+                        Mono.just(redisCatchStorage.queryPlayByDevice(deviceId,channelId))
+                            .flatMap(streamInfo -> {
+                                if(StrUtil.isEmpty(streamInfo.getStreamId())){
+                                    return Mono.error(new BusinessException("点播失败, redis缓存streamId等于null"));
+                                }
+                                String mediaServerId = streamInfo.getMediaServerId();
+                                MediaServerItem mediaInfo = mediaServerItemService.getOne(mediaServerId);
+
+                                JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamInfo.getStreamId());
+                                if (rtpInfo != null && rtpInfo.getBool("exist")) {
+                                    if (hookEvent != null) {
+                                        //todo
+//                        hookEvent.response(mediaServerItem, com.alibaba.fastjson.JSONObject.parseObject(JSON.toJSONString(streamInfo)));
+                                    }
+                                    clusterEventBus.publish(DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
+                                        ResponseEntity.ok(streamInfo));
+                                } else {
+                                    // TODO 点播前是否重置状态
+                                    redisCatchStorage.stopPlay(streamInfo);
+                                    return deviceChannelService.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId())
+                                        .flatMap(ignore->{
+                                            SSRCInfo ssrcInfo;
+                                            String streamId2 = null;
+                                            if (mediaServerItem.isRtpEnable()) {
+                                                streamId2 = String.format("%s_%s",deviceId, channelId);
+                                            }
+                                            ssrcInfo = mediaServerItemService.openRTPServer(mediaServerItem, streamId2);
+
+                                            return cmder.playStreamCmd(mediaServerItem, ssrcInfo, redisCatchStorage.getDevice(deviceId), channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
+                                                log.info("收到订阅消息: " + response.toString());
+                                                onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, msgId)
+                                                    .subscribe();
+                                            }, (event) -> {
+                                                mediaServerItemService.closeRTPServer(playResult.getDevice(), channelId);
+                                                WVPResult wvpResult = WVPResult.of(HttpStatus.INTERNAL_SERVER_ERROR.value(), String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg),null);
+                                                clusterEventBus.publish(DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
+                                                    ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(wvpResult));
+                                            })
+                                                .then();
+                                        });
+                                }
+                                return Mono.empty();
+                            })
+                            .switchIfEmpty(Mono.just(mediaServerItem)
+                                .flatMap(serverItem->{
+                                    SSRCInfo ssrcInfo;
+                                    String streamId = null;
+                                    if (serverItem.isRtpEnable()) {
+                                        streamId = String.format("%s_%s", deviceId, channelId);
+                                    }
+
+                                    ssrcInfo = mediaServerItemService.openRTPServer(serverItem, streamId);
+
+                                    // 发送点播消息
+                                    return cmder.playStreamCmd(serverItem, ssrcInfo, redisCatchStorage.getDevice(deviceId), channelId, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
+                                        log.info("收到订阅消息: " + response.toString());
+                                        onPublishHandlerForPlay(mediaServerItemInUse, response, deviceId, channelId, msgId).subscribe();
+                                        if (hookEvent != null) {
+//                                    hookEvent.response(mediaServerItem, response);
+                                        }
+                                    }, (event) -> {
+                                        WVPResult wvpResult = WVPResult.of(HttpStatus.INTERNAL_SERVER_ERROR.value(), String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
+                                        // 点播返回sip错误
+                                        mediaServerItemService.closeRTPServer(playResult.getDevice(), channelId);
+                                        clusterEventBus.publish(DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
+                                            ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(wvpResult));
+                                        if (errorEvent != null) {
+                                            //todo
 //                    errorEvent.response(event);
-//                }
-//
-//
-//            });
-//        } else {
-//            String streamId = streamInfo.getStreamId();
-//            if (streamId == null) {
-//                WVPResult wvpResult = new WVPResult();
-//                wvpResult.setCode(-1);
-//                wvpResult.setMsg("点播失败, redis缓存streamId等于null");
-//                msg.setData(wvpResult);
-//                resultHolder.invokeAllResult(msg);
-//                return playResult;
-//            }
-//            String mediaServerId = streamInfo.getMediaServerId();
-//            MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
-//
-//            JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
-//            if (rtpInfo != null && rtpInfo.getBoolean("exist")) {
-//
-//                WVPResult wvpResult = new WVPResult();
-//                wvpResult.setCode(0);
-//                wvpResult.setMsg("success");
-//                wvpResult.setData(streamInfo);
-//                msg.setData(wvpResult);
-//
-//                resultHolder.invokeAllResult(msg);
-//                if (hookEvent != null) {
-//                    hookEvent.response(mediaServerItem, JSONObject.parseObject(JSON.toJSONString(streamInfo)));
-//                }
-//            } else {
-//                // TODO 点播前是否重置状态
-//                redisCatchStorage.stopPlay(streamInfo);
-//                storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
-//                SSRCInfo ssrcInfo;
-//                String streamId2 = null;
-//                if (mediaServerItem.isRtpEnable()) {
-//                    streamId2 = String.format("%s_%s", device.getDeviceId(), channelId);
-//                }
-//                ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId2);
-//
-//                cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
-//                    log.info("收到订阅消息: " + response.toJSONString());
-//                    onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, uuid);
-//                }, (event) -> {
-//                    mediaServerService.closeRrpServer(playResult.getDevice(), channelId);
-//                    WVPResult wvpResult = new WVPResult();
-//                    wvpResult.setCode(-1);
-//                    wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg));
-//                    msg.setData(wvpResult);
-//                    resultHolder.invokeAllResult(msg);
-//                });
-//            }
-//        }
-//
-//        return playResult;
-//    }
-//
-////    @Override
-////    public void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
-////        RequestMessage msg = new RequestMessage();
-////        msg.setId(uuid);
-////        msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId);
-////        StreamInfo streamInfo = onPublishHandler(mediaServerItem, resonse, deviceId, channelId, uuid);
-////        if (streamInfo != null) {
-////            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
-////            if (deviceChannel != null) {
-////                deviceChannel.setStreamId(streamInfo.getStreamId());
-////                storager.startPlay(deviceId, channelId, streamInfo.getStreamId());
-////            }
-////            redisCatchStorage.startPlay(streamInfo);
-////            msg.setData(JSON.toJSONString(streamInfo));
-////
-////            WVPResult wvpResult = new WVPResult();
-////            wvpResult.setCode(0);
-////            wvpResult.setMsg("success");
-////            wvpResult.setData(streamInfo);
-////            msg.setData(wvpResult);
-////
-////            resultHolder.invokeAllResult(msg);
-////        } else {
-////            log.warn("设备预览API调用失败!");
-////            msg.setData("设备预览API调用失败!");
-////            resultHolder.invokeAllResult(msg);
-////        }
-////    }
-////
-////    @Override
-////    public MediaServerItem getNewMediaServerItem(Device device) {
-////        if (device == null) return null;
-////        String mediaServerId = device.getMediaServerId();
-////        MediaServerItem mediaServerItem;
-////        if (mediaServerId == null) {
-////            mediaServerItem = mediaServerService.getMediaServerForMinimumLoad();
-////        }else {
-////            mediaServerItem = mediaServerService.getOne(mediaServerId);
-////        }
-////        if (mediaServerItem == null) {
-////            log.warn("点播时未找到可使用的ZLM...");
-////        }
-////        return mediaServerItem;
-////    }
-////
+                                        }
+                                    })
+                                        .then();
+                                }))
+                            .subscribe()
+                ))
+            .doOnError(BusinessException.class, e -> cmder.streamByeCmd(deviceId, channelId).subscribe())
+            .single();
+    }
+
+
+    public Mono<Void> onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String msgId) {
+        RequestMessage msg = RequestMessage.of(deviceId, channelId, msgId, null);
+        return onPublishHandler(mediaServerItem, resonse, deviceId, channelId, msgId)
+            .flatMap(streamInfo ->
+                deviceChannelService.startPlay(deviceId, channelId, streamInfo.getStreamId())
+                    .concatWith(Mono.fromRunnable(()->{
+                        redisCatchStorage.startPlay(streamInfo);
+                        WVPResult<StreamInfo> wvpResult = WVPResult.of(HttpStatus.OK.value(), "success", streamInfo);
+                        clusterEventBus.publish(DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
+                            ResponseEntity.ok(wvpResult));
+                    }))
+                    .then()
+            )
+            .switchIfEmpty(Mono.fromRunnable(()->{
+                log.warn("设备预览API调用失败!");
+                clusterEventBus.publish(DeferredResultHolder.getTopicByDeviceIdAndChannelId(DeferredResultHolder.CALLBACK_CMD_PLAY, msg),
+                    ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(WVPResult.of(HttpStatus.INTERNAL_SERVER_ERROR.value(),"设备预览API调用失败",null)));
+            }));
+    }
+
+
+    /**
+     * 根据媒体设备获取相应的媒体流服务器
+     * @param device
+     * @return
+     */
+    public Mono<MediaServerItem> getNewMediaServerItem(MediaDevice device) {
+        if (device == null) {
+            return Mono.empty();
+        }
+        return Mono.just(device.getMediaServerId())
+            .flatMap(mediaServerItemService::findById)
+            //找不到设备相应的媒体流服务器则根据负载均衡获取相应的媒体流服务器
+            .switchIfEmpty(mediaServerItemService.getMediaServerForMinimumLoad())
+            .switchIfEmpty(Mono.fromRunnable(()-> log.warn("点播时未找到可使用的ZLM...")));
+    }
+    ////
 ////
 ////    @Override
 ////    public void onPublishHandlerForPlayBack(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
@@ -325,13 +297,14 @@
 ////    }
 ////
 ////
-////    public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
-////        String streamId = resonse.getString("stream");
-////        JSONArray tracks = resonse.getJSONArray("tracks");
-////        StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem,"rtp", streamId, tracks);
-////        streamInfo.setDeviceID(deviceId);
-////        streamInfo.setChannelId(channelId);
-////        return streamInfo;
-////    }
-//
-//}
+    public Mono<StreamInfo> onPublishHandler(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
+        String streamId = resonse.getStr("stream");
+        JSONArray tracks = resonse.getJSONArray("tracks");
+        return  mediaServerItemService.getStreamInfoByAppAndStream(mediaServerItem,"rtp", streamId, tracks)
+            .doOnNext(streamInfo -> {
+                streamInfo.setDeviceID(deviceId);
+                streamInfo.setChannelId(channelId);
+            });
+    }
+
+}

+ 0 - 23
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/SipService.java

@@ -1,23 +0,0 @@
-//package org.jetlinks.community.media.service;
-//
-//import org.hswebframework.web.crud.service.GenericReactiveCrudService;
-//import org.jetlinks.community.media.entity.SipServerConfig;
-//import org.springframework.stereotype.Service;
-//import reactor.core.publisher.Mono;
-//
-///**
-// * @author lifang
-// * @version 1.0.0
-// * @ClassName SipService.java
-// * @Description TODO
-// * @createTime 2022年01月15日 13:57:00
-// */
-//@Service
-//public class SipService  extends GenericReactiveCrudService<SipServerConfig, String> {
-//    public Mono<Void> doStart(SipServerConfig serverConfig){
-//
-//        return Mono.empty();
-//    }
-//
-//
-//}

+ 115 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/session/VideoStreamSessionManager.java

@@ -0,0 +1,115 @@
+package org.jetlinks.community.media.session;
+
+import cn.hutool.core.util.SerializeUtil;
+import gov.nist.javax.sip.stack.SIPDialog;
+import lombok.AllArgsConstructor;
+import org.jetlinks.community.media.contanst.VideoManagerConstants;
+import org.jetlinks.community.media.gb28181.bean.SsrcTransaction;
+import org.jetlinks.community.utils.RedisUtil;
+import org.jetlinks.core.cluster.ClusterManager;
+import org.springframework.stereotype.Component;
+
+import javax.sip.ClientTransaction;
+import javax.sip.Dialog;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @description:视频流session管理器,管理视频预览、预览回放的通信句柄
+ * @author: swwheihei
+ * @date:   2020年5月13日 下午4:03:02
+ */
+@Component
+public class VideoStreamSessionManager {
+
+	private final RedisUtil redisUtil;
+
+
+	private final String serverId;
+
+    public VideoStreamSessionManager(RedisUtil redisUtil, ClusterManager clusterManager) {
+        this.redisUtil = redisUtil;
+        this.serverId = clusterManager.getCurrentServerId();
+    }
+
+    public void put(String deviceId, String channelId , String ssrc, String streamId, String mediaServerId, ClientTransaction transaction){
+		SsrcTransaction ssrcTransaction = new SsrcTransaction();
+		ssrcTransaction.setDeviceId(deviceId);
+		ssrcTransaction.setChannelId(channelId);
+		ssrcTransaction.setStreamId(streamId);
+		byte[] transactionByteArray = SerializeUtil.serialize(transaction);
+		ssrcTransaction.setTransaction(transactionByteArray);
+		ssrcTransaction.setSsrc(ssrc);
+		ssrcTransaction.setMediaServerId(mediaServerId);
+
+		redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + serverId + "_" +  deviceId + "_" + channelId, ssrcTransaction);
+	}
+
+	public void put(String deviceId, String channelId , Dialog dialog){
+		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
+		if (ssrcTransaction != null) {
+			byte[] dialogByteArray = SerializeUtil.serialize(dialog);
+			ssrcTransaction.setDialog(dialogByteArray);
+		}
+		redisUtil.set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + serverId + "_" +  deviceId + "_" + channelId, ssrcTransaction);
+	}
+
+
+	public ClientTransaction getTransaction(String deviceId, String channelId){
+		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
+		if (ssrcTransaction == null){return null;}
+		byte[] transactionByteArray = ssrcTransaction.getTransaction();
+		ClientTransaction clientTransaction = (ClientTransaction)SerializeUtil.deserialize(transactionByteArray);
+		return clientTransaction;
+	}
+
+	public SIPDialog getDialog(String deviceId, String channelId){
+		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
+		if (ssrcTransaction == null){return null;}
+		byte[] dialogByteArray = ssrcTransaction.getDialog();
+		if (dialogByteArray == null){return null;}
+		SIPDialog dialog = (SIPDialog)SerializeUtil.deserialize(dialogByteArray);
+		return dialog;
+	}
+
+	public SsrcTransaction getSsrcTransaction(String deviceId, String channelId){
+		SsrcTransaction ssrcTransaction = (SsrcTransaction)redisUtil.get(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + serverId + "_" + deviceId + "_" + channelId);
+		return ssrcTransaction;
+	}
+
+	public String getStreamId(String deviceId, String channelId){
+		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
+		if (ssrcTransaction == null){return null;}
+		return ssrcTransaction.getStreamId();
+	}
+	public String getMediaServerId(String deviceId, String channelId){
+		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
+		if (ssrcTransaction == null){return null;}
+		return ssrcTransaction.getMediaServerId();
+	}
+
+	public String getSSRC(String deviceId, String channelId){
+		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
+		if (ssrcTransaction == null){return null;}
+		return ssrcTransaction.getSsrc();
+	}
+
+	public void remove(String deviceId, String channelId) {
+		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId);
+		if (ssrcTransaction == null) {
+		    return;
+        }
+		redisUtil.del(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + serverId + "_" +  deviceId + "_" + channelId);
+	}
+
+	public List<SsrcTransaction> getAllSsrc() {
+		List<Object> ssrcTransactionKeys = redisUtil.scan(String.format("%s_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ serverId + "_" ));
+		List<SsrcTransaction> result= new ArrayList<>();
+		for (int i = 0; i < ssrcTransactionKeys.size(); i++) {
+			String key = (String)ssrcTransactionKeys.get(i);
+			SsrcTransaction ssrcTransaction = (SsrcTransaction)redisUtil.get(key);
+			result.add(ssrcTransaction);
+		}
+		return result;
+	}
+}

+ 3 - 3
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/request/ISipRequestProcessor.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/AbstractSipProcessor.java

@@ -1,4 +1,4 @@
-package org.jetlinks.community.media.event.request;
+package org.jetlinks.community.media.sip;
 
 import lombok.extern.slf4j.Slf4j;
 import org.jetlinks.community.media.sip.processor.SipProcessorObserver;
@@ -12,7 +12,7 @@ import javax.sip.*;
  * @date:   2021年11月5日 15:47
  */
 @Slf4j
-public abstract class ISipRequestProcessor {
+public abstract class AbstractSipProcessor {
 
     @Autowired
     public SipProcessorObserver observer;
@@ -58,7 +58,7 @@ public abstract class ISipRequestProcessor {
     }
 
     public void processResponse(ResponseEvent event){
-
+        System.out.println(event);
     }
 
     public void processTimeout(TimeoutEvent event){

+ 1 - 3
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/SipContext.java

@@ -5,10 +5,8 @@ import io.vavr.Tuple;
 import io.vavr.Tuple3;
 import lombok.extern.slf4j.Slf4j;
 import org.hswebframework.web.exception.BusinessException;
-import org.jetlinks.community.media.entity.SipServerConfig;
+import org.jetlinks.community.media.bean.SipServerConfig;
 import javax.sip.*;
-import javax.sip.address.URI;
-import javax.sip.header.ToHeader;
 import javax.sip.header.ViaHeader;
 import java.util.*;
 

+ 4 - 4
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/SipRequestProcessorParent.java

@@ -10,8 +10,8 @@ import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.dom4j.io.SAXReader;
 import org.jetlinks.community.media.core.DigestServerAuthenticationHelper2016;
-import org.jetlinks.community.media.entity.SipServerConfig;
-import org.jetlinks.community.media.event.request.ISipRequestProcessor;
+import org.jetlinks.community.media.bean.SipServerConfig;
+
 import javax.sip.*;
 import javax.sip.address.Address;
 import javax.sip.address.AddressFactory;
@@ -29,10 +29,10 @@ import java.text.ParseException;
 /**
  * @description:处理接收IPCamera发来的SIP协议请求消息
  * @author: songww
- * @date:   2020年5月3日 下午4:42:22     
+ * @date:   2020年5月3日 下午4:42:22
  */
 @Slf4j
-public abstract class SipRequestProcessorParent extends ISipRequestProcessor {
+public abstract class SipRequestProcessorParent extends AbstractSipProcessor {
 
 
 

+ 40 - 40
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/SipServerHelper.java

@@ -8,16 +8,16 @@ import org.hswebframework.web.exception.BusinessException;
 import org.jetlinks.community.media.bean.EventResult;
 import org.jetlinks.community.media.bean.SSRCInfo;
 import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.SipServerConfig;
+import org.jetlinks.community.media.bean.SipServerConfig;
 import org.jetlinks.community.media.enums.StreamMode;
-import org.jetlinks.community.media.event.request.impl.InviteRequestProcessor;
-import org.jetlinks.community.media.event.request.impl.RegisterRequestProcessor;
-import org.jetlinks.community.media.event.request.message.MessageRequestProcessor;
-import org.jetlinks.community.media.event.request.message.notify.KeepaliveNotifyMessageHandler;
+import org.jetlinks.community.media.sip.request.impl.InviteRequestProcessor;
+import org.jetlinks.community.media.sip.request.impl.RegisterRequestProcessor;
+import org.jetlinks.community.media.sip.request.message.MessageRequestProcessor;
+import org.jetlinks.community.media.sip.request.message.notify.KeepaliveNotifyMessageHandler;
 import org.jetlinks.community.media.sip.processor.SipProcessorObserver;
 import org.jetlinks.community.media.transmit.SIPRequestHeaderProvider;
 import org.jetlinks.community.media.transmit.cmd.SipCommander;
-import org.jetlinks.community.media.zlm.dto.MediaServerItem;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
 import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.stereotype.Component;
 import reactor.core.publisher.Flux;
@@ -75,39 +75,39 @@ public class SipServerHelper implements BeanPostProcessor {
 
     @SneakyThrows
     public static void main(String[] args) {
-        SipProcessorObserver sipProcessorObserver = new SipProcessorObserver();
-        RegisterRequestProcessor registerRequestProcessor = new RegisterRequestProcessor(null);
-        registerRequestProcessor.observer=sipProcessorObserver;
-        registerRequestProcessor.init();
-        InviteRequestProcessor inviteRequestProcessor = new InviteRequestProcessor();
-        inviteRequestProcessor.observer=sipProcessorObserver;
-        inviteRequestProcessor.init();
-        MessageRequestProcessor messageRequestProcessor = new MessageRequestProcessor();
-        messageRequestProcessor.observer=sipProcessorObserver;
-        messageRequestProcessor.init();
-        KeepaliveNotifyMessageHandler keepaliveNotifyMessageHandler = new KeepaliveNotifyMessageHandler();
-        keepaliveNotifyMessageHandler.observer=sipProcessorObserver;
-        keepaliveNotifyMessageHandler.init();
-        keepaliveNotifyMessageHandler.messageRequestProcessor=messageRequestProcessor;
-        keepaliveNotifyMessageHandler.start();
-        new SipServerHelper(sipProcessorObserver).
-            createSip( SipServerConfig.of("1","0.0.0.0", 7000,"udp","340200000","utf-8","12345678",10L,""))
-            .subscribe();
-        SipCommander sipCommander = new SipCommander(new SIPRequestHeaderProvider());
-        SSRCInfo ssrcInfo = SSRCInfo.of(5003,null,null);
-        MediaDevice mediaDevice = new MediaDevice();
-        mediaDevice.setStreamMode(StreamMode.TCP_ACTIVE.name());
-        mediaDevice.setId("34020000001320000003");
-        mediaDevice.setHostAddress("192.168.104.244");
-        mediaDevice.setPort(5063);
-        mediaDevice.setTransport("udp");
-        MediaServerItem mediaServerItem = new MediaServerItem();
-        mediaServerItem.setSdpIp("192.168.104.244");
-        Flux<EventResult> eventResultFlux = sipCommander.playStreamCmd(mediaServerItem, ssrcInfo,mediaDevice, "34020000001320000003");
-        eventResultFlux
-            .doOnNext(s->{
-                System.out.println(s);
-            })
-            .subscribe();
+//        SipProcessorObserver sipProcessorObserver = new SipProcessorObserver();
+//        RegisterRequestProcessor registerRequestProcessor = new RegisterRequestProcessor(null);
+//        registerRequestProcessor.observer=sipProcessorObserver;
+//        registerRequestProcessor.init();
+//        InviteRequestProcessor inviteRequestProcessor = new InviteRequestProcessor(null,null);
+//        inviteRequestProcessor.observer=sipProcessorObserver;
+//        inviteRequestProcessor.init();
+//        MessageRequestProcessor messageRequestProcessor = new MessageRequestProcessor();
+//        messageRequestProcessor.observer=sipProcessorObserver;
+//        messageRequestProcessor.init();
+//        KeepaliveNotifyMessageHandler keepaliveNotifyMessageHandler = new KeepaliveNotifyMessageHandler();
+//        keepaliveNotifyMessageHandler.observer=sipProcessorObserver;
+//        keepaliveNotifyMessageHandler.init();
+//        keepaliveNotifyMessageHandler.messageRequestProcessor=messageRequestProcessor;
+//        keepaliveNotifyMessageHandler.start();
+//        new SipServerHelper(sipProcessorObserver).
+//            createSip( SipServerConfig.of("1","0.0.0.0", 7000,"udp","340200000","utf-8","12345678",10L,""))
+//            .subscribe();
+//        SipCommander sipCommander = new SipCommander(new SIPRequestHeaderProvider(null));
+//        SSRCInfo ssrcInfo = SSRCInfo.of(5003,null,null);
+//        MediaDevice mediaDevice = new MediaDevice();
+//        mediaDevice.setStreamMode(StreamMode.TCP_ACTIVE.name());
+//        mediaDevice.setId("34020000001320000003");
+//        mediaDevice.setHostAddress("192.168.104.244");
+//        mediaDevice.setPort(5063);
+//        mediaDevice.setTransport("udp");
+//        MediaServerItem mediaServerItem = new MediaServerItem();
+//        mediaServerItem.setSdpIp("192.168.104.244");
+//        Flux<EventResult> eventResultFlux = sipCommander.playStreamCmd(mediaServerItem, ssrcInfo,mediaDevice, "34020000001320000003");
+//        eventResultFlux
+//            .doOnNext(s->{
+//                System.out.println(s);
+//            })
+//            .subscribe();
     }
 }

+ 2 - 2
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/request/IMessageHandler.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/IMessageHandler.java

@@ -1,9 +1,9 @@
-package org.jetlinks.community.media.event.request;
+package org.jetlinks.community.media.sip.request;
 
 
 import org.dom4j.Element;
 import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.ParentPlatform;
+import org.jetlinks.community.media.bean.ParentPlatform;
 
 import javax.sip.RequestEvent;
 

+ 41 - 6
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/request/impl/InviteRequestProcessor.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/InviteRequestProcessor.java

@@ -1,17 +1,26 @@
-package org.jetlinks.community.media.event.request.impl;
+package org.jetlinks.community.media.sip.request.impl;
 
 import gov.nist.javax.sip.address.AddressImpl;
+import gov.nist.javax.sip.address.GenericURI;
 import gov.nist.javax.sip.address.SipUri;
-import lombok.SneakyThrows;
+import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.community.media.service.LocalMediaDeviceService;
+import org.jetlinks.community.media.sip.SipContext;
 import org.jetlinks.community.media.sip.SipRequestProcessorParent;
-import org.jetlinks.community.media.storage.IVideoManagerStorager;
+import org.jetlinks.community.media.transmit.SIPRequestHeaderProvider;
 import org.springframework.stereotype.Component;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.PeerUnavailableException;
 import javax.sip.RequestEvent;
+import javax.sip.SipProvider;
 import javax.sip.address.SipURI;
+import javax.sip.header.CallIdHeader;
 import javax.sip.header.FromHeader;
 import javax.sip.message.Request;
 import javax.sip.message.Response;
+import java.text.ParseException;
 
 /**
  * SIP命令类型: INVITE请求
@@ -19,13 +28,16 @@ import javax.sip.message.Response;
 @SuppressWarnings("rawtypes")
 @Component
 @Slf4j(topic = "sip-invite-request")
+@AllArgsConstructor
 public class InviteRequestProcessor extends SipRequestProcessorParent {
 
     //	@Autowired
 //	private SIPCommanderFroPlatform cmderFroPlatform;
 //
 //	@Autowired
-    private IVideoManagerStorager storager;
+//    private IVideoManagerStorager storager;
+
+    private final SIPRequestHeaderProvider headerProvider;
 //
 //	@Autowired
 //	private IRedisCatchStorage  redisCatchStorage;
@@ -40,7 +52,7 @@ public class InviteRequestProcessor extends SipRequestProcessorParent {
 //	private ZLMRTPServerFactory zlmrtpServerFactory;
 //
 //	@Autowired
-//	private IMediaServerService mediaServerService;
+	private final LocalMediaDeviceService mediaServerService;
 
     @Override
     public String getMethod() {
@@ -73,7 +85,30 @@ public class InviteRequestProcessor extends SipRequestProcessorParent {
                 responseAck(evt, Response.BAD_REQUEST);
                 return;
             }
-            responseAck(evt, Response.CALL_IS_BEING_FORWARDED);
+
+            mediaServerService.findById("34020000001320000003")
+                .doOnNext(device->{
+                    SipProvider sipProvider = SipContext.getSipProvider();
+                    String tm = Long.toString(System.currentTimeMillis());
+                    CallIdHeader callIdHeader =sipProvider.getNewCallId();
+                        try {
+                            Request inviteRequest = headerProvider
+                                .createInviteRequest(device, channelId, null, null, "FromInvt" + tm, null, "1000", callIdHeader);
+                            inviteRequest.setRequestURI( new GenericURI("1.15.89.83:10000"));
+                        } catch (ParseException e) {
+                            e.printStackTrace();
+                        } catch (InvalidArgumentException e) {
+                            e.printStackTrace();
+                        } catch (PeerUnavailableException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                )
+                .subscribe();
+
+
+
+//            responseAck(evt, Response.CALL_IS_BEING_FORWARDED);
         }catch (Exception e){
             e.printStackTrace();
         }

+ 3 - 3
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/request/impl/MessageRequestProcessor.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/MessageRequestProcessor.java

@@ -3,9 +3,9 @@
 //import org.dom4j.DocumentException;
 //import org.dom4j.Element;
 //import org.jetlinks.community.media.entity.MediaDevice;
-//import org.jetlinks.community.media.entity.ParentPlatform;
-//import org.jetlinks.community.media.event.request.IMessageHandler;
-//import org.jetlinks.community.media.event.request.ISipRequestProcessor;
+//import org.jetlinks.community.media.bean.ParentPlatform;
+//import org.jetlinks.community.media.sip.request.IMessageHandler;
+//import org.jetlinks.community.media.sip.request.ISipRequestProcessor;
 //import org.jetlinks.community.media.sip.SipRequestProcessorParent;
 //import org.jetlinks.community.media.sip.processor.SipProcessorObserver;
 //import org.jetlinks.community.utils.SipUtils;

+ 3 - 3
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/request/impl/RegisterRequestProcessor.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/RegisterRequestProcessor.java

@@ -1,12 +1,12 @@
-package org.jetlinks.community.media.event.request.impl;
+package org.jetlinks.community.media.sip.request.impl;
 import cn.hutool.core.util.StrUtil;
 import gov.nist.javax.sip.RequestEventExt;
 import gov.nist.javax.sip.header.Expires;
 import gov.nist.javax.sip.header.SIPDateHeader;
 import lombok.extern.slf4j.Slf4j;
 import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.SipServerConfig;
-import org.jetlinks.community.media.entity.WvpSipDate;
+import org.jetlinks.community.media.bean.SipServerConfig;
+import org.jetlinks.community.media.bean.WvpSipDate;
 import org.jetlinks.community.media.sip.SipContext;
 import org.jetlinks.community.media.sip.SipRequestProcessorParent;
 import org.jetlinks.community.utils.SipUtils;

+ 1 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/request/impl/TimeoutProcessor.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/TimeoutProcessor.java

@@ -1,4 +1,4 @@
-package org.jetlinks.community.media.event.request.impl;
+package org.jetlinks.community.media.sip.request.impl;
 
 import org.jetlinks.community.media.sip.SipRequestProcessorParent;
 

+ 2 - 2
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/request/message/IMessageHandler.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/IMessageHandler.java

@@ -1,8 +1,8 @@
-package org.jetlinks.community.media.event.request.message;
+package org.jetlinks.community.media.sip.request.message;
 
 import org.dom4j.Element;
 import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.ParentPlatform;
+import org.jetlinks.community.media.bean.ParentPlatform;
 
 import javax.sip.RequestEvent;
 

+ 1 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/request/message/MessageHandlerAbstract.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/MessageHandlerAbstract.java

@@ -1,4 +1,4 @@
-package org.jetlinks.community.media.event.request.message;
+package org.jetlinks.community.media.sip.request.message;
 
 import org.jetlinks.community.media.sip.SipRequestProcessorParent;
 import org.springframework.beans.factory.annotation.Autowired;

+ 2 - 8
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/request/message/MessageRequestProcessor.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/MessageRequestProcessor.java

@@ -1,7 +1,5 @@
-package org.jetlinks.community.media.event.request.message;
+package org.jetlinks.community.media.sip.request.message;
 
-import cn.hutool.core.util.StrUtil;
-import io.netty.util.internal.PlatformDependent;
 import io.vavr.Tuple;
 import io.vavr.Tuple3;
 import lombok.SneakyThrows;
@@ -9,16 +7,12 @@ import lombok.extern.slf4j.Slf4j;
 import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.ParentPlatform;
-import org.jetlinks.community.media.entity.PlatformCatalog;
+import org.jetlinks.community.media.bean.ParentPlatform;
 import org.jetlinks.community.media.service.LocalMediaDeviceService;
 import org.jetlinks.community.media.sip.SipContext;
 import org.jetlinks.community.media.sip.SipRequestProcessorParent;
-import org.jetlinks.community.media.sip.processor.SipProcessorObserver;
 import org.jetlinks.community.utils.SipUtils;
 import org.redisson.api.RedissonClient;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import reactor.core.publisher.EmitterProcessor;

+ 3 - 3
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/request/message/notify/KeepaliveNotifyMessageHandler.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/notify/KeepaliveNotifyMessageHandler.java

@@ -1,11 +1,11 @@
-package org.jetlinks.community.media.event.request.message.notify;
+package org.jetlinks.community.media.sip.request.message.notify;
 
 import lombok.extern.slf4j.Slf4j;
 import org.dom4j.Element;
 import org.jetlinks.community.media.contanst.CmdType;
 import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.ParentPlatform;
-import org.jetlinks.community.media.event.request.message.MessageHandlerAbstract;
+import org.jetlinks.community.media.bean.ParentPlatform;
+import org.jetlinks.community.media.sip.request.message.MessageHandlerAbstract;
 import org.jetlinks.core.event.EventBus;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;

+ 3 - 3
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/request/message/notify/NoSupportMessageHandler.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/notify/NoSupportMessageHandler.java

@@ -1,11 +1,11 @@
-package org.jetlinks.community.media.event.request.message.notify;
+package org.jetlinks.community.media.sip.request.message.notify;
 
 import lombok.extern.slf4j.Slf4j;
 import org.dom4j.Element;
 import org.jetlinks.community.media.contanst.CmdType;
 import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.ParentPlatform;
-import org.jetlinks.community.media.event.request.message.MessageHandlerAbstract;
+import org.jetlinks.community.media.bean.ParentPlatform;
+import org.jetlinks.community.media.sip.request.message.MessageHandlerAbstract;
 import org.springframework.stereotype.Component;
 
 import javax.sip.InvalidArgumentException;

+ 73 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/response/InviteResponseProcessor.java

@@ -0,0 +1,73 @@
+package org.jetlinks.community.media.sip.response;
+
+import gov.nist.javax.sip.ResponseEventExt;
+import gov.nist.javax.sip.stack.SIPDialog;
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.community.media.sip.AbstractSipProcessor;
+import org.springframework.stereotype.Component;
+import javax.sip.InvalidArgumentException;
+import javax.sip.ResponseEvent;
+import javax.sip.SipException;
+import javax.sip.address.SipURI;
+import javax.sip.header.CSeqHeader;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+import java.text.ParseException;
+
+
+/**
+ * @description: 处理INVITE响应
+ * @author: panlinlin
+ * @date: 2021年11月5日 16:40
+ */
+@Component
+@Slf4j
+public class InviteResponseProcessor extends AbstractSipProcessor {
+	/**
+	 * 处理invite响应
+	 *
+	 * @param evt 响应消息
+	 * @throws ParseException
+	 */
+	@Override
+	public void processResponse(ResponseEvent evt ){
+		try {
+			Response response = evt.getResponse();
+			int statusCode = response.getStatusCode();
+			// trying不会回复
+			if (statusCode == Response.TRYING) {
+			}
+			// 成功响应
+			// 下发ack
+			if (statusCode == Response.OK) {
+				ResponseEventExt event = (ResponseEventExt)evt;
+				SIPDialog dialog = (SIPDialog)evt.getDialog();
+				CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
+				Request reqAck = dialog.createAck(cseq.getSeqNumber());
+				SipURI requestURI = (SipURI) reqAck.getRequestURI();
+				try {
+					requestURI.setHost(event.getRemoteIpAddress());
+				} catch (ParseException e) {
+					e.printStackTrace();
+				}
+				requestURI.setPort(event.getRemotePort());
+				reqAck.setRequestURI(requestURI);
+				log.info("向 " + event.getRemoteIpAddress() + ":" + event.getRemotePort() + "回复ack");
+				SipURI sipURI = (SipURI)dialog.getRemoteParty().getURI();
+				String deviceId = requestURI.getUser();
+				String channelId = sipURI.getUser();
+
+				dialog.sendAck(reqAck);
+
+			}
+		} catch (InvalidArgumentException | SipException e) {
+			e.printStackTrace();
+		}
+	}
+
+    @Override
+    public String getMethod() {
+        return Request.INVITE;
+    }
+
+}

+ 5 - 7
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/response/RegisterResponseProcessor.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/response/RegisterResponseProcessor.java

@@ -1,12 +1,10 @@
-package org.jetlinks.community.media.event.response;
+package org.jetlinks.community.media.sip.response;
 
 import gov.nist.javax.sip.ResponseEventExt;
 import gov.nist.javax.sip.stack.SIPDialog;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.jetlinks.community.media.event.request.ISipRequestProcessor;
-import org.jetlinks.community.media.storage.IRedisCatchStorage;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.jetlinks.community.media.sip.AbstractSipProcessor;
 import org.springframework.stereotype.Component;
 import javax.sip.InvalidArgumentException;
 import javax.sip.ResponseEvent;
@@ -17,15 +15,15 @@ import javax.sip.message.Request;
 import javax.sip.message.Response;
 import java.text.ParseException;
 
-/**    
+/**
  * @description:Register响应处理器
  * @author: swwheihei
- * @date:   2020年5月3日 下午5:32:23     
+ * @date:   2020年5月3日 下午5:32:23
  */
 @Component
 @AllArgsConstructor
 @Slf4j
-public class RegisterResponseProcessor extends ISipRequestProcessor {
+public class RegisterResponseProcessor extends AbstractSipProcessor {
 
 	/**
 	 * 处理Register响应

+ 0 - 210
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/storage/IRedisCatchStorage.java

@@ -1,210 +0,0 @@
-package org.jetlinks.community.media.storage;
-
-import com.alibaba.fastjson.JSONObject;
-import org.jetlinks.community.media.bean.StreamInfo;
-import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.ParentPlatform;
-import org.jetlinks.community.media.zlm.dto.MediaItem;
-
-import java.util.List;
-import java.util.Map;
-
-public interface IRedisCatchStorage {
-
-    /**
-     * 计数器。为cseq进行计数
-     *
-     * @param method sip 方法
-     * @return
-     */
-    Long getCSEQ(String method);
-
-    /**
-     * 开始播放时将流存入
-     *
-     * @param stream 流信息
-     * @return
-     */
-    boolean startPlay(StreamInfo stream);
-
-
-    /**
-     * 停止播放时删除
-     *
-     * @return
-     */
-    boolean stopPlay(StreamInfo streamInfo);
-
-    /**
-     * 查询播放列表
-     * @return
-     */
-    StreamInfo queryPlay(StreamInfo streamInfo);
-
-    StreamInfo queryPlayByStreamId(String steamId);
-
-    StreamInfo queryPlaybackByStreamId(String steamId);
-
-    StreamInfo queryPlayByDevice(String deviceId, String channelId);
-
-    Map<String, StreamInfo> queryPlayByDeviceId(String deviceId);
-
-    boolean startPlayback(StreamInfo stream);
-
-    boolean stopPlayback(StreamInfo streamInfo);
-
-    StreamInfo queryPlaybackByDevice(String deviceId, String code);
-
-//    void updatePlatformCatchInfo(ParentPlatformCatch parentPlatformCatch);
-//
-//    ParentPlatformCatch queryPlatformCatchInfo(String platformGbId);
-
-    void delPlatformCatchInfo(String platformGbId);
-
-    void updatePlatformKeepalive(ParentPlatform parentPlatform);
-
-    void delPlatformKeepalive(String platformGbId);
-
-    void updatePlatformRegister(ParentPlatform parentPlatform);
-
-    void delPlatformRegister(String platformGbId);
-
-    void updatePlatformRegisterInfo(String callId, String platformGbId);
-
-    String queryPlatformRegisterInfo(String callId);
-
-    void delPlatformRegisterInfo(String callId);
-
-    void cleanPlatformRegisterInfos();
-
-//    void updateSendRTPSever(SendRtpItem sendRtpItem);
-
-    /**
-     * 查询RTP推送信息缓存
-     * @param platformGbId
-     * @param channelId
-     * @return sendRtpItem
-     */
-//    SendRtpItem querySendRTPServer(String platformGbId, String channelId);
-//
-//    List<SendRtpItem> querySendRTPServer(String platformGbId);
-
-    /**
-     * 删除RTP推送信息缓存
-     * @param platformGbId
-     * @param channelId
-     */
-    void deleteSendRTPServer(String platformGbId, String channelId);
-
-    /**
-     * 查询某个通道是否存在上级点播(RTP推送)
-     * @param channelId
-     */
-    boolean isChannelSendingRTP(String channelId);
-
-    /**
-     * 清空某个设备的所有缓存
-     * @param deviceId 设备ID
-     */
-    void clearCatchByDeviceId(String deviceId);
-
-    /**
-     * 获取mediaServer节点
-     * @param mediaServerId
-     * @return
-     */
-//    MediaServerItem getMediaInfo(String mediaServerId);
-
-    /**
-     * 设置所有设备离线
-     */
-    void outlineForAll();
-
-    /**
-     * 获取所有在线的
-     */
-    List<String> getOnlineForAll();
-
-    /**
-     * 在redis添加wvp的信息
-     */
-    void updateWVPInfo(JSONObject jsonObject, int time);
-
-    /**
-     * 发送推流生成与推流消失消息
-     * @param jsonObject 消息内容
-     */
-    void sendStreamChangeMsg(String type, JSONObject jsonObject);
-
-    /**
-     * 添加流信息到redis
-     * @param mediaServerItem
-     * @param app
-     * @param streamId
-     */
-//    void addStream(MediaServerItem mediaServerItem, String type, String app, String streamId, MediaItem item);
-
-    /**
-     * 移除流信息从redis
-     * @param mediaServerId
-     * @param app
-     * @param streamId
-     */
-    void removeStream(String mediaServerId, String type, String app, String streamId);
-
-
-    /**
-     * 移除流信息从redis
-     * @param mediaServerId
-     */
-    void removeStream(String mediaServerId, String type);
-
-    /**
-     * 开始下载录像时存入
-     * @param streamInfo
-     */
-    boolean startDownload(StreamInfo streamInfo);
-
-    StreamInfo queryDownloadByStreamId(String streamId);
-
-//    /**
-//     * 查找第三方系统留下的国标预设值
-//     * @param queryKey
-//     * @return
-//     */
-//    ThirdPartyGB queryMemberNoGBId(String queryKey);
-
-    List<MediaItem> getStreams(String mediaServerId, String pull);
-
-    /**
-     * 将device信息写入redis
-     * @param device
-     */
-    void updateDevice(MediaDevice device);
-
-    void removeDevice(String deviceId);
-
-    /**
-     * 获取Device
-     */
-    MediaDevice getDevice(String deviceId);
-
-    void resetAllCSEQ();
-
-//    void updateGpsMsgInfo(GPSMsgInfo gpsMsgInfo);
-
-//    GPSMsgInfo getGpsMsgInfo(String gbId);
-//    List<GPSMsgInfo> getAllGpsMsgInfo();
-
-    Long getSN(String method);
-
-    void resetAllSN();
-
-//    void updateSubscribe(String key, SubscribeInfo subscribeInfo);
-
-//    SubscribeInfo getSubscribe(String key);
-
-    void delSubscribe(String key);
-
-    MediaItem getStreamInfo(String app, String streamId, String mediaServerId);
-}

+ 27 - 27
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/storage/IVideoManagerStorager.java

@@ -1,38 +1,38 @@
 package org.jetlinks.community.media.storage;
 
-import org.jetlinks.community.media.entity.DeviceChannel;
+import org.jetlinks.community.media.entity.MediaDeviceChannel;
 import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.ParentPlatform;
+import org.jetlinks.community.media.bean.ParentPlatform;
 
 import java.util.List;
 
-/**    
+/**
  * @description:视频设备数据存储接口
  * @author: swwheihei
- * @date:   2020年5月6日 下午2:14:31     
+ * @date:   2020年5月6日 下午2:14:31
  */
 @SuppressWarnings("rawtypes")
 public interface IVideoManagerStorager {
 
-	/**   
+	/**
 	 * 根据设备ID判断设备是否存在
-	 * 
+	 *
 	 * @param deviceId 设备ID
 	 * @return true:存在  false:不存在
 	 */
 	public boolean exists(String deviceId);
-	
-	/**   
+
+	/**
 	 * 视频设备创建
-	 * 
+	 *
 	 * @param device 设备对象
 	 * @return true:创建成功  false:创建失败
 	 */
 	public boolean create(MediaDevice device);
-	
-	/**   
+
+	/**
 	 * 视频设备更新
-	 * 
+	 *
 	 * @param device 设备对象
 	 * @return true:创建成功  false:创建失败
 	 */
@@ -44,7 +44,7 @@ public interface IVideoManagerStorager {
 	 * @param deviceId 设备id
 	 * @param channel 通道
 	 */
-	public void updateChannel(String deviceId, DeviceChannel channel);
+	public void updateChannel(String deviceId, MediaDeviceChannel channel);
 
 	/**
 	 * 批量添加设备通道
@@ -52,7 +52,7 @@ public interface IVideoManagerStorager {
 	 * @param deviceId 设备id
 	 * @param channels 多个通道
 	 */
-	public int updateChannels(String deviceId, List<DeviceChannel> channels);
+	public int updateChannels(String deviceId, List<MediaDeviceChannel> channels);
 
 	/**
 	 * 开始播放
@@ -68,10 +68,10 @@ public interface IVideoManagerStorager {
 	 * @param channelId 通道ID
 	 */
 	public void stopPlay(String deviceId, String channelId);
-	
-	/**   
+
+	/**
 	 * 获取设备
-	 * 
+	 *
 	 * @param deviceId 设备ID
 	 * @return DShadow 设备对象
 	 */
@@ -86,8 +86,8 @@ public interface IVideoManagerStorager {
 //	 * @return
 //	 */
 //	public PageInfo queryChannelsByDeviceId(String id, String query, Boolean hasSubChannel, Boolean online, int page, int count);
-	
-	public List<DeviceChannel> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, String query, Boolean hasSubChannel, Boolean online, int start, int limit);
+
+	public List<MediaDeviceChannel> queryChannelsByDeviceIdWithStartAndLimit(String deviceId, String query, Boolean hasSubChannel, Boolean online, int start, int limit);
 
 	/**
 	 * 获取某个设备的通道列表
@@ -126,25 +126,25 @@ public interface IVideoManagerStorager {
 	 */
 	public List<MediaDevice> queryVideoDeviceList();
 
-	/**   
+	/**
 	 * 删除设备
-	 * 
+	 *
 	 * @param deviceId 设备ID
 	 * @return true:删除成功  false:删除失败
 	 */
 	public boolean delete(String deviceId);
-	
-	/**   
+
+	/**
 	 * 更新设备在线
-	 * 
+	 *
 	 * @param deviceId 设备ID
 	 * @return true:更新成功  false:更新失败
 	 */
 	public boolean online(String deviceId);
-	
-	/**   
+
+	/**
 	 * 更新设备离线
-	 * 
+	 *
 	 * @param deviceId 设备ID
 	 * @return true:更新成功  false:更新失败
 	 */

+ 461 - 453
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/storage/impl/RedisCatchStorageImpl.java

@@ -1,32 +1,43 @@
-//package org.jetlinks.community.media.storage.impl;
-//
-//import com.alibaba.fastjson.JSONObject;
-//import lombok.extern.slf4j.Slf4j;
-//import org.jetlinks.community.media.bean.StreamInfo;
-//import org.jetlinks.community.media.contanst.VideoManagerConstants;
-//import org.jetlinks.community.media.entity.DeviceChannel;
-//import org.jetlinks.community.media.entity.MediaDevice;
-//import org.jetlinks.community.media.entity.ParentPlatform;
-//import org.jetlinks.community.media.storage.IRedisCatchStorage;
-//import org.jetlinks.community.media.zlm.dto.MediaItem;
-//import org.jetlinks.community.media.zlm.dto.MediaServerItem;
-//import org.jetlinks.community.utils.RedisUtil;
-//import org.springframework.beans.factory.annotation.Autowired;
-//import org.springframework.stereotype.Component;
-//
-//import java.text.SimpleDateFormat;
-//import java.util.ArrayList;
-//import java.util.HashMap;
-//import java.util.List;
-//import java.util.Map;
-//
-//@SuppressWarnings("rawtypes")
-//@Component
-//@Slf4j
-//public class RedisCatchStorageImpl implements IRedisCatchStorage {
-//
-//
-//    @Autowired
+package org.jetlinks.community.media.storage.impl;
+
+import cn.hutool.json.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.community.media.bean.SendRtpItem;
+import org.jetlinks.community.media.bean.StreamInfo;
+import org.jetlinks.community.media.contanst.VideoManagerConstants;
+import org.jetlinks.community.media.entity.MediaDevice;
+import org.jetlinks.community.media.bean.ParentPlatform;
+import org.jetlinks.community.media.zlm.dto.MediaItem;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
+import org.jetlinks.community.utils.RedisUtil;
+import org.jetlinks.core.cluster.ClusterManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings("rawtypes")
+@Component
+@Slf4j
+public class RedisCatchStorageImpl {
+
+    private final RedisUtil redis;
+
+    private final String serverId;
+
+    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+    @Autowired
+    public RedisCatchStorageImpl(RedisUtil redis,
+                                 ClusterManager clusterManager) {
+        this.redis = redis;
+        this.serverId=clusterManager.getCurrentServerId();
+    }
+
+    //    @Autowired
 //	private final RedisUtil redis;
 //
 //    @Autowired
@@ -34,152 +45,145 @@
 //
 //    @Autowired
 //    private final UserSetup userSetup;
-//
-//    @Autowired
-//    public RedisCatchStorageImpl(RedisUtil redis, DeviceChannelMapper deviceChannelMapper, UserSetup userSetup) {
-//        this.redis = redis;
-//        this.deviceChannelMapper = deviceChannelMapper;
-//        this.userSetup = userSetup;
-//    }
-//
-//    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-//
-//    @Override
-//    public Long getCSEQ(String method) {
-//        String key = VideoManagerConstants.SIP_CSEQ_PREFIX  + userSetup.getServerId() + "_" +  method;
-//
-//        long result =  redis.incr(key, 1L);
-//        if (result > Integer.MAX_VALUE) {
-//            redis.set(key, 1);
-//            result = 1;
-//        }
-//        return result;
-//    }
-//
-//    @Override
-//    public Long getSN(String method) {
-//        String key = VideoManagerConstants.SIP_SN_PREFIX  + userSetup.getServerId() + "_" +  method;
-//
-//        long result =  redis.incr(key, 1L);
-//        if (result > Integer.MAX_VALUE) {
-//            redis.set(key, 1);
-//            result = 1;
-//        }
-//        return result;
-//    }
-//
-//    @Override
-//    public void resetAllCSEQ() {
-//        String scanKey = VideoManagerConstants.SIP_CSEQ_PREFIX  + userSetup.getServerId() + "_*";
-//        List<Object> keys = redis.scan(scanKey);
-//        for (int i = 0; i < keys.size(); i++) {
-//            String key = (String) keys.get(i);
-//            redis.set(key, 1);
-//        }
-//    }
-//
-//    @Override
-//    public void resetAllSN() {
-//        String scanKey = VideoManagerConstants.SIP_SN_PREFIX  + userSetup.getServerId() + "_*";
-//        List<Object> keys = redis.scan(scanKey);
-//        for (int i = 0; i < keys.size(); i++) {
-//            String key = (String) keys.get(i);
-//            redis.set(key, 1);
-//        }
-//    }
-//
-//    /**
-//     * 开始播放时将流存入redis
-//     *
-//     * @return
-//     */
-//    @Override
-//    public boolean startPlay(StreamInfo stream) {
-//        return redis.set(String.format("%S_%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, userSetup.getServerId(), stream.getStreamId(),stream.getDeviceID(), stream.getChannelId()),
-//                stream);
-//    }
-//
-//    /**
-//     * 停止播放时从redis删除
-//     *
-//     * @return
-//     */
-//    @Override
-//    public boolean stopPlay(StreamInfo streamInfo) {
-//        if (streamInfo == null) return false;
-//        return redis.del(String.format("%S_%s_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
-//                userSetup.getServerId(),
-//                streamInfo.getStreamId(),
-//                streamInfo.getDeviceID(),
-//                streamInfo.getChannelId()));
-//    }
-//
-//    /**
-//     * 查询播放列表
-//     * @return
-//     */
-//    @Override
-//    public StreamInfo queryPlay(StreamInfo streamInfo) {
-//        return (StreamInfo)redis.get(String.format("%S_%s_%s_%s_%s",
-//                VideoManagerConstants.PLAYER_PREFIX,
-//                userSetup.getServerId(),
-//                streamInfo.getStreamId(),
-//                streamInfo.getDeviceID(),
-//                streamInfo.getChannelId()));
-//    }
-//    @Override
-//    public StreamInfo queryPlayByStreamId(String streamId) {
-//        List<Object> playLeys = redis.scan(String.format("%S_%s_%s_*", VideoManagerConstants.PLAYER_PREFIX, userSetup.getServerId(), streamId));
-//        if (playLeys == null || playLeys.size() == 0) return null;
-//        return (StreamInfo)redis.get(playLeys.get(0).toString());
-//    }
-//
-//    @Override
-//    public StreamInfo queryPlaybackByStreamId(String streamId) {
-//        List<Object> playLeys = redis.scan(String.format("%S_%s_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, userSetup.getServerId(),  streamId));
-//        if (playLeys == null || playLeys.size() == 0) return null;
-//        return (StreamInfo)redis.get(playLeys.get(0).toString());
-//    }
-//
-//    @Override
-//    public StreamInfo queryPlayByDevice(String deviceId, String channelId) {
-////		List<Object> playLeys = redis.keys(String.format("%S_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
-//        List<Object> playLeys = redis.scan(String.format("%S_%s_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
-//                userSetup.getServerId(),
-//                deviceId,
-//                channelId));
-//        if (playLeys == null || playLeys.size() == 0) return null;
-//        return (StreamInfo)redis.get(playLeys.get(0).toString());
-//    }
-//
-//    @Override
-//    public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) {
-//        Map<String, StreamInfo> streamInfos = new HashMap<>();
-////		List<Object> playLeys = redis.keys(String.format("%S_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, deviceId));
-//        List<Object> players = redis.scan(String.format("%S_%s_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, userSetup.getServerId(),deviceId));
-//        if (players.size() == 0) return streamInfos;
-//        for (int i = 0; i < players.size(); i++) {
-//            String key = (String) players.get(i);
-//            StreamInfo streamInfo = (StreamInfo)redis.get(key);
-//            streamInfos.put(streamInfo.getDeviceID() + "_" + streamInfo.getChannelId(), streamInfo);
-//        }
-//        return streamInfos;
-//    }
-//
-//
-//    @Override
-//    public boolean startPlayback(StreamInfo stream) {
-//        return redis.set(String.format("%S_%s_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, userSetup.getServerId(),stream.getStreamId(),
-//                        stream.getDeviceID(), stream.getChannelId()), stream);
-//    }
-//
-//    @Override
-//    public boolean startDownload(StreamInfo streamInfo) {
-//        return redis.set(String.format("%S_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX, userSetup.getServerId(),streamInfo.getStreamId(),
-//                        streamInfo.getDeviceID(), streamInfo.getChannelId()), streamInfo);
-//    }
-//
-//    @Override
+
+    public Long getCSEQ(String method) {
+        String key = VideoManagerConstants.SIP_CSEQ_PREFIX  +serverId + "_" +  method;
+        long result =  redis.incr(key, 1L);
+        if (result > Integer.MAX_VALUE) {
+            redis.set(key, 1);
+            result = 1;
+        }
+        return result;
+    }
+
+
+    public Long getSN(String method) {
+        String key = VideoManagerConstants.SIP_SN_PREFIX  +serverId+ "_" +  method;
+
+        long result =  redis.incr(key, 1L);
+        if (result > Integer.MAX_VALUE) {
+            redis.set(key, 1);
+            result = 1;
+        }
+        return result;
+    }
+
+
+    public void resetAllCSEQ() {
+        String scanKey = VideoManagerConstants.SIP_CSEQ_PREFIX  +serverId + "_*";
+        List<Object> keys = redis.scan(scanKey);
+        for (int i = 0; i < keys.size(); i++) {
+            String key = (String) keys.get(i);
+            redis.set(key, 1);
+        }
+    }
+
+    public void resetAllSN() {
+        String scanKey = VideoManagerConstants.SIP_SN_PREFIX  + serverId+ "_*";
+        List<Object> keys = redis.scan(scanKey);
+        for (int i = 0; i < keys.size(); i++) {
+            String key = (String) keys.get(i);
+            redis.set(key, 1);
+        }
+    }
+
+    /**
+     * 开始播放时将流存入redis
+     *
+     * @return
+     */
+    public boolean startPlay(StreamInfo stream) {
+        return redis.set(String.format("%S_%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX,serverId, stream.getStreamId(),stream.getDeviceID(), stream.getChannelId()),
+                stream);
+    }
+
+    /**
+     * 停止播放时从redis删除
+     *
+     * @return
+     */
+    public boolean stopPlay(StreamInfo streamInfo) {
+        if (streamInfo == null) {
+            return false;
+        }
+        return redis.del(String.format("%S_%s_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
+            serverId,
+                streamInfo.getStreamId(),
+                streamInfo.getDeviceID(),
+                streamInfo.getChannelId()));
+    }
+
+    /**
+     * 查询播放列表
+     * @return
+     */
+
+    public StreamInfo queryPlay(StreamInfo streamInfo) {
+        return (StreamInfo)redis.get(String.format("%S_%s_%s_%s_%s",
+                VideoManagerConstants.PLAYER_PREFIX,
+                serverId,
+                streamInfo.getStreamId(),
+                streamInfo.getDeviceID(),
+                streamInfo.getChannelId()));
+    }
+
+    public StreamInfo queryPlayByStreamId(String streamId) {
+        List<Object> playLeys = redis.scan(String.format("%S_%s_%s_*", VideoManagerConstants.PLAYER_PREFIX, serverId, streamId));
+        if (playLeys == null || playLeys.size() == 0) {
+            return null;
+        }
+        return (StreamInfo)redis.get(playLeys.get(0).toString());
+    }
+
+
+    public StreamInfo queryPlaybackByStreamId(String streamId) {
+        List<Object> playLeys = redis.scan(String.format("%S_%s_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, serverId,  streamId));
+        if (playLeys == null || playLeys.size() == 0) {
+            return null;
+        }
+        return (StreamInfo)redis.get(playLeys.get(0).toString());
+    }
+
+
+    public StreamInfo queryPlayByDevice(String deviceId, String channelId) {
+        List<Object> playLeys = redis.scan(String.format("%S_%s_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX,
+            serverId,
+                deviceId,
+                channelId));
+        if (playLeys == null || playLeys.size() == 0){
+            return null;
+        }
+        return (StreamInfo)redis.get(playLeys.get(0).toString());
+    }
+
+
+    public Map<String, StreamInfo> queryPlayByDeviceId(String deviceId) {
+        Map<String, StreamInfo> streamInfos = new HashMap<>();
+        List<Object> players = redis.scan(String.format("%S_%s_*_%S_*", VideoManagerConstants.PLAYER_PREFIX, serverId,deviceId));
+        if (players.size() == 0) {
+            return streamInfos;
+        }
+        for (int i = 0; i < players.size(); i++) {
+            String key = (String) players.get(i);
+            StreamInfo streamInfo = (StreamInfo)redis.get(key);
+            streamInfos.put(streamInfo.getDeviceID() + "_" + streamInfo.getChannelId(), streamInfo);
+        }
+        return streamInfos;
+    }
+
+
+    public boolean startPlayback(StreamInfo stream) {
+        return redis.set(String.format("%S_%s_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,serverId,stream.getStreamId(),
+                        stream.getDeviceID(), stream.getChannelId()), stream);
+    }
+
+
+    public boolean startDownload(StreamInfo streamInfo) {
+        return redis.set(String.format("%S_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX, serverId,streamInfo.getStreamId(),
+                        streamInfo.getDeviceID(), streamInfo.getChannelId()), streamInfo);
+    }
+
+
 //    public boolean stopPlayback(StreamInfo streamInfo) {
 //        if (streamInfo == null) return false;
 //        DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(streamInfo.getDeviceID(), streamInfo.getChannelId());
@@ -189,304 +193,307 @@
 //            deviceChannelMapper.update(deviceChannel);
 //        }
 //        return redis.del(String.format("%S_%s_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
-//                userSetup.getServerId(),
+//            serverId,
 //                streamInfo.getStreamId(),
 //                streamInfo.getDeviceID(),
 //                streamInfo.getChannelId()));
 //    }
-//
-//    @Override
-//    public StreamInfo queryPlaybackByDevice(String deviceId, String code) {
-//        // String format = String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
-//        //         deviceId,
-//        //         code);
-//        List<Object> playLeys = redis.scan(String.format("%S_%s_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
-//                userSetup.getServerId(),
-//                deviceId,
-//                code));
-//        if (playLeys == null || playLeys.size() == 0) {
-//            playLeys = redis.scan(String.format("%S_%s_*_*_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
-//                    userSetup.getServerId(),
-//                    deviceId));
-//        }
-//        if (playLeys == null || playLeys.size() == 0) return null;
-//        return (StreamInfo)redis.get(playLeys.get(0).toString());
-//    }
-//
-//    @Override
+
+
+    public StreamInfo queryPlaybackByDevice(String deviceId, String code) {
+        // String format = String.format("%S_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
+        //         deviceId,
+        //         code);
+        List<Object> playLeys = redis.scan(String.format("%S_%s_*_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
+            serverId,
+                deviceId,
+                code));
+        if (playLeys == null || playLeys.size() == 0) {
+            playLeys = redis.scan(String.format("%S_%s_*_*_%s", VideoManagerConstants.PLAY_BLACK_PREFIX,
+                serverId,
+                    deviceId));
+        }
+        if (playLeys == null || playLeys.size() == 0) {
+            return null;
+        }
+        return (StreamInfo)redis.get(playLeys.get(0).toString());
+    }
+
 //    public void updatePlatformCatchInfo(ParentPlatformCatch parentPlatformCatch) {
-//        String key = VideoManagerConstants.PLATFORM_CATCH_PREFIX  + userSetup.getServerId() + "_" +  parentPlatformCatch.getId();
+//        String key = VideoManagerConstants.PLATFORM_CATCH_PREFIX  + serverId + "_" +  parentPlatformCatch.getId();
 //        redis.set(key, parentPlatformCatch);
 //    }
+
+
+    public void updatePlatformKeepalive(ParentPlatform parentPlatform) {
+        String key = VideoManagerConstants.PLATFORM_KEEPALIVE_PREFIX  + serverId + "_" + parentPlatform.getServerGBId();
+        redis.set(key, "", Integer.parseInt(parentPlatform.getKeepTimeout()));
+    }
+
+
+    public void updatePlatformRegister(ParentPlatform parentPlatform) {
+        String key = VideoManagerConstants.PLATFORM_REGISTER_PREFIX + serverId + "_" + parentPlatform.getServerGBId();
+        redis.set(key, "", Integer.parseInt(parentPlatform.getExpires()));
+    }
+
 //
-//    @Override
-//    public void updatePlatformKeepalive(ParentPlatform parentPlatform) {
-//        String key = VideoManagerConstants.PLATFORM_KEEPALIVE_PREFIX  + userSetup.getServerId() + "_" + parentPlatform.getServerGBId();
-//        redis.set(key, "", Integer.parseInt(parentPlatform.getKeepTimeout()));
-//    }
-//
-//    @Override
-//    public void updatePlatformRegister(ParentPlatform parentPlatform) {
-//        String key = VideoManagerConstants.PLATFORM_REGISTER_PREFIX + userSetup.getServerId() + "_" + parentPlatform.getServerGBId();
-//        redis.set(key, "", Integer.parseInt(parentPlatform.getExpires()));
-//    }
-//
-//    @Override
 //    public ParentPlatformCatch queryPlatformCatchInfo(String platformGbId) {
-//        return (ParentPlatformCatch)redis.get(VideoManagerConstants.PLATFORM_CATCH_PREFIX + userSetup.getServerId() + "_" + platformGbId);
-//    }
-//
-//    @Override
-//    public void delPlatformCatchInfo(String platformGbId) {
-//        redis.del(VideoManagerConstants.PLATFORM_CATCH_PREFIX + userSetup.getServerId() + "_" + platformGbId);
-//    }
-//
-//    @Override
-//    public void delPlatformKeepalive(String platformGbId) {
-//        redis.del(VideoManagerConstants.PLATFORM_KEEPALIVE_PREFIX + userSetup.getServerId() + "_" + platformGbId);
-//    }
-//
-//    @Override
-//    public void delPlatformRegister(String platformGbId) {
-//        redis.del(VideoManagerConstants.PLATFORM_REGISTER_PREFIX + userSetup.getServerId() + "_" + platformGbId);
-//    }
-//
-//
-//    @Override
-//    public void updatePlatformRegisterInfo(String callId, String platformGbId) {
-//        String key = VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetup.getServerId() + "_" + callId;
-//        redis.set(key, platformGbId);
-//    }
-//
-//
-//    @Override
-//    public String queryPlatformRegisterInfo(String callId) {
-//        return (String)redis.get(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetup.getServerId() + "_" + callId);
-//    }
-//
-//    @Override
-//    public void delPlatformRegisterInfo(String callId) {
-//        redis.del(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetup.getServerId() + "_" + callId);
-//    }
-//
-//    @Override
-//    public void cleanPlatformRegisterInfos() {
-//        List regInfos = redis.scan(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetup.getServerId() + "_" + "*");
-//        for (Object key : regInfos) {
-//            redis.del(key.toString());
-//        }
-//    }
-//
-//    @Override
-//    public void updateSendRTPSever(SendRtpItem sendRtpItem) {
-//        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + userSetup.getServerId() + "_" + sendRtpItem.getPlatformId() + "_" + sendRtpItem.getChannelId();
-//        redis.set(key, sendRtpItem);
-//    }
-//
-//    @Override
-//    public SendRtpItem querySendRTPServer(String platformGbId, String channelId) {
-//        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + userSetup.getServerId() + "_" + platformGbId + "_" + channelId;
-//        return (SendRtpItem)redis.get(key);
-//    }
-//
-//    @Override
-//    public List<SendRtpItem> querySendRTPServer(String platformGbId) {
-//        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + userSetup.getServerId() + "_" + platformGbId + "_*";
-//        List<Object> queryResult = redis.scan(key);
-//        List<SendRtpItem> result= new ArrayList<>();
-//
-//        for (int i = 0; i < queryResult.size(); i++) {
-//            String keyItem = (String) queryResult.get(i);
-//            result.add((SendRtpItem)redis.get(keyItem));
-//        }
-//
-//        return result;
-//    }
-//
-//    /**
-//     * 删除RTP推送信息缓存
-//     * @param platformGbId
-//     * @param channelId
-//     */
-//    @Override
-//    public void deleteSendRTPServer(String platformGbId, String channelId) {
-//        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + userSetup.getServerId() + "_" + platformGbId + "_" + channelId;
-//        redis.del(key);
-//    }
-//
-//    /**
-//     * 查询某个通道是否存在上级点播(RTP推送)
-//     * @param channelId
-//     */
-//    @Override
-//    public boolean isChannelSendingRTP(String channelId) {
-//        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + userSetup.getServerId() + "_" + "*_" + channelId;
-//        List<Object> RtpStreams = redis.scan(key);
-//        if (RtpStreams.size() > 0) {
-//            return true;
-//        } else {
-//            return false;
-//        }
-//    }
-//
-//    @Override
-//    public void clearCatchByDeviceId(String deviceId) {
-//        List<Object> playLeys = redis.scan(String.format("%S_%s_*_%s_*", VideoManagerConstants.PLAYER_PREFIX,
-//                userSetup.getServerId(),
-//                deviceId));
-//        if (playLeys.size() > 0) {
-//            for (Object key : playLeys) {
-//                redis.del(key.toString());
-//            }
-//        }
-//
-//        List<Object> playBackers = redis.scan(String.format("%S_%s_*_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX,
-//                userSetup.getServerId(),
-//                deviceId));
-//        if (playBackers.size() > 0) {
-//            for (Object key : playBackers) {
-//                redis.del(key.toString());
-//            }
-//        }
-//
-//        List<Object> deviceCache = redis.scan(String.format("%S%s_%s", VideoManagerConstants.DEVICE_PREFIX,
-//                userSetup.getServerId(),
-//                deviceId));
-//        if (deviceCache.size() > 0) {
-//            for (Object key : deviceCache) {
-//                redis.del(key.toString());
-//            }
-//        }
-//    }
-//
-//    @Override
-//    public void outlineForAll() {
-//        List<Object> onlineDevices = redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetup.getServerId() + "_" + "*" );
-//        for (int i = 0; i < onlineDevices.size(); i++) {
-//            String key = (String) onlineDevices.get(i);
-//            redis.del(key);
-//        }
-//    }
-//
-//    @Override
-//    public List<String> getOnlineForAll() {
-//        List<String> result = new ArrayList<>();
-//        List<Object> onlineDevices = redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX + userSetup.getServerId() + "_"  + "*" );
-//        for (int i = 0; i < onlineDevices.size(); i++) {
-//            String key = (String) onlineDevices.get(i);
-//            result.add((String) redis.get(key));
-//        }
-//        return result;
-//    }
-//
-//    @Override
-//    public void updateWVPInfo(JSONObject jsonObject, int time) {
-//        String key = VideoManagerConstants.WVP_SERVER_PREFIX + userSetup.getServerId();
-//        redis.set(key, jsonObject, time);
-//    }
-//
-//    @Override
-//    public void sendStreamChangeMsg(String type, JSONObject jsonObject) {
-//        String key = VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + type;
-//        log.debug("[redis 流变化事件] {}: {}", key, jsonObject.toString());
-//        redis.convertAndSend(key, jsonObject);
-//    }
-//
-//    @Override
-//    public void addStream(MediaServerItem mediaServerItem, String type, String app, String streamId, MediaItem mediaItem) {
-//        String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX  + userSetup.getServerId() + "_" + type + "_" + app + "_" + streamId + "_" + mediaServerItem.getId();
-//        redis.set(key, mediaItem);
-//    }
-//
-//    @Override
-//    public void removeStream(String mediaServerId, String type, String app, String streamId) {
-//        String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetup.getServerId() + "_" + type + "_"  + app + "_" + streamId + "_" + mediaServerId;
-//        redis.del(key);
-//    }
-//
-//    @Override
-//    public StreamInfo queryDownloadByStreamId(String streamId) {
-//        List<Object> playLeys = redis.scan(String.format("%S_%s_%s_*", VideoManagerConstants.DOWNLOAD_PREFIX, userSetup.getServerId(), streamId));
-//        if (playLeys == null || playLeys.size() == 0) return null;
-//        return (StreamInfo)redis.get(playLeys.get(0).toString());
-//    }
-//
-//    @Override
+//        return (ParentPlatformCatch)redis.get(VideoManagerConstants.PLATFORM_CATCH_PREFIX + serverId + "_" + platformGbId);
+//    }
+
+
+    public void delPlatformCatchInfo(String platformGbId) {
+        redis.del(VideoManagerConstants.PLATFORM_CATCH_PREFIX + serverId + "_" + platformGbId);
+    }
+
+
+    public void delPlatformKeepalive(String platformGbId) {
+        redis.del(VideoManagerConstants.PLATFORM_KEEPALIVE_PREFIX + serverId + "_" + platformGbId);
+    }
+
+
+    public void delPlatformRegister(String platformGbId) {
+        redis.del(VideoManagerConstants.PLATFORM_REGISTER_PREFIX + serverId + "_" + platformGbId);
+    }
+
+
+
+    public void updatePlatformRegisterInfo(String callId, String platformGbId) {
+        String key = VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + serverId + "_" + callId;
+        redis.set(key, platformGbId);
+    }
+
+
+
+    public String queryPlatformRegisterInfo(String callId) {
+        return (String)redis.get(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + serverId + "_" + callId);
+    }
+
+
+    public void delPlatformRegisterInfo(String callId) {
+        redis.del(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + serverId + "_" + callId);
+    }
+
+
+    public void cleanPlatformRegisterInfos() {
+        List regInfos = redis.scan(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + serverId + "_" + "*");
+        for (Object key : regInfos) {
+            redis.del(key.toString());
+        }
+    }
+
+
+    public void updateSendRTPSever(SendRtpItem sendRtpItem) {
+        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + serverId + "_" + sendRtpItem.getPlatformId() + "_" + sendRtpItem.getChannelId();
+        redis.set(key, sendRtpItem);
+    }
+
+
+    public SendRtpItem querySendRTPServer(String platformGbId, String channelId) {
+        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + serverId + "_" + platformGbId + "_" + channelId;
+        return (SendRtpItem)redis.get(key);
+    }
+
+
+    public List<SendRtpItem> querySendRTPServer(String platformGbId) {
+        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + serverId + "_" + platformGbId + "_*";
+        List<Object> queryResult = redis.scan(key);
+        List<SendRtpItem> result= new ArrayList<>();
+
+        for (int i = 0; i < queryResult.size(); i++) {
+            String keyItem = (String) queryResult.get(i);
+            result.add((SendRtpItem)redis.get(keyItem));
+        }
+
+        return result;
+    }
+
+    /**
+     * 删除RTP推送信息缓存
+     * @param platformGbId
+     * @param channelId
+     */
+
+    public void deleteSendRTPServer(String platformGbId, String channelId) {
+        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + serverId + "_" + platformGbId + "_" + channelId;
+        redis.del(key);
+    }
+
+    /**
+     * 查询某个通道是否存在上级点播(RTP推送)
+     * @param channelId
+     */
+
+    public boolean isChannelSendingRTP(String channelId) {
+        String key = VideoManagerConstants.PLATFORM_SEND_RTP_INFO_PREFIX + serverId + "_" + "*_" + channelId;
+        List<Object> RtpStreams = redis.scan(key);
+        if (RtpStreams.size() > 0) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+    public void clearCatchByDeviceId(String deviceId) {
+        List<Object> playLeys = redis.scan(String.format("%S_%s_*_%s_*", VideoManagerConstants.PLAYER_PREFIX,
+                serverId,
+                deviceId));
+        if (playLeys.size() > 0) {
+            for (Object key : playLeys) {
+                redis.del(key.toString());
+            }
+        }
+
+        List<Object> playBackers = redis.scan(String.format("%S_%s_*_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX,
+                serverId,
+                deviceId));
+        if (playBackers.size() > 0) {
+            for (Object key : playBackers) {
+                redis.del(key.toString());
+            }
+        }
+
+        List<Object> deviceCache = redis.scan(String.format("%S%s_%s", VideoManagerConstants.DEVICE_PREFIX,
+                serverId,
+                deviceId));
+        if (deviceCache.size() > 0) {
+            for (Object key : deviceCache) {
+                redis.del(key.toString());
+            }
+        }
+    }
+
+
+    public void outlineForAll() {
+        List<Object> onlineDevices = redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX + serverId + "_" + "*" );
+        for (int i = 0; i < onlineDevices.size(); i++) {
+            String key = (String) onlineDevices.get(i);
+            redis.del(key);
+        }
+    }
+
+
+    public List<String> getOnlineForAll() {
+        List<String> result = new ArrayList<>();
+        List<Object> onlineDevices = redis.scan(VideoManagerConstants.KEEPLIVEKEY_PREFIX + serverId + "_"  + "*" );
+        for (int i = 0; i < onlineDevices.size(); i++) {
+            String key = (String) onlineDevices.get(i);
+            result.add((String) redis.get(key));
+        }
+        return result;
+    }
+
+
+    public void updateWVPInfo(JSONObject jsonObject, int time) {
+        String key = VideoManagerConstants.WVP_SERVER_PREFIX + serverId;
+        redis.set(key, jsonObject, time);
+    }
+
+
+    public void sendStreamChangeMsg(String type, JSONObject jsonObject) {
+        String key = VideoManagerConstants.WVP_MSG_STREAM_CHANGE_PREFIX + type;
+        log.debug("[redis 流变化事件] {}: {}", key, jsonObject.toString());
+        redis.convertAndSend(key, jsonObject);
+    }
+
+
+    public void addStream(MediaServerItem mediaServerItem, String type, String app, String streamId, MediaItem mediaItem) {
+        String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX  + serverId + "_" + type + "_" + app + "_" + streamId + "_" + mediaServerItem.getId();
+        redis.set(key, mediaItem);
+    }
+
+
+    public void removeStream(String mediaServerId, String type, String app, String streamId) {
+        String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + serverId + "_" + type + "_"  + app + "_" + streamId + "_" + mediaServerId;
+        redis.del(key);
+    }
+
+
+    public StreamInfo queryDownloadByStreamId(String streamId) {
+        List<Object> playLeys = redis.scan(String.format("%S_%s_%s_*", VideoManagerConstants.DOWNLOAD_PREFIX, serverId, streamId));
+        if (playLeys == null || playLeys.size() == 0) {
+            return null;
+        }
+        return (StreamInfo)redis.get(playLeys.get(0).toString());
+    }
+
+
 //    public ThirdPartyGB queryMemberNoGBId(String queryKey) {
 //        String key = VideoManagerConstants.WVP_STREAM_GB_ID_PREFIX + queryKey;
 //        JSONObject jsonObject = (JSONObject)redis.get(key);
 //        return  JSONObject.toJavaObject(jsonObject, ThirdPartyGB.class);
 //    }
-//
-//    @Override
-//    public void removeStream(String mediaServerId, String type) {
-//        String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetup.getServerId() + "_" + type + "_*_*_" + mediaServerId;
-//        List<Object> streams = redis.scan(key);
-//        for (Object stream : streams) {
-//            redis.del((String) stream);
-//        }
-//    }
-//
-//    @Override
-//    public List<MediaItem> getStreams(String mediaServerId, String type) {
-//        List<MediaItem> result = new ArrayList<>();
-//        String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetup.getServerId() + "_" + type + "_*_*_" + mediaServerId;
-//        List<Object> streams = redis.scan(key);
-//        for (Object stream : streams) {
-//            MediaItem mediaItem = (MediaItem)redis.get((String) stream);
-//            result.add(mediaItem);
-//        }
-//        return result;
-//    }
-//
-//    @Override
-//    public void updateDevice(MediaDevice device) {
-//        String key = VideoManagerConstants.DEVICE_PREFIX + userSetup.getServerId() + "_" + device.getId();
-//        redis.set(key, device);
-//    }
-//
-//    @Override
-//    public void removeDevice(String deviceId) {
-//        String key = VideoManagerConstants.DEVICE_PREFIX + userSetup.getServerId() + "_" + deviceId;
-//        redis.del(key);
-//    }
-//
-//    @Override
-//    public MediaDevice getDevice(String deviceId) {
-//        String key = VideoManagerConstants.DEVICE_PREFIX + userSetup.getServerId() + "_" + deviceId;
-//        return (MediaDevice)redis.get(key);
-//    }
-//
-//    @Override
+
+
+    public void removeStream(String mediaServerId, String type) {
+        String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + serverId + "_" + type + "_*_*_" + mediaServerId;
+        List<Object> streams = redis.scan(key);
+        for (Object stream : streams) {
+            redis.del((String) stream);
+        }
+    }
+
+
+    public List<MediaItem> getStreams(String mediaServerId, String type) {
+        List<MediaItem> result = new ArrayList<>();
+        String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + serverId + "_" + type + "_*_*_" + mediaServerId;
+        List<Object> streams = redis.scan(key);
+        for (Object stream : streams) {
+            MediaItem mediaItem = (MediaItem)redis.get((String) stream);
+            result.add(mediaItem);
+        }
+        return result;
+    }
+
+
+    public void updateDevice(MediaDevice device) {
+        String key = VideoManagerConstants.DEVICE_PREFIX + serverId + "_" + device.getId();
+        redis.set(key, device);
+    }
+
+
+    public void removeDevice(String deviceId) {
+        String key = VideoManagerConstants.DEVICE_PREFIX + serverId + "_" + deviceId;
+        redis.del(key);
+    }
+
+
+    public MediaDevice getDevice(String deviceId) {
+        String key = VideoManagerConstants.DEVICE_PREFIX + serverId + "_" + deviceId;
+        return (MediaDevice)redis.get(key);
+    }
+
+
 //    public void updateGpsMsgInfo(GPSMsgInfo gpsMsgInfo) {
-//        String key = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetup.getServerId() + "_" + gpsMsgInfo.getId();
+//        String key = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + serverId + "_" + gpsMsgInfo.getId();
 //        redis.set(key, gpsMsgInfo, 60); // 默认GPS消息保存1分钟
 //    }
 //
-//    @Override
+//
 //    public GPSMsgInfo getGpsMsgInfo(String gbId) {
-//        String key = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetup.getServerId() + "_" + gbId;
+//        String key = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + serverId + "_" + gbId;
 //        return (GPSMsgInfo)redis.get(key);
 //    }
 //
-//    @Override
+//
 //    public void updateSubscribe(String key, SubscribeInfo subscribeInfo) {
 //        redis.set(key, subscribeInfo, subscribeInfo.getExpires());
 //    }
 //
-//    @Override
+//
 //    public SubscribeInfo getSubscribe(String key) {
 //        return (SubscribeInfo)redis.get(key);
 //    }
 //
-//    @Override
+//
 //    public void delSubscribe(String key) {
 //        redis.del(key);
 //    }
 //
-//    @Override
+//
 //    public List<GPSMsgInfo> getAllGpsMsgInfo() {
-//        String scanKey = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetup.getServerId() + "_*";
+//        String scanKey = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + serverId + "_*";
 //        List<GPSMsgInfo> result = new ArrayList<>();
 //        List<Object> keys = redis.scan(scanKey);
 //        for (int i = 0; i < keys.size(); i++) {
@@ -499,18 +506,19 @@
 //
 //        return result;
 //    }
-//
-//    @Override
-//    public MediaItem getStreamInfo(String app, String streamId, String mediaServerId) {
-//        String scanKey = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX  + userSetup.getServerId() + "_*_" + app + "_" + streamId + "_" + mediaServerId;
-//
-//        MediaItem result = null;
-//        List<Object> keys = redis.scan(scanKey);
-//        if (keys.size() > 0) {
-//            String key = (String) keys.get(0);
-//            result = (MediaItem)redis.get(key);
-//        }
-//
-//        return result;
-//    }
-//}
+
+
+    public MediaItem getStreamInfo(String app, String streamId, String mediaServerId) {
+        String scanKey = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX  + serverId + "_*_" + app + "_" + streamId + "_" + mediaServerId;
+
+        MediaItem result = null;
+        List<Object> keys = redis.scan(scanKey);
+        if (keys.size() > 0) {
+            String key = (String) keys.get(0);
+            result = (MediaItem)redis.get(key);
+        }
+
+        return result;
+    }
+
+}

+ 16 - 3
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/SIPRequestHeaderProvider.java

@@ -7,11 +7,14 @@ import org.jetlinks.community.media.bean.StreamInfo;
 import org.jetlinks.community.media.entity.MediaDevice;
 import org.jetlinks.community.media.entity.SipServerConfig;
 import org.jetlinks.community.media.entity.WvpSipDate;
+import org.jetlinks.community.media.bean.SipServerConfig;
+import org.jetlinks.community.media.bean.WvpSipDate;
 import org.jetlinks.community.media.sip.SipContext;
 import org.jetlinks.community.media.storage.IRedisCatchStorage;
 import org.jetlinks.community.utils.RedisUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.jetlinks.community.media.storage.impl.RedisCatchStorageImpl;
 import org.springframework.stereotype.Component;
 
 import javax.sip.Dialog;
@@ -34,11 +37,13 @@ import java.util.Locale;
  * @date: 2020年5月6日 上午9:29:02
  */
 @Component
+@AllArgsConstructor
 public class SIPRequestHeaderProvider {
 
 
 
 	private IRedisCatchStorage redisCatchStorage;
+	private final RedisCatchStorageImpl redisCatchStorage;
 
 //	@Autowired
 //	private VideoStreamSessionManager streamSession;
@@ -76,12 +81,13 @@ public class SIPRequestHeaderProvider {
 		return request;
 	}
 	int count=1;
-	public Request createInviteRequest(MediaDevice device,String destAddress, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+
+	public Request createInviteRequest(MediaDevice device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
         SipServerConfig sipConfig=SipContext.getConfig();
         SipFactory sipFactory=SipContext.getSipFactory();
 		Request request = null;
 		//请求行
-		SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, destAddress);
+		SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
 //        SipURI requestLine = sipFactory.createAddressFactory().createSipURI(channelId, device.getHostAddress());
 		//via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
@@ -97,10 +103,11 @@ public class SIPRequestHeaderProvider {
 		FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, fromTag);
 		//必须要有标记,否则无法创建会话,无法回应ack
 		//to
-		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId,destAddress);
+		SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(channelId,sipConfig.getDomain());
 		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
 		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress,null);
 		
+
 		//Forwards
 		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
 
@@ -108,8 +115,10 @@ public class SIPRequestHeaderProvider {
         //ceq
 //		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(Request.INVITE), Request.INVITE);
         CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(++count, Request.INVITE);
+		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(Request.INVITE), Request.INVITE);
 		request = sipFactory.createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
 		
+
 		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getHost()+":"+sipConfig.getPort()));
         request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
 		// Subject    媒体流发送者设备编码:发送端媒体流序列号,媒体流接收者设备编码:接收端媒体流序列号
@@ -132,6 +141,7 @@ public class SIPRequestHeaderProvider {
 		return request;
 	}
 	
+
 	public Request createPlaybackInviteRequest(MediaDevice device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException {
         SipServerConfig sipConfig=SipContext.getConfig();
         SipFactory sipFactory=SipContext.getSipFactory();
@@ -152,13 +162,16 @@ public class SIPRequestHeaderProvider {
 		Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI);
 		ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress,null);
 		
+
 		//Forwards
 		MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70);
 		
+
 		//ceq
 		CSeqHeader cSeqHeader = sipFactory.createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(Request.INVITE), Request.INVITE);
 		request = sipFactory.createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
 		
+
 		Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getHost()+":"+sipConfig.getPort()));
 		// Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
 		request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));

+ 148 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/callback/DeferredResultHolder.java

@@ -0,0 +1,148 @@
+package org.jetlinks.community.media.transmit.callback;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.async.DeferredResult;
+import reactor.core.publisher.EmitterProcessor;
+import reactor.core.publisher.Flux;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+/**
+ * @description: 异步请求处理
+ * @author: swwheihei
+ * @date:   2020年5月8日 下午7:59:05
+ */
+@SuppressWarnings(value = {"rawtypes", "unchecked"})
+@Component
+public class DeferredResultHolder {
+
+    public static final String CALLBACK_CMD_DEVICESTATUS = "CALLBACK_DEVICESTATUS";
+
+    public static final String CALLBACK_CMD_DEVICEINFO = "CALLBACK_DEVICEINFO";
+
+    public static final String CALLBACK_CMD_DEVICECONTROL = "CALLBACK_DEVICECONTROL";
+
+    public static final String CALLBACK_CMD_DEVICECONFIG = "CALLBACK_DEVICECONFIG";
+
+    public static final String CALLBACK_CMD_CONFIGDOWNLOAD = "CALLBACK_CONFIGDOWNLOAD";
+
+    public static final String CALLBACK_CMD_CATALOG = "CALLBACK_CATALOG";
+
+    public static final String CALLBACK_CMD_RECORDINFO = "CALLBACK_RECORDINFO";
+
+    public static final String CALLBACK_CMD_PLAY = "CALLBACK_PLAY";
+
+    public static final String CALLBACK_CMD_PLAYBACK = "CALLBACK_PLAY";
+
+    public static final String CALLBACK_CMD_DOWNLOAD = "CALLBACK_DOWNLOAD";
+
+    public static final String CALLBACK_CMD_STOP = "CALLBACK_STOP";
+
+    public static final String UPLOAD_FILE_CHANNEL = "UPLOAD_FILE_CHANNEL";
+
+    public static final String CALLBACK_CMD_MOBILEPOSITION = "CALLBACK_MOBILEPOSITION";
+
+    public static final String CALLBACK_CMD_PRESETQUERY = "CALLBACK_PRESETQUERY";
+
+    public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM";
+
+    public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST";
+
+    private static final String uniqueTopic="%s/%s/%s/%s";
+    private static final String matchTopics="%s/%s/%s/**";
+    /**
+     * 通过设备id、通道id、msgid获取唯一监听主题
+     * @param key
+     * @param requestMessage
+     * @return
+     */
+    public static String getTopicByMsgId(String key,RequestMessage requestMessage){
+        return String.format(uniqueTopic,key,requestMessage.getDeviceId(),requestMessage.getChannelId(),requestMessage.getMsgId());
+    }
+
+    /**
+     * 根据设备id和通道id匹配所有监听主题
+     * @param key
+     * @param requestMessage
+     * @return
+     */
+    public static String getTopicByDeviceIdAndChannelId(String key,RequestMessage requestMessage){
+        return String.format(matchTopics,key,requestMessage.getDeviceId(),requestMessage.getChannelId());
+    }
+//    private Map<String, Map<String, EmitterProcessor<ResponseEntity>>> map = new ConcurrentHashMap<>();
+
+
+//    /**
+//     *
+//     * @param key 数据类型
+//     * @param id 数据信息id
+//     * @param processor 数据处理流
+//     */
+//    public void put(String key, String id, EmitterProcessor<ResponseEntity> processor) {
+//        map.computeIfAbsent(key,k->new ConcurrentHashMap<>())
+//            .put(id,processor);
+//    }
+//
+//    public Flux<ResponseEntity> get(String key, String id) {
+//        Map<String, EmitterProcessor<ResponseEntity>> processorMap = map.getOrDefault(key, new ConcurrentHashMap<>());
+//        if (!processorMap.containsKey(id)) {
+//            return Flux.empty();
+//        }
+//        return processorMap.get(id).map(Function.identity());
+//    }
+//
+//    public boolean exist(String key, String id){
+//        return map.containsKey(key)&&map.get(key).containsKey(id);
+//    }
+//
+//    /**
+//     * 释放单个请求
+//     * @param msg
+//     */
+//    public void invokeResult(RequestMessage msg) {
+//        Map<String, EmitterProcessor<ResponseEntity>> deferredResultMap = map.get(msg.getKey());
+//        if (deferredResultMap == null) {
+//            return;
+//        }
+//        EmitterProcessor<ResponseEntity> result = deferredResultMap.get(msg.getId());
+//        if (result == null) {
+//            return;
+//        }
+//        if(!result.isDisposed()&&result.hasDownstreams()){
+//            result.sink().next(ResponseEntity.ok(msg.getData()));
+//        }
+//
+//        deferredResultMap.remove(msg.getId());
+//        if (deferredResultMap.size() == 0) {
+//            map.remove(msg.getKey());
+//        }
+//    }
+//
+//    /**
+//     * 释放所有的请求
+//     * @param msg
+//     */
+//    public void invokeAllResult(RequestMessage msg) {
+//        Map<String,  EmitterProcessor<ResponseEntity>> deferredResultMap = map.get(msg.getKey());
+//        if (deferredResultMap == null) {
+//            return;
+//        }
+//        Set<String> ids = deferredResultMap.keySet();
+//        for (String id : ids) {
+//            EmitterProcessor<ResponseEntity> result = deferredResultMap.get(id);
+//            if (result == null) {
+//                return;
+//            }
+//            if(!result.isDisposed()&&result.hasDownstreams()){
+//                result.sink().next(ResponseEntity.ok(msg.getData()));
+//            }
+//        }
+//        map.remove(msg.getKey());
+//
+//    }
+}

+ 32 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/callback/RequestMessage.java

@@ -0,0 +1,32 @@
+package org.jetlinks.community.media.transmit.callback;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.hswebframework.web.logger.ReactiveLogger;
+
+import javax.persistence.Transient;
+import java.io.Serializable;
+
+/**
+ * @description: 请求信息定义
+ * @author: swwheihei
+ * @date:   2020年5月8日 下午1:09:18
+ */
+@Data
+@AllArgsConstructor
+public class RequestMessage implements Serializable {
+
+	private String deviceId;
+	private String channelId;
+    private String msgId;
+	private Object data;
+
+    /**
+     * 是否根据设备id和通道id完成该次请求
+     * false:根据设备id+通道id+msgId完成该次请求
+     */
+	private Boolean invoke;
+    public static  RequestMessage of(String deviceId, String channelId, String msgId, Object data) {
+        return new RequestMessage(deviceId,channelId,msgId,data,null);
+    }
+}

+ 80 - 6
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/cmd/SipCommander.java

@@ -5,11 +5,12 @@ import org.hswebframework.web.exception.BusinessException;
 import org.jetlinks.community.media.bean.EventResult;
 import org.jetlinks.community.media.bean.SSRCInfo;
 import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.SipServerConfig;
+import org.jetlinks.community.media.bean.SipServerConfig;
+import org.jetlinks.community.media.gb28181.event.SipSubscribe;
 import org.jetlinks.community.media.sip.SipContext;
 import org.jetlinks.community.media.transmit.SIPRequestHeaderProvider;
 import org.jetlinks.community.media.zlm.ZLMHttpHookSubscribe;
-import org.jetlinks.community.media.zlm.dto.MediaServerItem;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
 import org.springframework.stereotype.Service;
 import reactor.core.publisher.*;
 
@@ -20,7 +21,6 @@ import java.text.ParseException;
 import java.time.Duration;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Consumer;
 
 /**
  * @author lifang
@@ -39,7 +39,8 @@ public class SipCommander {
     public static final Map<String, FluxProcessor<EventResult,EventResult>> replyProcessor=new ConcurrentHashMap<>(1024);
 
 
-    public Flux<EventResult> playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, MediaDevice device, String channelId) {
+    public Flux<EventResult> playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, MediaDevice device, String channelId,
+                                           ZLMHttpHookSubscribe.Event event, SipSubscribe.Event error) {
         String streamId = ssrcInfo.getStreamId();
         SipServerConfig sipConfig = SipContext.getConfig();
         try {
@@ -65,10 +66,8 @@ public class SipCommander {
             StringBuffer content = new StringBuffer(200);
             content.append("v=0\r\n");
             content.append("o="+ sipConfig.getId()+" 0 0 IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
-//            content.append("o=34020000002000000001 0 0 IN IP4 192.168.104.244 \r\n");
             content.append("s=Play\r\n");
             content.append("c=IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
-//            content.append("c=IN IP4 "+ mediaServerItem.getSdpIp() +"\r\n");
             content.append("t=0 0\r\n");
 
             //是否需要扩展sdp
@@ -170,6 +169,81 @@ public class SipCommander {
         }
     }
 
+    /**
+     * 视频流停止, 不使用回调 todo
+     */
+    public Mono<Void> streamByeCmd(String deviceId, String channelId) {
+        return Mono.empty() ;
+    }
+
+    /**
+     * 视频流停止
+     */
+//    public void streamByeCmd(String deviceId, String channelId, SipSubscribe.Event okEvent) {
+//        try {
+//            ClientTransaction transaction = streamSession.getTransaction(deviceId, channelId);
+//            if (transaction == null) {
+//                logger.warn("[ {} -> {}]停止视频流的时候发现事务已丢失", deviceId, channelId);
+//                SipSubscribe.EventResult<Object> eventResult = new SipSubscribe.EventResult<>();
+//                if (okEvent != null) {
+//                    okEvent.response(eventResult);
+//                }
+//                return;
+//            }
+//            SIPDialog dialog = streamSession.getDialog(deviceId, channelId);
+//            if (dialog == null) {
+//                logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", deviceId, channelId);
+//                return;
+//            }
+//            SipStack sipStack = udpSipProvider.getSipStack();
+//            SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog);
+//            if (dialog != sipDialog) {
+//                dialog = sipDialog;
+//            }else {
+//                dialog.setSipProvider(udpSipProvider);
+//                try {
+//                    Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
+//                    sipStackField.setAccessible(true);
+//                    sipStackField.set(dialog, sipStack);
+//                    Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
+//                    eventListenersField.setAccessible(true);
+//                    eventListenersField.set(dialog, new HashSet<>());
+//                } catch (NoSuchFieldException | IllegalAccessException e) {
+//                    e.printStackTrace();
+//                }
+//            }
+//
+//            Request byeRequest = dialog.createRequest(Request.BYE);
+//            SipURI byeURI = (SipURI) byeRequest.getRequestURI();
+//            SIPRequest request = (SIPRequest)transaction.getRequest();
+//            byeURI.setHost(request.getRemoteAddress().getHostName());
+//            byeURI.setPort(request.getRemotePort());
+//            ViaHeader viaHeader = (ViaHeader) byeRequest.getHeader(ViaHeader.NAME);
+//            String protocol = viaHeader.getTransport().toUpperCase();
+//            ClientTransaction clientTransaction = null;
+//            if("TCP".equals(protocol)) {
+//                clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
+//            } else if("UDP".equals(protocol)) {
+//                clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
+//            }
+//
+//            CallIdHeader callIdHeader = (CallIdHeader) byeRequest.getHeader(CallIdHeader.NAME);
+//            if (okEvent != null) {
+//                sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
+//            }
+//
+//            dialog.sendRequest(clientTransaction);
+//
+//            SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId);
+//            if (ssrcTransaction != null) {
+//                MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransaction.getMediaServerId());
+//                mediaServerService.releaseSsrc(mediaServerItem, ssrcTransaction.getSsrc());
+//                streamSession.remove(deviceId, channelId);
+//            }
+//        } catch (SipException | ParseException e) {
+//            e.printStackTrace();
+//        }
+//    }
 
     private Flux<EventResult> transmitRequest(SipProvider sipProvider, Request request) throws SipException {
         ClientTransaction clientTransaction = sipProvider.getNewClientTransaction(request);

+ 137 - 179
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMHttpHookListener.java

@@ -1,77 +1,36 @@
-//package org.jetlinks.community.media.zlm;
-//
-//import com.alibaba.fastjson.JSON;
-//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.gb28181.bean.Device;
-//import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
-//import com.genersoft.iot.vmp.media.zlm.dto.*;
-//import com.genersoft.iot.vmp.service.*;
-//import com.genersoft.iot.vmp.service.bean.SSRCInfo;
-//import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-//import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
-//import org.slf4j.Logger;
-//import org.slf4j.LoggerFactory;
-//import org.springframework.beans.factory.annotation.Autowired;
-//import org.springframework.http.HttpStatus;
-//import org.springframework.http.ResponseEntity;
-//import org.springframework.web.bind.annotation.*;
-//
-//import javax.servlet.http.HttpServletRequest;
-//import java.util.List;
-//import java.util.UUID;
-//
-///**
-// * @description:针对 ZLMediaServer的hook事件监听
-// * @author: swwheihei
-// * @date:   2020年5月8日 上午10:46:48
-// */
-//@RestController
-//@RequestMapping("/index/hook")
-//public class ZLMHttpHookListener {
-//
-//	private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class);
-//
-//	@Autowired
-//	private SIPCommander cmder;
-//
-//	@Autowired
-//	private IPlayService playService;
-//
-//	@Autowired
-//	private IVideoManagerStorager storager;
-//
-//	@Autowired
-//	private IRedisCatchStorage redisCatchStorage;
-//
-//	@Autowired
-//	private IMediaServerService mediaServerService;
-//
-//	@Autowired
-//	private IStreamProxyService streamProxyService;
-//
-//	@Autowired
-//	private IStreamPushService streamPushService;
-//
-//	@Autowired
-//	private IMediaService mediaService;
-//
-//	@Autowired
-//	private ZLMRESTfulUtils zlmresTfulUtils;
-//
-//	 @Autowired
-//	 private ZLMMediaListManager zlmMediaListManager;
-//
-//	@Autowired
-//	private ZLMHttpHookSubscribe subscribe;
-//
-//	@Autowired
-//	private UserSetup userSetup;
-//
-//	@Autowired
-//	private MediaConfig mediaConfig;
+package org.jetlinks.community.media.zlm;
+
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.web.authorization.annotation.Authorize;
+import org.jetlinks.community.media.bean.StreamInfo;
+import org.jetlinks.community.media.service.LocalMediaServerItemService;
+import org.jetlinks.community.media.storage.impl.RedisCatchStorageImpl;
+import org.jetlinks.community.media.zlm.dto.MediaItem;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
+import org.jetlinks.community.media.zlm.dto.OriginType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+import java.util.List;
+
+/**
+ * @description:针对 ZLMediaServer的hook事件监听
+ * @author: swwheihei
+ * @date:   2020年5月8日 上午10:46:48
+ */
+@RestController
+@RequestMapping("/index/hook")
+@Slf4j
+@Authorize(ignore = true)
+@AllArgsConstructor
+public class ZLMHttpHookListener {
+    private final LocalMediaServerItemService mediaServerItemService;
+    private final RedisCatchStorageImpl redisCatchStorage;
+    private final ZLMHttpHookSubscribe subscribe;
 //
 //	/**
 //	 * 服务器定时上报时间,上报间隔可配置,默认10s上报一次
@@ -288,86 +247,91 @@
 //		return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
 //	}
 //
-//	/**
-//	 * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
-//	 *
-//	 */
+    /**
+     * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
+     *
+     */
 //	@ResponseBody
-//	@PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
-//	public ResponseEntity<String> onStreamChanged(@RequestBody MediaItem item){
-//
-//		if (logger.isDebugEnabled()) {
-//			logger.debug("[ ZLM HOOK ]on_stream_changed API调用,参数:" + JSONObject.toJSONString(item));
-//		}
-//		String mediaServerId = item.getMediaServerId();
-//		JSONObject json = (JSONObject) JSON.toJSON(item);
-//		ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, json);
-//		if (subscribe != null ) {
-//			MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
-//			if (mediaInfo != null) {
-//				subscribe.response(mediaInfo, json);
-//			}
-//
-//		}
-//		// 流消失移除redis play
-//		String app = item.getApp();
-//		String streamId = item.getStream();
-//		String schema = item.getSchema();
-//		List<MediaItem.MediaTrack> tracks = item.getTracks();
-//		boolean regist = item.isRegist();
-//		if ("rtmp".equals(schema)){
-//			logger.info("on_stream_changed:注册->{}, app->{}, stream->{}", regist, app, streamId);
-//			if (regist) {
-//				mediaServerService.addCount(mediaServerId);
-//			}else {
-//				mediaServerService.removeCount(mediaServerId);
-//			}
-//			if ("rtp".equals(app) && !regist ) {
-//				StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
-//				if (streamInfo!=null){
-//					redisCatchStorage.stopPlay(streamInfo);
-//					storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
-//				}else{
-//					streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
-//					redisCatchStorage.stopPlayback(streamInfo);
-//				}
-//			}else {
-//				if (!"rtp".equals(app)){
-//					String type = OriginType.values()[item.getOriginType()].getType();
-//					MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
-//					if (mediaServerItem != null){
-//						if (regist) {
-//							redisCatchStorage.addStream(mediaServerItem, type, app, streamId, item);
-//							if (item.getOriginType() == OriginType.RTSP_PUSH.ordinal()
-//									|| item.getOriginType() == OriginType.RTMP_PUSH.ordinal()
-//									|| item.getOriginType() == OriginType.RTC_PUSH.ordinal() ) {
-//								zlmMediaListManager.addPush(item);
-//							}
-//						}else {
-//							// 兼容流注销时类型从redis记录获取
-//							MediaItem mediaItem = redisCatchStorage.getStreamInfo(app, streamId, mediaServerId);
-//							type = OriginType.values()[mediaItem.getOriginType()].getType();
-//							zlmMediaListManager.removeMedia(app, streamId);
-//							redisCatchStorage.removeStream(mediaServerItem.getId(), type, app, streamId);
-//						}
-//						// 发送流变化redis消息
-//						JSONObject jsonObject = new JSONObject();
-//						jsonObject.put("serverId", userSetup.getServerId());
-//						jsonObject.put("app", app);
-//						jsonObject.put("stream", streamId);
-//						jsonObject.put("register", regist);
-//						jsonObject.put("mediaServerId", mediaServerId);
-//						redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
-//					}
-//				}
-//			}
-//		}
-//
-//		JSONObject ret = new JSONObject();
-//		ret.put("code", 0);
-//		ret.put("msg", "success");
-//		return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
-//	}
+    @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
+    public Mono<ResponseEntity<String>> onStreamChanged(@RequestBody MediaItem item){
+        if (log.isDebugEnabled()) {
+            log.debug("[ ZLM HOOK ]on_stream_changed API调用,参数:" + JSONUtil.toJsonStr(item));
+        }
+        String mediaServerId = item.getMediaServerId();
+        JSONObject json = JSONUtil.parseObj(item);
+        Mono<ResponseEntity<String>> result=null;
+        //todo 这里订阅/发布采用eventBus 模式进行
+//        ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, json);
+//        if (subscribe != null ) {
+//            MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+//            if (mediaInfo != null) {
+//                subscribe.response(mediaInfo, json);
+//            }
+//        }
+        // 流消失移除redis play
+        //获取流应用名
+        String app = item.getApp();
+        //获取流ID
+        String streamId = item.getStream();
+        //rtsp或rtmp
+        String schema = item.getSchema();
+        //音频轨道
+        List<MediaItem.MediaTrack> tracks = item.getTracks();
+        boolean regist = item.isRegist();
+        if ("rtmp".equals(schema)){
+            log.info("on_stream_changed:注册(true)/注销(false)->{}, app->{}, stream->{}", regist, app, streamId);
+            if (regist) {
+                result=mediaServerItemService.addCount(mediaServerId).then(Mono.empty());
+            }else {
+                result=mediaServerItemService.removeCount(mediaServerId).then(Mono.empty());
+            }
+            if ("rtp".equals(app) && !regist ) {
+                StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId);
+                if (streamInfo!=null){
+                    redisCatchStorage.stopPlay(streamInfo);
+//                    storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
+                }else{
+                    streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
+//                    redisCatchStorage.stopPlayback(streamInfo);
+                }
+            }else {
+                if (!"rtp".equals(app)){
+                    String type = OriginType.values()[item.getOriginType()].getType();
+//                    MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
+                    MediaServerItem mediaServerItem =null;
+                    if (mediaServerItem != null){
+                        if (regist) {
+                            redisCatchStorage.addStream(mediaServerItem, type, app, streamId, item);
+                            if (item.getOriginType() == OriginType.RTSP_PUSH.ordinal()
+                                || item.getOriginType() == OriginType.RTMP_PUSH.ordinal()
+                                || item.getOriginType() == OriginType.RTC_PUSH.ordinal() ) {
+//                                zlmMediaListManager.addPush(item);
+                            }
+                        }else {
+                            // 兼容流注销时类型从redis记录获取
+                            MediaItem mediaItem = redisCatchStorage.getStreamInfo(app, streamId, mediaServerId);
+                            type = OriginType.values()[mediaItem.getOriginType()].getType();
+//                            zlmMediaListManager.removeMedia(app, streamId);
+                            redisCatchStorage.removeStream(mediaServerItem.getId(), type, app, streamId);
+                        }
+                        // 发送流变化redis消息
+                        JSONObject jsonObject = new JSONObject()
+                            .append("serverId", mediaServerId)
+                            .append("app", app)
+                            .append("stream", streamId)
+                            .append("register", regist)
+                            .append("mediaServerId", mediaServerId);
+                        redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
+                    }
+                }
+            }
+        }
+        JSONObject ret = new JSONObject()
+            .append("code", 0)
+            .append("msg", "success");
+        return result==null?Mono.just(ResponseEntity.ok(ret.toString())):result.thenReturn(ResponseEntity.ok(ret.toString()));
+//        return Mono.just(ResponseEntity.ok(ret.toString()));
+    }
 //
 //	/**
 //	 * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。
@@ -474,29 +438,23 @@
 //		ret.put("msg", "success");
 //		return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
 //	}
-//
-//	/**
-//	 * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。
-//	 *
-//	 */
-//	@ResponseBody
-//	@PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
-//	public ResponseEntity<String> onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){
-//
-//		if (logger.isDebugEnabled()) {
-//			logger.debug("[ ZLM HOOK ]on_server_started API调用,参数:" + jsonObject.toString());
-//		}
-//		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) {
-//			for (ZLMHttpHookSubscribe.Event subscribe : subscribes) {
-//				subscribe.response(null, jsonObject);
-//			}
-//		}
-//		JSONObject ret = new JSONObject();
-//		ret.put("code", 0);
-//		ret.put("msg", "success");
-//		return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
-//	}
-//}
+
+    /**
+     * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。
+     *
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
+    public Mono<ResponseEntity<String>> onServerStarted(ServerWebExchange exchange, @RequestBody JSONObject jsonObject){
+        if (log.isDebugEnabled()) {
+            log.debug("[ ZLM HOOK ]on_server_started API调用,参数:" + jsonObject.toString());
+        }
+        String remoteAddr = exchange.getRequest().getRemoteAddress().getAddress().toString();
+        jsonObject.append("ip", remoteAddr);
+        subscribe.publish(ZLMHttpHookSubscribe.HookType.on_server_started,null,jsonObject);
+        JSONObject ret = new JSONObject()
+            .append("code", 0)
+            .append("msg", "success");
+        return Mono.just(ResponseEntity.ok(ret.toString()));
+    }
+}

+ 48 - 22
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMHttpHookSubscribe.java

@@ -1,11 +1,17 @@
 package org.jetlinks.community.media.zlm;
 
-import com.alibaba.fastjson.JSONObject;
-import org.jetlinks.community.media.zlm.dto.MediaServerItem;
+import cn.hutool.json.JSONObject;
+import io.reactivex.functions.BiConsumer;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.web.logger.ReactiveLogger;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
+import org.jetlinks.core.event.EventBus;
 import org.springframework.stereotype.Component;
+import reactor.core.publisher.Mono;
 
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
 
 /**
  * @description:针对 ZLMediaServer的hook事件订阅
@@ -13,6 +19,7 @@ import java.util.concurrent.ConcurrentHashMap;
  * @date:   2020年12月2日 21:17:32
  */
 @Component
+@Slf4j
 public class ZLMHttpHookSubscribe {
 
     public enum HookType{
@@ -31,42 +38,41 @@ public class ZLMHttpHookSubscribe {
         on_server_keepalive
     }
 
-    public interface Event{
-        void response(MediaServerItem mediaServerItem, JSONObject response);
+    public interface Event extends BiConsumer<MediaServerItem, JSONObject> {
+
     }
 
-    private Map<HookType, Map<JSONObject, Event>> allSubscribes = new ConcurrentHashMap<>();
+    private Map<HookType, Map<JSONObject, ZLMHttpHookSubscribe.Event>> allSubscribes = new ConcurrentHashMap<>();
+
 
     public void addSubscribe(HookType type, JSONObject hookResponse, ZLMHttpHookSubscribe.Event event) {
-        Map<JSONObject, Event> eventMap = allSubscribes.get(type);
-        if (eventMap == null) {
-            eventMap = new HashMap<JSONObject, Event>();
-            allSubscribes.put(type,eventMap);
-        }
-        eventMap.put(hookResponse, event);
+        allSubscribes
+            .computeIfAbsent(type,k->new HashMap<>())
+            .put(Optional.ofNullable(hookResponse).orElse(new JSONObject()),event);
     }
 
     public ZLMHttpHookSubscribe.Event getSubscribe(HookType type, JSONObject hookResponse) {
         ZLMHttpHookSubscribe.Event event= null;
+        hookResponse=Optional.ofNullable(hookResponse).orElse(new JSONObject());
         Map<JSONObject, Event> eventMap = allSubscribes.get(type);
         if (eventMap == null) {
             return null;
         }
-        for (JSONObject key : eventMap.keySet()) {
+        for (JSONObject subscribe : eventMap.keySet()) {
             Boolean result = null;
-            for (String s : key.keySet()) {
+            for (String s : subscribe.keySet()) {
                 if (result == null) {
-                    result = key.getString(s).equals(hookResponse.getString(s));
+                    result = subscribe.getStr(s).equals(hookResponse.getStr(s));
                 }else {
-                    if (key.getString(s) == null) {
+                    if (subscribe.getStr(s) == null) {
                         continue;
                     }
-                    result = result && key.getString(s).equals(hookResponse.getString(s));
+                    result = result && subscribe.getStr(s).equals(hookResponse.getStr(s));
                 }
 
             }
             if (null != result && result) {
-                event = eventMap.get(key);
+                event = eventMap.get(subscribe);
             }
         }
         return event;
@@ -74,6 +80,7 @@ public class ZLMHttpHookSubscribe {
 
     public void removeSubscribe(HookType type, JSONObject hookResponse) {
         Map<JSONObject, Event> eventMap = allSubscribes.get(type);
+        hookResponse=Optional.ofNullable(hookResponse).orElse(new JSONObject());
         if (eventMap == null) {
             return;
         }
@@ -84,10 +91,12 @@ public class ZLMHttpHookSubscribe {
             Boolean result = null;
             for (String s : key.keySet()) {
                 if (result == null) {
-                    result = key.getString(s).equals(hookResponse.getString(s));
+                    result = key.getStr(s).equals(hookResponse.getStr(s));
                 }else {
-                    if (key.getString(s) == null) continue;
-                    result = result && key.getString(s).equals(hookResponse.getString(s));
+                    if (key.getStr(s) == null) {
+                        continue;
+                    }
+                    result = result && key.getStr(s).equals(hookResponse.getStr(s));
                 }
             }
             if (null != result && result){
@@ -101,18 +110,35 @@ public class ZLMHttpHookSubscribe {
      * @param type
      * @return
      */
-    public List<Event> getSubscribes(HookType type) {
+    public List<ZLMHttpHookSubscribe.Event> getSubscribes(HookType type) {
         // ZLMHttpHookSubscribe.Event event= null;
         Map<JSONObject, Event> eventMap = allSubscribes.get(type);
         if (eventMap == null) {
             return null;
         }
-        List<Event> result = new ArrayList<>();
+        List<ZLMHttpHookSubscribe.Event> result = new ArrayList<>();
         for (JSONObject key : eventMap.keySet()) {
             result.add(eventMap.get(key));
         }
         return result;
     }
 
+    /**
+     * 向订阅进行发布
+     * @param type
+     * @return
+     */
+    public void publish(HookType type,MediaServerItem item,JSONObject object) {
+        Optional
+            .ofNullable(getSubscribes(type))
+            .orElse(new ArrayList<>())
+            .forEach(subscribe-> {
+                try {
+                    subscribe.accept(item,object);
+                } catch (Exception e) {
+                    log.error("ZLM发布信息失败,",e);
+                }
+            });
+    }
 
 }

+ 5 - 5
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMRESTfulUtils.java

@@ -1,10 +1,10 @@
 package org.jetlinks.community.media.zlm;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
 import lombok.extern.slf4j.Slf4j;
 import okhttp3.*;
-import org.jetlinks.community.media.zlm.dto.MediaServerItem;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
 import org.springframework.stereotype.Component;
 
 import javax.validation.constraints.NotNull;
@@ -55,7 +55,7 @@ public class ZLMRESTfulUtils {
                         ResponseBody responseBody = response.body();
                         if (responseBody != null) {
                             String responseStr = responseBody.string();
-                            responseJSON = JSON.parseObject(responseStr);
+                            responseJSON = JSONUtil.parseObj(responseStr);
                         }
                     }else {
                         response.close();
@@ -75,7 +75,7 @@ public class ZLMRESTfulUtils {
                         if (response.isSuccessful()) {
                             try {
                                 String responseStr = Objects.requireNonNull(response.body()).string();
-                                callback.run(JSON.parseObject(responseStr));
+                                callback.run(JSONUtil.parseObj(responseStr));
                             } catch (IOException e) {
                                 log.error(String.format("[ %s ]请求失败: %s", url, e.getMessage()));
                             }

+ 39 - 18
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMRTPServerFactory.java

@@ -1,11 +1,11 @@
 package org.jetlinks.community.media.zlm;
 
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.jetlinks.community.media.bean.SendRtpItem;
-import org.jetlinks.community.media.zlm.dto.MediaServerItem;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 
@@ -29,7 +29,7 @@ public class ZLMRTPServerFactory {
             if (data != null) {
                 for (int i = 0; i < data.size(); i++) {
                     JSONObject dataItem = data.getJSONObject(i);
-                    currentStreams.put(dataItem.getString("stream_id"), dataItem.getInteger("port"));
+                    currentStreams.put(dataItem.getStr("stream_id"), dataItem.getInt("port"));
                 }
             }
         }
@@ -57,9 +57,9 @@ public class ZLMRTPServerFactory {
         JSONObject openRtpServerResultJson = zlmresTfulUtils.openRtpServer(mediaServerItem, param);
 
         if (openRtpServerResultJson != null) {
-            switch (openRtpServerResultJson.getInteger("code")){
+            switch (openRtpServerResultJson.getInt("code")){
                 case 0:
-                    result= openRtpServerResultJson.getInteger("port");
+                    result= openRtpServerResultJson.getInt("port");
                     break;
                 case -300: // id已经存在, 可能已经在其他端口推流
                     Map<String, Object> closeRtpServerParam = new HashMap<>();
@@ -71,7 +71,7 @@ public class ZLMRTPServerFactory {
                     result= createRTPServer(mediaServerItem, streamId);
                     break;
                 default:
-                    log.error("创建RTP Server 失败 {}: " + openRtpServerResultJson.getString("msg"),  param.get("port"));
+                    log.error("创建RTP Server 失败 {}: " + openRtpServerResultJson.getStr("msg"),  param.get("port"));
                     break;
             }
         }else {
@@ -88,10 +88,31 @@ public class ZLMRTPServerFactory {
             param.put("stream_id", streamId);
             JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(serverItem, param);
             if (jsonObject != null ) {
-                if (jsonObject.getInteger("code") == 0) {
-                    result = jsonObject.getInteger("hit") == 1;
+                if (jsonObject.getInt("code") == 0) {
+                    result = jsonObject.getInt("hit") == 1;
                 }else {
-                    log.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
+                    log.error("关闭RTP Server 失败: " + jsonObject.getStr("msg"));
+                }
+            }else {
+                //  检查ZLM状态
+                log.error("关闭RTP Server 失败: 请检查ZLM服务");
+            }
+        }
+        return result;
+    }
+
+
+    public boolean closeRTPServer(MediaServerItem serverItem, String streamId) {
+        boolean result = false;
+        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.getInt("code") == 0) {
+                    result = jsonObject.getInt("hit") == 1;
+                }else {
+                    log.error("关闭RTP Server 失败: " + jsonObject.getStr("msg"));
                 }
             }else {
                 //  检查ZLM状态
@@ -208,11 +229,11 @@ public class ZLMRTPServerFactory {
         JSONObject jsonObject = zlmresTfulUtils.startSendRtp(mediaServerItem, param);
         if (jsonObject == null) {
             log.error("RTP推流失败: 请检查ZLM服务");
-        } else if (jsonObject.getInteger("code") == 0) {
+        } else if (jsonObject.getInt("code") == 0) {
             result= true;
-            log.info("RTP推流[ {}/{} ]请求成功,本地推流端口:{}" ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"));
+            log.info("RTP推流[ {}/{} ]请求成功,本地推流端口:{}" ,param.get("app"), param.get("stream"), jsonObject.getStr("local_port"));
         } else {
-            log.error("RTP推流失败: " + jsonObject.getString("msg"));
+            log.error("RTP推流失败: " + jsonObject.getStr("msg"));
         }
         return result;
     }
@@ -222,7 +243,7 @@ public class ZLMRTPServerFactory {
      */
     public Boolean isRtpReady(MediaServerItem mediaServerItem, String streamId) {
         JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem,"rtp", "rtmp", streamId);
-        return (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online"));
+        return (mediaInfo.getInt("code") == 0 && mediaInfo.getBool("online"));
     }
 
     /**
@@ -230,7 +251,7 @@ public class ZLMRTPServerFactory {
      */
     public Boolean isStreamReady(MediaServerItem mediaServerItem, String app, String streamId) {
         JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem, app, "rtmp", streamId);
-        return (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online"));
+        return (mediaInfo.getInt("code") == 0 && mediaInfo.getBool("online"));
     }
 
     /**
@@ -243,7 +264,7 @@ public class ZLMRTPServerFactory {
         if (mediaInfo == null) {
             return 0;
         }
-        return mediaInfo.getInteger("totalReaderCount");
+        return mediaInfo.getInt("totalReaderCount");
     }
 
     /**
@@ -254,11 +275,11 @@ public class ZLMRTPServerFactory {
         JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaServerItem, param);
         if (jsonObject == null) {
             log.error("停止RTP推流失败: 请检查ZLM服务");
-        } else if (jsonObject.getInteger("code") == 0) {
+        } else if (jsonObject.getInt("code") == 0) {
             result= true;
             log.info("停止RTP推流成功");
         } else {
-            log.error("停止RTP推流失败: " + jsonObject.getString("msg"));
+            log.error("停止RTP推流失败: " + jsonObject.getStr("msg"));
         }
         return result;
     }

+ 231 - 195
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMRunner.java

@@ -1,200 +1,236 @@
-//package org.jetlinks.community.media.zlm;
-//
-//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.gb28181.event.EventPublisher;
-//import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
-//import com.genersoft.iot.vmp.service.IMediaServerService;
-//import com.genersoft.iot.vmp.service.IStreamProxyService;
-//import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-//import org.slf4j.Logger;
-//import org.slf4j.LoggerFactory;
-//import org.springframework.beans.factory.annotation.Autowired;
-//import org.springframework.beans.factory.annotation.Qualifier;
-//import org.springframework.boot.CommandLineRunner;
-//import org.springframework.core.annotation.Order;
-//import org.springframework.scheduling.annotation.Async;
-//import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
-//import org.springframework.stereotype.Component;
-//import org.springframework.util.StringUtils;
-//
-//import java.util.*;
-//
-//@Component
-//@Order(value=1)
-//public class ZLMRunner implements CommandLineRunner {
-//
-//    private final static Logger logger = LoggerFactory.getLogger(ZLMRunner.class);
-//
-//    private Map<String, Boolean> startGetMedia;
-//
-//    @Autowired
-//    private ZLMRESTfulUtils zlmresTfulUtils;
-//
-//    @Autowired
-//    private ZLMHttpHookSubscribe hookSubscribe;
-//
-//    @Autowired
-//    private IStreamProxyService streamProxyService;
-//
+package org.jetlinks.community.media.zlm;
+
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jetlinks.community.media.config.MediaConfig;
+import org.jetlinks.community.media.service.LocalMediaServerItemService;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
+import java.time.Duration;
+import java.util.*;
+
+@Component
+@Slf4j
+@AutoConfigureAfter(LocalMediaServerItemService.class)
+@AllArgsConstructor
+@EnableConfigurationProperties(MediaConfig.class)
+public class ZLMRunner implements CommandLineRunner {
+
+    private Map<String, Boolean> startGetMedia;
+
+
+    private final ZLMRESTfulUtils zlmresTfulUtils;
+
+
+    private final ZLMHttpHookSubscribe hookSubscribe;
+
 //    @Autowired
 //    private EventPublisher publisher;
-//
-//    @Autowired
-//    private IMediaServerService mediaServerService;
-//
-//    @Autowired
-//    private IRedisCatchStorage redisCatchStorage;
-//
-//    @Autowired
-//    private MediaConfig mediaConfig;
-//
-//    @Qualifier("taskExecutor")
-//    @Autowired
-//    private ThreadPoolTaskExecutor taskExecutor;
-//
-//    @Override
-//    public void run(String... strings) throws Exception {
-//        mediaServerService.clearMediaServerForOnline();
-//        MediaServerItem defaultMediaServer = mediaServerService.getDefaultMediaServer();
-//        if (defaultMediaServer == null) {
-//            mediaServerService.addToDatabase(mediaConfig.getMediaSerItem());
-//        }else {
-//            MediaServerItem mediaSerItem = mediaConfig.getMediaSerItem();
-//            mediaSerItem.setId(defaultMediaServer.getId());
-//            mediaServerService.updateToDatabase(mediaSerItem);
-//        }
-//
-//        // 订阅 zlm启动事件, 新的zlm也会从这里进入系统
-//        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_started,null,
-//                (MediaServerItem mediaServerItem, JSONObject response)->{
-//            ZLMServerConfig zlmServerConfig = JSONObject.toJavaObject(response, ZLMServerConfig.class);
-//            if (zlmServerConfig !=null ) {
-//                if (startGetMedia != null) {
-//                    startGetMedia.remove(zlmServerConfig.getGeneralMediaServerId());
-//                }
-//                mediaServerService.zlmServerOnline(zlmServerConfig);
+
+    private final LocalMediaServerItemService mediaServerItemService;
+
+    private final MediaConfig mediaConfig;
+
+
+    @Override
+    public void run(String... strings)  {
+        mediaServerItemService.clearMediaServerForOnline()
+            //获取默认服务器配置
+            .flatMap(ignore-> mediaServerItemService.getDefaultMediaServer())
+            //更新默认服务器配置
+            .flatMap(defaultMediaServer->{
+                MediaServerItem mediaSerItem = mediaConfig.getMediaSerItem();
+                mediaSerItem.setId(defaultMediaServer.getId());
+                return mediaServerItemService.save(mediaSerItem);
+            })
+            //添加默认媒体服务器配置
+            .switchIfEmpty(mediaServerItemService.save(mediaConfig.getMediaSerItem()))
+
+            //订阅 zlm启动事件
+            .doOnNext(ignore->subscribeOnServerStarted())
+            //订阅 zlm保活事件
+            .doOnNext(ignore->subscribeOnServerKeepalive())
+
+            //获取所有的zlm, 并开启主动连接
+            .flatMap(ignore->startAllConnection())
+
+            //20s进行超时处理
+            .delayElement(Duration.ofSeconds(20))
+
+            .flatMap(ignore->timeOutHandle())
+
+            .subscribe();
+    }
+
+    /**
+     * 连接超时处理
+     * @return  Mono
+     */
+    private Mono<Void> timeOutHandle(){
+        if (startGetMedia != null) {
+            Set<String> allZlmId = startGetMedia.keySet();
+            for (String id : allZlmId) {
+                log.error("[ {} ]]主动连接失败,不再主动连接", id);
+                if (!Boolean.TRUE.equals(startGetMedia.get(id))){
+                    startGetMedia.remove(id);
+                    //  TODO 清理数据库中与redis不匹配的zlm,TODO 并对重启前使用此在zlm的通道发送bye
+                };
+            }
+
+        }
+        return Mono.empty();
+    }
+    /**
+     * 订阅 zlm启动事件, 新的zlm也会从这里进入系统
+     * @return Mono
+     */
+    private void subscribeOnServerStarted(){
+        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_started,null,
+            (MediaServerItem mediaServerItem, JSONObject response)->{
+                ZLMServerConfig zlmServerConfig = JSONUtil.toBean(response, ZLMServerConfig.class);
+                if (zlmServerConfig !=null ) {
+                    if (startGetMedia != null) {
+                        startGetMedia.remove(zlmServerConfig.getGeneralMediaServerId());
+                    }
+                    mediaServerItemService.zlmServerOnline(zlmServerConfig)
+                        .subscribe();
+                }
+            });
+    }
+
+    /**
+     * 订阅 zlm保活事件, 当zlm离线时做业务的处理
+     * @return Mono
+     */
+    private void subscribeOnServerKeepalive(){
+        // 订阅 zlm保活事件, 当zlm离线时做业务的处理
+        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_keepalive,null,
+            (MediaServerItem mediaServerItem, JSONObject response)->{
+                String mediaServerId = response.getStr("mediaServerId");
+                if (mediaServerId !=null ) {
+                    mediaServerItemService
+                        .updateMediaServerKeepalive(mediaServerId, response.getJSONObject("data"))
+                        .subscribe();
+                }
+            });
+    }
+
+    /**
+     * 获取所有zlm 并开启主动连接
+     * @return  Mono
+     */
+    private Mono<Void> startAllConnection(){
+        return  mediaServerItemService
+            .createQuery()
+            .fetch()
+            .defaultIfEmpty(
+                mediaConfig.getMediaSerItem()
+            )
+            .doOnNext(mediaServerItem ->{
+                //将媒体服务器进行标记
+                startGetMedia=Optional.ofNullable(startGetMedia).orElse(new HashMap<>());
+                startGetMedia.put(mediaServerItem.getId(), true);
+            })
+            .parallel()
+            .runOn(Schedulers.parallel())
+            .flatMap(this::connectZlmServer)
+            .then();
+
+    }
+
+
+
+    /**
+     * 连接服务器
+     * @param mediaServerItem
+     * @return  Mono
+     */
+    private Mono<Void> connectZlmServer(MediaServerItem mediaServerItem){
+        return getMediaServerConfig(mediaServerItem, 1)
+            .doOnNext(zlmServerConfig ->{
+                zlmServerConfig.setIp(mediaServerItem.getIp());
+                zlmServerConfig.setHttpPort(mediaServerItem.getHttpPort());
+                startGetMedia.remove(mediaServerItem.getId());
+            } )
+            .flatMap(mediaServerItemService::zlmServerOnline);
+    }
+
+    private Mono<ZLMServerConfig> getMediaServerConfig(MediaServerItem mediaServerItem, Integer index) {
+        if (startGetMedia == null) { return Mono.empty();}
+
+        if ( startGetMedia.get(mediaServerItem.getId()) == null || !startGetMedia.get(mediaServerItem.getId())) {
+            return Mono.empty();
+        }
+        return  mediaServerItemService
+            .findById(mediaServerItem.getId())
+            .filter(ignore->Boolean.TRUE.equals(mediaServerItem.isDefaultServer()))
+            //给予默认值
+            .defaultIfEmpty(mediaServerItem)
+            .flatMap(ignore->Mono.just( zlmresTfulUtils.getMediaServerConfig(mediaServerItem)))
+            .flatMap(responseJson->{
+                ZLMServerConfig zlmServerConfig=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());
+                }
+                return Mono.just(zlmServerConfig);
+            })
+            .switchIfEmpty(Mono.fromRunnable(()->{
+                log.error("[ {} ]-[ {}:{} ]第{}次主动连接失败, 2s后重试",
+                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort(), index);
+                if (index == 1 && !StringUtils.isEmpty(mediaServerItem.getId())) {
+                    log.info("[ {} ]-[ {}:{} ]第{}次主动连接失败, 开始清理相关资源",
+                        mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort(), index);
+                    //todo
+//                    publisher.zlmOfflineEventPublish(mediaServerItem.getId());
+                }
+            })
+                .delayElement(Duration.ofSeconds(2))
+                .flatMap(serverItem->getMediaServerConfig(mediaServerItem,index+1)))
+            ;
+
+    }
+
+    /**
+     * zlm 连接成功或者zlm重启后
+     */
+//    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());
 //            }
-//        });
-//
-//        // 订阅 zlm保活事件, 当zlm离线时做业务的处理
-//        hookSubscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_server_keepalive,null,
-//                (MediaServerItem mediaServerItem, JSONObject response)->{
-//                    String mediaServerId = response.getString("mediaServerId");
-//                    if (mediaServerId !=null ) {
-//                        mediaServerService.updateMediaServerKeepalive(mediaServerId, response.getJSONObject("data"));
-//                    }
-//                });
-//
-//        // 获取zlm信息
-//        logger.info("[zlm接入]等待默认zlm中...");
-//
-//        // 获取所有的zlm, 并开启主动连接
-//        List<MediaServerItem> all = mediaServerService.getAllFromDatabase();
-//        if (all.size() == 0) {
-//            all.add(mediaConfig.getMediaSerItem());
-//        }
-//        for (MediaServerItem mediaServerItem : all) {
-//            if (startGetMedia == null) startGetMedia = new HashMap<>();
-//            startGetMedia.put(mediaServerItem.getId(), true);
-//            taskExecutor.execute(()->{
-//                connectZlmServer(mediaServerItem);
-//            });
-//        }
-//        Timer timer = new Timer();
-//        // 10分钟后未连接到则不再去主动连接, TODO 并对重启前使用此在zlm的通道发送bye
-//        timer.schedule(new TimerTask() {
-//            @Override
-//            public void run() {
-//            if (startGetMedia != null) {
-//                Set<String> allZlmId = startGetMedia.keySet();
-//                for (String id : allZlmId) {
-//                    logger.error("[ {} ]]主动连接失败,不再主动连接", id);
-//                }
-//                startGetMedia = null;
-//            }
-//            //  TODO 清理数据库中与redis不匹配的zlm
-//            }
-//        }, 60 * 1000 * 10);
-//    }
-//
-//    @Async
-//    public void connectZlmServer(MediaServerItem mediaServerItem){
-//        ZLMServerConfig zlmServerConfig = getMediaServerConfig(mediaServerItem, 1);
-//        if (zlmServerConfig != null) {
-//            zlmServerConfig.setIp(mediaServerItem.getIp());
-//            zlmServerConfig.setHttpPort(mediaServerItem.getHttpPort());
-//            startGetMedia.remove(mediaServerItem.getId());
-//            mediaServerService.zlmServerOnline(zlmServerConfig);
 //        }
 //    }
-//
-//    public ZLMServerConfig getMediaServerConfig(MediaServerItem mediaServerItem, int index) {
-//        if (startGetMedia == null) { return null;}
-//        if (!mediaServerItem.isDefaultServer() && mediaServerService.getOne(mediaServerItem.getId()) == null) {
-//            return null;
-//        }
-//        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("[ {} ]-[ {}:{} ]第{}次主动连接失败, 2s后重试",
-//                    mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort(), index);
-//            if (index == 1 && !StringUtils.isEmpty(mediaServerItem.getId())) {
-//                logger.info("[ {} ]-[ {}:{} ]第{}次主动连接失败, 开始清理相关资源",
-//                        mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort(), index);
-//                publisher.zlmOfflineEventPublish(mediaServerItem.getId());
-//            }
-//            try {
-//                Thread.sleep(2000);
-//            } catch (InterruptedException e) {
-//                e.printStackTrace();
-//            }
-//            ZLMServerConfig = getMediaServerConfig(mediaServerItem, index += 1);
-//        }
-//        return ZLMServerConfig;
-//
-//    }
-//
-//    /**
-//     * zlm 连接成功或者zlm重启后
-//     */
-////    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());
-////            }
-////        }
-////    }
-//}
+}

+ 23 - 269
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/dto/MediaItem.java

@@ -1,7 +1,10 @@
 package org.jetlinks.community.media.zlm.dto;
 
+import lombok.Data;
+
 import java.util.List;
 
+@Data
 public class MediaItem {
 
     /**
@@ -9,11 +12,31 @@ public class MediaItem {
      */
     private boolean regist;
 
+    /**
+     * 存活时间,单位秒
+     */
+    private Long aliveSecond;
+
+    /**
+     * 服务器id
+     */
+    private String mediaServerId;
+
     /**
      * 应用名
      */
     private String app;
 
+    /**
+     * 数据产生速度,单位byte/s
+     */
+    private Long bytesSpeed;
+
+    /**
+     * GMT unix系统时间戳,单位秒
+     */
+    private Long createStamp;
+
     /**
      * 流id
      */
@@ -58,26 +81,6 @@ public class MediaItem {
      */
     private String originUrl;
 
-    /**
-     * 服务器id
-     */
-    private String mediaServerId;
-
-    /**
-     * GMT unix系统时间戳,单位秒
-     */
-    private Long createStamp;
-
-    /**
-     * 存活时间,单位秒
-     */
-    private Long aliveSecond;
-
-    /**
-     * 数据产生速度,单位byte/s
-     */
-    private Long bytesSpeed;
-
     /**
      * 音视频轨道
      */
@@ -88,14 +91,6 @@ public class MediaItem {
      */
     private String vhost;
 
-    public boolean isRegist() {
-        return regist;
-    }
-
-    public void setRegist(boolean regist) {
-        this.regist = regist;
-    }
-
     /**
      * 是否是docker部署, docker部署不会自动更新zlm使用的端口,需要自己手动修改
      */
@@ -152,85 +147,6 @@ public class MediaItem {
          */
         private int width;
 
-        public int getChannels() {
-            return channels;
-        }
-
-        public void setChannels(int channels) {
-            this.channels = channels;
-        }
-
-        public int getCodecId() {
-            return codecId;
-        }
-
-        public void setCodecId(int codecId) {
-            this.codecId = codecId;
-        }
-
-        public String getCodecIdName() {
-            return codecIdName;
-        }
-
-        public void setCodecIdName(String codecIdName) {
-            this.codecIdName = codecIdName;
-        }
-
-        public int getCodecType() {
-            return codecType;
-        }
-
-        public void setCodecType(int codecType) {
-            this.codecType = codecType;
-        }
-
-        public boolean isReady() {
-            return ready;
-        }
-
-        public void setReady(boolean ready) {
-            this.ready = ready;
-        }
-
-        public int getSampleBit() {
-            return sampleBit;
-        }
-
-        public void setSampleBit(int sampleBit) {
-            this.sampleBit = sampleBit;
-        }
-
-        public int getSampleRate() {
-            return sampleRate;
-        }
-
-        public void setSampleRate(int sampleRate) {
-            this.sampleRate = sampleRate;
-        }
-
-        public int getFps() {
-            return fps;
-        }
-
-        public void setFps(int fps) {
-            this.fps = fps;
-        }
-
-        public int getHeight() {
-            return height;
-        }
-
-        public void setHeight(int height) {
-            this.height = height;
-        }
-
-        public int getWidth() {
-            return width;
-        }
-
-        public void setWidth(int width) {
-            this.width = width;
-        }
     }
 
     public static class OriginSock{
@@ -239,167 +155,5 @@ public class MediaItem {
         private int local_port;
         private String peer_ip;
         private int peer_port;
-
-        public String getIdentifier() {
-            return identifier;
-        }
-
-        public void setIdentifier(String identifier) {
-            this.identifier = identifier;
-        }
-
-        public String getLocal_ip() {
-            return local_ip;
-        }
-
-        public void setLocal_ip(String local_ip) {
-            this.local_ip = local_ip;
-        }
-
-        public int getLocal_port() {
-            return local_port;
-        }
-
-        public void setLocal_port(int local_port) {
-            this.local_port = local_port;
-        }
-
-        public String getPeer_ip() {
-            return peer_ip;
-        }
-
-        public void setPeer_ip(String peer_ip) {
-            this.peer_ip = peer_ip;
-        }
-
-        public int getPeer_port() {
-            return peer_port;
-        }
-
-        public void setPeer_port(int peer_port) {
-            this.peer_port = peer_port;
-        }
-    }
-
-    public String getApp() {
-        return app;
-    }
-
-    public void setApp(String app) {
-        this.app = app;
-    }
-
-    public String getStream() {
-        return stream;
-    }
-
-    public void setStream(String stream) {
-        this.stream = stream;
-    }
-
-    public String getTotalReaderCount() {
-        return totalReaderCount;
-    }
-
-    public void setTotalReaderCount(String totalReaderCount) {
-        this.totalReaderCount = totalReaderCount;
-    }
-
-
-    public int getOriginType() {
-        return originType;
-    }
-
-    public void setOriginType(int originType) {
-        this.originType = originType;
-    }
-
-
-    public String getOriginTypeStr() {
-        return originTypeStr;
-    }
-
-    public void setOriginTypeStr(String originTypeStr) {
-        this.originTypeStr = originTypeStr;
-    }
-
-    public String getOriginUrl() {
-        return originUrl;
-    }
-
-    public void setOriginUrl(String originUrl) {
-        this.originUrl = originUrl;
-    }
-
-    public Long getCreateStamp() {
-        return createStamp;
-    }
-
-    public void setCreateStamp(Long createStamp) {
-        this.createStamp = createStamp;
-    }
-
-    public Long getAliveSecond() {
-        return aliveSecond;
-    }
-
-    public void setAliveSecond(Long aliveSecond) {
-        this.aliveSecond = aliveSecond;
-    }
-
-    public List<MediaTrack> getTracks() {
-        return tracks;
-    }
-
-    public void setTracks(List<MediaTrack> tracks) {
-        this.tracks = tracks;
-    }
-
-    public String getSchema() {
-        return schema;
-    }
-
-    public void setSchema(String schema) {
-        this.schema = schema;
-    }
-
-    public void setOriginSock(OriginSock originSock) {
-        this.originSock = originSock;
-    }
-
-    public Long getBytesSpeed() {
-        return bytesSpeed;
-    }
-
-    public void setBytesSpeed(Long bytesSpeed) {
-        this.bytesSpeed = bytesSpeed;
-    }
-
-    public String getVhost() {
-        return vhost;
-    }
-
-    public void setVhost(String vhost) {
-        this.vhost = vhost;
-    }
-
-    public OriginSock getOriginSock() {
-        return originSock;
-    }
-
-    public boolean isDocker() {
-        return docker;
-    }
-
-    public void setDocker(boolean docker) {
-        this.docker = docker;
-    }
-
-    public String getMediaServerId() {
-        return mediaServerId;
-    }
-
-    public void setMediaServerId(String mediaServerId) {
-        this.mediaServerId = mediaServerId;
     }
 }

+ 1 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/dto/StreamProxyItem.java

@@ -1,7 +1,7 @@
 package org.jetlinks.community.media.zlm.dto;
 
 import lombok.Data;
-import org.jetlinks.community.media.entity.GbStream;
+import org.jetlinks.community.media.bean.GbStream;
 @Data
 public class StreamProxyItem extends GbStream {
 

+ 1 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/dto/StreamPushItem.java

@@ -1,7 +1,7 @@
 package org.jetlinks.community.media.zlm.dto;
 
 import lombok.Data;
-import org.jetlinks.community.media.entity.GbStream;
+import org.jetlinks.community.media.bean.GbStream;
 
 import javax.validation.constraints.NotNull;
 import java.util.List;

+ 46 - 9
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/dto/MediaServerItem.java → jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/entity/MediaServerItem.java

@@ -1,68 +1,106 @@
-package org.jetlinks.community.media.zlm.dto;
+package org.jetlinks.community.media.zlm.entity;
 
 
+import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 //import org.jetlinks.community.media.zlm.ZLMServerConfig;
-import org.jetlinks.community.media.session.SsrcConfig;
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType;
+import org.hswebframework.web.api.crud.entity.GenericEntity;
+import org.hswebframework.web.api.crud.entity.RecordCreationEntity;
+import org.jetlinks.community.media.bean.SsrcConfig;
 import org.jetlinks.community.media.zlm.ZLMServerConfig;
 import org.springframework.util.StringUtils;
 
-import java.util.HashMap;
-@Data
-public class MediaServerItem{
-
-    private String id;
+import javax.persistence.Column;
+import javax.persistence.Index;
+import javax.persistence.Table;
+import java.sql.JDBCType;
 
+@Data
+@Table(name = "media_server_item")
+public class MediaServerItem extends GenericEntity<String> {
+    @Column(name = "ip")
+    @Schema(
+        description = "产品id"
+    )
     private String ip;
 
+    @Column(name = "hook_ip")
     private String hookIp;
 
+    @Column(name = "sdp_ip")
     private String sdpIp;
 
+    @Column(name = "stream_ip")
     private String streamIp;
 
+    @Column(name = "http_port")
     private int httpPort;
 
+    @Column(name = "http_ssl_port")
     private int httpSSlPort;
 
+    @Column(name = "rtmp_port")
     private int rtmpPort;
 
+    @Column(name = "rtmp_ssl_port")
     private int rtmpSSlPort;
 
+    @Column(name = "rtp_proxy_port")
     private int rtpProxyPort;
 
+    @Column(name = "rtsp_port")
     private int rtspPort;
 
+    @Column(name = "rtsp_ssl_port")
     private int rtspSSLPort;
 
+    @Column(name = "auto_config")
     private boolean autoConfig;
 
+    @Column(name = "secret")
     private String secret;
 
+    @Column(name = "stream_none_reader_delay_ms")
     private int streamNoneReaderDelayMS;
 
+    @Column(name = "hook_alive_interval")
     private int hookAliveInterval;
 
+    @Column(name = "rtp_enable")
     private boolean rtpEnable;
 
+    @Column(name = "status")
     private boolean status;
 
+    @Column(name = "rtp_port_range")
     private String rtpPortRange;
 
+    @Column(name = "send_rtp_port_range")
     private String sendRtpPortRange;
 
+    @Column(name = "record_assist_port")
     private int recordAssistPort;
 
+    @Column(name = "create_time")
     private String createTime;
 
+    @Column(name = "update_time")
     private String updateTime;
 
+    @Column(name = "last_keepaliveT_time")
     private String lastKeepaliveTime;
 
+    @Column(name = "default_server")
     private boolean defaultServer;
 
+    @Column(name = "ssrc_config")
+    @ColumnType(jdbcType = JDBCType.CLOB)
     private SsrcConfig ssrcConfig;
 
+    @Column(name = "current_port")
     private int currentPort;
 
 
@@ -76,7 +114,7 @@ public class MediaServerItem{
     }
 
     public MediaServerItem(ZLMServerConfig zlmServerConfig, String sipIp) {
-        id = zlmServerConfig.getGeneralMediaServerId();
+        this.setId( zlmServerConfig.getGeneralMediaServerId());
         ip = zlmServerConfig.getIp();
         hookIp = StringUtils.isEmpty(zlmServerConfig.getHookIp())? sipIp: zlmServerConfig.getHookIp();
         sdpIp = StringUtils.isEmpty(zlmServerConfig.getSdpIp())? zlmServerConfig.getIp(): zlmServerConfig.getSdpIp();
@@ -96,6 +134,5 @@ public class MediaServerItem{
         rtpPortRange = "30000,30500"; // 默认使用30000,30500作为级联时发送流的端口号
         sendRtpPortRange = "30000,30500"; // 默认使用30000,30500作为级联时发送流的端口号
         recordAssistPort = 0; // 默认关闭
-
     }
 }

+ 11 - 11
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/utils/RedisUtil.java

@@ -1,7 +1,7 @@
 package org.jetlinks.community.utils;
 
-import com.alibaba.fastjson.JSONObject;
-import org.springframework.beans.factory.annotation.Autowired;
+import cn.hutool.json.JSONObject;
+import lombok.AllArgsConstructor;
 import org.springframework.data.redis.core.*;
 import org.springframework.stereotype.Component;
 import org.springframework.util.CollectionUtils;
@@ -9,19 +9,19 @@ import org.springframework.util.CollectionUtils;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 
-/**    
+/**
  * @description:Redis工具类
  * @author: swwheihei
- * @date:   2020年5月6日 下午8:27:29     
+ * @date:   2020年5月6日 下午8:27:29
  */
 @Component
+@AllArgsConstructor
 @SuppressWarnings(value = {"rawtypes", "unchecked"})
 public class RedisUtil {
 
-	@Autowired
-    private RedisTemplate redisTemplate;
-	
-	/**
+    private final RedisTemplate redisTemplate;
+
+    /**
      * 指定缓存失效时间
      * @param key 键
      * @param time 时间(秒)
@@ -68,8 +68,8 @@ public class RedisUtil {
      * @param key 键(一个或者多个)
      */
     public boolean del(String... key) {
-    	try {
-    		if (key != null && key.length > 0) {
+        try {
+            if (key != null && key.length > 0) {
                 if (key.length == 1) {
                     redisTemplate.delete(key[0]);
                 } else {
@@ -706,6 +706,7 @@ public class RedisUtil {
      * @return
      */
     public List<Object> scan(String query) {
+        //todo 仅能scan一次,此问题有待解决
         Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
             Set<String> keysTmp = new HashSet<>();
             Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(query).count(1000).build());
@@ -734,7 +735,6 @@ public class RedisUtil {
     public void convertAndSend(String channel, JSONObject msg) {
 //        redisTemplate.convertAndSend(channel, msg);
         redisTemplate.convertAndSend(channel, msg);
-
     }
 
 }

+ 4 - 5
jetlinks-standalone/pom.xml

@@ -223,11 +223,10 @@
             <artifactId>spring-boot-starter</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>servlet-api</artifactId>
-            <version>RELEASE</version>
-        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>javax.servlet</groupId>-->
+<!--            <artifactId>servlet-api</artifactId>-->
+<!--        </dependency>-->
 
         <dependency>
             <groupId>org.hswebframework.web</groupId>

+ 3 - 1
jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/JetLinksApplication.java

@@ -13,6 +13,8 @@ import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Profile;
 import org.springframework.context.event.EventListener;
 import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.config.EnableWebFlux;
+
 import javax.annotation.PostConstruct;
 
 
@@ -20,7 +22,7 @@ import javax.annotation.PostConstruct;
     RedissonAutoConfiguration.class
 })
 @EnableCaching
-@EnableEasyormRepository("org.jetlinks.community.**.entity")
+@EnableEasyormRepository({"org.jetlinks.community.**.entity","org.jetlinks.community.media.**.entity"})
 @EnableAopAuthorize
 @EnableAccessLogger
 @Slf4j

+ 8 - 0
jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/ClusterConfiguration.java

@@ -5,6 +5,7 @@ import lombok.Getter;
 import org.jetlinks.community.rule.engine.cluster.ClusterUniqueWork;
 import org.jetlinks.community.standalone.configuration.cluster.ClusterDeviceMessageBrokeMessageBroker;
 import org.jetlinks.community.standalone.configuration.cluster.ClusterDeviceMessageConnector;
+import org.jetlinks.core.cluster.ClusterEventBus;
 import org.jetlinks.core.cluster.ClusterManager;
 import org.jetlinks.core.device.DeviceRegistry;
 import org.jetlinks.core.event.EventBus;
@@ -20,6 +21,7 @@ import org.jetlinks.supports.config.ClusterConfigStorageManager;
 import org.redisson.api.RedissonClient;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
 import org.springframework.context.annotation.Bean;
@@ -92,4 +94,10 @@ public class ClusterConfiguration {
 //        ) {
         return new ClusterUniqueWork(properties.getServerId(), properties.getClusterName()+properties.getServerId(), eventBus, evaluator,clusterManager,redissonClient);
     }
+
+    @Bean(initMethod = "init")
+    @ConditionalOnBean({EventBus.class,ClusterManager.class})
+    public ClusterEventBus clusterEventBus(EventBus eventBus, ClusterManager clusterManager) {
+        return new ClusterEventBus( clusterManager,eventBus);
+    }
 }

+ 2 - 1
jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/CroConfiguration.java

@@ -2,6 +2,7 @@ package org.jetlinks.community.standalone.configuration;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 import org.springframework.web.filter.CorsFilter;
@@ -18,7 +19,7 @@ import java.io.FileOutputStream;
 @Configuration
 public class CroConfiguration{
     @Bean
-    public CorsFilter corsFilter() {
+    public CorsFilter jetLinksCorsFilter() {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
 
         CorsConfiguration config = new CorsConfiguration();

+ 4 - 0
jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/JetLinksConfiguration.java

@@ -17,6 +17,7 @@ import org.jetlinks.community.device.timeseries.DeviceTimeSeriesMetric;
 import org.jetlinks.community.micrometer.MeterRegistryManager;
 import org.jetlinks.community.standalone.configuration.cluster.RedisClusterManager;
 import org.jetlinks.core.ProtocolSupports;
+import org.jetlinks.core.cluster.ClusterEventBus;
 import org.jetlinks.core.cluster.ClusterManager;
 import org.jetlinks.core.config.ConfigStorageManager;
 import org.jetlinks.core.device.DeviceOperationBroker;
@@ -41,6 +42,7 @@ import org.jetlinks.supports.server.DefaultSendToDeviceMessageHandler;
 import org.jetlinks.supports.server.monitor.MicrometerGatewayServerMetrics;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -111,6 +113,8 @@ public class JetLinksConfiguration {
         return new RedisClusterManager(properties.getClusterName(), properties.getServerId(), template);
     }
 
+
+
     @Bean
     public ClusterDeviceRegistry clusterDeviceRegistry(ProtocolSupports supports,
                                                        ClusterManager manager,

+ 35 - 7
jetlinks-standalone/src/main/resources/application.yml

@@ -16,9 +16,9 @@ spring:
   resources:
     static-locations: file:./static/,/,classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/, classpath:/public/
   redis:
-    host: 192.168.104.114
+    host: 1.15.89.83
 #    host: 1.15.89.83
-    port: 6379
+    port: 9736
     password: 6E6985E1F7CB40F24A\.
     lettuce:
       pool:
@@ -26,10 +26,11 @@ spring:
         max-idle: 8
     timeout: 20s
     serializer: jdk # 设置fst时,redis key使用string序列化,value使用 fst序列化.
+    database: 3
   #    database: 3
   #        max-wait: 10s
   r2dbc:
-    url: r2dbc:postgresql://192.168.104.114:5432/jetlinks?stringtype=unspecified
+    url: r2dbc:postgresql://1.15.89.83:5432/jetlinks?stringtype=unspecified
 #    url: r2dbc:mysql://192.168.100.32:3306/jetlinks
 #    username: root
 #    password: 123456
@@ -44,10 +45,12 @@ spring:
     elasticsearch:
       client:
         reactive:
-          endpoints: 192.168.104.114:9200
+          endpoints: 1.15.89.83:9200
           max-in-memory-size: 100MB
           socket-timeout: 1000
           connection-timeout: 10000
+  main:
+    allow-bean-definition-overriding: true
 easyorm:
   default-schema: public # 数据库默认的schema
   dialect: postgres #数据库方言
@@ -56,9 +59,9 @@ elasticsearch:
     enabled: false # 为true时使用内嵌的elasticsearch,不建议在生产环境中使用
     data-path: ./data/elasticsearch
     port: 9200
-    host: 192.168.104.114
+    host: 1.15.89.83
   client:
-    host: 192.168.100.114
+    host: 1.15.89.83
     port: 9200
     max-conn-total: 128
     connect-timeout: 5000
@@ -213,10 +216,35 @@ springdoc:
       paths-to-match:
         - /visualization/**
         - /visualization/catalog/**
+    - group: GB28181接口
+      packages-to-scan:
+        - org.jetlinks.community.media
+      paths-to-match:
+        - /index/hook
   cache:
     disabled: true
+#  use-management-port: true
 visual:
   base-path: "http://127.0.0.1:8844"
   urls:
     big-screen-path: "http://192.168.104.115:9022/"
-    vis-configuration: "http://192.168.104.115:9022/"
+    vis-configuration: "http://192.168.104.115:9022/"
+
+#zlm 默认服务器配置
+media:
+  id: 123456
+  # [必须修改] zlm服务器的内网IP
+  ip: 1.15.89.83
+  # [必须修改] zlm服务器的http.port
+  http-port: 8080
+  # [可选] zlm服务器的hook.admin_params=secret
+  secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
+  # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用
+  record-assist-port: 18081
+  # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试
+  # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输
+  rtp-enable: true
+  # [可选] 在此范围内选择端口用于媒体流传输,
+  send-rtp-port-range: 10000,10000 # 端口范围
+  # [可选] 国标级联在此范围内选择端口发送媒体流,
+  rtp-port-range: 10000,10000 # 端口范围

+ 22 - 32
jetlinks-standalone/src/test/java/org/jetlinks/community/BridgeTest.java

@@ -1,24 +1,16 @@
 package org.jetlinks.community;
 
-import cn.hutool.core.lang.Pair;
-import cn.hutool.core.map.MapUtil;
 import lombok.extern.slf4j.Slf4j;
-import org.jetlinks.community.bridge.entity.AliIotBridgeDeviceConfig;
-import org.jetlinks.community.bridge.entity.AliIotBridgeEntity;
-import org.jetlinks.community.bridge.server.aliyun.AliBridgeGateway;
-import org.jetlinks.community.media.bean.EventResult;
 import org.jetlinks.community.media.bean.SSRCInfo;
-import org.jetlinks.community.media.entity.MediaDevice;
-import org.jetlinks.community.media.entity.SipServerConfig;
+import org.jetlinks.community.media.bean.SipServerConfig;
+import org.jetlinks.community.media.controller.PlayController;
 import org.jetlinks.community.media.service.LocalMediaDeviceService;
 import org.jetlinks.community.media.sip.SipServerHelper;
 import org.jetlinks.community.media.transmit.cmd.SipCommander;
-import org.jetlinks.community.media.zlm.dto.MediaServerItem;
+import org.jetlinks.community.media.zlm.entity.MediaServerItem;
 import org.jetlinks.community.standalone.JetLinksApplication;
 import org.jetlinks.core.device.DeviceRegistry;
 import org.jetlinks.core.event.EventBus;
-import org.jetlinks.core.message.DeviceOnlineMessage;
-import org.jetlinks.core.message.property.ReportPropertyMessage;
 import org.jetlinks.supports.server.DecodedClientMessageHandler;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -26,11 +18,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
-import java.util.Arrays;
-
 /**
  * @author lifang
  * @version 1.0.0
@@ -55,27 +44,28 @@ public class BridgeTest {
     SipServerHelper serverHelper;
     @Autowired
     LocalMediaDeviceService mediaDeviceService;
-    //
+    @Autowired
+    PlayController playController;
 //
     @Test
     public void init() throws InterruptedException {
-        serverHelper.createSip( SipServerConfig.of("34020000002000000001","192.168.104.244", 7001,"udp","340200000","utf-8","12345678",10L,"1")).subscribe();
-        Thread.sleep(6000);
-        System.out.println("开始调用invite方法");
-        SSRCInfo ssrcInfo = SSRCInfo.of(5003,"1000",null);
-        MediaServerItem mediaServerItem = new MediaServerItem();
-        mediaServerItem.setSdpIp("192.168.104.244");
-        mediaDeviceService.findById("34020000001320000003")
-            .flatMap(device->{
-                    device.setStreamMode("udp");
-                    return  sipCommander.playStreamCmd(mediaServerItem, ssrcInfo,device, "34020000002000000001")
-                        .doOnNext(s->{
-                            System.out.println(s);
-                        })
-                        .then(Mono.empty());
-                }
-            )
-            .subscribe();
+//        serverHelper.createSip( SipServerConfig.of("34020000002000000001","192.168.10.100", 7001,"udp","340200000","utf-8","12345678",10L,"1")).subscribe();
+//        Thread.sleep(3000);
+//        System.out.println("开始调用invite方法");
+//        SSRCInfo ssrcInfo = SSRCInfo.of(10000,"1000",null);
+//        MediaServerItem mediaServerItem = new MediaServerItem();
+//        mediaServerItem.setSdpIp("1.15.89.83");
+//        mediaDeviceService.findById("34020000001320000003")
+//            .flatMap(device->{
+//                    device.setStreamMode("udp");
+//                    return  sipCommander.playStreamCmd(mediaServerItem, ssrcInfo,device, "34020000002000000001")
+//                        .doOnNext(s->{
+//                            System.out.println(s);
+//                        })
+//                        .then(Mono.empty());
+//                }
+//            )
+//            .subscribe();
 
         while (true){
 

+ 6 - 0
pom.xml

@@ -37,6 +37,7 @@
         <redisson.version>3.13.6</redisson.version>
         <sip.version>1.3.0-91</sip.version>
         <californium.version>2.2.3</californium.version>
+        <servlet.version>3.0-alpha-1</servlet.version>
     </properties>
 
 
@@ -184,6 +185,11 @@
                 <!--<artifactId>jain-sip-ri</artifactId>-->
                 <!--<version>${sip.version}</version>-->
             <!--</dependency>-->
+            <dependency>
+                <groupId>javax.servlet</groupId>
+                <artifactId>servlet-api</artifactId>
+                <version>${servlet.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>com.aliyun</groupId>