浏览代码

gb28181 点播

lifang 3 年之前
父节点
当前提交
c4b0621191
共有 62 个文件被更改,包括 2611 次插入1351 次删除
  1. 7 2
      jetlinks-manager/media-manager/pom.xml
  2. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/GbStream.java
  3. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/ParentPlatform.java
  4. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/PlatformCatalog.java
  5. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/PlatformGbStream.java
  6. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/SipServerConfig.java
  7. 2 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/SsrcConfig.java
  8. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/WvpSipDate.java
  9. 4 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/config/MediaConfig.java
  10. 16 7
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/contanst/VideoManagerConstants.java
  11. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/controller/ApiStreamController.java
  12. 332 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/controller/PlayController.java
  13. 0 199
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/DeviceChannel.java
  14. 323 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/entity/MediaDeviceChannel.java
  15. 68 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/bean/SsrcTransaction.java
  16. 140 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/event/SipSubscribe.java
  17. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/result/PlayResult.java
  18. 13 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/gb28181/result/WVPResult.java
  19. 35 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalMediaDeviceChannelService.java
  20. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalMediaDeviceService.java
  21. 711 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalMediaServerItemService.java
  22. 0 597
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalMediaServerItemServiceImpl.java
  23. 267 294
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalPlayService.java
  24. 0 23
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/SipService.java
  25. 115 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/session/VideoStreamSessionManager.java
  26. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/AbstractSipProcessor.java
  27. 1 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/SipContext.java
  28. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/SipRequestProcessorParent.java
  29. 39 39
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/SipServerHelper.java
  30. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/IMessageHandler.java
  31. 1 6
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/InviteRequestProcessor.java
  32. 3 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/MessageRequestProcessor.java
  33. 3 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/RegisterRequestProcessor.java
  34. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/impl/TimeoutProcessor.java
  35. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/IMessageHandler.java
  36. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/MessageHandlerAbstract.java
  37. 2 8
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/MessageRequestProcessor.java
  38. 3 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/notify/KeepaliveNotifyMessageHandler.java
  39. 3 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/request/message/notify/NoSupportMessageHandler.java
  40. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/response/InviteResponseProcessor.java
  41. 2 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/sip/response/RegisterResponseProcessor.java
  42. 27 27
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/storage/IVideoManagerStorager.java
  43. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/storage/impl/RedisCatchStorageImpl.java
  44. 5 3
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/SIPRequestHeaderProvider.java
  45. 148 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/callback/DeferredResultHolder.java
  46. 32 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/callback/RequestMessage.java
  47. 80 2
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/cmd/SipCommander.java
  48. 5 8
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMHttpHookListener.java
  49. 25 11
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMHttpHookSubscribe.java
  50. 21 0
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMRTPServerFactory.java
  51. 52 51
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMRunner.java
  52. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/dto/StreamProxyItem.java
  53. 1 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/dto/StreamPushItem.java
  54. 35 1
      jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/entity/MediaServerItem.java
  55. 4 7
      jetlinks-standalone/pom.xml
  56. 2 3
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/JetLinksApplication.java
  57. 8 0
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/ClusterConfiguration.java
  58. 0 1
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/CroConfiguration.java
  59. 4 0
      jetlinks-standalone/src/main/java/org/jetlinks/community/standalone/configuration/JetLinksConfiguration.java
  60. 22 0
      jetlinks-standalone/src/main/resources/application.yml
  61. 21 19
      jetlinks-standalone/src/test/java/org/jetlinks/community/BridgeTest.java
  62. 5 0
      pom.xml

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

@@ -35,9 +35,14 @@
             <version>${hsweb.framework.version}</version>
         </dependency>
 
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.boot</groupId>-->
+<!--            <artifactId>spring-boot-starter-web</artifactId>-->
+<!--        </dependency>-->
+
         <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
         </dependency>
 
         <dependency>

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

+ 2 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/bean/SsrcConfig.java

@@ -1,11 +1,12 @@
 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) {

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

@@ -181,7 +181,11 @@ public class MediaConfig {
         return sendRtpPortRange;
     }
 
+    private MediaServerItem mediaServerItem;
     public MediaServerItem getMediaSerItem(){
+        if(mediaServerItem!=null){
+            return mediaServerItem;
+        }
         MediaServerItem mediaServerItem = new MediaServerItem();
         mediaServerItem.setId(id);
         mediaServerItem.setIp(ip);

+ 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_";

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

@@ -10,7 +10,7 @@
 //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.gb28181.result.PlayResult;
 //import org.jetlinks.community.media.zlm.entity.MediaServerItem;
 //import org.jetlinks.community.utils.SipUtils;
 //import org.redisson.api.RedissonClient;

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

+ 0 - 597
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/service/LocalMediaServerItemServiceImpl.java

@@ -1,597 +0,0 @@
-package org.jetlinks.community.media.service;
-
-
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.json.JSONObject;
-import lombok.extern.slf4j.Slf4j;
-import org.hswebframework.web.crud.service.GenericReactiveCrudService;
-import org.jetlinks.community.media.contanst.VideoManagerConstants;
-import org.jetlinks.community.media.bean.SsrcConfig;
-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.boot.CommandLineRunner;
-import org.springframework.stereotype.Service;
-import reactor.core.publisher.Mono;
-
-import java.text.SimpleDateFormat;
-
-/**
- * 媒体服务器节点管理
- */
-@Service
-@Slf4j
-public class LocalMediaServerItemServiceImpl 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;
-
-    @Autowired
-    public LocalMediaServerItemServiceImpl(
-                                       ClusterManager clusterManager,
-                                       RedisUtil redisUtil)  {
-        this.serverId= clusterManager.getCurrentServerId();
-        this.redisUtil=redisUtil;
-    }
-
-    //    @Autowired
-//    private final UserSetup userSetup;
-//
-//    @Autowired
-//    private  final ZLMRESTfulUtils zlmresTfulUtils;
-//
-//    @Autowired
-//    private  final MediaServerMapper mediaServerMapper;
-//
-//    @Autowired
-//    private  final VideoStreamSessionManager streamSession;
-//
-//    @Autowired
-//    private final  ZLMRTPServerFactory zlmrtpServerFactory;
-//
-//    @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,"");
-                    mediaServerItem.setSsrcConfig(ssrcConfig);
-                }
-            })
-            .doOnNext(mediaServerItem->redisUtil.set(VideoManagerConstants.MEDIA_SERVER_PREFIX + serverId + "_" + mediaServerItem.getId(),mediaServerItem))
-            .subscribe();
-    }
-
-//
-//    @Override
-//    public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId) {
-//        return openRTPServer(mediaServerItem, streamId, false);
-//    }
-//
-//    @Override
-//    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 + userSetup.getServerId() + "_" + 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 new SSRCInfo(rtpServerPort, ssrc, streamId);
-//        }
-//    }
-//
-//    @Override
-//    public void closeRTPServer(Device device, String channelId) {
-//        String mediaServerId = streamSession.getMediaServerId(device.getDeviceId(), channelId);
-//        MediaServerItem mediaServerItem = this.getOne(mediaServerId);
-//        if (mediaServerItem != null) {
-//            String streamId = String.format("%s_%s", device.getDeviceId(), channelId);
-//            zlmrtpServerFactory.closeRTPServer(mediaServerItem, streamId);
-//            releaseSsrc(mediaServerItem, streamSession.getSSRC(device.getDeviceId(), channelId));
-//        }
-//        streamSession.remove(device.getDeviceId(), channelId);
-//    }
-//
-//    @Override
-//    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 + userSetup.getServerId() + "_" + mediaServerItem.getId();
-//        redisUtil.set(key, mediaServerItem);
-//    }
-//
-//    /**
-//     * zlm 重启后重置他的推流信息, TODO 给正在使用的设备发送停止命令
-//     */
-//    @Override
-//    public void clearRTPServer(MediaServerItem mediaServerItem) {
-//        mediaServerItem.setSsrcConfig(new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getDomain()));
-//        redisUtil.zAdd(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetup.getServerId(), 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
-//     */
-//    @Override
-//    public MediaServerItem getOne(String mediaServerId) {
-//        if (mediaServerId == null) {
-//            return null;
-//        }
-//        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetup.getServerId() + "_" + 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 null;
-    }
-
-    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;
-//    }
-
-
-    //todo
-    public Mono<Integer> addToDatabase(MediaServerItem mediaSerItem) {
-        return this.createUpdate().execute();
-    }
-
-  //todo
-    public Mono<Integer> updateToDatabase(MediaServerItem mediaSerItem) {
-        return this.createUpdate().execute();
-    }
-
-    /**
-     * 处理zlm上线 todo
-     * @param zlmServerConfig zlm上线携带的参数
-     */
-    public Mono<Void> zlmServerOnline(ZLMServerConfig zlmServerConfig) {
-//        log.info("[ ZLM:{} ]-[ {}:{} ]已连接",
-//                zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
-//
-//        MediaServerItem serverItem = mediaServerMapper.queryOne(zlmServerConfig.getGeneralMediaServerId());
-//        if (serverItem == null) {
-//            serverItem = mediaServerMapper.queryOneByHostAndPort(zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
-//        }
-//        if (serverItem == null) {
-//            log.warn("[未注册的zlm] 拒接接入:来自{}:{}", zlmServerConfig.getIp(),zlmServerConfig.getHttpPort() );
-//            return;
-//        }
-//        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);
-//
-//        if (StringUtils.isEmpty(serverItem.getId())) {
-//            serverItem.setId(zlmServerConfig.getGeneralMediaServerId());
-//            mediaServerMapper.updateByHostAndPort(serverItem);
-//        }else {
-//            mediaServerMapper.update(serverItem);
-//        }
-//        String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetup.getServerId() + "_" + zlmServerConfig.getGeneralMediaServerId();
-//        if (redisUtil.get(key) == null) {
-//            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);
-//        resetOnlineServerItem(serverItem);
-//        updateMediaServerKeepalive(serverItem.getId(), null);
-//        setZLMConfig(serverItem, "0".equals(zlmServerConfig.getHookEnable()));
-//
-//        publisher.zlmOnlineEventPublish(serverItem.getId());
-        return Mono.empty();
-
-    }
-//
-//
-//    @Override
-//    public void zlmServerOffline(String mediaServerId) {
-//        delete(mediaServerId);
-//    }
-//
-//    @Override
-//    public void resetOnlineServerItem(MediaServerItem serverItem) {
-//        // 更新缓存
-//        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetup.getServerId();
-//        // 使用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.getInteger("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
-//     */
-//    @Override
-//    public MediaServerItem getMediaServerForMinimumLoad() {
-//        String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetup.getServerId();
-//
-//        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 getOne(mediaServerId);
-//    }
-//
-//    /**
-//     * 对zlm服务器进行基础配置
-//     * @param mediaServerItem 服务ID
-//     * @param restart 是否重启zlm
-//     */
-//    @Override
-//    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.getInteger("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);
-//    }
-//
-
-    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();
-    }
-}

+ 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.entity.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;
+	}
+}

+ 1 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/event/AbstractSipProcessor.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;
+package org.jetlinks.community.media.sip;
 
 import lombok.extern.slf4j.Slf4j;
 import org.jetlinks.community.media.sip.processor.SipProcessorObserver;

+ 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.*;
 

+ 2 - 2
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.AbstractSipProcessor;
+import org.jetlinks.community.media.bean.SipServerConfig;
+
 import javax.sip.*;
 import javax.sip.address.Address;
 import javax.sip.address.AddressFactory;

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

@@ -8,12 +8,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.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;
@@ -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(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());
-        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;
 

+ 1 - 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,30 +1,25 @@
-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.AllArgsConstructor;
-import lombok.SneakyThrows;
 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 reactor.core.publisher.Mono;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.PeerUnavailableException;
 import javax.sip.RequestEvent;
 import javax.sip.SipProvider;
 import javax.sip.address.SipURI;
-import javax.sip.address.URI;
 import javax.sip.header.CallIdHeader;
 import javax.sip.header.FromHeader;
 import javax.sip.message.Request;
 import javax.sip.message.Response;
-import java.net.URL;
 import java.text.ParseException;
 
 /**

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

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

@@ -1,9 +1,9 @@
-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.extern.slf4j.Slf4j;
-import org.jetlinks.community.media.event.AbstractSipProcessor;
+import org.jetlinks.community.media.sip.AbstractSipProcessor;
 import org.springframework.stereotype.Component;
 import javax.sip.InvalidArgumentException;
 import javax.sip.ResponseEvent;

+ 2 - 2
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,10 +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.AbstractSipProcessor;
+import org.jetlinks.community.media.sip.AbstractSipProcessor;
 import org.springframework.stereotype.Component;
 import javax.sip.InvalidArgumentException;
 import javax.sip.ResponseEvent;

+ 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:更新失败
 	 */

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

@@ -6,7 +6,7 @@ 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.entity.ParentPlatform;
+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;

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

@@ -2,10 +2,11 @@ package org.jetlinks.community.media.transmit;
 
 
 import gov.nist.javax.sip.header.SIPDateHeader;
+import lombok.AllArgsConstructor;
 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.impl.RedisCatchStorageImpl;
 import org.springframework.stereotype.Component;
@@ -30,11 +31,12 @@ import java.util.Locale;
  * @date: 2020年5月6日 上午9:29:02
  */
 @Component
+@AllArgsConstructor
 public class SIPRequestHeaderProvider {
 
 
 
-	private RedisCatchStorageImpl redisCatchStorage;
+	private final RedisCatchStorageImpl redisCatchStorage;
 
 //	@Autowired
 //	private VideoStreamSessionManager streamSession;

+ 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 - 2
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/transmit/cmd/SipCommander.java

@@ -5,9 +5,11 @@ 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.entity.MediaServerItem;
 import org.springframework.stereotype.Service;
 import reactor.core.publisher.*;
@@ -37,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 {
@@ -166,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);

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

@@ -6,17 +6,15 @@ 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.LocalMediaServerItemServiceImpl;
+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.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Mono;
-
-import javax.servlet.http.HttpServletRequest;
 import java.util.List;
 
 /**
@@ -30,7 +28,7 @@ import java.util.List;
 @Authorize(ignore = true)
 @AllArgsConstructor
 public class ZLMHttpHookListener {
-    private final LocalMediaServerItemServiceImpl mediaServerItemService;
+    private final LocalMediaServerItemService mediaServerItemService;
     private final RedisCatchStorageImpl redisCatchStorage;
     private final ZLMHttpHookSubscribe subscribe;
 //
@@ -447,12 +445,11 @@ public class ZLMHttpHookListener {
      */
     @ResponseBody
     @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
-    public Mono<ResponseEntity<String>> onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject){
+    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 = request.getRemoteAddr();
+        String remoteAddr = exchange.getRequest().getRemoteAddress().getAddress().toString();
         jsonObject.append("ip", remoteAddr);
         subscribe.publish(ZLMHttpHookSubscribe.HookType.on_server_started,null,jsonObject);
         JSONObject ret = new JSONObject()

+ 25 - 11
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMHttpHookSubscribe.java

@@ -1,8 +1,13 @@
 package org.jetlinks.community.media.zlm;
 
 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;
@@ -14,6 +19,7 @@ import java.util.function.BiFunction;
  * @date:   2020年12月2日 21:17:32
  */
 @Component
+@Slf4j
 public class ZLMHttpHookSubscribe {
 
     public enum HookType{
@@ -32,7 +38,7 @@ public class ZLMHttpHookSubscribe {
         on_server_keepalive
     }
 
-    public interface Event extends BiFunction<MediaServerItem, JSONObject,Void>{
+    public interface Event extends BiConsumer<MediaServerItem, JSONObject> {
 
     }
 
@@ -40,32 +46,33 @@ public class ZLMHttpHookSubscribe {
 
 
     public void addSubscribe(HookType type, JSONObject hookResponse, ZLMHttpHookSubscribe.Event event) {
-         allSubscribes
-             .computeIfAbsent(type,k->new HashMap<>())
-             .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.getStr(s).equals(hookResponse.getStr(s));
+                    result = subscribe.getStr(s).equals(hookResponse.getStr(s));
                 }else {
-                    if (key.getStr(s) == null) {
+                    if (subscribe.getStr(s) == null) {
                         continue;
                     }
-                    result = result && key.getStr(s).equals(hookResponse.getStr(s));
+                    result = result && subscribe.getStr(s).equals(hookResponse.getStr(s));
                 }
 
             }
             if (null != result && result) {
-                event = eventMap.get(key);
+                event = eventMap.get(subscribe);
             }
         }
         return event;
@@ -73,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;
         }
@@ -124,7 +132,13 @@ public class ZLMHttpHookSubscribe {
         Optional
             .ofNullable(getSubscribes(type))
             .orElse(new ArrayList<>())
-            .forEach(subscribe->subscribe.apply(item,object));
+            .forEach(subscribe-> {
+                try {
+                    subscribe.accept(item,object);
+                } catch (Exception e) {
+                    log.error("ZLM发布信息失败,",e);
+                }
+            });
     }
 
 }

+ 21 - 0
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMRTPServerFactory.java

@@ -101,6 +101,27 @@ public class ZLMRTPServerFactory {
         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状态
+                log.error("关闭RTP Server 失败: 请检查ZLM服务");
+            }
+        }
+        return result;
+    }
+
     private int getPortFromportRange(MediaServerItem mediaServerItem) {
         int currentPort = mediaServerItem.getCurrentPort();
         if (currentPort == 0) {

+ 52 - 51
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/ZLMRunner.java

@@ -7,9 +7,10 @@ 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.LocalMediaServerItemServiceImpl;
+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;
@@ -20,6 +21,7 @@ import java.util.*;
 
 @Component
 @Slf4j
+@AutoConfigureAfter(LocalMediaServerItemService.class)
 @AllArgsConstructor
 @EnableConfigurationProperties(MediaConfig.class)
 public class ZLMRunner implements CommandLineRunner {
@@ -35,7 +37,7 @@ public class ZLMRunner implements CommandLineRunner {
 //    @Autowired
 //    private EventPublisher publisher;
 
-    private final LocalMediaServerItemServiceImpl mediaServerItemService;
+    private final LocalMediaServerItemService mediaServerItemService;
 
     private final MediaConfig mediaConfig;
 
@@ -49,15 +51,16 @@ public class ZLMRunner implements CommandLineRunner {
             .flatMap(defaultMediaServer->{
                 MediaServerItem mediaSerItem = mediaConfig.getMediaSerItem();
                 mediaSerItem.setId(defaultMediaServer.getId());
-                return mediaServerItemService.updateToDatabase(mediaSerItem);
+                return mediaServerItemService.save(mediaSerItem);
             })
             //添加默认媒体服务器配置
-            .switchIfEmpty(mediaServerItemService.addToDatabase(mediaConfig.getMediaSerItem()).then(Mono.empty()))
+            .switchIfEmpty(mediaServerItemService.save(mediaConfig.getMediaSerItem()))
+
             //订阅 zlm启动事件
-            .flatMap(ignore->subscribeOnServerStarted())
+            .doOnNext(ignore->subscribeOnServerStarted())
             //订阅 zlm保活事件
-            .flatMap(ignore->subscribeOnServerKeepalive())
-            .log()
+            .doOnNext(ignore->subscribeOnServerKeepalive())
+
             //获取所有的zlm, 并开启主动连接
             .flatMap(ignore->startAllConnection())
 
@@ -78,9 +81,12 @@ public class ZLMRunner implements CommandLineRunner {
             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
+                };
             }
-            startGetMedia = null;
-            //  TODO 清理数据库中与redis不匹配的zlm
+
         }
         return Mono.empty();
     }
@@ -88,41 +94,35 @@ public class ZLMRunner implements CommandLineRunner {
      * 订阅 zlm启动事件, 新的zlm也会从这里进入系统
      * @return Mono
      */
-    private Mono<Void> subscribeOnServerStarted(){
-        return Mono.fromRunnable(()->{
-            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();
+    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());
                     }
-                    return null;
-                });
-        });
+                    mediaServerItemService.zlmServerOnline(zlmServerConfig)
+                        .subscribe();
+                }
+            });
     }
 
     /**
      * 订阅 zlm保活事件, 当zlm离线时做业务的处理
      * @return Mono
      */
-    private Mono<Void> subscribeOnServerKeepalive(){
-        return Mono.fromRunnable(()->{
-            // 订阅 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();
-                    }
-                    return null;
-                });
-        });
+    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();
+                }
+            });
     }
 
     /**
@@ -133,7 +133,9 @@ public class ZLMRunner implements CommandLineRunner {
         return  mediaServerItemService
             .createQuery()
             .fetch()
-            .defaultIfEmpty(mediaConfig.getMediaSerItem())
+            .defaultIfEmpty(
+                mediaConfig.getMediaSerItem()
+            )
             .doOnNext(mediaServerItem ->{
                 //将媒体服务器进行标记
                 startGetMedia=Optional.ofNullable(startGetMedia).orElse(new HashMap<>());
@@ -169,10 +171,11 @@ public class ZLMRunner implements CommandLineRunner {
         if ( startGetMedia.get(mediaServerItem.getId()) == null || !startGetMedia.get(mediaServerItem.getId())) {
             return Mono.empty();
         }
-        final int pos=index;
         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;
@@ -183,20 +186,18 @@ public class ZLMRunner implements CommandLineRunner {
                 }
                 return Mono.just(zlmServerConfig);
             })
-            //响应配置不存在,使用默认配置
-            .switchIfEmpty(Mono.just(mediaServerItem)
-                .doOnNext(serverItem->{
-                    log.error("[ {} ]-[ {}:{} ]第{}次主动连接失败, 2s后重试",
-                        mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort(), pos);
-                    if (pos == 1 && !StringUtils.isEmpty(mediaServerItem.getId())) {
-                        log.info("[ {} ]-[ {}:{} ]第{}次主动连接失败, 开始清理相关资源",
-                            mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort(), pos);
-                        //todo
+            .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(serverItem,pos+1)))
+                .flatMap(serverItem->getMediaServerConfig(mediaServerItem,index+1)))
             ;
 
     }

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

+ 35 - 1
jetlinks-manager/media-manager/src/main/java/org/jetlinks/community/media/zlm/entity/MediaServerItem.java

@@ -1,72 +1,106 @@
 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 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 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;
 
 

+ 4 - 7
jetlinks-standalone/pom.xml

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

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

@@ -6,16 +6,15 @@ import org.hswebframework.web.authorization.events.AuthorizingHandleBeforeEvent;
 import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
 import org.hswebframework.web.logging.aop.EnableAccessLogger;
 import org.hswebframework.web.logging.events.AccessLoggerAfterEvent;
-import org.jetlinks.community.media.config.MediaConfig;
 import org.redisson.spring.starter.RedissonAutoConfiguration;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
 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;
 
 

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

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

@@ -16,7 +16,6 @@ import java.io.FileOutputStream;
  * @Description TODO
  * @createTime 2021年10月18日 16:47:00
  */
-@Primary
 @Configuration
 public class CroConfiguration{
     @Bean

+ 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,

+ 22 - 0
jetlinks-standalone/src/main/resources/application.yml

@@ -49,6 +49,8 @@ spring:
           max-in-memory-size: 100MB
           socket-timeout: 1000
           connection-timeout: 10000
+  main:
+    allow-bean-definition-overriding: true
 easyorm:
   default-schema: public # 数据库默认的schema
   dialect: postgres #数据库方言
@@ -221,8 +223,28 @@ springdoc:
         - /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/"
+
+#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 # 端口范围

+ 21 - 19
jetlinks-standalone/src/test/java/org/jetlinks/community/BridgeTest.java

@@ -2,7 +2,8 @@ package org.jetlinks.community;
 
 import lombok.extern.slf4j.Slf4j;
 import org.jetlinks.community.media.bean.SSRCInfo;
-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;
@@ -43,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.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();
+//        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){
 

+ 5 - 0
pom.xml

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