Pārlūkot izejas kodu

修复心跳异常,支持通道列表,推流列表,拉流代理列表直接查看对应的云端录像,兼容大于INT的ssrc的格式化

648540858 2 gadi atpakaļ
vecāks
revīzija
91e49745e4
33 mainītis faili ar 5362 papildinājumiem un 5155 dzēšanām
  1. 63 63
      src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
  2. 176 176
      src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java
  3. 141 141
      src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java
  4. 187 187
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java
  5. 102 102
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/GBStringMsgParser.java
  6. 144 144
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
  7. 118 118
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RemoteAddressInfo.java
  8. 139 139
      src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
  9. 160 160
      src/main/java/com/genersoft/iot/vmp/gb28181/task/ISubscribeTask.java
  10. 39 39
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java
  11. 364 364
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  12. 318 318
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
  13. 1424 1424
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  14. 149 149
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  15. 8 3
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/IMessageHandler.java
  16. 646 646
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java
  17. 777 777
      src/main/java/com/genersoft/iot/vmp/media/zlm/SendRtpPortManager.java
  18. 1 1
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java
  19. 11 0
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java
  20. 1 1
      src/main/java/com/genersoft/iot/vmp/service/IMediaService.java
  21. 99 2
      src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java
  22. 5 0
      src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java
  23. 50 50
      src/main/java/com/genersoft/iot/vmp/utils/GitUtil.java
  24. 45 45
      src/main/java/com/genersoft/iot/vmp/utils/SystemInfoUtils.java
  25. 47 47
      src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
  26. 2 2
      src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil2.java
  27. 2 2
      src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java
  28. 2 4
      src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java
  29. 110 51
      web_src/src/components/CloudRecordDetail.vue
  30. 6 0
      web_src/src/components/DeviceList.vue
  31. 6 0
      web_src/src/components/StreamProxyList.vue
  32. 9 0
      web_src/src/components/UserManager.vue
  33. 11 0
      web_src/src/components/common/ h265web.vue

+ 63 - 63
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java

@@ -1,63 +1,63 @@
-package com.genersoft.iot.vmp;
-
-import com.genersoft.iot.vmp.utils.GitUtil;
-import com.genersoft.iot.vmp.utils.SpringBeanFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.builder.SpringApplicationBuilder;
-import org.springframework.boot.web.servlet.ServletComponentScan;
-import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.scheduling.annotation.EnableScheduling;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.SessionCookieConfig;
-import javax.servlet.SessionTrackingMode;
-import java.util.Collections;
-
-/**
- * 启动类
- */
-@ServletComponentScan("com.genersoft.iot.vmp.conf")
-@SpringBootApplication
-@EnableScheduling
-public class VManageBootstrap extends SpringBootServletInitializer {
-
-	private final static Logger logger = LoggerFactory.getLogger(VManageBootstrap.class);
-
-	private static String[] args;
-	private static ConfigurableApplicationContext context;
-	public static void main(String[] args) {
-		VManageBootstrap.args = args;
-		VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args);
-		GitUtil gitUtil1 = SpringBeanFactory.getBean("gitUtil");
-		logger.info("构建版本: {}", gitUtil1.getBuildVersion());
-		logger.info("构建时间: {}", gitUtil1.getBuildDate());
-		logger.info("GIT最后提交时间: {}", gitUtil1.getCommitTime());
-	}
-	// 项目重启
-	public static void restart() {
-		context.close();
-		VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args);
-	}
-
-	@Override
-	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
-		return application.sources(VManageBootstrap.class);
-	}
-
-	@Override
-	public void onStartup(ServletContext servletContext) throws ServletException {
-		super.onStartup(servletContext);
-
-		servletContext.setSessionTrackingModes(
-				Collections.singleton(SessionTrackingMode.COOKIE)
-		);
-		SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig();
-		sessionCookieConfig.setHttpOnly(true);
-
-	}
-}
+package com.genersoft.iot.vmp;
+
+import com.genersoft.iot.vmp.utils.GitUtil;
+import com.genersoft.iot.vmp.utils.SpringBeanFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.ServletComponentScan;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import java.util.Collections;
+
+/**
+ * 启动类
+ */
+@ServletComponentScan("com.genersoft.iot.vmp.conf")
+@SpringBootApplication
+@EnableScheduling
+public class VManageBootstrap extends SpringBootServletInitializer {
+
+	private final static Logger logger = LoggerFactory.getLogger(VManageBootstrap.class);
+
+	private static String[] args;
+	private static ConfigurableApplicationContext context;
+	public static void main(String[] args) {
+		VManageBootstrap.args = args;
+		VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args);
+		GitUtil gitUtil1 = SpringBeanFactory.getBean("gitUtil");
+		logger.info("构建版本: {}", gitUtil1.getBuildVersion());
+		logger.info("构建时间: {}", gitUtil1.getBuildDate());
+		logger.info("GIT最后提交时间: {}", gitUtil1.getCommitTime());
+	}
+	// 项目重启
+	public static void restart() {
+		context.close();
+		VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args);
+	}
+
+	@Override
+	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+		return application.sources(VManageBootstrap.class);
+	}
+
+	@Override
+	public void onStartup(ServletContext servletContext) throws ServletException {
+		super.onStartup(servletContext);
+
+		servletContext.setSessionTrackingModes(
+				Collections.singleton(SessionTrackingMode.COOKIE)
+		);
+		SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig();
+		sessionCookieConfig.setHttpOnly(true);
+
+	}
+}

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

@@ -1,176 +1,176 @@
-package com.genersoft.iot.vmp.common;
-
-/**    
- * @description: 定义常量   
- * @author: swwheihei
- * @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_";
-
-	public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_";
-
-	public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS_";
-
-	public static final String DEVICE_PREFIX = "VMP_DEVICE_";
-
-	// 设备同步完成
-	public static final String DEVICE_SYNC_PREFIX = "VMP_DEVICE_SYNC_";
-
-	public static final String CACHEKEY_PREFIX = "VMP_CHANNEL_";
-
-	public static final String KEEPLIVEKEY_PREFIX = "VMP_KEEPALIVE_";
-
-	// TODO 此处多了一个_,暂不修改
-	public static final String INVITE_PREFIX = "VMP_INVITE";
-	public static final String PLAYER_PREFIX = "VMP_INVITE_PLAY_";
-	public static final String PLAY_BLACK_PREFIX = "VMP_INVITE_PLAYBACK_";
-	public static final String DOWNLOAD_PREFIX = "VMP_INVITE_DOWNLOAD_";
-
-	public static final String PLATFORM_KEEPALIVE_PREFIX = "VMP_PLATFORM_KEEPALIVE_";
-
-	public static final String PLATFORM_CATCH_PREFIX = "VMP_PLATFORM_CATCH_";
-
-	public static final String PLATFORM_REGISTER_PREFIX = "VMP_PLATFORM_REGISTER_";
-
-	public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_PLATFORM_REGISTER_INFO_";
-
-	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_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_";
-
-	public static final String MEDIA_TRANSACTION_USED_PREFIX = "VMP_MEDIA_TRANSACTION_";
-
-	public static final String MEDIA_STREAM_AUTHORITY = "MEDIA_STREAM_AUTHORITY_";
-
-	public static final String SIP_CSEQ_PREFIX = "VMP_SIP_CSEQ_";
-
-	public static final String SIP_SN_PREFIX = "VMP_SIP_SN_";
-
-	public static final String SIP_SUBSCRIBE_PREFIX = "VMP_SIP_SUBSCRIBE_";
-
-	public static final String SYSTEM_INFO_CPU_PREFIX = "VMP_SYSTEM_INFO_CPU_";
-
-	public static final String SYSTEM_INFO_MEM_PREFIX = "VMP_SYSTEM_INFO_MEM_";
-
-	public static final String SYSTEM_INFO_NET_PREFIX = "VMP_SYSTEM_INFO_NET_";
-
-	public static final String SYSTEM_INFO_DISK_PREFIX = "VMP_SYSTEM_INFO_DISK_";
-
-	public static final String REGISTER_EXPIRE_TASK_KEY_PREFIX = "VMP_device_register_expire_";
-
-
-
-
-	//************************** redis 消息*********************************
-
-	/**
-	 * 流变化的通知
-	 */
-	public static final String WVP_MSG_STREAM_CHANGE_PREFIX = "WVP_MSG_STREAM_CHANGE_";
-
-	/**
-	 * 接收推流设备的GPS变化通知
-	 */
-	public static final String VM_MSG_GPS = "VM_MSG_GPS";
-
-	/**
-	 * 接收推流设备的GPS变化通知
-	 */
-	public static final String VM_MSG_PUSH_STREAM_STATUS_CHANGE = "VM_MSG_PUSH_STREAM_STATUS_CHANGE";
-	/**
-	 * 接收推流设备列表更新变化通知
-	 */
-	public static final String VM_MSG_PUSH_STREAM_LIST_CHANGE = "VM_MSG_PUSH_STREAM_LIST_CHANGE";
-
-	/**
-	 * redis 消息通知设备推流到平台
-	 */
-	public static final String VM_MSG_STREAM_PUSH_REQUESTED = "VM_MSG_STREAM_PUSH_REQUESTED";
-
-	/**
-	 * redis 消息通知上级平台开始观看流
-	 */
-	public static final String VM_MSG_STREAM_START_PLAY_NOTIFY = "VM_MSG_STREAM_START_PLAY_NOTIFY";
-
-	/**
-	 * redis 消息通知上级平台停止观看流
-	 */
-	public static final String VM_MSG_STREAM_STOP_PLAY_NOTIFY = "VM_MSG_STREAM_STOP_PLAY_NOTIFY";
-
-	/**
-	 * redis 消息接收关闭一个推流
-	 */
-	public static final String VM_MSG_STREAM_PUSH_CLOSE_REQUESTED = "VM_MSG_STREAM_PUSH_CLOSE_REQUESTED";
-
-
-	/**
-	 * redis 消息通知平台通知设备推流结果
-	 */
-	public static final String VM_MSG_STREAM_PUSH_RESPONSE = "VM_MSG_STREAM_PUSH_RESPONSE";
-
-	/**
-	 * redis 通知平台关闭推流
-	 */
-	public static final String VM_MSG_STREAM_PUSH_CLOSE = "VM_MSG_STREAM_PUSH_CLOSE";
-
-	/**
-	 * redis 消息请求所有的在线通道
-	 */
-	public static final String VM_MSG_GET_ALL_ONLINE_REQUESTED = "VM_MSG_GET_ALL_ONLINE_REQUESTED";
-
-	/**
-	 * 移动位置订阅通知
-	 */
-	public static final String VM_MSG_SUBSCRIBE_MOBILE_POSITION = "mobileposition";
-
-	/**
-	 * 报警订阅的通知(收到报警向redis发出通知)
-	 */
-	public static final String VM_MSG_SUBSCRIBE_ALARM = "alarm";
-
-
-	/**
-	 * 报警通知的发送 (收到redis发出的通知,转发给其他平台)
-	 */
-	public static final String VM_MSG_SUBSCRIBE_ALARM_RECEIVE= "alarm_receive";
-
-	/**
-	 * 设备状态订阅的通知
-	 */
-	public static final String VM_MSG_SUBSCRIBE_DEVICE_STATUS = "device";
-
-
-	//**************************    第三方  ****************************************
-
-	public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_";
-	public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_";
-	public static final String WVP_OTHER_SEND_RTP_INFO = "VMP_OTHER_SEND_RTP_INFO_";
-	public static final String WVP_OTHER_SEND_PS_INFO = "VMP_OTHER_SEND_PS_INFO_";
-	public static final String WVP_OTHER_RECEIVE_RTP_INFO = "VMP_OTHER_RECEIVE_RTP_INFO_";
-	public static final String WVP_OTHER_RECEIVE_PS_INFO = "VMP_OTHER_RECEIVE_PS_INFO_";
-
-	/**
-	 * Redis Const
-	 * 设备录像信息结果前缀
-	 */
-	public static final String REDIS_RECORD_INFO_RES_PRE = "GB_RECORD_INFO_RES_";
-	/**
-	 * Redis Const
-	 * 设备录像信息结果前缀
-	 */
-	public static final String REDIS_RECORD_INFO_RES_COUNT_PRE = "GB_RECORD_INFO_RES_COUNT:";
-
-}
+package com.genersoft.iot.vmp.common;
+
+/**    
+ * @description: 定义常量   
+ * @author: swwheihei
+ * @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_";
+
+	public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_";
+
+	public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS_";
+
+	public static final String DEVICE_PREFIX = "VMP_DEVICE_";
+
+	// 设备同步完成
+	public static final String DEVICE_SYNC_PREFIX = "VMP_DEVICE_SYNC_";
+
+	public static final String CACHEKEY_PREFIX = "VMP_CHANNEL_";
+
+	public static final String KEEPLIVEKEY_PREFIX = "VMP_KEEPALIVE_";
+
+	// TODO 此处多了一个_,暂不修改
+	public static final String INVITE_PREFIX = "VMP_INVITE";
+	public static final String PLAYER_PREFIX = "VMP_INVITE_PLAY_";
+	public static final String PLAY_BLACK_PREFIX = "VMP_INVITE_PLAYBACK_";
+	public static final String DOWNLOAD_PREFIX = "VMP_INVITE_DOWNLOAD_";
+
+	public static final String PLATFORM_KEEPALIVE_PREFIX = "VMP_PLATFORM_KEEPALIVE_";
+
+	public static final String PLATFORM_CATCH_PREFIX = "VMP_PLATFORM_CATCH_";
+
+	public static final String PLATFORM_REGISTER_PREFIX = "VMP_PLATFORM_REGISTER_";
+
+	public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_PLATFORM_REGISTER_INFO_";
+
+	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_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_";
+
+	public static final String MEDIA_TRANSACTION_USED_PREFIX = "VMP_MEDIA_TRANSACTION_";
+
+	public static final String MEDIA_STREAM_AUTHORITY = "MEDIA_STREAM_AUTHORITY_";
+
+	public static final String SIP_CSEQ_PREFIX = "VMP_SIP_CSEQ_";
+
+	public static final String SIP_SN_PREFIX = "VMP_SIP_SN_";
+
+	public static final String SIP_SUBSCRIBE_PREFIX = "VMP_SIP_SUBSCRIBE_";
+
+	public static final String SYSTEM_INFO_CPU_PREFIX = "VMP_SYSTEM_INFO_CPU_";
+
+	public static final String SYSTEM_INFO_MEM_PREFIX = "VMP_SYSTEM_INFO_MEM_";
+
+	public static final String SYSTEM_INFO_NET_PREFIX = "VMP_SYSTEM_INFO_NET_";
+
+	public static final String SYSTEM_INFO_DISK_PREFIX = "VMP_SYSTEM_INFO_DISK_";
+
+	public static final String REGISTER_EXPIRE_TASK_KEY_PREFIX = "VMP_device_register_expire_";
+
+
+
+
+	//************************** redis 消息*********************************
+
+	/**
+	 * 流变化的通知
+	 */
+	public static final String WVP_MSG_STREAM_CHANGE_PREFIX = "WVP_MSG_STREAM_CHANGE_";
+
+	/**
+	 * 接收推流设备的GPS变化通知
+	 */
+	public static final String VM_MSG_GPS = "VM_MSG_GPS";
+
+	/**
+	 * 接收推流设备的GPS变化通知
+	 */
+	public static final String VM_MSG_PUSH_STREAM_STATUS_CHANGE = "VM_MSG_PUSH_STREAM_STATUS_CHANGE";
+	/**
+	 * 接收推流设备列表更新变化通知
+	 */
+	public static final String VM_MSG_PUSH_STREAM_LIST_CHANGE = "VM_MSG_PUSH_STREAM_LIST_CHANGE";
+
+	/**
+	 * redis 消息通知设备推流到平台
+	 */
+	public static final String VM_MSG_STREAM_PUSH_REQUESTED = "VM_MSG_STREAM_PUSH_REQUESTED";
+
+	/**
+	 * redis 消息通知上级平台开始观看流
+	 */
+	public static final String VM_MSG_STREAM_START_PLAY_NOTIFY = "VM_MSG_STREAM_START_PLAY_NOTIFY";
+
+	/**
+	 * redis 消息通知上级平台停止观看流
+	 */
+	public static final String VM_MSG_STREAM_STOP_PLAY_NOTIFY = "VM_MSG_STREAM_STOP_PLAY_NOTIFY";
+
+	/**
+	 * redis 消息接收关闭一个推流
+	 */
+	public static final String VM_MSG_STREAM_PUSH_CLOSE_REQUESTED = "VM_MSG_STREAM_PUSH_CLOSE_REQUESTED";
+
+
+	/**
+	 * redis 消息通知平台通知设备推流结果
+	 */
+	public static final String VM_MSG_STREAM_PUSH_RESPONSE = "VM_MSG_STREAM_PUSH_RESPONSE";
+
+	/**
+	 * redis 通知平台关闭推流
+	 */
+	public static final String VM_MSG_STREAM_PUSH_CLOSE = "VM_MSG_STREAM_PUSH_CLOSE";
+
+	/**
+	 * redis 消息请求所有的在线通道
+	 */
+	public static final String VM_MSG_GET_ALL_ONLINE_REQUESTED = "VM_MSG_GET_ALL_ONLINE_REQUESTED";
+
+	/**
+	 * 移动位置订阅通知
+	 */
+	public static final String VM_MSG_SUBSCRIBE_MOBILE_POSITION = "mobileposition";
+
+	/**
+	 * 报警订阅的通知(收到报警向redis发出通知)
+	 */
+	public static final String VM_MSG_SUBSCRIBE_ALARM = "alarm";
+
+
+	/**
+	 * 报警通知的发送 (收到redis发出的通知,转发给其他平台)
+	 */
+	public static final String VM_MSG_SUBSCRIBE_ALARM_RECEIVE= "alarm_receive";
+
+	/**
+	 * 设备状态订阅的通知
+	 */
+	public static final String VM_MSG_SUBSCRIBE_DEVICE_STATUS = "device";
+
+
+	//**************************    第三方  ****************************************
+
+	public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_";
+	public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_";
+	public static final String WVP_OTHER_SEND_RTP_INFO = "VMP_OTHER_SEND_RTP_INFO_";
+	public static final String WVP_OTHER_SEND_PS_INFO = "VMP_OTHER_SEND_PS_INFO_";
+	public static final String WVP_OTHER_RECEIVE_RTP_INFO = "VMP_OTHER_RECEIVE_RTP_INFO_";
+	public static final String WVP_OTHER_RECEIVE_PS_INFO = "VMP_OTHER_RECEIVE_PS_INFO_";
+
+	/**
+	 * Redis Const
+	 * 设备录像信息结果前缀
+	 */
+	public static final String REDIS_RECORD_INFO_RES_PRE = "GB_RECORD_INFO_RES_";
+	/**
+	 * Redis Const
+	 * 设备录像信息结果前缀
+	 */
+	public static final String REDIS_RECORD_INFO_RES_COUNT_PRE = "GB_RECORD_INFO_RES_COUNT:";
+
+}

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

@@ -1,141 +1,141 @@
-package com.genersoft.iot.vmp.gb28181;
-
-import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.conf.UserSetting;
-import com.genersoft.iot.vmp.gb28181.bean.GbStringMsgParserFactory;
-import com.genersoft.iot.vmp.gb28181.conf.DefaultProperties;
-import com.genersoft.iot.vmp.gb28181.transmit.ISIPProcessorObserver;
-import gov.nist.javax.sip.SipProviderImpl;
-import gov.nist.javax.sip.SipStackImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.CommandLineRunner;
-import org.springframework.core.annotation.Order;
-import org.springframework.stereotype.Component;
-import org.springframework.util.ObjectUtils;
-
-import javax.sip.*;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-
-@Component
-@Order(value=10)
-public class SipLayer implements CommandLineRunner {
-
-	private final static Logger logger = LoggerFactory.getLogger(SipLayer.class);
-
-	@Autowired
-	private SipConfig sipConfig;
-
-	@Autowired
-	private ISIPProcessorObserver sipProcessorObserver;
-
-	@Autowired
-	private UserSetting userSetting;
-
-	private final Map<String, SipProviderImpl> tcpSipProviderMap = new ConcurrentHashMap<>();
-	private final Map<String, SipProviderImpl> udpSipProviderMap = new ConcurrentHashMap<>();
-
-	@Override
-	public void run(String... args) {
-		List<String> monitorIps = new ArrayList<>();
-		// 使用逗号分割多个ip
-		String separator = ",";
-		if (sipConfig.getIp().indexOf(separator) > 0) {
-			String[] split = sipConfig.getIp().split(separator);
-			monitorIps.addAll(Arrays.asList(split));
-		}else {
-			monitorIps.add(sipConfig.getIp());
-		}
-
-		SipFactory.getInstance().setPathName("gov.nist");
-		if (monitorIps.size() > 0) {
-			for (String monitorIp : monitorIps) {
-				addListeningPoint(monitorIp, sipConfig.getPort());
-			}
-			if (udpSipProviderMap.size() + tcpSipProviderMap.size() == 0) {
-				System.exit(1);
-			}
-		}
-	}
-
-	private void addListeningPoint(String monitorIp, int port){
-		SipStackImpl sipStack;
-		try {
-			sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties("GB28181_SIP", userSetting.getSipLog()));
-			sipStack.setMessageParserFactory(new GbStringMsgParserFactory());
-		} catch (PeerUnavailableException e) {
-			logger.error("[SIP SERVER] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp);
-			return;
-		}
-
-		try {
-			ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "TCP");
-			SipProviderImpl tcpSipProvider = (SipProviderImpl)sipStack.createSipProvider(tcpListeningPoint);
-
-			tcpSipProvider.setDialogErrorsAutomaticallyHandled();
-			tcpSipProvider.addSipListener(sipProcessorObserver);
-			tcpSipProviderMap.put(monitorIp, tcpSipProvider);
-			logger.info("[SIP SERVER] tcp://{}:{} 启动成功", monitorIp, port);
-		} catch (TransportNotSupportedException
-				 | TooManyListenersException
-				 | ObjectInUseException
-				 | InvalidArgumentException e) {
-			logger.error("[SIP SERVER] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
-					, monitorIp, port);
-		}
-
-		try {
-			ListeningPoint udpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "UDP");
-
-			SipProviderImpl udpSipProvider = (SipProviderImpl)sipStack.createSipProvider(udpListeningPoint);
-			udpSipProvider.addSipListener(sipProcessorObserver);
-
-			udpSipProviderMap.put(monitorIp, udpSipProvider);
-
-			logger.info("[SIP SERVER] udp://{}:{} 启动成功", monitorIp, port);
-		} catch (TransportNotSupportedException
-				 | TooManyListenersException
-				 | ObjectInUseException
-				 | InvalidArgumentException e) {
-			logger.error("[SIP SERVER] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
-					, monitorIp, port);
-		}
-	}
-
-	public SipProviderImpl getUdpSipProvider(String ip) {
-		if (ObjectUtils.isEmpty(ip)) {
-			return null;
-		}
-		return udpSipProviderMap.get(ip);
-	}
-
-	public SipProviderImpl getUdpSipProvider() {
-		if (udpSipProviderMap.size() != 1) {
-			return null;
-		}
-		return udpSipProviderMap.values().stream().findFirst().get();
-	}
-
-	public SipProviderImpl getTcpSipProvider() {
-		if (tcpSipProviderMap.size() != 1) {
-			return null;
-		}
-		return tcpSipProviderMap.values().stream().findFirst().get();
-	}
-
-	public SipProviderImpl getTcpSipProvider(String ip) {
-		if (ObjectUtils.isEmpty(ip)) {
-			return null;
-		}
-		return tcpSipProviderMap.get(ip);
-	}
-
-	public String getLocalIp(String deviceLocalIp) {
-		if (!ObjectUtils.isEmpty(deviceLocalIp)) {
-			return deviceLocalIp;
-		}
-		return getUdpSipProvider().getListeningPoint().getIPAddress();
-	}
-}
+package com.genersoft.iot.vmp.gb28181;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.gb28181.bean.GbStringMsgParserFactory;
+import com.genersoft.iot.vmp.gb28181.conf.DefaultProperties;
+import com.genersoft.iot.vmp.gb28181.transmit.ISIPProcessorObserver;
+import gov.nist.javax.sip.SipProviderImpl;
+import gov.nist.javax.sip.SipStackImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+
+import javax.sip.*;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+@Order(value=10)
+public class SipLayer implements CommandLineRunner {
+
+	private final static Logger logger = LoggerFactory.getLogger(SipLayer.class);
+
+	@Autowired
+	private SipConfig sipConfig;
+
+	@Autowired
+	private ISIPProcessorObserver sipProcessorObserver;
+
+	@Autowired
+	private UserSetting userSetting;
+
+	private final Map<String, SipProviderImpl> tcpSipProviderMap = new ConcurrentHashMap<>();
+	private final Map<String, SipProviderImpl> udpSipProviderMap = new ConcurrentHashMap<>();
+
+	@Override
+	public void run(String... args) {
+		List<String> monitorIps = new ArrayList<>();
+		// 使用逗号分割多个ip
+		String separator = ",";
+		if (sipConfig.getIp().indexOf(separator) > 0) {
+			String[] split = sipConfig.getIp().split(separator);
+			monitorIps.addAll(Arrays.asList(split));
+		}else {
+			monitorIps.add(sipConfig.getIp());
+		}
+
+		SipFactory.getInstance().setPathName("gov.nist");
+		if (monitorIps.size() > 0) {
+			for (String monitorIp : monitorIps) {
+				addListeningPoint(monitorIp, sipConfig.getPort());
+			}
+			if (udpSipProviderMap.size() + tcpSipProviderMap.size() == 0) {
+				System.exit(1);
+			}
+		}
+	}
+
+	private void addListeningPoint(String monitorIp, int port){
+		SipStackImpl sipStack;
+		try {
+			sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties("GB28181_SIP", userSetting.getSipLog()));
+			sipStack.setMessageParserFactory(new GbStringMsgParserFactory());
+		} catch (PeerUnavailableException e) {
+			logger.error("[SIP SERVER] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp);
+			return;
+		}
+
+		try {
+			ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "TCP");
+			SipProviderImpl tcpSipProvider = (SipProviderImpl)sipStack.createSipProvider(tcpListeningPoint);
+
+			tcpSipProvider.setDialogErrorsAutomaticallyHandled();
+			tcpSipProvider.addSipListener(sipProcessorObserver);
+			tcpSipProviderMap.put(monitorIp, tcpSipProvider);
+			logger.info("[SIP SERVER] tcp://{}:{} 启动成功", monitorIp, port);
+		} catch (TransportNotSupportedException
+				 | TooManyListenersException
+				 | ObjectInUseException
+				 | InvalidArgumentException e) {
+			logger.error("[SIP SERVER] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
+					, monitorIp, port);
+		}
+
+		try {
+			ListeningPoint udpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "UDP");
+
+			SipProviderImpl udpSipProvider = (SipProviderImpl)sipStack.createSipProvider(udpListeningPoint);
+			udpSipProvider.addSipListener(sipProcessorObserver);
+
+			udpSipProviderMap.put(monitorIp, udpSipProvider);
+
+			logger.info("[SIP SERVER] udp://{}:{} 启动成功", monitorIp, port);
+		} catch (TransportNotSupportedException
+				 | TooManyListenersException
+				 | ObjectInUseException
+				 | InvalidArgumentException e) {
+			logger.error("[SIP SERVER] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确"
+					, monitorIp, port);
+		}
+	}
+
+	public SipProviderImpl getUdpSipProvider(String ip) {
+		if (ObjectUtils.isEmpty(ip)) {
+			return null;
+		}
+		return udpSipProviderMap.get(ip);
+	}
+
+	public SipProviderImpl getUdpSipProvider() {
+		if (udpSipProviderMap.size() != 1) {
+			return null;
+		}
+		return udpSipProviderMap.values().stream().findFirst().get();
+	}
+
+	public SipProviderImpl getTcpSipProvider() {
+		if (tcpSipProviderMap.size() != 1) {
+			return null;
+		}
+		return tcpSipProviderMap.values().stream().findFirst().get();
+	}
+
+	public SipProviderImpl getTcpSipProvider(String ip) {
+		if (ObjectUtils.isEmpty(ip)) {
+			return null;
+		}
+		return tcpSipProviderMap.get(ip);
+	}
+
+	public String getLocalIp(String deviceLocalIp) {
+		if (!ObjectUtils.isEmpty(deviceLocalIp)) {
+			return deviceLocalIp;
+		}
+		return getUdpSipProvider().getListeningPoint().getIPAddress();
+	}
+}

+ 187 - 187
src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java

@@ -1,187 +1,187 @@
-package com.genersoft.iot.vmp.gb28181.bean;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-
-/**
- * @author lin
- */
-@Schema(description = "报警信息")
-public class DeviceAlarm {
-
-	/**
-	 * 数据库id
-	 */
-	@Schema(description = "数据库id")
-	private String id;
-
-	/**
-	 * 设备Id
-	 */
-	@Schema(description = "设备的国标编号")
-	private String deviceId;
-
-	/**
-	 * 通道Id
-	 */
-	@Schema(description = "通道的国标编号")
-	private String channelId;
-
-	/**
-	 * 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情
-	 */
-	@Schema(description = "报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情")
-	private String alarmPriority;
-
-	/**
-	 * 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,
-	 * 7其他报警;可以为直接组合如12为电话报警或 设备报警-
-	 */
-	@Schema(description = "报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,\n" +
-			"\t * 7其他报警;可以为直接组合如12为电话报警或设备报警")
-	private String alarmMethod;
-
-	/**
-	 * 报警时间
-	 */
-	@Schema(description = "报警时间")
-	private String alarmTime;
-
-	/**
-	 * 报警内容描述
-	 */
-	@Schema(description = "报警内容描述")
-	private String alarmDescription;
-
-	/**
-	 * 经度
-	 */
-	@Schema(description = "经度")
-	private double longitude;
-
-	/**
-	 * 纬度
-	 */
-	@Schema(description = "纬度")
-	private double latitude;
-
-	/**
-	 * 报警类型,
-	 * 报警方式为2时,不携带 AlarmType为默认的报警设备报警,
-	 * 携带 AlarmType取值及对应报警类型如下:
-	 * 		1-视频丢失报警;
-	 * 		2-设备防拆报警;
-	 * 		3-存储设备磁盘满报警;
-	 * 		4-设备高温报警;
-	 * 		5-设备低温报警。
-	 * 报警方式为5时,取值如下:
-	 * 		1-人工视频报警;
-	 * 		2-运动目标检测报警;
-	 * 		3-遗留物检测报警;
-	 * 		4-物体移除检测报警;
-	 * 		5-绊线检测报警;
-	 * 		6-入侵检测报警;
-	 * 		7-逆行检测报警;
-	 * 		8-徘徊检测报警;
-	 * 		9-流量统计报警;
-	 * 		10-密度检测报警;
-	 * 		11-视频异常检测报警;
-	 * 		12-快速移动报警。
-	 * 报警方式为6时,取值下:
-	 * 		1-存储设备磁盘故障报警;
-	 * 		2-存储设备风扇故障报警。
-	 */
-	@Schema(description = "报警类型")
-	private String alarmType;
-
-	@Schema(description = "创建时间")
-	private String createTime;
-
-
-	public String getId() {
-		return id;
-	}
-
-	public void setId(String id) {
-		this.id = id;
-	}
-
-	public String getDeviceId() {
-		return deviceId;
-	}
-
-	public void setDeviceId(String deviceId) {
-		this.deviceId = deviceId;
-	}
-
-	public String getAlarmPriority() {
-		return alarmPriority;
-	}
-
-	public void setAlarmPriority(String alarmPriority) {
-		this.alarmPriority = alarmPriority;
-	}
-
-	public String getAlarmMethod() {
-		return alarmMethod;
-	}
-
-	public void setAlarmMethod(String alarmMethod) {
-		this.alarmMethod = alarmMethod;
-	}
-
-	public String getAlarmTime() {
-		return alarmTime;
-	}
-
-	public void setAlarmTime(String alarmTime) {
-		this.alarmTime = alarmTime;
-	}
-
-	public String getAlarmDescription() {
-		return alarmDescription;
-	}
-
-	public void setAlarmDescription(String alarmDescription) {
-		this.alarmDescription = alarmDescription;
-	}
-
-	public double getLongitude() {
-		return longitude;
-	}
-
-	public void setLongitude(double longitude) {
-		this.longitude = longitude;
-	}
-
-	public double getLatitude() {
-		return latitude;
-	}
-
-	public void setLatitude(double latitude) {
-		this.latitude = latitude;
-	}
-
-	public String getAlarmType() {
-		return alarmType;
-	}
-
-	public void setAlarmType(String alarmType) {
-		this.alarmType = alarmType;
-	}
-
-	public String getChannelId() {
-		return channelId;
-	}
-
-	public void setChannelId(String channelId) {
-		this.channelId = channelId;
-	}
-
-	public String getCreateTime() {
-		return createTime;
-	}
-
-	public void setCreateTime(String createTime) {
-		this.createTime = createTime;
-	}
-}
+package com.genersoft.iot.vmp.gb28181.bean;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+/**
+ * @author lin
+ */
+@Schema(description = "报警信息")
+public class DeviceAlarm {
+
+	/**
+	 * 数据库id
+	 */
+	@Schema(description = "数据库id")
+	private String id;
+
+	/**
+	 * 设备Id
+	 */
+	@Schema(description = "设备的国标编号")
+	private String deviceId;
+
+	/**
+	 * 通道Id
+	 */
+	@Schema(description = "通道的国标编号")
+	private String channelId;
+
+	/**
+	 * 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情
+	 */
+	@Schema(description = "报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情")
+	private String alarmPriority;
+
+	/**
+	 * 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,
+	 * 7其他报警;可以为直接组合如12为电话报警或 设备报警-
+	 */
+	@Schema(description = "报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,\n" +
+			"\t * 7其他报警;可以为直接组合如12为电话报警或设备报警")
+	private String alarmMethod;
+
+	/**
+	 * 报警时间
+	 */
+	@Schema(description = "报警时间")
+	private String alarmTime;
+
+	/**
+	 * 报警内容描述
+	 */
+	@Schema(description = "报警内容描述")
+	private String alarmDescription;
+
+	/**
+	 * 经度
+	 */
+	@Schema(description = "经度")
+	private double longitude;
+
+	/**
+	 * 纬度
+	 */
+	@Schema(description = "纬度")
+	private double latitude;
+
+	/**
+	 * 报警类型,
+	 * 报警方式为2时,不携带 AlarmType为默认的报警设备报警,
+	 * 携带 AlarmType取值及对应报警类型如下:
+	 * 		1-视频丢失报警;
+	 * 		2-设备防拆报警;
+	 * 		3-存储设备磁盘满报警;
+	 * 		4-设备高温报警;
+	 * 		5-设备低温报警。
+	 * 报警方式为5时,取值如下:
+	 * 		1-人工视频报警;
+	 * 		2-运动目标检测报警;
+	 * 		3-遗留物检测报警;
+	 * 		4-物体移除检测报警;
+	 * 		5-绊线检测报警;
+	 * 		6-入侵检测报警;
+	 * 		7-逆行检测报警;
+	 * 		8-徘徊检测报警;
+	 * 		9-流量统计报警;
+	 * 		10-密度检测报警;
+	 * 		11-视频异常检测报警;
+	 * 		12-快速移动报警。
+	 * 报警方式为6时,取值下:
+	 * 		1-存储设备磁盘故障报警;
+	 * 		2-存储设备风扇故障报警。
+	 */
+	@Schema(description = "报警类型")
+	private String alarmType;
+
+	@Schema(description = "创建时间")
+	private String createTime;
+
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getDeviceId() {
+		return deviceId;
+	}
+
+	public void setDeviceId(String deviceId) {
+		this.deviceId = deviceId;
+	}
+
+	public String getAlarmPriority() {
+		return alarmPriority;
+	}
+
+	public void setAlarmPriority(String alarmPriority) {
+		this.alarmPriority = alarmPriority;
+	}
+
+	public String getAlarmMethod() {
+		return alarmMethod;
+	}
+
+	public void setAlarmMethod(String alarmMethod) {
+		this.alarmMethod = alarmMethod;
+	}
+
+	public String getAlarmTime() {
+		return alarmTime;
+	}
+
+	public void setAlarmTime(String alarmTime) {
+		this.alarmTime = alarmTime;
+	}
+
+	public String getAlarmDescription() {
+		return alarmDescription;
+	}
+
+	public void setAlarmDescription(String alarmDescription) {
+		this.alarmDescription = alarmDescription;
+	}
+
+	public double getLongitude() {
+		return longitude;
+	}
+
+	public void setLongitude(double longitude) {
+		this.longitude = longitude;
+	}
+
+	public double getLatitude() {
+		return latitude;
+	}
+
+	public void setLatitude(double latitude) {
+		this.latitude = latitude;
+	}
+
+	public String getAlarmType() {
+		return alarmType;
+	}
+
+	public void setAlarmType(String alarmType) {
+		this.alarmType = alarmType;
+	}
+
+	public String getChannelId() {
+		return channelId;
+	}
+
+	public void setChannelId(String channelId) {
+		this.channelId = channelId;
+	}
+
+	public String getCreateTime() {
+		return createTime;
+	}
+
+	public void setCreateTime(String createTime) {
+		this.createTime = createTime;
+	}
+}

+ 102 - 102
src/main/java/com/genersoft/iot/vmp/gb28181/bean/GBStringMsgParser.java

@@ -1,102 +1,102 @@
-package com.genersoft.iot.vmp.gb28181.bean;
-
-
-import io.swagger.v3.oas.annotations.media.Schema;
-
-import java.time.Instant;
-import java.util.List;
-
-/**    
- * @description:设备录像信息bean 
- * @author: swwheihei
- * @date:   2020年5月8日 下午2:05:56     
- */
-@Schema(description = "设备录像查询结果信息")
-public class RecordInfo {
-
-	@Schema(description = "设备编号")
-	private String deviceId;
-
-	@Schema(description = "通道编号")
-	private String channelId;
-
-	@Schema(description = "命令序列号")
-	private String sn;
-
-	@Schema(description = "设备名称")
-	private String name;
-
-	@Schema(description = "列表总数")
-	private int sumNum;
-
-	private int count;
-
-	private Instant lastTime;
-
-	@Schema(description = "")
-	private List<RecordItem> recordList;
-
-	public String getDeviceId() {
-		return deviceId;
-	}
-
-	public void setDeviceId(String deviceId) {
-		this.deviceId = deviceId;
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	public int getSumNum() {
-		return sumNum;
-	}
-
-	public void setSumNum(int sumNum) {
-		this.sumNum = sumNum;
-	}
-
-	public List<RecordItem> getRecordList() {
-		return recordList;
-	}
-
-	public void setRecordList(List<RecordItem> recordList) {
-		this.recordList = recordList;
-	}
-
-	public String getChannelId() {
-		return channelId;
-	}
-
-	public void setChannelId(String channelId) {
-		this.channelId = channelId;
-	}
-
-	public String getSn() {
-		return sn;
-	}
-
-	public void setSn(String sn) {
-		this.sn = sn;
-	}
-
-	public Instant getLastTime() {
-		return lastTime;
-	}
-
-	public void setLastTime(Instant lastTime) {
-		this.lastTime = lastTime;
-	}
-
-	public int getCount() {
-		return count;
-	}
-
-	public void setCount(int count) {
-		this.count = count;
-	}
-}
+package com.genersoft.iot.vmp.gb28181.bean;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.time.Instant;
+import java.util.List;
+
+/**    
+ * @description:设备录像信息bean 
+ * @author: swwheihei
+ * @date:   2020年5月8日 下午2:05:56     
+ */
+@Schema(description = "设备录像查询结果信息")
+public class RecordInfo {
+
+	@Schema(description = "设备编号")
+	private String deviceId;
+
+	@Schema(description = "通道编号")
+	private String channelId;
+
+	@Schema(description = "命令序列号")
+	private String sn;
+
+	@Schema(description = "设备名称")
+	private String name;
+
+	@Schema(description = "列表总数")
+	private int sumNum;
+
+	private int count;
+
+	private Instant lastTime;
+
+	@Schema(description = "")
+	private List<RecordItem> recordList;
+
+	public String getDeviceId() {
+		return deviceId;
+	}
+
+	public void setDeviceId(String deviceId) {
+		this.deviceId = deviceId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public int getSumNum() {
+		return sumNum;
+	}
+
+	public void setSumNum(int sumNum) {
+		this.sumNum = sumNum;
+	}
+
+	public List<RecordItem> getRecordList() {
+		return recordList;
+	}
+
+	public void setRecordList(List<RecordItem> recordList) {
+		this.recordList = recordList;
+	}
+
+	public String getChannelId() {
+		return channelId;
+	}
+
+	public void setChannelId(String channelId) {
+		this.channelId = channelId;
+	}
+
+	public String getSn() {
+		return sn;
+	}
+
+	public void setSn(String sn) {
+		this.sn = sn;
+	}
+
+	public Instant getLastTime() {
+		return lastTime;
+	}
+
+	public void setLastTime(Instant lastTime) {
+		this.lastTime = lastTime;
+	}
+
+	public int getCount() {
+		return count;
+	}
+
+	public void setCount(int count) {
+		this.count = count;
+	}
+}

+ 144 - 144
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java

@@ -1,144 +1,144 @@
-package com.genersoft.iot.vmp.gb28181.bean;
-
-
-import com.genersoft.iot.vmp.utils.DateUtil;
-import io.swagger.v3.oas.annotations.media.Schema;
-import org.jetbrains.annotations.NotNull;
-
-import java.time.Instant;
-import java.time.temporal.TemporalAccessor;
-
-/**
- * @description:设备录像bean 
- * @author: swwheihei
- * @date:   2020年5月8日 下午2:06:54     
- */
-@Schema(description = "设备录像详情")
-public class RecordItem  implements Comparable<RecordItem>{
-
-	@Schema(description = "设备编号")
-	private String deviceId;
-
-	@Schema(description = "名称")
-	private String name;
-
-	@Schema(description = "文件路径名 (可选)")
-	private String filePath;
-
-	@Schema(description = "录像文件大小,单位:Byte(可选)")
-	private String fileSize;
-
-	@Schema(description = "录像地址(可选)")
-	private String address;
-
-	@Schema(description = "录像开始时间(可选)")
-	private String startTime;
-
-	@Schema(description = "录像结束时间(可选)")
-	private String endTime;
-
-	@Schema(description = "保密属性(必选)缺省为0;0:不涉密,1:涉密")
-	private int secrecy;
-
-	@Schema(description = "录像产生类型(可选)time或alarm 或 manua")
-	private String type;
-
-	@Schema(description = "录像触发者ID(可选)")
-	private String recorderId;
-
-	public String getDeviceId() {
-		return deviceId;
-	}
-
-	public void setDeviceId(String deviceId) {
-		this.deviceId = deviceId;
-	}
-
-	public String getName() {
-		return name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	public String getFilePath() {
-		return filePath;
-	}
-
-	public void setFilePath(String filePath) {
-		this.filePath = filePath;
-	}
-
-	public String getAddress() {
-		return address;
-	}
-
-	public void setAddress(String address) {
-		this.address = address;
-	}
-
-	public String getStartTime() {
-		return startTime;
-	}
-
-	public void setStartTime(String startTime) {
-		this.startTime = startTime;
-	}
-
-	public String getEndTime() {
-		return endTime;
-	}
-
-	public void setEndTime(String endTime) {
-		this.endTime = endTime;
-	}
-
-	public int getSecrecy() {
-		return secrecy;
-	}
-
-	public void setSecrecy(int secrecy) {
-		this.secrecy = secrecy;
-	}
-
-	public String getType() {
-		return type;
-	}
-
-	public void setType(String type) {
-		this.type = type;
-	}
-
-	public String getRecorderId() {
-		return recorderId;
-	}
-
-	public void setRecorderId(String recorderId) {
-		this.recorderId = recorderId;
-	}
-
-	public String getFileSize() {
-		return fileSize;
-	}
-
-	public void setFileSize(String fileSize) {
-		this.fileSize = fileSize;
-	}
-
-	@Override
-	public int compareTo(@NotNull RecordItem recordItem) {
-		TemporalAccessor startTimeNow = DateUtil.formatter.parse(startTime);
-		TemporalAccessor startTimeParam = DateUtil.formatter.parse(recordItem.getStartTime());
-		Instant startTimeParamInstant = Instant.from(startTimeParam);
-		Instant startTimeNowInstant = Instant.from(startTimeNow);
-		if (startTimeNowInstant.equals(startTimeParamInstant)) {
-			return 0;
-		}else if (Instant.from(startTimeParam).isAfter(Instant.from(startTimeNow)) ) {
-			return -1;
-		}else {
-			return 1;
-		}
-
-	}
-}
+package com.genersoft.iot.vmp.gb28181.bean;
+
+
+import com.genersoft.iot.vmp.utils.DateUtil;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.jetbrains.annotations.NotNull;
+
+import java.time.Instant;
+import java.time.temporal.TemporalAccessor;
+
+/**
+ * @description:设备录像bean 
+ * @author: swwheihei
+ * @date:   2020年5月8日 下午2:06:54     
+ */
+@Schema(description = "设备录像详情")
+public class RecordItem  implements Comparable<RecordItem>{
+
+	@Schema(description = "设备编号")
+	private String deviceId;
+
+	@Schema(description = "名称")
+	private String name;
+
+	@Schema(description = "文件路径名 (可选)")
+	private String filePath;
+
+	@Schema(description = "录像文件大小,单位:Byte(可选)")
+	private String fileSize;
+
+	@Schema(description = "录像地址(可选)")
+	private String address;
+
+	@Schema(description = "录像开始时间(可选)")
+	private String startTime;
+
+	@Schema(description = "录像结束时间(可选)")
+	private String endTime;
+
+	@Schema(description = "保密属性(必选)缺省为0;0:不涉密,1:涉密")
+	private int secrecy;
+
+	@Schema(description = "录像产生类型(可选)time或alarm 或 manua")
+	private String type;
+
+	@Schema(description = "录像触发者ID(可选)")
+	private String recorderId;
+
+	public String getDeviceId() {
+		return deviceId;
+	}
+
+	public void setDeviceId(String deviceId) {
+		this.deviceId = deviceId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getFilePath() {
+		return filePath;
+	}
+
+	public void setFilePath(String filePath) {
+		this.filePath = filePath;
+	}
+
+	public String getAddress() {
+		return address;
+	}
+
+	public void setAddress(String address) {
+		this.address = address;
+	}
+
+	public String getStartTime() {
+		return startTime;
+	}
+
+	public void setStartTime(String startTime) {
+		this.startTime = startTime;
+	}
+
+	public String getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(String endTime) {
+		this.endTime = endTime;
+	}
+
+	public int getSecrecy() {
+		return secrecy;
+	}
+
+	public void setSecrecy(int secrecy) {
+		this.secrecy = secrecy;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		this.type = type;
+	}
+
+	public String getRecorderId() {
+		return recorderId;
+	}
+
+	public void setRecorderId(String recorderId) {
+		this.recorderId = recorderId;
+	}
+
+	public String getFileSize() {
+		return fileSize;
+	}
+
+	public void setFileSize(String fileSize) {
+		this.fileSize = fileSize;
+	}
+
+	@Override
+	public int compareTo(@NotNull RecordItem recordItem) {
+		TemporalAccessor startTimeNow = DateUtil.formatter.parse(startTime);
+		TemporalAccessor startTimeParam = DateUtil.formatter.parse(recordItem.getStartTime());
+		Instant startTimeParamInstant = Instant.from(startTimeParam);
+		Instant startTimeNowInstant = Instant.from(startTimeNow);
+		if (startTimeNowInstant.equals(startTimeParamInstant)) {
+			return 0;
+		}else if (Instant.from(startTimeParam).isAfter(Instant.from(startTimeNow)) ) {
+			return -1;
+		}else {
+			return 1;
+		}
+
+	}
+}

+ 118 - 118
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RemoteAddressInfo.java

@@ -1,118 +1,118 @@
-package com.genersoft.iot.vmp.gb28181.event;
-
-import com.genersoft.iot.vmp.gb28181.bean.*;
-import com.genersoft.iot.vmp.gb28181.event.device.RequestTimeoutEvent;
-import com.genersoft.iot.vmp.gb28181.event.record.RecordEndEvent;
-import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
-import com.genersoft.iot.vmp.media.zlm.event.ZLMOfflineEvent;
-import com.genersoft.iot.vmp.media.zlm.event.ZLMOnlineEvent;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationEventPublisher;
-import org.springframework.stereotype.Component;
-
-import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEvent;
-
-import javax.sip.TimeoutEvent;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**    
- * @description:Event事件通知推送器,支持推送在线事件、离线事件
- * @author: swwheihei
- * @date:   2020年5月6日 上午11:30:50     
- */
-@Component
-public class EventPublisher {
-
-	@Autowired
-    private ApplicationEventPublisher applicationEventPublisher;
-	
-	/**
-	 * 设备报警事件
-	 * @param deviceAlarm
-	 */
-	public void deviceAlarmEventPublish(DeviceAlarm deviceAlarm) {
-		AlarmEvent alarmEvent = new AlarmEvent(this);
-		alarmEvent.setAlarmInfo(deviceAlarm);
-		applicationEventPublisher.publishEvent(alarmEvent);
-	}
-
-	public void zlmOfflineEventPublish(String mediaServerId){
-		ZLMOfflineEvent outEvent = new ZLMOfflineEvent(this);
-		outEvent.setMediaServerId(mediaServerId);
-		applicationEventPublisher.publishEvent(outEvent);
-	}
-
-	public void zlmOnlineEventPublish(String mediaServerId) {
-		ZLMOnlineEvent outEvent = new ZLMOnlineEvent(this);
-		outEvent.setMediaServerId(mediaServerId);
-		applicationEventPublisher.publishEvent(outEvent);
-	}
-
-
-	public void catalogEventPublish(String platformId, DeviceChannel deviceChannel, String type) {
-		List<DeviceChannel> deviceChannelList = new ArrayList<>();
-		deviceChannelList.add(deviceChannel);
-		catalogEventPublish(platformId, deviceChannelList, type);
-	}
-
-
-	public void requestTimeOut(TimeoutEvent timeoutEvent) {
-		RequestTimeoutEvent requestTimeoutEvent = new RequestTimeoutEvent(this);
-		requestTimeoutEvent.setTimeoutEvent(timeoutEvent);
-		applicationEventPublisher.publishEvent(requestTimeoutEvent);
-	}
-
-
-	/**
-	 *
-	 * @param platformId
-	 * @param deviceChannels
-	 * @param type
-	 */
-	public void catalogEventPublish(String platformId, List<DeviceChannel> deviceChannels, String type) {
-		CatalogEvent outEvent = new CatalogEvent(this);
-		List<DeviceChannel> channels = new ArrayList<>();
-		if (deviceChannels.size() > 1) {
-			// 数据去重
-			Set<String> gbIdSet = new HashSet<>();
-			for (DeviceChannel deviceChannel : deviceChannels) {
-				if (!gbIdSet.contains(deviceChannel.getChannelId())) {
-					gbIdSet.add(deviceChannel.getChannelId());
-					channels.add(deviceChannel);
-				}
-			}
-		}else {
-			channels = deviceChannels;
-		}
-		outEvent.setDeviceChannels(channels);
-		outEvent.setType(type);
-		outEvent.setPlatformId(platformId);
-		applicationEventPublisher.publishEvent(outEvent);
-	}
-
-
-	public void catalogEventPublishForStream(String platformId, List<GbStream> gbStreams, String type) {
-		CatalogEvent outEvent = new CatalogEvent(this);
-		outEvent.setGbStreams(gbStreams);
-		outEvent.setType(type);
-		outEvent.setPlatformId(platformId);
-		applicationEventPublisher.publishEvent(outEvent);
-	}
-
-
-	public void catalogEventPublishForStream(String platformId, GbStream gbStream, String type) {
-		List<GbStream> gbStreamList = new ArrayList<>();
-		gbStreamList.add(gbStream);
-		catalogEventPublishForStream(platformId, gbStreamList, type);
-	}
-
-	public void recordEndEventPush(RecordInfo recordInfo) {
-		RecordEndEvent outEvent = new RecordEndEvent(this);
-		outEvent.setRecordInfo(recordInfo);
-		applicationEventPublisher.publishEvent(outEvent);
-	}
-
-}
+package com.genersoft.iot.vmp.gb28181.event;
+
+import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.event.device.RequestTimeoutEvent;
+import com.genersoft.iot.vmp.gb28181.event.record.RecordEndEvent;
+import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
+import com.genersoft.iot.vmp.media.zlm.event.ZLMOfflineEvent;
+import com.genersoft.iot.vmp.media.zlm.event.ZLMOnlineEvent;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+
+import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEvent;
+
+import javax.sip.TimeoutEvent;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**    
+ * @description:Event事件通知推送器,支持推送在线事件、离线事件
+ * @author: swwheihei
+ * @date:   2020年5月6日 上午11:30:50     
+ */
+@Component
+public class EventPublisher {
+
+	@Autowired
+    private ApplicationEventPublisher applicationEventPublisher;
+	
+	/**
+	 * 设备报警事件
+	 * @param deviceAlarm
+	 */
+	public void deviceAlarmEventPublish(DeviceAlarm deviceAlarm) {
+		AlarmEvent alarmEvent = new AlarmEvent(this);
+		alarmEvent.setAlarmInfo(deviceAlarm);
+		applicationEventPublisher.publishEvent(alarmEvent);
+	}
+
+	public void zlmOfflineEventPublish(String mediaServerId){
+		ZLMOfflineEvent outEvent = new ZLMOfflineEvent(this);
+		outEvent.setMediaServerId(mediaServerId);
+		applicationEventPublisher.publishEvent(outEvent);
+	}
+
+	public void zlmOnlineEventPublish(String mediaServerId) {
+		ZLMOnlineEvent outEvent = new ZLMOnlineEvent(this);
+		outEvent.setMediaServerId(mediaServerId);
+		applicationEventPublisher.publishEvent(outEvent);
+	}
+
+
+	public void catalogEventPublish(String platformId, DeviceChannel deviceChannel, String type) {
+		List<DeviceChannel> deviceChannelList = new ArrayList<>();
+		deviceChannelList.add(deviceChannel);
+		catalogEventPublish(platformId, deviceChannelList, type);
+	}
+
+
+	public void requestTimeOut(TimeoutEvent timeoutEvent) {
+		RequestTimeoutEvent requestTimeoutEvent = new RequestTimeoutEvent(this);
+		requestTimeoutEvent.setTimeoutEvent(timeoutEvent);
+		applicationEventPublisher.publishEvent(requestTimeoutEvent);
+	}
+
+
+	/**
+	 *
+	 * @param platformId
+	 * @param deviceChannels
+	 * @param type
+	 */
+	public void catalogEventPublish(String platformId, List<DeviceChannel> deviceChannels, String type) {
+		CatalogEvent outEvent = new CatalogEvent(this);
+		List<DeviceChannel> channels = new ArrayList<>();
+		if (deviceChannels.size() > 1) {
+			// 数据去重
+			Set<String> gbIdSet = new HashSet<>();
+			for (DeviceChannel deviceChannel : deviceChannels) {
+				if (!gbIdSet.contains(deviceChannel.getChannelId())) {
+					gbIdSet.add(deviceChannel.getChannelId());
+					channels.add(deviceChannel);
+				}
+			}
+		}else {
+			channels = deviceChannels;
+		}
+		outEvent.setDeviceChannels(channels);
+		outEvent.setType(type);
+		outEvent.setPlatformId(platformId);
+		applicationEventPublisher.publishEvent(outEvent);
+	}
+
+
+	public void catalogEventPublishForStream(String platformId, List<GbStream> gbStreams, String type) {
+		CatalogEvent outEvent = new CatalogEvent(this);
+		outEvent.setGbStreams(gbStreams);
+		outEvent.setType(type);
+		outEvent.setPlatformId(platformId);
+		applicationEventPublisher.publishEvent(outEvent);
+	}
+
+
+	public void catalogEventPublishForStream(String platformId, GbStream gbStream, String type) {
+		List<GbStream> gbStreamList = new ArrayList<>();
+		gbStreamList.add(gbStream);
+		catalogEventPublishForStream(platformId, gbStreamList, type);
+	}
+
+	public void recordEndEventPush(RecordInfo recordInfo) {
+		RecordEndEvent outEvent = new RecordEndEvent(this);
+		outEvent.setRecordInfo(recordInfo);
+		applicationEventPublisher.publishEvent(outEvent);
+	}
+
+}

+ 139 - 139
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java

@@ -1,139 +1,139 @@
-package com.genersoft.iot.vmp.gb28181.session;
-
-import com.genersoft.iot.vmp.common.InviteSessionType;
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.conf.UserSetting;
-import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
-import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
-import com.genersoft.iot.vmp.utils.JsonUtil;
-import com.genersoft.iot.vmp.utils.redis.RedisUtil;
-import gov.nist.javax.sip.message.SIPResponse;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.stereotype.Component;
-import org.springframework.util.ObjectUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 视频流session管理器,管理视频预览、预览回放的通信句柄
- */
-@Component
-public class VideoStreamSessionManager {
-
-	@Autowired
-	private UserSetting userSetting;
-
-	@Autowired
-	private RedisTemplate<Object, Object> redisTemplate;
-
-	/**
-	 * 添加一个点播/回放的事务信息
-	 * 后续可以通过流Id/callID
-	 * @param deviceId 设备ID
-	 * @param channelId 通道ID
-	 * @param callId 一次请求的CallID
-	 * @param stream 流名称
-	 * @param mediaServerId 所使用的流媒体ID
-	 * @param response 回复
-	 */
-	public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, SIPResponse response, InviteSessionType type){
-		SsrcTransaction ssrcTransaction = new SsrcTransaction();
-		ssrcTransaction.setDeviceId(deviceId);
-		ssrcTransaction.setChannelId(channelId);
-		ssrcTransaction.setStream(stream);
-		ssrcTransaction.setSipTransactionInfo(new SipTransactionInfo(response));
-		ssrcTransaction.setCallId(callId);
-		ssrcTransaction.setSsrc(ssrc);
-		ssrcTransaction.setMediaServerId(mediaServerId);
-		ssrcTransaction.setType(type);
-
-		redisTemplate.opsForValue().set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId()
-				+ "_" +  deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction);
-	}
-
-	public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){
-
-		if (ObjectUtils.isEmpty(deviceId)) {
-			deviceId ="*";
-		}
-		if (ObjectUtils.isEmpty(channelId)) {
-			channelId ="*";
-		}
-		if (ObjectUtils.isEmpty(callId)) {
-			callId ="*";
-		}
-		if (ObjectUtils.isEmpty(stream)) {
-			stream ="*";
-		}
-		String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream;
-		List<Object> scanResult = RedisUtil.scan(redisTemplate, key);
-		if (scanResult.size() == 0) {
-			return null;
-		}
-		return (SsrcTransaction)redisTemplate.opsForValue().get(scanResult.get(0));
-	}
-
-	public List<SsrcTransaction> getSsrcTransactionForAll(String deviceId, String channelId, String callId, String stream){
-		if (ObjectUtils.isEmpty(deviceId)) {
-			deviceId ="*";
-		}
-		if (ObjectUtils.isEmpty(channelId)) {
-			channelId ="*";
-		}
-		if (ObjectUtils.isEmpty(callId)) {
-			callId ="*";
-		}
-		if (ObjectUtils.isEmpty(stream)) {
-			stream ="*";
-		}
-		String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream;
-		List<Object> scanResult = RedisUtil.scan(redisTemplate, key);
-		if (scanResult.size() == 0) {
-			return null;
-		}
-		List<SsrcTransaction> result = new ArrayList<>();
-		for (Object keyObj : scanResult) {
-			result.add((SsrcTransaction)redisTemplate.opsForValue().get(keyObj));
-		}
-		return result;
-	}
-
-	public String getMediaServerId(String deviceId, String channelId, String stream){
-		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
-		if (ssrcTransaction == null) {
-			return null;
-		}
-		return ssrcTransaction.getMediaServerId();
-	}
-
-	public String getSSRC(String deviceId, String channelId, String stream){
-		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
-		if (ssrcTransaction == null) {
-			return null;
-		}
-		return ssrcTransaction.getSsrc();
-	}
-	
-	public void remove(String deviceId, String channelId, String stream) {
-		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
-		if (ssrcTransaction == null) {
-			return;
-		}
-		redisTemplate.delete(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_"
-				+  deviceId + "_" + channelId + "_" + ssrcTransaction.getCallId() + "_" + ssrcTransaction.getStream());
-	}
-
-
-	public List<SsrcTransaction> getAllSsrc() {
-		List<Object> ssrcTransactionKeys = RedisUtil.scan(redisTemplate, String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId()));
-		List<SsrcTransaction> result= new ArrayList<>();
-		for (Object ssrcTransactionKey : ssrcTransactionKeys) {
-			String key = (String) ssrcTransactionKey;
-			SsrcTransaction ssrcTransaction = JsonUtil.redisJsonToObject(redisTemplate, key, SsrcTransaction.class);
-			result.add(ssrcTransaction);
-		}
-		return result;
-	}
-}
+package com.genersoft.iot.vmp.gb28181.session;
+
+import com.genersoft.iot.vmp.common.InviteSessionType;
+import com.genersoft.iot.vmp.common.VideoManagerConstants;
+import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
+import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
+import com.genersoft.iot.vmp.utils.JsonUtil;
+import com.genersoft.iot.vmp.utils.redis.RedisUtil;
+import gov.nist.javax.sip.message.SIPResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 视频流session管理器,管理视频预览、预览回放的通信句柄
+ */
+@Component
+public class VideoStreamSessionManager {
+
+	@Autowired
+	private UserSetting userSetting;
+
+	@Autowired
+	private RedisTemplate<Object, Object> redisTemplate;
+
+	/**
+	 * 添加一个点播/回放的事务信息
+	 * 后续可以通过流Id/callID
+	 * @param deviceId 设备ID
+	 * @param channelId 通道ID
+	 * @param callId 一次请求的CallID
+	 * @param stream 流名称
+	 * @param mediaServerId 所使用的流媒体ID
+	 * @param response 回复
+	 */
+	public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, SIPResponse response, InviteSessionType type){
+		SsrcTransaction ssrcTransaction = new SsrcTransaction();
+		ssrcTransaction.setDeviceId(deviceId);
+		ssrcTransaction.setChannelId(channelId);
+		ssrcTransaction.setStream(stream);
+		ssrcTransaction.setSipTransactionInfo(new SipTransactionInfo(response));
+		ssrcTransaction.setCallId(callId);
+		ssrcTransaction.setSsrc(ssrc);
+		ssrcTransaction.setMediaServerId(mediaServerId);
+		ssrcTransaction.setType(type);
+
+		redisTemplate.opsForValue().set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId()
+				+ "_" +  deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction);
+	}
+
+	public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){
+
+		if (ObjectUtils.isEmpty(deviceId)) {
+			deviceId ="*";
+		}
+		if (ObjectUtils.isEmpty(channelId)) {
+			channelId ="*";
+		}
+		if (ObjectUtils.isEmpty(callId)) {
+			callId ="*";
+		}
+		if (ObjectUtils.isEmpty(stream)) {
+			stream ="*";
+		}
+		String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream;
+		List<Object> scanResult = RedisUtil.scan(redisTemplate, key);
+		if (scanResult.size() == 0) {
+			return null;
+		}
+		return (SsrcTransaction)redisTemplate.opsForValue().get(scanResult.get(0));
+	}
+
+	public List<SsrcTransaction> getSsrcTransactionForAll(String deviceId, String channelId, String callId, String stream){
+		if (ObjectUtils.isEmpty(deviceId)) {
+			deviceId ="*";
+		}
+		if (ObjectUtils.isEmpty(channelId)) {
+			channelId ="*";
+		}
+		if (ObjectUtils.isEmpty(callId)) {
+			callId ="*";
+		}
+		if (ObjectUtils.isEmpty(stream)) {
+			stream ="*";
+		}
+		String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream;
+		List<Object> scanResult = RedisUtil.scan(redisTemplate, key);
+		if (scanResult.size() == 0) {
+			return null;
+		}
+		List<SsrcTransaction> result = new ArrayList<>();
+		for (Object keyObj : scanResult) {
+			result.add((SsrcTransaction)redisTemplate.opsForValue().get(keyObj));
+		}
+		return result;
+	}
+
+	public String getMediaServerId(String deviceId, String channelId, String stream){
+		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
+		if (ssrcTransaction == null) {
+			return null;
+		}
+		return ssrcTransaction.getMediaServerId();
+	}
+
+	public String getSSRC(String deviceId, String channelId, String stream){
+		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
+		if (ssrcTransaction == null) {
+			return null;
+		}
+		return ssrcTransaction.getSsrc();
+	}
+	
+	public void remove(String deviceId, String channelId, String stream) {
+		SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream);
+		if (ssrcTransaction == null) {
+			return;
+		}
+		redisTemplate.delete(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_"
+				+  deviceId + "_" + channelId + "_" + ssrcTransaction.getCallId() + "_" + ssrcTransaction.getStream());
+	}
+
+
+	public List<SsrcTransaction> getAllSsrc() {
+		List<Object> ssrcTransactionKeys = RedisUtil.scan(redisTemplate, String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId()));
+		List<SsrcTransaction> result= new ArrayList<>();
+		for (Object ssrcTransactionKey : ssrcTransactionKeys) {
+			String key = (String) ssrcTransactionKey;
+			SsrcTransaction ssrcTransaction = JsonUtil.redisJsonToObject(redisTemplate, key, SsrcTransaction.class);
+			result.add(ssrcTransaction);
+		}
+		return result;
+	}
+}

+ 160 - 160
src/main/java/com/genersoft/iot/vmp/gb28181/task/ISubscribeTask.java

@@ -1,160 +1,160 @@
-package com.genersoft.iot.vmp.gb28181.transmit.callback;
-
-import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx;
-import org.springframework.stereotype.Component;
-import org.springframework.util.ObjectUtils;
-import org.springframework.web.context.request.async.DeferredResult;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**    
- * @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_PLAYBACK";
-
-	public static final String CALLBACK_CMD_DOWNLOAD = "CALLBACK_DOWNLOAD";
-
-	public static final String CALLBACK_CMD_PROXY = "CALLBACK_PROXY";
-
-	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_MOBILE_POSITION = "CALLBACK_CMD_MOBILE_POSITION";
-
-	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";
-
-	public static final String CALLBACK_CMD_SNAP= "CALLBACK_SNAP";
-
-	private Map<String, Map<String, DeferredResultEx>> map = new ConcurrentHashMap<>();
-
-
-	public void put(String key, String id, DeferredResultEx result) {
-		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
-		if (deferredResultMap == null) {
-			deferredResultMap = new ConcurrentHashMap<>();
-			map.put(key, deferredResultMap);
-		}
-		deferredResultMap.put(id, result);
-	}
-
-	public void put(String key, String id, DeferredResult result) {
-		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
-		if (deferredResultMap == null) {
-			deferredResultMap = new ConcurrentHashMap<>();
-			map.put(key, deferredResultMap);
-		}
-		deferredResultMap.put(id, new DeferredResultEx(result));
-	}
-	
-	public DeferredResultEx get(String key, String id) {
-		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
-		if (deferredResultMap == null || ObjectUtils.isEmpty(id)) {
-			return null;
-		}
-		return deferredResultMap.get(id);
-	}
-
-	public Collection<DeferredResultEx> getAllByKey(String key) {
-		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
-		if (deferredResultMap == null) {
-			return null;
-		}
-		return deferredResultMap.values();
-	}
-
-	public boolean exist(String key, String id){
-		if (key == null) {
-			return false;
-		}
-		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
-		if (id == null) {
-			return deferredResultMap != null;
-		}else {
-			return deferredResultMap != null && deferredResultMap.get(id) != null;
-		}
-	}
-
-	/**
-	 * 释放单个请求
-	 * @param msg
-	 */
-	public void invokeResult(RequestMessage msg) {
-		Map<String, DeferredResultEx> deferredResultMap = map.get(msg.getKey());
-		if (deferredResultMap == null) {
-			return;
-		}
-		DeferredResultEx result = deferredResultMap.get(msg.getId());
-		if (result == null) {
-			return;
-		}
-		result.getDeferredResult().setResult(msg.getData());
-		deferredResultMap.remove(msg.getId());
-		if (deferredResultMap.size() == 0) {
-			map.remove(msg.getKey());
-		}
-	}
-
-	/**
-	 * 释放所有的请求
-	 * @param msg
-	 */
-	public void invokeAllResult(RequestMessage msg) {
-		Map<String, DeferredResultEx> deferredResultMap = map.get(msg.getKey());
-		if (deferredResultMap == null) {
-			return;
-		}
-		synchronized (this) {
-			deferredResultMap = map.get(msg.getKey());
-			if (deferredResultMap == null) {
-				return;
-			}
-			Set<String> ids = deferredResultMap.keySet();
-			for (String id : ids) {
-				DeferredResultEx result = deferredResultMap.get(id);
-				if (result == null) {
-					return;
-				}
-				if (result.getFilter() != null) {
-					Object handler = result.getFilter().handler(msg.getData());
-					result.getDeferredResult().setResult(handler);
-				}else {
-					result.getDeferredResult().setResult(msg.getData());
-				}
-
-			}
-			map.remove(msg.getKey());
-		}
-	}
-
-
-}
+package com.genersoft.iot.vmp.gb28181.transmit.callback;
+
+import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**    
+ * @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_PLAYBACK";
+
+	public static final String CALLBACK_CMD_DOWNLOAD = "CALLBACK_DOWNLOAD";
+
+	public static final String CALLBACK_CMD_PROXY = "CALLBACK_PROXY";
+
+	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_MOBILE_POSITION = "CALLBACK_CMD_MOBILE_POSITION";
+
+	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";
+
+	public static final String CALLBACK_CMD_SNAP= "CALLBACK_SNAP";
+
+	private Map<String, Map<String, DeferredResultEx>> map = new ConcurrentHashMap<>();
+
+
+	public void put(String key, String id, DeferredResultEx result) {
+		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
+		if (deferredResultMap == null) {
+			deferredResultMap = new ConcurrentHashMap<>();
+			map.put(key, deferredResultMap);
+		}
+		deferredResultMap.put(id, result);
+	}
+
+	public void put(String key, String id, DeferredResult result) {
+		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
+		if (deferredResultMap == null) {
+			deferredResultMap = new ConcurrentHashMap<>();
+			map.put(key, deferredResultMap);
+		}
+		deferredResultMap.put(id, new DeferredResultEx(result));
+	}
+	
+	public DeferredResultEx get(String key, String id) {
+		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
+		if (deferredResultMap == null || ObjectUtils.isEmpty(id)) {
+			return null;
+		}
+		return deferredResultMap.get(id);
+	}
+
+	public Collection<DeferredResultEx> getAllByKey(String key) {
+		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
+		if (deferredResultMap == null) {
+			return null;
+		}
+		return deferredResultMap.values();
+	}
+
+	public boolean exist(String key, String id){
+		if (key == null) {
+			return false;
+		}
+		Map<String, DeferredResultEx> deferredResultMap = map.get(key);
+		if (id == null) {
+			return deferredResultMap != null;
+		}else {
+			return deferredResultMap != null && deferredResultMap.get(id) != null;
+		}
+	}
+
+	/**
+	 * 释放单个请求
+	 * @param msg
+	 */
+	public void invokeResult(RequestMessage msg) {
+		Map<String, DeferredResultEx> deferredResultMap = map.get(msg.getKey());
+		if (deferredResultMap == null) {
+			return;
+		}
+		DeferredResultEx result = deferredResultMap.get(msg.getId());
+		if (result == null) {
+			return;
+		}
+		result.getDeferredResult().setResult(msg.getData());
+		deferredResultMap.remove(msg.getId());
+		if (deferredResultMap.size() == 0) {
+			map.remove(msg.getKey());
+		}
+	}
+
+	/**
+	 * 释放所有的请求
+	 * @param msg
+	 */
+	public void invokeAllResult(RequestMessage msg) {
+		Map<String, DeferredResultEx> deferredResultMap = map.get(msg.getKey());
+		if (deferredResultMap == null) {
+			return;
+		}
+		synchronized (this) {
+			deferredResultMap = map.get(msg.getKey());
+			if (deferredResultMap == null) {
+				return;
+			}
+			Set<String> ids = deferredResultMap.keySet();
+			for (String id : ids) {
+				DeferredResultEx result = deferredResultMap.get(id);
+				if (result == null) {
+					return;
+				}
+				if (result.getFilter() != null) {
+					Object handler = result.getFilter().handler(msg.getData());
+					result.getDeferredResult().setResult(handler);
+				}else {
+					result.getDeferredResult().setResult(msg.getData());
+				}
+
+			}
+			map.remove(msg.getKey());
+		}
+	}
+
+
+}

+ 39 - 39
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java

@@ -1,39 +1,39 @@
-package com.genersoft.iot.vmp.gb28181.transmit.callback;
-
-/**    
- * @description: 请求信息定义   
- * @author: swwheihei
- * @date:   2020年5月8日 下午1:09:18     
- */
-public class RequestMessage {
-	
-	private String id;
-
-	private String key;
-
-	private Object data;
-
-	public String getId() {
-		return id;
-	}
-
-	public void setId(String id) {
-		this.id = id;
-	}
-
-	public void setKey(String key) {
-		this.key = key;
-	}
-
-	public String getKey() {
-		return key;
-	}
-
-	public Object getData() {
-		return data;
-	}
-
-	public void setData(Object data) {
-		this.data = data;
-	}
-}
+package com.genersoft.iot.vmp.gb28181.transmit.callback;
+
+/**    
+ * @description: 请求信息定义   
+ * @author: swwheihei
+ * @date:   2020年5月8日 下午1:09:18     
+ */
+public class RequestMessage {
+	
+	private String id;
+
+	private String key;
+
+	private Object data;
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public void setKey(String key) {
+		this.key = key;
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+	public Object getData() {
+		return data;
+	}
+
+	public void setData(Object data) {
+		this.data = data;
+	}
+}

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

@@ -1,364 +1,364 @@
-package com.genersoft.iot.vmp.gb28181.transmit.cmd;
-
-import com.genersoft.iot.vmp.common.StreamInfo;
-import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
-import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
-import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
-import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
-import com.genersoft.iot.vmp.service.bean.SSRCInfo;
-import gov.nist.javax.sip.message.SIPRequest;
-
-import javax.sip.InvalidArgumentException;
-import javax.sip.SipException;
-import java.text.ParseException;
-
-/**    
- * @description:设备能力接口,用于定义设备的控制、查询能力   
- * @author: swwheihei
- * @date:   2020年5月3日 下午9:16:34     
- */
-public interface ISIPCommander {
-
-	/**
-	 * 云台方向放控制,使用配置文件中的默认镜头移动速度
-	 * 
-	 * @param device  控制设备
-	 * @param channelId  预览通道
-	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
-     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
-	 */
-	void ptzdirectCmd(Device device,String channelId,int leftRight, int upDown) throws InvalidArgumentException, ParseException, SipException;
-	
-	/**
-	 * 云台方向放控制
-	 * 
-	 * @param device  控制设备
-	 * @param channelId  预览通道
-	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
-     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
-     * @param moveSpeed  镜头移动速度
-	 */
-	void ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed) throws InvalidArgumentException, ParseException, SipException;
-	
-	/**
-	 * 云台缩放控制,使用配置文件中的默认镜头缩放速度
-	 * 
-	 * @param device  控制设备
-	 * @param channelId  预览通道
-     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
-	 */
-	void ptzZoomCmd(Device device,String channelId,int inOut) throws InvalidArgumentException, ParseException, SipException;
-	
-	/**
-	 * 云台缩放控制
-	 * 
-	 * @param device  控制设备
-	 * @param channelId  预览通道
-     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
-	 */
-	void ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed) throws InvalidArgumentException, ParseException, SipException;
-	
-	/**
-	 * 云台控制,支持方向与缩放控制
-	 * 
-	 * @param device  控制设备
-	 * @param channelId  预览通道
-	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
-     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
-     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
-     * @param moveSpeed  镜头移动速度
-     * @param zoomSpeed  镜头缩放速度
-	 */
-	void ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) throws InvalidArgumentException, SipException, ParseException;
-	
-	/**
-	 * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令
-	 * 
-	 * @param device  		控制设备
-	 * @param channelId		预览通道
-	 * @param cmdCode		指令码
-     * @param parameter1	数据1
-     * @param parameter2	数据2
-     * @param combineCode2	组合码2
-	 */
-	void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException;
-	
-	/**
-	 * 前端控制指令(用于转发上级指令)
-	 * @param device		控制设备
-	 * @param channelId		预览通道
-	 * @param cmdString		前端控制指令串
-	 */
-	void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 请求预览视频流
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 */
-	void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 请求回放视频流
-	 * 
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 */
-	void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 请求历史媒体下载
-	 * 
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 * @param downloadSpeed 下载倍速参数
-	 */ 
-	void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-						   String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent,
-						   SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 视频流停止
-	 */
-	void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
-
-	void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException;
-
-	/**
-	 * 回放暂停
-	 */
-	void playPauseCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException;
-
-	/**
-	 * 回放恢复
-	 */
-	void playResumeCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException;
-
-	/**
-	 * 回放拖动播放
-	 */
-	void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) throws InvalidArgumentException, ParseException, SipException;
-
-	/**
-	 * 回放倍速播放
-	 */
-	void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException;
-	
-	/**
-	 * 回放控制
-	 * @param device
-	 * @param streamInfo
-	 * @param content
-	 */
-	void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
-
-
-    /**
-	 * 语音广播
-	 * 
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 */
-	void audioBroadcastCmd(Device device,String channelId);
-	
-	/**
-	 * 语音广播
-	 * 
-	 * @param device  视频设备
-	 */
-	void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
-	void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
-	
-	/**
-	 * 音视频录像控制
-	 * 
-	 * @param device  		视频设备
-	 * @param channelId  	预览通道
-	 * @param recordCmdStr	录像命令:Record / StopRecord
-	 */
-	void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
-	
-	/**
-	 * 远程启动控制命令
-	 * 
-	 * @param device	视频设备
-	 */
-	void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 报警布防/撤防命令
-	 * 
-	 * @param device  	视频设备
-	 */
-	void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
-	
-	/**
-	 * 报警复位命令
-	 * 
-	 * @param device		视频设备
-	 * @param alarmMethod	报警方式(可选)
-	 * @param alarmType		报警类型(可选)
-	 */
-	void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
-	
-	/**
-	 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
-	 * 
-	 * @param device  视频设备
-	 * @param channelId  预览通道
-	 */
-	void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 看守位控制命令
-	 *
-	 * @param device      视频设备
-	 * @param channelId      通道id,非通道则是设备本身
-	 * @param enabled     看守位使能:1 = 开启,0 = 关闭
-	 * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
-	 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
-	 */
-	void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 设备配置命令
-	 * 
-	 * @param device  视频设备
-	 */
-	void deviceConfigCmd(Device device);
-	
-	/**
-	 * 设备配置命令:basicParam
-	 * 
-	 * @param device  			视频设备
-	 * @param channelId			通道编码(可选)
-	 * @param name				设备/通道名称(可选)
-	 * @param expiration		注册过期时间(可选)
-	 * @param heartBeatInterval	心跳间隔时间(可选)
-	 * @param heartBeatCount	心跳超时次数(可选)
-	 */  
-	void deviceBasicConfigCmd(Device device, String channelId, String name, String expiration, String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 查询设备状态
-	 * 
-	 * @param device 视频设备
-	 */
-	void deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
-	/**
-	 * 查询设备信息
-	 * 
-	 * @param device 视频设备
-	 * @return 
-	 */
-	void deviceInfoQuery(Device device) throws InvalidArgumentException, SipException, ParseException;
-	
-	/**
-	 * 查询目录列表
-	 * 
-	 * @param device 视频设备
-	 */
-	void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException;
-	
-	/**
-	 * 查询录像信息
-	 * 
-	 * @param device 视频设备
-	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
-	 * @param sn
-	 */
-	void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn,  Integer Secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
-	/**
-	 * 查询报警信息
-	 * 
-	 * @param device		视频设备
-	 * @param startPriority	报警起始级别(可选)
-	 * @param endPriority	报警终止级别(可选)
-	 * @param alarmMethod	报警方式条件(可选)
-	 * @param alarmType		报警类型
-	 * @param startTime		报警发生起始时间(可选)
-	 * @param endTime		报警发生终止时间(可选)
-	 * @return				true = 命令发送成功
-	 */
-	void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod,
-							String alarmType, String startTime, String endTime, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
-	/**
-	 * 查询设备配置
-	 * 
-	 * @param device 		视频设备
-	 * @param channelId		通道编码(可选)
-	 * @param configType	配置类型:
-	 */
-	void deviceConfigQuery(Device device, String channelId, String configType,  SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
-	/**
-	 * 查询设备预置位置
-	 * 
-	 * @param device 视频设备
-	 */
-	void presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-	
-	/**
-	 * 查询移动设备位置数据
-	 * 
-	 * @param device 视频设备
-	 */
-	void mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 订阅、取消订阅移动位置
-	 * 
-	 * @param device	视频设备
-	 * @return			true = 命令发送成功
-	 */
-	SIPRequest mobilePositionSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 订阅、取消订阅报警信息
-	 * @param device		视频设备
-	 * @param expires		订阅过期时间(0 = 取消订阅)
-	 * @param startPriority	报警起始级别(可选)
-	 * @param endPriority	报警终止级别(可选)
-	 * @param alarmType		报警类型
-	 * @param startTime		报警发生起始时间(可选)
-	 * @param endTime		报警发生终止时间(可选)
-	 * @return				true = 命令发送成功
-	 */
-	void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 订阅、取消订阅目录信息
-	 * @param device		视频设备
-	 * @return				true = 命令发送成功
-	 */
-	SIPRequest catalogSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
-
-	/**
-	 * 拉框控制命令
-	 *
-	 * @param device    控制设备
-	 * @param channelId 通道id
-	 * @param cmdString 前端控制指令串
-	 */
-	void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException;
-
-
-	/**
-	 * 向设备发送报警NOTIFY消息, 用于互联结构下,此时将设备当成一个平级平台看待
-	 * @param device 设备
-	 * @param deviceAlarm 报警信息信息
-	 * @return
-	 */
-	void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException;
-
-}
+package com.genersoft.iot.vmp.gb28181.transmit.cmd;
+
+import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
+import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.bean.SSRCInfo;
+import gov.nist.javax.sip.message.SIPRequest;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
+
+/**    
+ * @description:设备能力接口,用于定义设备的控制、查询能力   
+ * @author: swwheihei
+ * @date:   2020年5月3日 下午9:16:34     
+ */
+public interface ISIPCommander {
+
+	/**
+	 * 云台方向放控制,使用配置文件中的默认镜头移动速度
+	 * 
+	 * @param device  控制设备
+	 * @param channelId  预览通道
+	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
+	 */
+	void ptzdirectCmd(Device device,String channelId,int leftRight, int upDown) throws InvalidArgumentException, ParseException, SipException;
+	
+	/**
+	 * 云台方向放控制
+	 * 
+	 * @param device  控制设备
+	 * @param channelId  预览通道
+	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
+     * @param moveSpeed  镜头移动速度
+	 */
+	void ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed) throws InvalidArgumentException, ParseException, SipException;
+	
+	/**
+	 * 云台缩放控制,使用配置文件中的默认镜头缩放速度
+	 * 
+	 * @param device  控制设备
+	 * @param channelId  预览通道
+     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
+	 */
+	void ptzZoomCmd(Device device,String channelId,int inOut) throws InvalidArgumentException, ParseException, SipException;
+	
+	/**
+	 * 云台缩放控制
+	 * 
+	 * @param device  控制设备
+	 * @param channelId  预览通道
+     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
+	 */
+	void ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed) throws InvalidArgumentException, ParseException, SipException;
+	
+	/**
+	 * 云台控制,支持方向与缩放控制
+	 * 
+	 * @param device  控制设备
+	 * @param channelId  预览通道
+	 * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
+     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
+     * @param moveSpeed  镜头移动速度
+     * @param zoomSpeed  镜头缩放速度
+	 */
+	void ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) throws InvalidArgumentException, SipException, ParseException;
+	
+	/**
+	 * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令
+	 * 
+	 * @param device  		控制设备
+	 * @param channelId		预览通道
+	 * @param cmdCode		指令码
+     * @param parameter1	数据1
+     * @param parameter2	数据2
+     * @param combineCode2	组合码2
+	 */
+	void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException;
+	
+	/**
+	 * 前端控制指令(用于转发上级指令)
+	 * @param device		控制设备
+	 * @param channelId		预览通道
+	 * @param cmdString		前端控制指令串
+	 */
+	void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 请求预览视频流
+	 * @param device  视频设备
+	 * @param channelId  预览通道
+	 */
+	void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 请求回放视频流
+	 * 
+	 * @param device  视频设备
+	 * @param channelId  预览通道
+	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
+	 */
+	void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 请求历史媒体下载
+	 * 
+	 * @param device  视频设备
+	 * @param channelId  预览通道
+	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
+	 * @param downloadSpeed 下载倍速参数
+	 */ 
+	void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
+						   String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent,
+						   SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 视频流停止
+	 */
+	void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
+
+	void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException;
+
+	/**
+	 * 回放暂停
+	 */
+	void playPauseCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException;
+
+	/**
+	 * 回放恢复
+	 */
+	void playResumeCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException;
+
+	/**
+	 * 回放拖动播放
+	 */
+	void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) throws InvalidArgumentException, ParseException, SipException;
+
+	/**
+	 * 回放倍速播放
+	 */
+	void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException;
+	
+	/**
+	 * 回放控制
+	 * @param device
+	 * @param streamInfo
+	 * @param content
+	 */
+	void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
+
+
+    /**
+	 * 语音广播
+	 * 
+	 * @param device  视频设备
+	 * @param channelId  预览通道
+	 */
+	void audioBroadcastCmd(Device device,String channelId);
+	
+	/**
+	 * 语音广播
+	 * 
+	 * @param device  视频设备
+	 */
+	void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+	void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
+	
+	/**
+	 * 音视频录像控制
+	 * 
+	 * @param device  		视频设备
+	 * @param channelId  	预览通道
+	 * @param recordCmdStr	录像命令:Record / StopRecord
+	 */
+	void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+	
+	/**
+	 * 远程启动控制命令
+	 * 
+	 * @param device	视频设备
+	 */
+	void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 报警布防/撤防命令
+	 * 
+	 * @param device  	视频设备
+	 */
+	void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+	
+	/**
+	 * 报警复位命令
+	 * 
+	 * @param device		视频设备
+	 * @param alarmMethod	报警方式(可选)
+	 * @param alarmType		报警类型(可选)
+	 */
+	void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+	
+	/**
+	 * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
+	 * 
+	 * @param device  视频设备
+	 * @param channelId  预览通道
+	 */
+	void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 看守位控制命令
+	 *
+	 * @param device      视频设备
+	 * @param channelId      通道id,非通道则是设备本身
+	 * @param enabled     看守位使能:1 = 开启,0 = 关闭
+	 * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
+	 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
+	 */
+	void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 设备配置命令
+	 * 
+	 * @param device  视频设备
+	 */
+	void deviceConfigCmd(Device device);
+	
+	/**
+	 * 设备配置命令:basicParam
+	 * 
+	 * @param device  			视频设备
+	 * @param channelId			通道编码(可选)
+	 * @param name				设备/通道名称(可选)
+	 * @param expiration		注册过期时间(可选)
+	 * @param heartBeatInterval	心跳间隔时间(可选)
+	 * @param heartBeatCount	心跳超时次数(可选)
+	 */  
+	void deviceBasicConfigCmd(Device device, String channelId, String name, String expiration, String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 查询设备状态
+	 * 
+	 * @param device 视频设备
+	 */
+	void deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	
+	/**
+	 * 查询设备信息
+	 * 
+	 * @param device 视频设备
+	 * @return 
+	 */
+	void deviceInfoQuery(Device device) throws InvalidArgumentException, SipException, ParseException;
+	
+	/**
+	 * 查询目录列表
+	 * 
+	 * @param device 视频设备
+	 */
+	void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException;
+	
+	/**
+	 * 查询录像信息
+	 * 
+	 * @param device 视频设备
+	 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+	 * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
+	 * @param sn
+	 */
+	void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn,  Integer Secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	
+	/**
+	 * 查询报警信息
+	 * 
+	 * @param device		视频设备
+	 * @param startPriority	报警起始级别(可选)
+	 * @param endPriority	报警终止级别(可选)
+	 * @param alarmMethod	报警方式条件(可选)
+	 * @param alarmType		报警类型
+	 * @param startTime		报警发生起始时间(可选)
+	 * @param endTime		报警发生终止时间(可选)
+	 * @return				true = 命令发送成功
+	 */
+	void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod,
+							String alarmType, String startTime, String endTime, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	
+	/**
+	 * 查询设备配置
+	 * 
+	 * @param device 		视频设备
+	 * @param channelId		通道编码(可选)
+	 * @param configType	配置类型:
+	 */
+	void deviceConfigQuery(Device device, String channelId, String configType,  SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	
+	/**
+	 * 查询设备预置位置
+	 * 
+	 * @param device 视频设备
+	 */
+	void presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+	
+	/**
+	 * 查询移动设备位置数据
+	 * 
+	 * @param device 视频设备
+	 */
+	void mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 订阅、取消订阅移动位置
+	 * 
+	 * @param device	视频设备
+	 * @return			true = 命令发送成功
+	 */
+	SIPRequest mobilePositionSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 订阅、取消订阅报警信息
+	 * @param device		视频设备
+	 * @param expires		订阅过期时间(0 = 取消订阅)
+	 * @param startPriority	报警起始级别(可选)
+	 * @param endPriority	报警终止级别(可选)
+	 * @param alarmType		报警类型
+	 * @param startTime		报警发生起始时间(可选)
+	 * @param endTime		报警发生终止时间(可选)
+	 * @return				true = 命令发送成功
+	 */
+	void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 订阅、取消订阅目录信息
+	 * @param device		视频设备
+	 * @return				true = 命令发送成功
+	 */
+	SIPRequest catalogSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+
+	/**
+	 * 拉框控制命令
+	 *
+	 * @param device    控制设备
+	 * @param channelId 通道id
+	 * @param cmdString 前端控制指令串
+	 */
+	void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException;
+
+
+	/**
+	 * 向设备发送报警NOTIFY消息, 用于互联结构下,此时将设备当成一个平级平台看待
+	 * @param device 设备
+	 * @param deviceAlarm 报警信息信息
+	 * @return
+	 */
+	void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException;
+
+}

+ 318 - 318
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java

@@ -1,318 +1,318 @@
-package com.genersoft.iot.vmp.gb28181.transmit.cmd;
-
-import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.gb28181.SipLayer;
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
-import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
-import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.utils.GitUtil;
-import gov.nist.javax.sip.message.SIPRequest;
-import gov.nist.javax.sip.message.SIPResponse;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import javax.sip.InvalidArgumentException;
-import javax.sip.PeerUnavailableException;
-import javax.sip.SipException;
-import javax.sip.SipFactory;
-import javax.sip.address.Address;
-import javax.sip.address.SipURI;
-import javax.sip.header.*;
-import javax.sip.message.Request;
-import java.text.ParseException;
-import java.util.ArrayList;
-
-/**
- * @description:摄像头命令request创造器 TODO 冗余代码太多待优化
- * @author: swwheihei
- * @date: 2020年5月6日 上午9:29:02
- */
-@Component
-public class SIPRequestHeaderProvider {
-
-	@Autowired
-	private SipConfig sipConfig;
-	
-	@Autowired
-	private SipLayer sipLayer;
-
-	@Autowired
-	private GitUtil gitUtil;
-
-	@Autowired
-	private IRedisCatchStorage redisCatchStorage;
-
-	@Autowired
-	private VideoStreamSessionManager streamSession;
-	
-	public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		// sipuri
-		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-		// from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag);
-		// to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag);
-
-		// Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-		// ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
-
-		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
-				toHeader, viaHeaders, maxForwards);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
-		request.setContent(content, contentTypeHeader);
-		return request;
-	}
-	
-	public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		//请求行
-		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		//via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		HeaderFactory headerFactory = SipFactory.getInstance().createHeaderFactory();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-
-		//from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
-		//to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
-		
-		//Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-		
-		//ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
-		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		// Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-		// Subject
-		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
-		request.addHeader(subjectHeader);
-		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
-		request.setContent(content, contentTypeHeader);
-		return request;
-	}
-	
-	public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		//请求行
-		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-		//from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
-		//to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
-		
-		//Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-		
-		//ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
-		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
-		
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		// Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		// Subject
-		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
-		request.addHeader(subjectHeader);
-
-		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
-		request.setContent(content, contentTypeHeader);
-		return request;
-	}
-
-	public Request createByteRequest(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		//请求行
-		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
-		viaHeaders.add(viaHeader);
-		//from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
-		//to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
-
-		//Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-
-		//ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
-		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
-		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		return request;
-	}
-
-	public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-		Request request = null;
-		// sipuri
-		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(),
-				device.getTransport(), SipUtils.getNewViaTag());
-		viaHeader.setRPort();
-		viaHeaders.add(viaHeader);
-		// from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag());
-		// to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, requestOld == null ? null :requestOld.getToTag());
-
-		// Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-
-		// ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.SUBSCRIBE);
-
-		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader,
-				toHeader, viaHeaders, maxForwards);
-
-
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-
-		// Expires
-		ExpiresHeader expireHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(expires);
-		request.addHeader(expireHeader);
-
-		// Event
-		EventHeader eventHeader = SipFactory.getInstance().createHeaderFactory().createEventHeader(event);
-
-		int random = (int) Math.floor(Math.random() * 10000);
-		eventHeader.setEventId(random + "");
-		request.addHeader(eventHeader);
-
-		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
-		request.setContent(content, contentTypeHeader);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		return request;
-	}
-
-	public SIPRequest createInfoRequest(Device device, String channelId, String content, SipTransactionInfo transactionInfo)
-			throws SipException, ParseException, InvalidArgumentException {
-		if (device == null || transactionInfo == null) {
-			return null;
-		}
-		SIPRequest request = null;
-		//请求行
-		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
-		viaHeaders.add(viaHeader);
-		//from
-		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
-		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
-		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
-		//to
-		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
-		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
-		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
-
-		//Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-
-		//ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INFO);
-		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
-		request = (SIPRequest)SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		if (content != null) {
-			ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application",
-					"MANSRTSP");
-			request.setContent(content, contentTypeHeader);
-		}
-		return request;
-	}
-
-	public Request createAckRequest(String localIp, SipURI sipURI, SIPResponse sipResponse) throws ParseException, InvalidArgumentException, PeerUnavailableException {
-
-
-		// via
-		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(localIp, sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag());
-		viaHeaders.add(viaHeader);
-
-		//Forwards
-		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
-
-		//ceq
-		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK);
-
-		Request request = SipFactory.getInstance().createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards);
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), localIp + ":"+sipConfig.getPort()));
-		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
-
-		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
-
-		return request;
-	}
-}
+package com.genersoft.iot.vmp.gb28181.transmit.cmd;
+
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.utils.GitUtil;
+import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.PeerUnavailableException;
+import javax.sip.SipException;
+import javax.sip.SipFactory;
+import javax.sip.address.Address;
+import javax.sip.address.SipURI;
+import javax.sip.header.*;
+import javax.sip.message.Request;
+import java.text.ParseException;
+import java.util.ArrayList;
+
+/**
+ * @description:摄像头命令request创造器 TODO 冗余代码太多待优化
+ * @author: swwheihei
+ * @date: 2020年5月6日 上午9:29:02
+ */
+@Component
+public class SIPRequestHeaderProvider {
+
+	@Autowired
+	private SipConfig sipConfig;
+	
+	@Autowired
+	private SipLayer sipLayer;
+
+	@Autowired
+	private GitUtil gitUtil;
+
+	@Autowired
+	private IRedisCatchStorage redisCatchStorage;
+
+	@Autowired
+	private VideoStreamSessionManager streamSession;
+	
+	public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		// sipuri
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+		// from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag);
+		// to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag);
+
+		// Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+		// ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
+
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
+				toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		request.setContent(content, contentTypeHeader);
+		return request;
+	}
+	
+	public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		//请求行
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		//via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		HeaderFactory headerFactory = SipFactory.getInstance().createHeaderFactory();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+
+		//from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		//to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
+		
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+		
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		// Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+		// Subject
+		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
+		request.addHeader(subjectHeader);
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
+		request.setContent(content, contentTypeHeader);
+		return request;
+	}
+	
+	public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		//请求行
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag);
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+		//from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
+		//to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
+		
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+		
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+		
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		// Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		// Subject
+		SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
+		request.addHeader(subjectHeader);
+
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
+		request.setContent(content, contentTypeHeader);
+		return request;
+	}
+
+	public Request createByteRequest(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		//请求行
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+		viaHeaders.add(viaHeader);
+		//from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
+		//to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
+
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
+		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		return request;
+	}
+
+	public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+		Request request = null;
+		// sipuri
+		SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(),
+				device.getTransport(), SipUtils.getNewViaTag());
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+		// from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag());
+		// to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, requestOld == null ? null :requestOld.getToTag());
+
+		// Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+
+		// ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.SUBSCRIBE);
+
+		request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader,
+				toHeader, viaHeaders, maxForwards);
+
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		// Expires
+		ExpiresHeader expireHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(expires);
+		request.addHeader(expireHeader);
+
+		// Event
+		EventHeader eventHeader = SipFactory.getInstance().createHeaderFactory().createEventHeader(event);
+
+		int random = (int) Math.floor(Math.random() * 10000);
+		eventHeader.setEventId(random + "");
+		request.addHeader(eventHeader);
+
+		ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
+		request.setContent(content, contentTypeHeader);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		return request;
+	}
+
+	public SIPRequest createInfoRequest(Device device, String channelId, String content, SipTransactionInfo transactionInfo)
+			throws SipException, ParseException, InvalidArgumentException {
+		if (device == null || transactionInfo == null) {
+			return null;
+		}
+		SIPRequest request = null;
+		//请求行
+		SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
+		viaHeaders.add(viaHeader);
+		//from
+		SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
+		Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag());
+		//to
+		SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
+		Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,	transactionInfo.getToTag());
+
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INFO);
+		CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
+		request = (SIPRequest)SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		if (content != null) {
+			ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application",
+					"MANSRTSP");
+			request.setContent(content, contentTypeHeader);
+		}
+		return request;
+	}
+
+	public Request createAckRequest(String localIp, SipURI sipURI, SIPResponse sipResponse) throws ParseException, InvalidArgumentException, PeerUnavailableException {
+
+
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(localIp, sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag());
+		viaHeaders.add(viaHeader);
+
+		//Forwards
+		MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK);
+
+		Request request = SipFactory.getInstance().createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), localIp + ":"+sipConfig.getPort()));
+		request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
+
+		return request;
+	}
+}

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

@@ -1,1424 +1,1424 @@
-package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
-
-import com.genersoft.iot.vmp.common.InviteSessionType;
-import com.genersoft.iot.vmp.common.StreamInfo;
-import com.genersoft.iot.vmp.conf.SipConfig;
-import com.genersoft.iot.vmp.conf.UserSetting;
-import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
-import com.genersoft.iot.vmp.gb28181.SipLayer;
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
-import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
-import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
-import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
-import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
-import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
-import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
-import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
-import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
-import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
-import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
-import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
-import com.genersoft.iot.vmp.service.IMediaServerService;
-import com.genersoft.iot.vmp.service.bean.SSRCInfo;
-import com.genersoft.iot.vmp.utils.DateUtil;
-import gov.nist.javax.sip.message.SIPRequest;
-import gov.nist.javax.sip.message.SIPResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.DependsOn;
-import org.springframework.stereotype.Component;
-import org.springframework.util.ObjectUtils;
-
-import javax.sip.InvalidArgumentException;
-import javax.sip.ResponseEvent;
-import javax.sip.SipException;
-import javax.sip.SipFactory;
-import javax.sip.header.CallIdHeader;
-import javax.sip.message.Request;
-import java.text.ParseException;
-
-/**
- * @description:设备能力接口,用于定义设备的控制、查询能力
- * @author: swwheihei
- * @date: 2020年5月3日 下午9:22:48
- */
-@Component
-@DependsOn("sipLayer")
-public class SIPCommander implements ISIPCommander {
-
-    private final Logger logger = LoggerFactory.getLogger(SIPCommander.class);
-
-    @Autowired
-    private SipConfig sipConfig;
-
-    @Autowired
-    private SipLayer sipLayer;
-
-    @Autowired
-    private SIPSender sipSender;
-    
-    @Autowired
-    private SIPRequestHeaderProvider headerProvider;
-
-    @Autowired
-    private VideoStreamSessionManager streamSession;
-
-    @Autowired
-    private UserSetting userSetting;
-
-    @Autowired
-    private ZlmHttpHookSubscribe subscribe;
-
-
-
-    @Autowired
-    private IMediaServerService mediaServerService;
-
-
-    /**
-     * 云台方向放控制,使用配置文件中的默认镜头移动速度
-     *
-     * @param device    控制设备
-     * @param channelId 预览通道
-     * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
-     * @param upDown    镜头上移下移 0:停止 1:上移 2:下移
-     */
-    @Override
-    public void ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) throws InvalidArgumentException, ParseException, SipException {
-        ptzCmd(device, channelId, leftRight, upDown, 0, sipConfig.getPtzSpeed(), 0);
-    }
-
-    /**
-     * 云台方向放控制
-     *
-     * @param device    控制设备
-     * @param channelId 预览通道
-     * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
-     * @param upDown    镜头上移下移 0:停止 1:上移 2:下移
-     * @param moveSpeed 镜头移动速度
-     */
-    @Override
-    public void ptzdirectCmd(Device device, String channelId, int leftRight, int upDown, int moveSpeed) throws InvalidArgumentException, ParseException, SipException {
-        ptzCmd(device, channelId, leftRight, upDown, 0, moveSpeed, 0);
-    }
-
-    /**
-     * 云台缩放控制,使用配置文件中的默认镜头缩放速度
-     *
-     * @param device    控制设备
-     * @param channelId 预览通道
-     * @param inOut     镜头放大缩小 0:停止 1:缩小 2:放大
-     */
-    @Override
-    public void ptzZoomCmd(Device device, String channelId, int inOut) throws InvalidArgumentException, ParseException, SipException {
-        ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getPtzSpeed());
-    }
-
-    /**
-     * 云台缩放控制
-     *
-     * @param device    控制设备
-     * @param channelId 预览通道
-     * @param inOut     镜头放大缩小 0:停止 1:缩小 2:放大
-     * @param zoomSpeed 镜头缩放速度
-     */
-    @Override
-    public void ptzZoomCmd(Device device, String channelId, int inOut, int zoomSpeed) throws InvalidArgumentException, ParseException, SipException {
-        ptzCmd(device, channelId, 0, 0, inOut, 0, zoomSpeed);
-    }
-
-    /**
-     * 云台指令码计算
-     *
-     * @param cmdCode      指令码
-     * @param parameter1   数据1
-     * @param parameter2   数据2
-     * @param combineCode2 组合码2
-     */
-    public static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) {
-        StringBuilder builder = new StringBuilder("A50F01");
-        String strTmp;
-        strTmp = String.format("%02X", cmdCode);
-        builder.append(strTmp, 0, 2);
-        strTmp = String.format("%02X", parameter1);
-        builder.append(strTmp, 0, 2);
-        strTmp = String.format("%02X", parameter2);
-        builder.append(strTmp, 0, 2);
-        //优化zoom变倍速率
-        if ((combineCode2 > 0) && (combineCode2 <16))
-        {
-            combineCode2 = 16;
-        }
-        strTmp = String.format("%X", combineCode2);
-        builder.append(strTmp, 0, 1).append("0");
-        //计算校验码
-        int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + parameter1 + parameter2 + (combineCode2 & 0XF0)) % 0X100;
-        strTmp = String.format("%02X", checkCode);
-        builder.append(strTmp, 0, 2);
-        return builder.toString();
-    }
-
-    /**
-     * 云台控制,支持方向与缩放控制
-     *
-     * @param device    控制设备
-     * @param channelId 预览通道
-     * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
-     * @param upDown    镜头上移下移 0:停止 1:上移 2:下移
-     * @param inOut     镜头放大缩小 0:停止 1:缩小 2:放大
-     * @param moveSpeed 镜头移动速度
-     * @param zoomSpeed 镜头缩放速度
-     */
-    @Override
-    public void ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
-                       int zoomSpeed) throws InvalidArgumentException, SipException, ParseException {
-        String cmdStr = SipUtils.cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
-        StringBuilder ptzXml = new StringBuilder(200);
-        String charset = device.getCharset();
-        ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        ptzXml.append("<Control>\r\n");
-        ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
-        ptzXml.append("<Info>\r\n");
-        ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
-        ptzXml.append("</Info>\r\n");
-        ptzXml.append("</Control>\r\n");
-        
-        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
-    }
-
-    /**
-     * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令
-     *
-     * @param device       控制设备
-     * @param channelId    预览通道
-     * @param cmdCode      指令码
-     * @param parameter1   数据1
-     * @param parameter2   数据2
-     * @param combineCode2 组合码2
-     */
-    @Override
-    public void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException {
-
-        String cmdStr = frontEndCmdString(cmdCode, parameter1, parameter2, combineCode2);
-        StringBuffer ptzXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        ptzXml.append("<Control>\r\n");
-        ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
-        ptzXml.append("<Info>\r\n");
-        ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
-        ptzXml.append("</Info>\r\n");
-        ptzXml.append("</Control>\r\n");
-
-
-
-
-        SIPRequest request = (SIPRequest) headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
-
-    }
-
-    /**
-     * 前端控制指令(用于转发上级指令)
-     *
-     * @param device    控制设备
-     * @param channelId 预览通道
-     * @param cmdString 前端控制指令串
-     */
-    @Override
-    public void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer ptzXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        ptzXml.append("<Control>\r\n");
-        ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        ptzXml.append("<PTZCmd>" + cmdString + "</PTZCmd>\r\n");
-        ptzXml.append("<Info>\r\n");
-        ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
-        ptzXml.append("</Info>\r\n");
-        ptzXml.append("</Control>\r\n");
-        
-        
-        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request, errorEvent, okEvent);
-
-    }
-
-    /**
-     * 请求预览视频流
-     *
-     * @param device     视频设备
-     * @param channelId  预览通道
-     * @param event      hook订阅
-     * @param errorEvent sip错误订阅
-     */
-    @Override
-    public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-                              ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-        String stream = ssrcInfo.getStream();
-
-        if (device == null) {
-            return;
-        }
-
-        logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort());
-        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
-        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, HookParam hookParam) -> {
-            if (event != null) {
-                event.response(mediaServerItemInUse, hookParam);
-                subscribe.removeSubscribe(hookSubscribe);
-            }
-        });
-        String sdpIp;
-        if (!ObjectUtils.isEmpty(device.getSdpIp())) {
-            sdpIp = device.getSdpIp();
-        }else {
-            sdpIp = mediaServerItem.getSdpIp();
-        }
-        StringBuffer content = new StringBuffer(200);
-        content.append("v=0\r\n");
-        content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
-        content.append("s=Play\r\n");
-        content.append("c=IN IP4 " + sdpIp + "\r\n");
-        content.append("t=0 0\r\n");
-
-        if (userSetting.isSeniorSdp()) {
-            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-            } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n");
-            }
-            content.append("a=recvonly\r\n");
-            content.append("a=rtpmap:96 PS/90000\r\n");
-            content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
-            content.append("a=rtpmap:126 H264/90000\r\n");
-            content.append("a=rtpmap:125 H264S/90000\r\n");
-            content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
-            content.append("a=rtpmap:99 H265/90000\r\n");
-            content.append("a=rtpmap:98 H264/90000\r\n");
-            content.append("a=rtpmap:97 MPEG4/90000\r\n");
-            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式
-                content.append("a=setup:passive\r\n");
-                content.append("a=connection:new\r\n");
-            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式
-                content.append("a=setup:active\r\n");
-                content.append("a=connection:new\r\n");
-            }
-        } else {
-            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
-            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
-            } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) {
-                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 97 98 99\r\n");
-            }
-            content.append("a=recvonly\r\n");
-            content.append("a=rtpmap:96 PS/90000\r\n");
-            content.append("a=rtpmap:98 H264/90000\r\n");
-            content.append("a=rtpmap:97 MPEG4/90000\r\n");
-            content.append("a=rtpmap:99 H265/90000\r\n");
-            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式
-                content.append("a=setup:passive\r\n");
-                content.append("a=connection:new\r\n");
-            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式
-                content.append("a=setup:active\r\n");
-                content.append("a=connection:new\r\n");
-            }
-        }
-
-        if( device.isSwitchPrimarySubStream() ){
-            if("TP-LINK".equals(device.getManufacturer())){
-                if (device.isSwitchPrimarySubStream()){
-                    content.append("a=streamMode:sub\r\n");
-                }else {
-                    content.append("a=streamMode:main\r\n");
-                }
-            }else {
-                if (device.isSwitchPrimarySubStream()){
-                    content.append("a=streamprofile:1\r\n");
-                }else {
-                    content.append("a=streamprofile:0\r\n");
-                }
-            }
-        }
-
-        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
-        // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
-//			content.append("f=v/2/5/25/1/4000a/1/8/1" + "\r\n"); // 未发现支持此特性的设备
-
-
-
-        Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> {
-            streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
-            mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
-            errorEvent.response(e);
-        }), e -> {
-            ResponseEvent responseEvent = (ResponseEvent) e.event;
-            SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response,
-                    InviteSessionType.PLAY);
-            okEvent.response(e);
-        });
-    }
-
-    /**
-     * 请求回放视频流
-     *
-     * @param device    视频设备
-     * @param channelId 预览通道
-     * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
-     * @param endTime   结束时间,格式要求:yyyy-MM-dd HH:mm:ss
-     */
-    @Override
-    public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-                                  String startTime, String endTime, ZlmHttpHookSubscribe.Event hookEvent,
-                                  SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-
-
-        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort());
-        String sdpIp;
-        if (!ObjectUtils.isEmpty(device.getSdpIp())) {
-            sdpIp = device.getSdpIp();
-        }else {
-            sdpIp = mediaServerItem.getSdpIp();
-        }
-        StringBuffer content = new StringBuffer(200);
-        content.append("v=0\r\n");
-        content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
-        content.append("s=Playback\r\n");
-        content.append("u=" + channelId + ":0\r\n");
-        content.append("c=IN IP4 " + sdpIp + "\r\n");
-        content.append("t=" + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime) + " "
-                + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) + "\r\n");
-
-        String streamMode = device.getStreamMode();
-
-        if (userSetting.isSeniorSdp()) {
-            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-            } else if ("UDP".equalsIgnoreCase(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n");
-            }
-            content.append("a=recvonly\r\n");
-            content.append("a=rtpmap:96 PS/90000\r\n");
-            content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
-            content.append("a=rtpmap:126 H264/90000\r\n");
-            content.append("a=rtpmap:125 H264S/90000\r\n");
-            content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
-            content.append("a=rtpmap:99 H265/90000\r\n");
-            content.append("a=rtpmap:98 H264/90000\r\n");
-            content.append("a=rtpmap:97 MPEG4/90000\r\n");
-            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) { // tcp被动模式
-                content.append("a=setup:passive\r\n");
-                content.append("a=connection:new\r\n");
-            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) { // tcp主动模式
-                content.append("a=setup:active\r\n");
-                content.append("a=connection:new\r\n");
-            }
-        } else {
-            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
-            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
-            } else if ("UDP".equalsIgnoreCase(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 97 98 99\r\n");
-            }
-            content.append("a=recvonly\r\n");
-            content.append("a=rtpmap:96 PS/90000\r\n");
-            content.append("a=rtpmap:97 MPEG4/90000\r\n");
-            content.append("a=rtpmap:98 H264/90000\r\n");
-            content.append("a=rtpmap:99 H265/90000\r\n");
-            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) {
-                // tcp被动模式
-                content.append("a=setup:passive\r\n");
-                content.append("a=connection:new\r\n");
-            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) {
-                // tcp主动模式
-                content.append("a=setup:active\r\n");
-                content.append("a=connection:new\r\n");
-            }
-        }
-
-        //ssrc
-        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");
-
-        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
-        // 添加订阅
-        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, HookParam hookParam) -> {
-            if (hookEvent != null) {
-                hookEvent.response(mediaServerItemInUse, hookParam);
-            }
-            subscribe.removeSubscribe(hookSubscribe);
-        });
-        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()), ssrcInfo.getSsrc());
-
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
-            ResponseEvent responseEvent = (ResponseEvent) event.event;
-            SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            streamSession.put(device.getDeviceId(), channelId,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAYBACK);
-            okEvent.response(event);
-        });
-    }
-
-    /**
-     * 请求历史媒体下载
-     *
-     * @param device        视频设备
-     * @param channelId     预览通道
-     * @param startTime     开始时间,格式要求:yyyy-MM-dd HH:mm:ss
-     * @param endTime       结束时间,格式要求:yyyy-MM-dd HH:mm:ss
-     * @param downloadSpeed 下载倍速参数
-     */
-    @Override
-    public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-                                  String startTime, String endTime, int downloadSpeed,
-                                  ZlmHttpHookSubscribe.Event hookEvent,
-                                  SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort());
-        String sdpIp;
-        if (!ObjectUtils.isEmpty(device.getSdpIp())) {
-            sdpIp = device.getSdpIp();
-        }else {
-            sdpIp = mediaServerItem.getSdpIp();
-        }
-        StringBuffer content = new StringBuffer(200);
-        content.append("v=0\r\n");
-        content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
-        content.append("s=Download\r\n");
-        content.append("u=" + channelId + ":0\r\n");
-        content.append("c=IN IP4 " + sdpIp + "\r\n");
-        content.append("t=" + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime) + " "
-                + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) + "\r\n");
-
-        String streamMode = device.getStreamMode().toUpperCase();
-
-        if (userSetting.isSeniorSdp()) {
-            if ("TCP-PASSIVE".equals(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-            } else if ("TCP-ACTIVE".equals(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
-            } else if ("UDP".equals(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n");
-            }
-            content.append("a=recvonly\r\n");
-            content.append("a=rtpmap:96 PS/90000\r\n");
-            content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
-            content.append("a=rtpmap:126 H264/90000\r\n");
-            content.append("a=rtpmap:125 H264S/90000\r\n");
-            content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
-            content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
-            content.append("a=fmtp:99 profile-level-id=3\r\n");
-            content.append("a=rtpmap:98 H264/90000\r\n");
-            content.append("a=rtpmap:97 MPEG4/90000\r\n");
-            if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式
-                content.append("a=setup:passive\r\n");
-                content.append("a=connection:new\r\n");
-            } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
-                content.append("a=setup:active\r\n");
-                content.append("a=connection:new\r\n");
-            }
-        } else {
-            if ("TCP-PASSIVE".equals(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
-            } else if ("TCP-ACTIVE".equals(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
-            } else if ("UDP".equals(streamMode)) {
-                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 97 98 99\r\n");
-            }
-            content.append("a=recvonly\r\n");
-            content.append("a=rtpmap:96 PS/90000\r\n");
-            content.append("a=rtpmap:97 MPEG4/90000\r\n");
-            content.append("a=rtpmap:98 H264/90000\r\n");
-            content.append("a=rtpmap:99 H265/90000\r\n");
-            if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式
-                content.append("a=setup:passive\r\n");
-                content.append("a=connection:new\r\n");
-            } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
-                content.append("a=setup:active\r\n");
-                content.append("a=connection:new\r\n");
-            }
-        }
-        content.append("a=downloadspeed:" + downloadSpeed + "\r\n");
-
-        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
-        logger.debug("此时请求下载信令的ssrc===>{}",ssrcInfo.getSsrc());
-        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
-        // 添加订阅
-        CallIdHeader newCallIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport());
-        String callId= newCallIdHeader.getCallId();
-        subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> {
-            logger.debug("sipc 添加订阅===callId {}",callId);
-            hookEvent.response(mediaServerItemInUse, hookParam);
-            subscribe.removeSubscribe(hookSubscribe);
-            hookSubscribe.getContent().put("regist", false);
-            hookSubscribe.getContent().put("schema", "rtsp");
-            // 添加流注销的订阅,注销了后向设备发送bye
-            subscribe.addSubscribe(hookSubscribe,
-                    (mediaServerItemForEnd, hookParam1) -> {
-                        logger.info("[录像]下载结束, 发送BYE");
-                        try {
-                            streamByeCmd(device, channelId, ssrcInfo.getStream(), callId);
-                        } catch (InvalidArgumentException | ParseException | SipException |
-                                 SsrcTransactionNotFoundException e) {
-                            logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage());
-                        }
-                    });
-        });
-
-        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,newCallIdHeader, ssrcInfo.getSsrc());
-
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
-            ResponseEvent responseEvent = (ResponseEvent) event.event;
-            SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            String contentString =new String(response.getRawContent());
-            String ssrc = SipUtils.getSsrcFromSdp(contentString);
-            streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, InviteSessionType.DOWNLOAD);
-            okEvent.response(event);
-        });
-    }
-
-    /**
-     * 视频流停止, 不使用回调
-     */
-    @Override
-    public void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException {
-        streamByeCmd(device, channelId, stream, callId, null);
-    }
-
-    /**
-     * 视频流停止
-     */
-    @Override
-    public void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
-        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callId, stream);
-        if (ssrcTransaction == null) {
-            throw new SsrcTransactionNotFoundException(device.getDeviceId(), channelId, callId, stream);
-        }
-
-        mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
-        mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
-        streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
-
-        Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
-    }
-
-    /**
-     * 语音广播
-     *
-     * @param device    视频设备
-     * @param channelId 预览通道
-     */
-    @Override
-    public void audioBroadcastCmd(Device device, String channelId) {
-    }
-
-    /**
-     * 语音广播
-     *
-     * @param device 视频设备
-     */
-    @Override
-    public void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer broadcastXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        broadcastXml.append("<Notify>\r\n");
-        broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
-        broadcastXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
-        broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
-        broadcastXml.append("</Notify>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
-
-    }
-
-    @Override
-    public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer broadcastXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        broadcastXml.append("<Notify>\r\n");
-        broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
-        broadcastXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
-        broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
-        broadcastXml.append("</Notify>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
-
-    }
-
-
-    /**
-     * 音视频录像控制
-     *
-     * @param device       视频设备
-     * @param channelId    预览通道
-     * @param recordCmdStr 录像命令:Record / StopRecord
-     */
-    @Override
-    public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Control>\r\n");
-        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        if (ObjectUtils.isEmpty(channelId)) {
-            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        } else {
-            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        }
-        cmdXml.append("<RecordCmd>" + recordCmdStr + "</RecordCmd>\r\n");
-        cmdXml.append("</Control>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
-    }
-
-    /**
-     * 远程启动控制命令
-     *
-     * @param device 视频设备
-     */
-    @Override
-    public void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Control>\r\n");
-        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        cmdXml.append("<TeleBoot>Boot</TeleBoot>\r\n");
-        cmdXml.append("</Control>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
-    }
-
-    /**
-     * 报警布防/撤防命令
-     *
-     * @param device      视频设备
-     * @param guardCmdStr "SetGuard"/"ResetGuard"
-     */
-    @Override
-    public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Control>\r\n");
-        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        cmdXml.append("<GuardCmd>" + guardCmdStr + "</GuardCmd>\r\n");
-        cmdXml.append("</Control>\r\n");
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
-    }
-
-    /**
-     * 报警复位命令
-     *
-     * @param device 视频设备
-     */
-    @Override
-    public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Control>\r\n");
-        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        cmdXml.append("<AlarmCmd>ResetAlarm</AlarmCmd>\r\n");
-        if (!ObjectUtils.isEmpty(alarmMethod) || !ObjectUtils.isEmpty(alarmType)) {
-            cmdXml.append("<Info>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(alarmMethod)) {
-            cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(alarmType)) {
-            cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(alarmMethod) || !ObjectUtils.isEmpty(alarmType)) {
-            cmdXml.append("</Info>\r\n");
-        }
-        cmdXml.append("</Control>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
-    }
-
-    /**
-     * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
-     *
-     * @param device    视频设备
-     * @param channelId 预览通道
-     */
-    @Override
-    public void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Control>\r\n");
-        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        if (ObjectUtils.isEmpty(channelId)) {
-            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        } else {
-            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        }
-        cmdXml.append("<IFameCmd>Send</IFameCmd>\r\n");
-        cmdXml.append("</Control>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
-    }
-
-    /**
-     * 看守位控制命令
-     *
-     * @param device      视频设备
-     * @param channelId      通道id,非通道则是设备本身
-     * @param enabled     看守位使能:1 = 开启,0 = 关闭
-     * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
-     * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
-     */
-    @Override
-    public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Control>\r\n");
-        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        if (ObjectUtils.isEmpty(channelId)) {
-            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        } else {
-            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        }
-        cmdXml.append("<HomePosition>\r\n");
-        if (NumericUtil.isInteger(enabled) && (!enabled.equals("0"))) {
-            cmdXml.append("<Enabled>1</Enabled>\r\n");
-            if (NumericUtil.isInteger(resetTime)) {
-                cmdXml.append("<ResetTime>" + resetTime + "</ResetTime>\r\n");
-            } else {
-                cmdXml.append("<ResetTime>0</ResetTime>\r\n");
-            }
-            if (NumericUtil.isInteger(presetIndex)) {
-                cmdXml.append("<PresetIndex>" + presetIndex + "</PresetIndex>\r\n");
-            } else {
-                cmdXml.append("<PresetIndex>0</PresetIndex>\r\n");
-            }
-        } else {
-            cmdXml.append("<Enabled>0</Enabled>\r\n");
-        }
-        cmdXml.append("</HomePosition>\r\n");
-        cmdXml.append("</Control>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
-    }
-
-    /**
-     * 设备配置命令
-     *
-     * @param device 视频设备
-     */
-    @Override
-    public void deviceConfigCmd(Device device) {
-        // TODO Auto-generated method stub
-    }
-
-    /**
-     * 设备配置命令:basicParam
-     *
-     * @param device            视频设备
-     * @param channelId         通道编码(可选)
-     * @param name              设备/通道名称(可选)
-     * @param expiration        注册过期时间(可选)
-     * @param heartBeatInterval 心跳间隔时间(可选)
-     * @param heartBeatCount    心跳超时次数(可选)
-     */
-    @Override
-    public void deviceBasicConfigCmd(Device device, String channelId, String name, String expiration,
-                                     String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Control>\r\n");
-        cmdXml.append("<CmdType>DeviceConfig</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        if (ObjectUtils.isEmpty(channelId)) {
-            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        } else {
-            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        }
-        cmdXml.append("<BasicParam>\r\n");
-        if (!ObjectUtils.isEmpty(name)) {
-            cmdXml.append("<Name>" + name + "</Name>\r\n");
-        }
-        if (NumericUtil.isInteger(expiration)) {
-            if (Integer.valueOf(expiration) > 0) {
-                cmdXml.append("<Expiration>" + expiration + "</Expiration>\r\n");
-            }
-        }
-        if (NumericUtil.isInteger(heartBeatInterval)) {
-            if (Integer.valueOf(heartBeatInterval) > 0) {
-                cmdXml.append("<HeartBeatInterval>" + heartBeatInterval + "</HeartBeatInterval>\r\n");
-            }
-        }
-        if (NumericUtil.isInteger(heartBeatCount)) {
-            if (Integer.valueOf(heartBeatCount) > 0) {
-                cmdXml.append("<HeartBeatCount>" + heartBeatCount + "</HeartBeatCount>\r\n");
-            }
-        }
-        cmdXml.append("</BasicParam>\r\n");
-        cmdXml.append("</Control>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
-    }
-
-    /**
-     * 查询设备状态
-     *
-     * @param device 视频设备
-     */
-    @Override
-    public void deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        String charset = device.getCharset();
-        StringBuffer catalogXml = new StringBuffer(200);
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        catalogXml.append("<Query>\r\n");
-        catalogXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
-        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        catalogXml.append("</Query>\r\n");
-
-        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
-    }
-
-    /**
-     * 查询设备信息
-     *
-     * @param device 视频设备
-     */
-    @Override
-    public void deviceInfoQuery(Device device) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer catalogXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        catalogXml.append("<Query>\r\n");
-        catalogXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
-        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        catalogXml.append("</Query>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
-
-    }
-
-    /**
-     * 查询目录列表
-     *
-     * @param device 视频设备
-     */
-    @Override
-    public void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException {
-
-        StringBuffer catalogXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        catalogXml.append("<Query>\r\n");
-        catalogXml.append("  <CmdType>Catalog</CmdType>\r\n");
-        catalogXml.append("  <SN>" + sn + "</SN>\r\n");
-        catalogXml.append("  <DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        catalogXml.append("</Query>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
-    }
-
-    /**
-     * 查询录像信息
-     *
-     * @param device    视频设备
-     * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
-     * @param endTime   结束时间,格式要求:yyyy-MM-dd HH:mm:ss
-     */
-    @Override
-    public void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-        if (secrecy == null) {
-            secrecy = 0;
-        }
-        if (type == null) {
-            type = "all";
-        }
-
-        StringBuffer recordInfoXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        recordInfoXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        recordInfoXml.append("<Query>\r\n");
-        recordInfoXml.append("<CmdType>RecordInfo</CmdType>\r\n");
-        recordInfoXml.append("<SN>" + sn + "</SN>\r\n");
-        recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        if (startTime != null) {
-            recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>\r\n");
-        }
-        if (endTime != null) {
-            recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>\r\n");
-        }
-        if (secrecy != null) {
-            recordInfoXml.append("<Secrecy> " + secrecy + " </Secrecy>\r\n");
-        }
-        if (type != null) {
-            // 大华NVR要求必须增加一个值为all的文本元素节点Type
-            recordInfoXml.append("<Type>" + type + "</Type>\r\n");
-        }
-        recordInfoXml.append("</Query>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(),
-                SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
-    }
-
-    /**
-     * 查询报警信息
-     *
-     * @param device        视频设备
-     * @param startPriority 报警起始级别(可选)
-     * @param endPriority   报警终止级别(可选)
-     * @param alarmMethod   报警方式条件(可选)
-     * @param alarmType     报警类型
-     * @param startTime     报警发生起始时间(可选)
-     * @param endTime       报警发生终止时间(可选)
-     * @return true = 命令发送成功
-     */
-    @Override
-    public void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType,
-                               String startTime, String endTime, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Query>\r\n");
-        cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        if (!ObjectUtils.isEmpty(startPriority)) {
-            cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(endPriority)) {
-            cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(alarmMethod)) {
-            cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(alarmType)) {
-            cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(startTime)) {
-            cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(endTime)) {
-            cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
-        }
-        cmdXml.append("</Query>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
-    }
-
-    /**
-     * 查询设备配置
-     *
-     * @param device     视频设备
-     * @param channelId  通道编码(可选)
-     * @param configType 配置类型:
-     */
-    @Override
-    public void deviceConfigQuery(Device device, String channelId, String configType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Query>\r\n");
-        cmdXml.append("<CmdType>ConfigDownload</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        if (ObjectUtils.isEmpty(channelId)) {
-            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        } else {
-            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        }
-        cmdXml.append("<ConfigType>" + configType + "</ConfigType>\r\n");
-        cmdXml.append("</Query>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
-    }
-
-    /**
-     * 查询设备预置位置
-     *
-     * @param device 视频设备
-     */
-    @Override
-    public void presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Query>\r\n");
-        cmdXml.append("<CmdType>PresetQuery</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        if (ObjectUtils.isEmpty(channelId)) {
-            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        } else {
-            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        }
-        cmdXml.append("</Query>\r\n");
-
-
-        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
-    }
-
-    /**
-     * 查询移动设备位置数据
-     *
-     * @param device 视频设备
-     */
-    @Override
-    public void mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer mobilePostitionXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        mobilePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        mobilePostitionXml.append("<Query>\r\n");
-        mobilePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
-        mobilePostitionXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        mobilePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        mobilePostitionXml.append("<Interval>60</Interval>\r\n");
-        mobilePostitionXml.append("</Query>\r\n");
-
-        
-
-        Request request = headerProvider.createMessageRequest(device, mobilePostitionXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
-
-    }
-
-    /**
-     * 订阅、取消订阅移动位置
-     *
-     * @param device 视频设备
-     * @return true = 命令发送成功
-     */
-    @Override
-    public SIPRequest mobilePositionSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer subscribePostitionXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        subscribePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        subscribePostitionXml.append("<Query>\r\n");
-        subscribePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
-        subscribePostitionXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        subscribePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        if (device.getSubscribeCycleForMobilePosition() > 0) {
-            subscribePostitionXml.append("<Interval>" + device.getMobilePositionSubmissionInterval() + "</Interval>\r\n");
-        }
-        subscribePostitionXml.append("</Query>\r\n");
-
-        CallIdHeader callIdHeader;
-
-        if (requestOld != null) {
-            callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
-        } else {
-            callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
-        }
-        SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), requestOld, device.getSubscribeCycleForMobilePosition(), "presence",callIdHeader); //Position;id=" + tm.substring(tm.length() - 4));
-
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
-        return request;
-    }
-
-    /**
-     * 订阅、取消订阅报警信息
-     *
-     * @param device        视频设备
-     * @param expires       订阅过期时间(0 = 取消订阅)
-     * @param startPriority 报警起始级别(可选)
-     * @param endPriority   报警终止级别(可选)
-     * @param alarmMethod   报警方式条件(可选)
-     * @param alarmType     报警类型
-     * @param startTime     报警发生起始时间(可选)
-     * @param endTime       报警发生终止时间(可选)
-     * @return true = 命令发送成功
-     */
-    @Override
-    public void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Query>\r\n");
-        cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        if (!ObjectUtils.isEmpty(startPriority)) {
-            cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(endPriority)) {
-            cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(alarmMethod)) {
-            cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(startTime)) {
-            cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
-        }
-        if (!ObjectUtils.isEmpty(endTime)) {
-            cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
-        }
-        cmdXml.append("</Query>\r\n");
-
-        
-
-        Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "presence",sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
-
-    }
-
-    @Override
-    public SIPRequest catalogSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer cmdXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        cmdXml.append("<Query>\r\n");
-        cmdXml.append("<CmdType>Catalog</CmdType>\r\n");
-        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        cmdXml.append("</Query>\r\n");
-
-        CallIdHeader callIdHeader;
-
-        if (requestOld != null) {
-            callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
-        } else {
-            callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
-        }
-
-        // 有效时间默认为60秒以上
-        SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), requestOld, device.getSubscribeCycleForCatalog(), "Catalog",
-                callIdHeader);
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
-        return request;
-    }
-
-    @Override
-    public void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException {
-
-        StringBuffer dragXml = new StringBuffer(200);
-        String charset = device.getCharset();
-        dragXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
-        dragXml.append("<Control>\r\n");
-        dragXml.append("<CmdType>DeviceControl</CmdType>\r\n");
-        dragXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        if (ObjectUtils.isEmpty(channelId)) {
-            dragXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
-        } else {
-            dragXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
-        }
-        dragXml.append(cmdString);
-        dragXml.append("</Control>\r\n");
-        
-        Request request = headerProvider.createMessageRequest(device, dragXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        logger.debug("拉框信令: " + request.toString());
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
-    }
-
-
-    
-
-
-    /**
-     * 回放暂停
-     */
-    @Override
-    public void playPauseCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException {
-        StringBuffer content = new StringBuffer(200);
-        content.append("PAUSE RTSP/1.0\r\n");
-        content.append("CSeq: " + getInfoCseq() + "\r\n");
-        content.append("PauseTime: now\r\n");
-
-        playbackControlCmd(device, streamInfo, content.toString(), null, null);
-    }
-
-
-    /**
-     * 回放恢复
-     */
-    @Override
-    public void playResumeCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException {
-        StringBuffer content = new StringBuffer(200);
-        content.append("PLAY RTSP/1.0\r\n");
-        content.append("CSeq: " + getInfoCseq() + "\r\n");
-        content.append("Range: npt=now-\r\n");
-
-        playbackControlCmd(device, streamInfo, content.toString(), null, null);
-    }
-
-    /**
-     * 回放拖动播放
-     */
-    @Override
-    public void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) throws InvalidArgumentException, ParseException, SipException {
-        StringBuffer content = new StringBuffer(200);
-        content.append("PLAY RTSP/1.0\r\n");
-        content.append("CSeq: " + getInfoCseq() + "\r\n");
-        content.append("Range: npt=" + Math.abs(seekTime) + "-\r\n");
-
-        playbackControlCmd(device, streamInfo, content.toString(), null, null);
-    }
-
-    /**
-     * 回放倍速播放
-     */
-    @Override
-    public void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException {
-        StringBuffer content = new StringBuffer(200);
-        content.append("PLAY RTSP/1.0\r\n");
-        content.append("CSeq: " + getInfoCseq() + "\r\n");
-        content.append("Scale: " + String.format("%.6f", speed) + "\r\n");
-
-        playbackControlCmd(device, streamInfo, content.toString(), null, null);
-    }
-
-    private int getInfoCseq() {
-        return (int) ((Math.random() * 9 + 1) * Math.pow(10, 8));
-    }
-
-    @Override
-    public void playbackControlCmd(Device device, StreamInfo streamInfo, String content, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException {
-
-        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), streamInfo.getChannelId(), null, streamInfo.getStream());
-        if (ssrcTransaction == null) {
-            logger.info("[回放控制]未找到视频流信息,设备:{}, 流ID: {}", device.getDeviceId(), streamInfo.getStream());
-            return;
-        }
-
-        SIPRequest request = headerProvider.createInfoRequest(device, streamInfo.getChannelId(), content.toString(), ssrcTransaction.getSipTransactionInfo());
-        if (request == null) {
-            logger.info("[回放控制]构建Request信息失败,设备:{}, 流ID: {}", device.getDeviceId(), streamInfo.getStream());
-            return;
-        }
-
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
-    }
-
-    @Override
-    public void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException {
-        if (device == null) {
-            return;
-        }
-        logger.info("[发送报警通知]设备: {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
-                deviceAlarm.getLongitude(), deviceAlarm.getLatitude());
-
-        String characterSet = device.getCharset();
-        StringBuffer deviceStatusXml = new StringBuffer(600);
-        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
-        deviceStatusXml.append("<Notify>\r\n");
-        deviceStatusXml.append("<CmdType>Alarm</CmdType>\r\n");
-        deviceStatusXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
-        deviceStatusXml.append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n");
-        deviceStatusXml.append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n");
-        deviceStatusXml.append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n");
-        deviceStatusXml.append("<AlarmTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(deviceAlarm.getAlarmTime()) + "</AlarmTime>\r\n");
-        deviceStatusXml.append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n");
-        deviceStatusXml.append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n");
-        deviceStatusXml.append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n");
-        deviceStatusXml.append("<info>\r\n");
-        deviceStatusXml.append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n");
-        deviceStatusXml.append("</info>\r\n");
-        deviceStatusXml.append("</Notify>\r\n");
-
-        
-        Request request = headerProvider.createMessageRequest(device, deviceStatusXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
-
-
-    }
-}
+package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
+
+import com.genersoft.iot.vmp.common.InviteSessionType;
+import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.conf.SipConfig;
+import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
+import com.genersoft.iot.vmp.gb28181.SipLayer;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
+import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
+import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
+import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
+import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
+import com.genersoft.iot.vmp.service.IMediaServerService;
+import com.genersoft.iot.vmp.service.bean.SSRCInfo;
+import com.genersoft.iot.vmp.utils.DateUtil;
+import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.ResponseEvent;
+import javax.sip.SipException;
+import javax.sip.SipFactory;
+import javax.sip.header.CallIdHeader;
+import javax.sip.message.Request;
+import java.text.ParseException;
+
+/**
+ * @description:设备能力接口,用于定义设备的控制、查询能力
+ * @author: swwheihei
+ * @date: 2020年5月3日 下午9:22:48
+ */
+@Component
+@DependsOn("sipLayer")
+public class SIPCommander implements ISIPCommander {
+
+    private final Logger logger = LoggerFactory.getLogger(SIPCommander.class);
+
+    @Autowired
+    private SipConfig sipConfig;
+
+    @Autowired
+    private SipLayer sipLayer;
+
+    @Autowired
+    private SIPSender sipSender;
+    
+    @Autowired
+    private SIPRequestHeaderProvider headerProvider;
+
+    @Autowired
+    private VideoStreamSessionManager streamSession;
+
+    @Autowired
+    private UserSetting userSetting;
+
+    @Autowired
+    private ZlmHttpHookSubscribe subscribe;
+
+
+
+    @Autowired
+    private IMediaServerService mediaServerService;
+
+
+    /**
+     * 云台方向放控制,使用配置文件中的默认镜头移动速度
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown    镜头上移下移 0:停止 1:上移 2:下移
+     */
+    @Override
+    public void ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) throws InvalidArgumentException, ParseException, SipException {
+        ptzCmd(device, channelId, leftRight, upDown, 0, sipConfig.getPtzSpeed(), 0);
+    }
+
+    /**
+     * 云台方向放控制
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown    镜头上移下移 0:停止 1:上移 2:下移
+     * @param moveSpeed 镜头移动速度
+     */
+    @Override
+    public void ptzdirectCmd(Device device, String channelId, int leftRight, int upDown, int moveSpeed) throws InvalidArgumentException, ParseException, SipException {
+        ptzCmd(device, channelId, leftRight, upDown, 0, moveSpeed, 0);
+    }
+
+    /**
+     * 云台缩放控制,使用配置文件中的默认镜头缩放速度
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param inOut     镜头放大缩小 0:停止 1:缩小 2:放大
+     */
+    @Override
+    public void ptzZoomCmd(Device device, String channelId, int inOut) throws InvalidArgumentException, ParseException, SipException {
+        ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getPtzSpeed());
+    }
+
+    /**
+     * 云台缩放控制
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param inOut     镜头放大缩小 0:停止 1:缩小 2:放大
+     * @param zoomSpeed 镜头缩放速度
+     */
+    @Override
+    public void ptzZoomCmd(Device device, String channelId, int inOut, int zoomSpeed) throws InvalidArgumentException, ParseException, SipException {
+        ptzCmd(device, channelId, 0, 0, inOut, 0, zoomSpeed);
+    }
+
+    /**
+     * 云台指令码计算
+     *
+     * @param cmdCode      指令码
+     * @param parameter1   数据1
+     * @param parameter2   数据2
+     * @param combineCode2 组合码2
+     */
+    public static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) {
+        StringBuilder builder = new StringBuilder("A50F01");
+        String strTmp;
+        strTmp = String.format("%02X", cmdCode);
+        builder.append(strTmp, 0, 2);
+        strTmp = String.format("%02X", parameter1);
+        builder.append(strTmp, 0, 2);
+        strTmp = String.format("%02X", parameter2);
+        builder.append(strTmp, 0, 2);
+        //优化zoom变倍速率
+        if ((combineCode2 > 0) && (combineCode2 <16))
+        {
+            combineCode2 = 16;
+        }
+        strTmp = String.format("%X", combineCode2);
+        builder.append(strTmp, 0, 1).append("0");
+        //计算校验码
+        int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + parameter1 + parameter2 + (combineCode2 & 0XF0)) % 0X100;
+        strTmp = String.format("%02X", checkCode);
+        builder.append(strTmp, 0, 2);
+        return builder.toString();
+    }
+
+    /**
+     * 云台控制,支持方向与缩放控制
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
+     * @param upDown    镜头上移下移 0:停止 1:上移 2:下移
+     * @param inOut     镜头放大缩小 0:停止 1:缩小 2:放大
+     * @param moveSpeed 镜头移动速度
+     * @param zoomSpeed 镜头缩放速度
+     */
+    @Override
+    public void ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
+                       int zoomSpeed) throws InvalidArgumentException, SipException, ParseException {
+        String cmdStr = SipUtils.cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
+        StringBuilder ptzXml = new StringBuilder(200);
+        String charset = device.getCharset();
+        ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        ptzXml.append("<Control>\r\n");
+        ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
+        ptzXml.append("<Info>\r\n");
+        ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
+        ptzXml.append("</Info>\r\n");
+        ptzXml.append("</Control>\r\n");
+        
+        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
+    }
+
+    /**
+     * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令
+     *
+     * @param device       控制设备
+     * @param channelId    预览通道
+     * @param cmdCode      指令码
+     * @param parameter1   数据1
+     * @param parameter2   数据2
+     * @param combineCode2 组合码2
+     */
+    @Override
+    public void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException {
+
+        String cmdStr = frontEndCmdString(cmdCode, parameter1, parameter2, combineCode2);
+        StringBuffer ptzXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        ptzXml.append("<Control>\r\n");
+        ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
+        ptzXml.append("<Info>\r\n");
+        ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
+        ptzXml.append("</Info>\r\n");
+        ptzXml.append("</Control>\r\n");
+
+
+
+
+        SIPRequest request = (SIPRequest) headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
+
+    }
+
+    /**
+     * 前端控制指令(用于转发上级指令)
+     *
+     * @param device    控制设备
+     * @param channelId 预览通道
+     * @param cmdString 前端控制指令串
+     */
+    @Override
+    public void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer ptzXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        ptzXml.append("<Control>\r\n");
+        ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        ptzXml.append("<PTZCmd>" + cmdString + "</PTZCmd>\r\n");
+        ptzXml.append("<Info>\r\n");
+        ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
+        ptzXml.append("</Info>\r\n");
+        ptzXml.append("</Control>\r\n");
+        
+        
+        Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request, errorEvent, okEvent);
+
+    }
+
+    /**
+     * 请求预览视频流
+     *
+     * @param device     视频设备
+     * @param channelId  预览通道
+     * @param event      hook订阅
+     * @param errorEvent sip错误订阅
+     */
+    @Override
+    public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
+                              ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+        String stream = ssrcInfo.getStream();
+
+        if (device == null) {
+            return;
+        }
+
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort());
+        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
+        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, HookParam hookParam) -> {
+            if (event != null) {
+                event.response(mediaServerItemInUse, hookParam);
+                subscribe.removeSubscribe(hookSubscribe);
+            }
+        });
+        String sdpIp;
+        if (!ObjectUtils.isEmpty(device.getSdpIp())) {
+            sdpIp = device.getSdpIp();
+        }else {
+            sdpIp = mediaServerItem.getSdpIp();
+        }
+        StringBuffer content = new StringBuffer(200);
+        content.append("v=0\r\n");
+        content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
+        content.append("s=Play\r\n");
+        content.append("c=IN IP4 " + sdpIp + "\r\n");
+        content.append("t=0 0\r\n");
+
+        if (userSetting.isSeniorSdp()) {
+            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n");
+            }
+            content.append("a=recvonly\r\n");
+            content.append("a=rtpmap:96 PS/90000\r\n");
+            content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:126 H264/90000\r\n");
+            content.append("a=rtpmap:125 H264S/90000\r\n");
+            content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:99 H265/90000\r\n");
+            content.append("a=rtpmap:98 H264/90000\r\n");
+            content.append("a=rtpmap:97 MPEG4/90000\r\n");
+            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        } else {
+            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("UDP".equalsIgnoreCase(device.getStreamMode())) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 97 98 99\r\n");
+            }
+            content.append("a=recvonly\r\n");
+            content.append("a=rtpmap:96 PS/90000\r\n");
+            content.append("a=rtpmap:98 H264/90000\r\n");
+            content.append("a=rtpmap:97 MPEG4/90000\r\n");
+            content.append("a=rtpmap:99 H265/90000\r\n");
+            if ("TCP-PASSIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(device.getStreamMode())) { // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        }
+
+        if( device.isSwitchPrimarySubStream() ){
+            if("TP-LINK".equals(device.getManufacturer())){
+                if (device.isSwitchPrimarySubStream()){
+                    content.append("a=streamMode:sub\r\n");
+                }else {
+                    content.append("a=streamMode:main\r\n");
+                }
+            }else {
+                if (device.isSwitchPrimarySubStream()){
+                    content.append("a=streamprofile:1\r\n");
+                }else {
+                    content.append("a=streamprofile:0\r\n");
+                }
+            }
+        }
+
+        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
+        // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
+//			content.append("f=v/2/5/25/1/4000a/1/8/1" + "\r\n"); // 未发现支持此特性的设备
+
+
+
+        Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> {
+            streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+            mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+            errorEvent.response(e);
+        }), e -> {
+            ResponseEvent responseEvent = (ResponseEvent) e.event;
+            SIPResponse response = (SIPResponse) responseEvent.getResponse();
+            streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response,
+                    InviteSessionType.PLAY);
+            okEvent.response(e);
+        });
+    }
+
+    /**
+     * 请求回放视频流
+     *
+     * @param device    视频设备
+     * @param channelId 预览通道
+     * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+     * @param endTime   结束时间,格式要求:yyyy-MM-dd HH:mm:ss
+     */
+    @Override
+    public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
+                                  String startTime, String endTime, ZlmHttpHookSubscribe.Event hookEvent,
+                                  SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort());
+        String sdpIp;
+        if (!ObjectUtils.isEmpty(device.getSdpIp())) {
+            sdpIp = device.getSdpIp();
+        }else {
+            sdpIp = mediaServerItem.getSdpIp();
+        }
+        StringBuffer content = new StringBuffer(200);
+        content.append("v=0\r\n");
+        content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
+        content.append("s=Playback\r\n");
+        content.append("u=" + channelId + ":0\r\n");
+        content.append("c=IN IP4 " + sdpIp + "\r\n");
+        content.append("t=" + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime) + " "
+                + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) + "\r\n");
+
+        String streamMode = device.getStreamMode();
+
+        if (userSetting.isSeniorSdp()) {
+            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("UDP".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n");
+            }
+            content.append("a=recvonly\r\n");
+            content.append("a=rtpmap:96 PS/90000\r\n");
+            content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:126 H264/90000\r\n");
+            content.append("a=rtpmap:125 H264S/90000\r\n");
+            content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:99 H265/90000\r\n");
+            content.append("a=rtpmap:98 H264/90000\r\n");
+            content.append("a=rtpmap:97 MPEG4/90000\r\n");
+            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) { // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) { // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        } else {
+            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("UDP".equalsIgnoreCase(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 97 98 99\r\n");
+            }
+            content.append("a=recvonly\r\n");
+            content.append("a=rtpmap:96 PS/90000\r\n");
+            content.append("a=rtpmap:97 MPEG4/90000\r\n");
+            content.append("a=rtpmap:98 H264/90000\r\n");
+            content.append("a=rtpmap:99 H265/90000\r\n");
+            if ("TCP-PASSIVE".equalsIgnoreCase(streamMode)) {
+                // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equalsIgnoreCase(streamMode)) {
+                // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        }
+
+        //ssrc
+        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");
+
+        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
+        // 添加订阅
+        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, HookParam hookParam) -> {
+            if (hookEvent != null) {
+                hookEvent.response(mediaServerItemInUse, hookParam);
+            }
+            subscribe.removeSubscribe(hookSubscribe);
+        });
+        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()), ssrcInfo.getSsrc());
+
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
+            ResponseEvent responseEvent = (ResponseEvent) event.event;
+            SIPResponse response = (SIPResponse) responseEvent.getResponse();
+            streamSession.put(device.getDeviceId(), channelId,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAYBACK);
+            okEvent.response(event);
+        });
+    }
+
+    /**
+     * 请求历史媒体下载
+     *
+     * @param device        视频设备
+     * @param channelId     预览通道
+     * @param startTime     开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+     * @param endTime       结束时间,格式要求:yyyy-MM-dd HH:mm:ss
+     * @param downloadSpeed 下载倍速参数
+     */
+    @Override
+    public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
+                                  String startTime, String endTime, int downloadSpeed,
+                                  ZlmHttpHookSubscribe.Event hookEvent,
+                                  SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort());
+        String sdpIp;
+        if (!ObjectUtils.isEmpty(device.getSdpIp())) {
+            sdpIp = device.getSdpIp();
+        }else {
+            sdpIp = mediaServerItem.getSdpIp();
+        }
+        StringBuffer content = new StringBuffer(200);
+        content.append("v=0\r\n");
+        content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
+        content.append("s=Download\r\n");
+        content.append("u=" + channelId + ":0\r\n");
+        content.append("c=IN IP4 " + sdpIp + "\r\n");
+        content.append("t=" + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime) + " "
+                + DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) + "\r\n");
+
+        String streamMode = device.getStreamMode().toUpperCase();
+
+        if (userSetting.isSeniorSdp()) {
+            if ("TCP-PASSIVE".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("TCP-ACTIVE".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 126 125 99 34 98 97\r\n");
+            } else if ("UDP".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 126 125 99 34 98 97\r\n");
+            }
+            content.append("a=recvonly\r\n");
+            content.append("a=rtpmap:96 PS/90000\r\n");
+            content.append("a=fmtp:126 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:126 H264/90000\r\n");
+            content.append("a=rtpmap:125 H264S/90000\r\n");
+            content.append("a=fmtp:125 profile-level-id=42e01e\r\n");
+            content.append("a=rtpmap:99 MP4V-ES/90000\r\n");
+            content.append("a=fmtp:99 profile-level-id=3\r\n");
+            content.append("a=rtpmap:98 H264/90000\r\n");
+            content.append("a=rtpmap:97 MPEG4/90000\r\n");
+            if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        } else {
+            if ("TCP-PASSIVE".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("TCP-ACTIVE".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 96 97 98 99\r\n");
+            } else if ("UDP".equals(streamMode)) {
+                content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 96 97 98 99\r\n");
+            }
+            content.append("a=recvonly\r\n");
+            content.append("a=rtpmap:96 PS/90000\r\n");
+            content.append("a=rtpmap:97 MPEG4/90000\r\n");
+            content.append("a=rtpmap:98 H264/90000\r\n");
+            content.append("a=rtpmap:99 H265/90000\r\n");
+            if ("TCP-PASSIVE".equals(streamMode)) { // tcp被动模式
+                content.append("a=setup:passive\r\n");
+                content.append("a=connection:new\r\n");
+            } else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式
+                content.append("a=setup:active\r\n");
+                content.append("a=connection:new\r\n");
+            }
+        }
+        content.append("a=downloadspeed:" + downloadSpeed + "\r\n");
+
+        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
+        logger.debug("此时请求下载信令的ssrc===>{}",ssrcInfo.getSsrc());
+        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
+        // 添加订阅
+        CallIdHeader newCallIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport());
+        String callId= newCallIdHeader.getCallId();
+        subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> {
+            logger.debug("sipc 添加订阅===callId {}",callId);
+            hookEvent.response(mediaServerItemInUse, hookParam);
+            subscribe.removeSubscribe(hookSubscribe);
+            hookSubscribe.getContent().put("regist", false);
+            hookSubscribe.getContent().put("schema", "rtsp");
+            // 添加流注销的订阅,注销了后向设备发送bye
+            subscribe.addSubscribe(hookSubscribe,
+                    (mediaServerItemForEnd, hookParam1) -> {
+                        logger.info("[录像]下载结束, 发送BYE");
+                        try {
+                            streamByeCmd(device, channelId, ssrcInfo.getStream(), callId);
+                        } catch (InvalidArgumentException | ParseException | SipException |
+                                 SsrcTransactionNotFoundException e) {
+                            logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage());
+                        }
+                    });
+        });
+
+        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,newCallIdHeader, ssrcInfo.getSsrc());
+
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
+            ResponseEvent responseEvent = (ResponseEvent) event.event;
+            SIPResponse response = (SIPResponse) responseEvent.getResponse();
+            String contentString =new String(response.getRawContent());
+            String ssrc = SipUtils.getSsrcFromSdp(contentString);
+            streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, InviteSessionType.DOWNLOAD);
+            okEvent.response(event);
+        });
+    }
+
+    /**
+     * 视频流停止, 不使用回调
+     */
+    @Override
+    public void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException {
+        streamByeCmd(device, channelId, stream, callId, null);
+    }
+
+    /**
+     * 视频流停止
+     */
+    @Override
+    public void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
+        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callId, stream);
+        if (ssrcTransaction == null) {
+            throw new SsrcTransactionNotFoundException(device.getDeviceId(), channelId, callId, stream);
+        }
+
+        mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
+        mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
+        streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
+
+        Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
+    }
+
+    /**
+     * 语音广播
+     *
+     * @param device    视频设备
+     * @param channelId 预览通道
+     */
+    @Override
+    public void audioBroadcastCmd(Device device, String channelId) {
+    }
+
+    /**
+     * 语音广播
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer broadcastXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        broadcastXml.append("<Notify>\r\n");
+        broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
+        broadcastXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
+        broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
+        broadcastXml.append("</Notify>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
+
+    }
+
+    @Override
+    public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer broadcastXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        broadcastXml.append("<Notify>\r\n");
+        broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
+        broadcastXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
+        broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
+        broadcastXml.append("</Notify>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+
+    }
+
+
+    /**
+     * 音视频录像控制
+     *
+     * @param device       视频设备
+     * @param channelId    预览通道
+     * @param recordCmdStr 录像命令:Record / StopRecord
+     */
+    @Override
+    public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("<RecordCmd>" + recordCmdStr + "</RecordCmd>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
+    }
+
+    /**
+     * 远程启动控制命令
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        cmdXml.append("<TeleBoot>Boot</TeleBoot>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
+    }
+
+    /**
+     * 报警布防/撤防命令
+     *
+     * @param device      视频设备
+     * @param guardCmdStr "SetGuard"/"ResetGuard"
+     */
+    @Override
+    public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        cmdXml.append("<GuardCmd>" + guardCmdStr + "</GuardCmd>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
+    }
+
+    /**
+     * 报警复位命令
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        cmdXml.append("<AlarmCmd>ResetAlarm</AlarmCmd>\r\n");
+        if (!ObjectUtils.isEmpty(alarmMethod) || !ObjectUtils.isEmpty(alarmType)) {
+            cmdXml.append("<Info>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmMethod)) {
+            cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmType)) {
+            cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmMethod) || !ObjectUtils.isEmpty(alarmType)) {
+            cmdXml.append("</Info>\r\n");
+        }
+        cmdXml.append("</Control>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
+    }
+
+    /**
+     * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧
+     *
+     * @param device    视频设备
+     * @param channelId 预览通道
+     */
+    @Override
+    public void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("<IFameCmd>Send</IFameCmd>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
+    }
+
+    /**
+     * 看守位控制命令
+     *
+     * @param device      视频设备
+     * @param channelId      通道id,非通道则是设备本身
+     * @param enabled     看守位使能:1 = 开启,0 = 关闭
+     * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
+     * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
+     */
+    @Override
+    public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("<HomePosition>\r\n");
+        if (NumericUtil.isInteger(enabled) && (!enabled.equals("0"))) {
+            cmdXml.append("<Enabled>1</Enabled>\r\n");
+            if (NumericUtil.isInteger(resetTime)) {
+                cmdXml.append("<ResetTime>" + resetTime + "</ResetTime>\r\n");
+            } else {
+                cmdXml.append("<ResetTime>0</ResetTime>\r\n");
+            }
+            if (NumericUtil.isInteger(presetIndex)) {
+                cmdXml.append("<PresetIndex>" + presetIndex + "</PresetIndex>\r\n");
+            } else {
+                cmdXml.append("<PresetIndex>0</PresetIndex>\r\n");
+            }
+        } else {
+            cmdXml.append("<Enabled>0</Enabled>\r\n");
+        }
+        cmdXml.append("</HomePosition>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
+    }
+
+    /**
+     * 设备配置命令
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void deviceConfigCmd(Device device) {
+        // TODO Auto-generated method stub
+    }
+
+    /**
+     * 设备配置命令:basicParam
+     *
+     * @param device            视频设备
+     * @param channelId         通道编码(可选)
+     * @param name              设备/通道名称(可选)
+     * @param expiration        注册过期时间(可选)
+     * @param heartBeatInterval 心跳间隔时间(可选)
+     * @param heartBeatCount    心跳超时次数(可选)
+     */
+    @Override
+    public void deviceBasicConfigCmd(Device device, String channelId, String name, String expiration,
+                                     String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Control>\r\n");
+        cmdXml.append("<CmdType>DeviceConfig</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("<BasicParam>\r\n");
+        if (!ObjectUtils.isEmpty(name)) {
+            cmdXml.append("<Name>" + name + "</Name>\r\n");
+        }
+        if (NumericUtil.isInteger(expiration)) {
+            if (Integer.valueOf(expiration) > 0) {
+                cmdXml.append("<Expiration>" + expiration + "</Expiration>\r\n");
+            }
+        }
+        if (NumericUtil.isInteger(heartBeatInterval)) {
+            if (Integer.valueOf(heartBeatInterval) > 0) {
+                cmdXml.append("<HeartBeatInterval>" + heartBeatInterval + "</HeartBeatInterval>\r\n");
+            }
+        }
+        if (NumericUtil.isInteger(heartBeatCount)) {
+            if (Integer.valueOf(heartBeatCount) > 0) {
+                cmdXml.append("<HeartBeatCount>" + heartBeatCount + "</HeartBeatCount>\r\n");
+            }
+        }
+        cmdXml.append("</BasicParam>\r\n");
+        cmdXml.append("</Control>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+    }
+
+    /**
+     * 查询设备状态
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        String charset = device.getCharset();
+        StringBuffer catalogXml = new StringBuffer(200);
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        catalogXml.append("<Query>\r\n");
+        catalogXml.append("<CmdType>DeviceStatus</CmdType>\r\n");
+        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        catalogXml.append("</Query>\r\n");
+
+        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+    }
+
+    /**
+     * 查询设备信息
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void deviceInfoQuery(Device device) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer catalogXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        catalogXml.append("<Query>\r\n");
+        catalogXml.append("<CmdType>DeviceInfo</CmdType>\r\n");
+        catalogXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        catalogXml.append("</Query>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
+
+    }
+
+    /**
+     * 查询目录列表
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException {
+
+        StringBuffer catalogXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        catalogXml.append("<Query>\r\n");
+        catalogXml.append("  <CmdType>Catalog</CmdType>\r\n");
+        catalogXml.append("  <SN>" + sn + "</SN>\r\n");
+        catalogXml.append("  <DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        catalogXml.append("</Query>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+    }
+
+    /**
+     * 查询录像信息
+     *
+     * @param device    视频设备
+     * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss
+     * @param endTime   结束时间,格式要求:yyyy-MM-dd HH:mm:ss
+     */
+    @Override
+    public void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+        if (secrecy == null) {
+            secrecy = 0;
+        }
+        if (type == null) {
+            type = "all";
+        }
+
+        StringBuffer recordInfoXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        recordInfoXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        recordInfoXml.append("<Query>\r\n");
+        recordInfoXml.append("<CmdType>RecordInfo</CmdType>\r\n");
+        recordInfoXml.append("<SN>" + sn + "</SN>\r\n");
+        recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        if (startTime != null) {
+            recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>\r\n");
+        }
+        if (endTime != null) {
+            recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>\r\n");
+        }
+        if (secrecy != null) {
+            recordInfoXml.append("<Secrecy> " + secrecy + " </Secrecy>\r\n");
+        }
+        if (type != null) {
+            // 大华NVR要求必须增加一个值为all的文本元素节点Type
+            recordInfoXml.append("<Type>" + type + "</Type>\r\n");
+        }
+        recordInfoXml.append("</Query>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(),
+                SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
+    }
+
+    /**
+     * 查询报警信息
+     *
+     * @param device        视频设备
+     * @param startPriority 报警起始级别(可选)
+     * @param endPriority   报警终止级别(可选)
+     * @param alarmMethod   报警方式条件(可选)
+     * @param alarmType     报警类型
+     * @param startTime     报警发生起始时间(可选)
+     * @param endTime       报警发生终止时间(可选)
+     * @return true = 命令发送成功
+     */
+    @Override
+    public void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType,
+                               String startTime, String endTime, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Query>\r\n");
+        cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        if (!ObjectUtils.isEmpty(startPriority)) {
+            cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(endPriority)) {
+            cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmMethod)) {
+            cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmType)) {
+            cmdXml.append("<AlarmType>" + alarmType + "</AlarmType>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(startTime)) {
+            cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(endTime)) {
+            cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
+        }
+        cmdXml.append("</Query>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+    }
+
+    /**
+     * 查询设备配置
+     *
+     * @param device     视频设备
+     * @param channelId  通道编码(可选)
+     * @param configType 配置类型:
+     */
+    @Override
+    public void deviceConfigQuery(Device device, String channelId, String configType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Query>\r\n");
+        cmdXml.append("<CmdType>ConfigDownload</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("<ConfigType>" + configType + "</ConfigType>\r\n");
+        cmdXml.append("</Query>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+    }
+
+    /**
+     * 查询设备预置位置
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Query>\r\n");
+        cmdXml.append("<CmdType>PresetQuery</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            cmdXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        cmdXml.append("</Query>\r\n");
+
+
+        Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+    }
+
+    /**
+     * 查询移动设备位置数据
+     *
+     * @param device 视频设备
+     */
+    @Override
+    public void mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer mobilePostitionXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        mobilePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        mobilePostitionXml.append("<Query>\r\n");
+        mobilePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
+        mobilePostitionXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        mobilePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        mobilePostitionXml.append("<Interval>60</Interval>\r\n");
+        mobilePostitionXml.append("</Query>\r\n");
+
+        
+
+        Request request = headerProvider.createMessageRequest(device, mobilePostitionXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
+
+    }
+
+    /**
+     * 订阅、取消订阅移动位置
+     *
+     * @param device 视频设备
+     * @return true = 命令发送成功
+     */
+    @Override
+    public SIPRequest mobilePositionSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer subscribePostitionXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        subscribePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        subscribePostitionXml.append("<Query>\r\n");
+        subscribePostitionXml.append("<CmdType>MobilePosition</CmdType>\r\n");
+        subscribePostitionXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        subscribePostitionXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        if (device.getSubscribeCycleForMobilePosition() > 0) {
+            subscribePostitionXml.append("<Interval>" + device.getMobilePositionSubmissionInterval() + "</Interval>\r\n");
+        }
+        subscribePostitionXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader;
+
+        if (requestOld != null) {
+            callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
+        } else {
+            callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
+        }
+        SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), requestOld, device.getSubscribeCycleForMobilePosition(), "presence",callIdHeader); //Position;id=" + tm.substring(tm.length() - 4));
+
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
+        return request;
+    }
+
+    /**
+     * 订阅、取消订阅报警信息
+     *
+     * @param device        视频设备
+     * @param expires       订阅过期时间(0 = 取消订阅)
+     * @param startPriority 报警起始级别(可选)
+     * @param endPriority   报警终止级别(可选)
+     * @param alarmMethod   报警方式条件(可选)
+     * @param alarmType     报警类型
+     * @param startTime     报警发生起始时间(可选)
+     * @param endTime       报警发生终止时间(可选)
+     * @return true = 命令发送成功
+     */
+    @Override
+    public void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Query>\r\n");
+        cmdXml.append("<CmdType>Alarm</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        if (!ObjectUtils.isEmpty(startPriority)) {
+            cmdXml.append("<StartAlarmPriority>" + startPriority + "</StartAlarmPriority>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(endPriority)) {
+            cmdXml.append("<EndAlarmPriority>" + endPriority + "</EndAlarmPriority>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(alarmMethod)) {
+            cmdXml.append("<AlarmMethod>" + alarmMethod + "</AlarmMethod>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(startTime)) {
+            cmdXml.append("<StartAlarmTime>" + startTime + "</StartAlarmTime>\r\n");
+        }
+        if (!ObjectUtils.isEmpty(endTime)) {
+            cmdXml.append("<EndAlarmTime>" + endTime + "</EndAlarmTime>\r\n");
+        }
+        cmdXml.append("</Query>\r\n");
+
+        
+
+        Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "presence",sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
+
+    }
+
+    @Override
+    public SIPRequest catalogSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer cmdXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        cmdXml.append("<Query>\r\n");
+        cmdXml.append("<CmdType>Catalog</CmdType>\r\n");
+        cmdXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        cmdXml.append("</Query>\r\n");
+
+        CallIdHeader callIdHeader;
+
+        if (requestOld != null) {
+            callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId());
+        } else {
+            callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport());
+        }
+
+        // 有效时间默认为60秒以上
+        SIPRequest request = (SIPRequest) headerProvider.createSubscribeRequest(device, cmdXml.toString(), requestOld, device.getSubscribeCycleForCatalog(), "Catalog",
+                callIdHeader);
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
+        return request;
+    }
+
+    @Override
+    public void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException {
+
+        StringBuffer dragXml = new StringBuffer(200);
+        String charset = device.getCharset();
+        dragXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
+        dragXml.append("<Control>\r\n");
+        dragXml.append("<CmdType>DeviceControl</CmdType>\r\n");
+        dragXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        if (ObjectUtils.isEmpty(channelId)) {
+            dragXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
+        } else {
+            dragXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
+        }
+        dragXml.append(cmdString);
+        dragXml.append("</Control>\r\n");
+        
+        Request request = headerProvider.createMessageRequest(device, dragXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        logger.debug("拉框信令: " + request.toString());
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
+    }
+
+
+    
+
+
+    /**
+     * 回放暂停
+     */
+    @Override
+    public void playPauseCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException {
+        StringBuffer content = new StringBuffer(200);
+        content.append("PAUSE RTSP/1.0\r\n");
+        content.append("CSeq: " + getInfoCseq() + "\r\n");
+        content.append("PauseTime: now\r\n");
+
+        playbackControlCmd(device, streamInfo, content.toString(), null, null);
+    }
+
+
+    /**
+     * 回放恢复
+     */
+    @Override
+    public void playResumeCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException {
+        StringBuffer content = new StringBuffer(200);
+        content.append("PLAY RTSP/1.0\r\n");
+        content.append("CSeq: " + getInfoCseq() + "\r\n");
+        content.append("Range: npt=now-\r\n");
+
+        playbackControlCmd(device, streamInfo, content.toString(), null, null);
+    }
+
+    /**
+     * 回放拖动播放
+     */
+    @Override
+    public void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) throws InvalidArgumentException, ParseException, SipException {
+        StringBuffer content = new StringBuffer(200);
+        content.append("PLAY RTSP/1.0\r\n");
+        content.append("CSeq: " + getInfoCseq() + "\r\n");
+        content.append("Range: npt=" + Math.abs(seekTime) + "-\r\n");
+
+        playbackControlCmd(device, streamInfo, content.toString(), null, null);
+    }
+
+    /**
+     * 回放倍速播放
+     */
+    @Override
+    public void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException {
+        StringBuffer content = new StringBuffer(200);
+        content.append("PLAY RTSP/1.0\r\n");
+        content.append("CSeq: " + getInfoCseq() + "\r\n");
+        content.append("Scale: " + String.format("%.6f", speed) + "\r\n");
+
+        playbackControlCmd(device, streamInfo, content.toString(), null, null);
+    }
+
+    private int getInfoCseq() {
+        return (int) ((Math.random() * 9 + 1) * Math.pow(10, 8));
+    }
+
+    @Override
+    public void playbackControlCmd(Device device, StreamInfo streamInfo, String content, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException {
+
+        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), streamInfo.getChannelId(), null, streamInfo.getStream());
+        if (ssrcTransaction == null) {
+            logger.info("[回放控制]未找到视频流信息,设备:{}, 流ID: {}", device.getDeviceId(), streamInfo.getStream());
+            return;
+        }
+
+        SIPRequest request = headerProvider.createInfoRequest(device, streamInfo.getChannelId(), content.toString(), ssrcTransaction.getSipTransactionInfo());
+        if (request == null) {
+            logger.info("[回放控制]构建Request信息失败,设备:{}, 流ID: {}", device.getDeviceId(), streamInfo.getStream());
+            return;
+        }
+
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
+    }
+
+    @Override
+    public void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException {
+        if (device == null) {
+            return;
+        }
+        logger.info("[发送报警通知]设备: {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
+                deviceAlarm.getLongitude(), deviceAlarm.getLatitude());
+
+        String characterSet = device.getCharset();
+        StringBuffer deviceStatusXml = new StringBuffer(600);
+        deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
+        deviceStatusXml.append("<Notify>\r\n");
+        deviceStatusXml.append("<CmdType>Alarm</CmdType>\r\n");
+        deviceStatusXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
+        deviceStatusXml.append("<DeviceID>" + deviceAlarm.getChannelId() + "</DeviceID>\r\n");
+        deviceStatusXml.append("<AlarmPriority>" + deviceAlarm.getAlarmPriority() + "</AlarmPriority>\r\n");
+        deviceStatusXml.append("<AlarmMethod>" + deviceAlarm.getAlarmMethod() + "</AlarmMethod>\r\n");
+        deviceStatusXml.append("<AlarmTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(deviceAlarm.getAlarmTime()) + "</AlarmTime>\r\n");
+        deviceStatusXml.append("<AlarmDescription>" + deviceAlarm.getAlarmDescription() + "</AlarmDescription>\r\n");
+        deviceStatusXml.append("<Longitude>" + deviceAlarm.getLongitude() + "</Longitude>\r\n");
+        deviceStatusXml.append("<Latitude>" + deviceAlarm.getLatitude() + "</Latitude>\r\n");
+        deviceStatusXml.append("<info>\r\n");
+        deviceStatusXml.append("<AlarmType>" + deviceAlarm.getAlarmType() + "</AlarmType>\r\n");
+        deviceStatusXml.append("</info>\r\n");
+        deviceStatusXml.append("</Notify>\r\n");
+
+        
+        Request request = headerProvider.createMessageRequest(device, deviceStatusXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
+
+
+    }
+}

+ 149 - 149
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java

@@ -1,149 +1,149 @@
-package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info;
-
-import com.genersoft.iot.vmp.common.InviteInfo;
-import com.genersoft.iot.vmp.common.InviteSessionType;
-import com.genersoft.iot.vmp.gb28181.bean.*;
-import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
-import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
-import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
-import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
-import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
-import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
-import com.genersoft.iot.vmp.service.IInviteStreamService;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-import gov.nist.javax.sip.message.SIPRequest;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import javax.sip.InvalidArgumentException;
-import javax.sip.RequestEvent;
-import javax.sip.SipException;
-import javax.sip.header.CallIdHeader;
-import javax.sip.header.ContentTypeHeader;
-import javax.sip.message.Response;
-import java.text.ParseException;
-
-@Component
-public class InfoRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
-
-    private final static Logger logger = LoggerFactory.getLogger(InfoRequestProcessor.class);
-
-    private final String method = "INFO";
-
-    @Autowired
-    private SIPProcessorObserver sipProcessorObserver;
-
-    @Autowired
-    private IVideoManagerStorage storage;
-
-    @Autowired
-    private SipSubscribe sipSubscribe;
-
-    @Autowired
-    private IRedisCatchStorage redisCatchStorage;
-
-    @Autowired
-    private IInviteStreamService inviteStreamService;
-
-    @Autowired
-    private IVideoManagerStorage storager;
-
-    @Autowired
-    private SIPCommander cmder;
-
-    @Autowired
-    private VideoStreamSessionManager sessionManager;
-
-    @Override
-    public void afterPropertiesSet() throws Exception {
-        // 添加消息处理的订阅
-        sipProcessorObserver.addRequestProcessor(method, this);
-    }
-
-    @Override
-    public void process(RequestEvent evt) {
-        logger.debug("接收到消息:" + evt.getRequest());
-        SIPRequest request = (SIPRequest) evt.getRequest();
-        String deviceId = SipUtils.getUserIdFromFromHeader(request);
-        CallIdHeader callIdHeader = request.getCallIdHeader();
-        // 先从会话内查找
-        SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
-
-        // 兼容海康 媒体通知 消息from字段不是设备ID的问题
-        if (ssrcTransaction != null) {
-            deviceId = ssrcTransaction.getDeviceId();
-        }
-        // 查询设备是否存在
-        Device device = redisCatchStorage.getDevice(deviceId);
-        // 查询上级平台是否存在
-        ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId);
-        try {
-            if (device != null && parentPlatform != null) {
-                logger.warn("[重复]平台与设备编号重复:{}", deviceId);
-                String hostAddress = request.getRemoteAddress().getHostAddress();
-                int remotePort = request.getRemotePort();
-                if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) {
-                    parentPlatform = null;
-                }else {
-                    device = null;
-                }
-            }
-            if (device == null && parentPlatform == null) {
-                // 不存在则回复404
-                responseAck(request, Response.NOT_FOUND, "device "+ deviceId +" not found");
-                logger.warn("[设备未找到 ]: {}", deviceId);
-                if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){
-                    DeviceNotFoundEvent deviceNotFoundEvent = new DeviceNotFoundEvent(evt.getDialog());
-                    deviceNotFoundEvent.setCallId(callIdHeader.getCallId());
-                    SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(deviceNotFoundEvent);
-                    sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()).response(eventResult);
-                };
-            }else {
-                ContentTypeHeader header = (ContentTypeHeader)evt.getRequest().getHeader(ContentTypeHeader.NAME);
-                String contentType = header.getContentType();
-                String contentSubType = header.getContentSubType();
-                if ("Application".equalsIgnoreCase(contentType) && "MANSRTSP".equalsIgnoreCase(contentSubType)) {
-                    SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
-                    String streamId = sendRtpItem.getStreamId();
-                    InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId);
-                    if (null == inviteInfo) {
-                        responseAck(request, Response.NOT_FOUND, "stream " + streamId + " not found");
-                        return;
-                    }
-                    Device device1 = storager.queryVideoDevice(inviteInfo.getDeviceId());
-                    if (inviteInfo.getStreamInfo() != null) {
-                        cmder.playbackControlCmd(device1,inviteInfo.getStreamInfo(),new String(evt.getRequest().getRawContent()),eventResult -> {
-                            // 失败的回复
-                            try {
-                                responseAck(request, eventResult.statusCode, eventResult.msg);
-                            } catch (SipException | InvalidArgumentException | ParseException e) {
-                                logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage());
-                            }
-                        }, eventResult -> {
-                            // 成功的回复
-                            try {
-                                responseAck(request, eventResult.statusCode);
-                            } catch (SipException | InvalidArgumentException | ParseException e) {
-                                logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage());
-                            }
-                        });
-                    }
-
-                }
-            }
-        } catch (SipException e) {
-            logger.warn("SIP 回复错误", e);
-        } catch (InvalidArgumentException e) {
-            logger.warn("参数无效", e);
-        } catch (ParseException e) {
-            logger.warn("SIP回复时解析异常", e);
-        }
-    }
-
-
-}
+package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info;
+
+import com.genersoft.iot.vmp.common.InviteInfo;
+import com.genersoft.iot.vmp.common.InviteSessionType;
+import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
+import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
+import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
+import com.genersoft.iot.vmp.service.IInviteStreamService;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.SipException;
+import javax.sip.header.CallIdHeader;
+import javax.sip.header.ContentTypeHeader;
+import javax.sip.message.Response;
+import java.text.ParseException;
+
+@Component
+public class InfoRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
+
+    private final static Logger logger = LoggerFactory.getLogger(InfoRequestProcessor.class);
+
+    private final String method = "INFO";
+
+    @Autowired
+    private SIPProcessorObserver sipProcessorObserver;
+
+    @Autowired
+    private IVideoManagerStorage storage;
+
+    @Autowired
+    private SipSubscribe sipSubscribe;
+
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
+    @Autowired
+    private IInviteStreamService inviteStreamService;
+
+    @Autowired
+    private IVideoManagerStorage storager;
+
+    @Autowired
+    private SIPCommander cmder;
+
+    @Autowired
+    private VideoStreamSessionManager sessionManager;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        // 添加消息处理的订阅
+        sipProcessorObserver.addRequestProcessor(method, this);
+    }
+
+    @Override
+    public void process(RequestEvent evt) {
+        logger.debug("接收到消息:" + evt.getRequest());
+        SIPRequest request = (SIPRequest) evt.getRequest();
+        String deviceId = SipUtils.getUserIdFromFromHeader(request);
+        CallIdHeader callIdHeader = request.getCallIdHeader();
+        // 先从会话内查找
+        SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null);
+
+        // 兼容海康 媒体通知 消息from字段不是设备ID的问题
+        if (ssrcTransaction != null) {
+            deviceId = ssrcTransaction.getDeviceId();
+        }
+        // 查询设备是否存在
+        Device device = redisCatchStorage.getDevice(deviceId);
+        // 查询上级平台是否存在
+        ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId);
+        try {
+            if (device != null && parentPlatform != null) {
+                logger.warn("[重复]平台与设备编号重复:{}", deviceId);
+                String hostAddress = request.getRemoteAddress().getHostAddress();
+                int remotePort = request.getRemotePort();
+                if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) {
+                    parentPlatform = null;
+                }else {
+                    device = null;
+                }
+            }
+            if (device == null && parentPlatform == null) {
+                // 不存在则回复404
+                responseAck(request, Response.NOT_FOUND, "device "+ deviceId +" not found");
+                logger.warn("[设备未找到 ]: {}", deviceId);
+                if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){
+                    DeviceNotFoundEvent deviceNotFoundEvent = new DeviceNotFoundEvent(evt.getDialog());
+                    deviceNotFoundEvent.setCallId(callIdHeader.getCallId());
+                    SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(deviceNotFoundEvent);
+                    sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()).response(eventResult);
+                };
+            }else {
+                ContentTypeHeader header = (ContentTypeHeader)evt.getRequest().getHeader(ContentTypeHeader.NAME);
+                String contentType = header.getContentType();
+                String contentSubType = header.getContentSubType();
+                if ("Application".equalsIgnoreCase(contentType) && "MANSRTSP".equalsIgnoreCase(contentSubType)) {
+                    SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
+                    String streamId = sendRtpItem.getStreamId();
+                    InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId);
+                    if (null == inviteInfo) {
+                        responseAck(request, Response.NOT_FOUND, "stream " + streamId + " not found");
+                        return;
+                    }
+                    Device device1 = storager.queryVideoDevice(inviteInfo.getDeviceId());
+                    if (inviteInfo.getStreamInfo() != null) {
+                        cmder.playbackControlCmd(device1,inviteInfo.getStreamInfo(),new String(evt.getRequest().getRawContent()),eventResult -> {
+                            // 失败的回复
+                            try {
+                                responseAck(request, eventResult.statusCode, eventResult.msg);
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage());
+                            }
+                        }, eventResult -> {
+                            // 成功的回复
+                            try {
+                                responseAck(request, eventResult.statusCode);
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage());
+                            }
+                        });
+                    }
+
+                }
+            }
+        } catch (SipException e) {
+            logger.warn("SIP 回复错误", e);
+        } catch (InvalidArgumentException e) {
+            logger.warn("参数无效", e);
+        } catch (ParseException e) {
+            logger.warn("SIP回复时解析异常", e);
+        }
+    }
+
+
+}

+ 8 - 3
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/IMessageHandler.java

@@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import gov.nist.javax.sip.message.SIPRequest;
+import org.apache.commons.lang3.ObjectUtils;
 import org.dom4j.Element;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -68,7 +69,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
         } catch (SipException | InvalidArgumentException | ParseException e) {
             logger.error("[命令发送失败] 心跳回复: {}", e.getMessage());
         }
-        if (DateUtil.getDifferenceForNow(device.getKeepaliveTime()) <= 3000L){
+        if (!ObjectUtils.isEmpty(device.getKeepaliveTime()) && DateUtil.getDifferenceForNow(device.getKeepaliveTime()) <= 3000L) {
             logger.info("[收到心跳] 心跳发送过于频繁,已忽略 device: {}, callId: {}", device.getDeviceId(), request.getCallIdHeader().getCallId());
             return;
         }
@@ -109,7 +110,11 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp
 
     @Override
     public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element element) {
-        // 不会收到上级平台的心跳信息
-
+        // 个别平台保活不回复200OK会判定离线
+        try {
+            responseAck((SIPRequest) evt.getRequest(), Response.OK);
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.error("[命令发送失败] 心跳回复: {}", e.getMessage());
+        }
     }
 }

+ 646 - 646
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java

@@ -1,647 +1,647 @@
-package com.genersoft.iot.vmp.gb28181.utils;
-
-import com.alibaba.fastjson2.JSONArray;
-import com.alibaba.fastjson2.JSONObject;
-import com.genersoft.iot.vmp.common.CivilCodePo;
-import com.genersoft.iot.vmp.conf.CivilCodeFileConf;
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
-import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
-import com.genersoft.iot.vmp.utils.DateUtil;
-import org.apache.commons.lang3.math.NumberUtils;
-import org.dom4j.Attribute;
-import org.dom4j.Document;
-import org.dom4j.DocumentException;
-import org.dom4j.Element;
-import org.dom4j.io.SAXReader;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.ObjectUtils;
-import org.springframework.util.ReflectionUtils;
-
-import javax.sip.RequestEvent;
-import javax.sip.message.Request;
-import java.io.ByteArrayInputStream;
-import java.io.StringReader;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.util.*;
-
-/**
- * 基于dom4j的工具包
- *
- *
- */
-public class XmlUtil {
-    /**
-     * 日志服务
-     */
-    private static Logger logger = LoggerFactory.getLogger(XmlUtil.class);
-
-    /**
-     * 解析XML为Document对象
-     *
-     * @param xml 被解析的XMl
-     *
-     * @return Document
-     */
-    public static Element parseXml(String xml) {
-        Document document = null;
-        //
-        StringReader sr = new StringReader(xml);
-        SAXReader saxReader = new SAXReader();
-        try {
-            document = saxReader.read(sr);
-        } catch (DocumentException e) {
-            logger.error("解析失败", e);
-        }
-        return null == document ? null : document.getRootElement();
-    }
-
-    /**
-     * 获取element对象的text的值
-     *
-     * @param em  节点的对象
-     * @param tag 节点的tag
-     * @return 节点
-     */
-    public static String getText(Element em, String tag) {
-        if (null == em) {
-            return null;
-        }
-        Element e = em.element(tag);
-        //
-        return null == e ? null : e.getText().trim();
-    }
-
-    /**
-     * 递归解析xml节点,适用于 多节点数据
-     *
-     * @param node     node
-     * @param nodeName nodeName
-     * @return List<Map<String, Object>>
-     */
-    public static List<Map<String, Object>> listNodes(Element node, String nodeName) {
-        if (null == node) {
-            return null;
-        }
-        // 初始化返回
-        List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
-        // 首先获取当前节点的所有属性节点
-        List<Attribute> list = node.attributes();
-
-        Map<String, Object> map = null;
-        // 遍历属性节点
-        for (Attribute attribute : list) {
-            if (nodeName.equals(node.getName())) {
-                if (null == map) {
-                    map = new HashMap<String, Object>();
-                    listMap.add(map);
-                }
-                // 取到的节点属性放到map中
-                map.put(attribute.getName(), attribute.getValue());
-            }
-
-        }
-        // 遍历当前节点下的所有节点 ,nodeName 要解析的节点名称
-        // 使用递归
-        Iterator<Element> iterator = node.elementIterator();
-        while (iterator.hasNext()) {
-            Element e = iterator.next();
-            listMap.addAll(listNodes(e, nodeName));
-        }
-        return listMap;
-    }
-
-    /**
-     * xml转json
-     *
-     * @param element
-     * @param json
-     */
-    public static void node2Json(Element element, JSONObject json) {
-        // 如果是属性
-        for (Object o : element.attributes()) {
-            Attribute attr = (Attribute) o;
-            if (!ObjectUtils.isEmpty(attr.getValue())) {
-                json.put("@" + attr.getName(), attr.getValue());
-            }
-        }
-        List<Element> chdEl = element.elements();
-        if (chdEl.isEmpty() && !ObjectUtils.isEmpty(element.getText())) {// 如果没有子元素,只有一个值
-            json.put(element.getName(), element.getText());
-        }
-
-        for (Element e : chdEl) {   // 有子元素
-            if (!e.elements().isEmpty()) {  // 子元素也有子元素
-                JSONObject chdjson = new JSONObject();
-                node2Json(e, chdjson);
-                Object o = json.get(e.getName());
-                if (o != null) {
-                    JSONArray jsona = null;
-                    if (o instanceof JSONObject) {  // 如果此元素已存在,则转为jsonArray
-                        JSONObject jsono = (JSONObject) o;
-                        json.remove(e.getName());
-                        jsona = new JSONArray();
-                        jsona.add(jsono);
-                        jsona.add(chdjson);
-                    }
-                    if (o instanceof JSONArray) {
-                        jsona = (JSONArray) o;
-                        jsona.add(chdjson);
-                    }
-                    json.put(e.getName(), jsona);
-                } else {
-                    if (!chdjson.isEmpty()) {
-                        json.put(e.getName(), chdjson);
-                    }
-                }
-            } else { // 子元素没有子元素
-                for (Object o : element.attributes()) {
-                    Attribute attr = (Attribute) o;
-                    if (!ObjectUtils.isEmpty(attr.getValue())) {
-                        json.put("@" + attr.getName(), attr.getValue());
-                    }
-                }
-                if (!e.getText().isEmpty()) {
-                    json.put(e.getName(), e.getText());
-                }
-            }
-        }
-    }
-    public static  Element getRootElement(RequestEvent evt) throws DocumentException {
-
-        return getRootElement(evt, "gb2312");
-    }
-
-    public static Element getRootElement(RequestEvent evt, String charset) throws DocumentException {
-        Request request = evt.getRequest();
-        return getRootElement(request.getRawContent(), charset);
-    }
-
-    public static Element getRootElement(byte[] content, String charset) throws DocumentException {
-        if (charset == null) {
-            charset = "gb2312";
-        }
-        SAXReader reader = new SAXReader();
-        reader.setEncoding(charset);
-        Document xml = reader.read(new ByteArrayInputStream(content));
-        return xml.getRootElement();
-    }
-
-    private enum ChannelType{
-        CivilCode, BusinessGroup,VirtualOrganization,Other
-    }
-
-    public static DeviceChannel channelContentHandler(Element itemDevice, Device device, String event, CivilCodeFileConf civilCodeFileConf){
-        DeviceChannel deviceChannel = new DeviceChannel();
-        deviceChannel.setDeviceId(device.getDeviceId());
-        Element channdelIdElement = itemDevice.element("DeviceID");
-        if (channdelIdElement == null) {
-            logger.warn("解析Catalog消息时发现缺少 DeviceID");
-            return null;
-        }
-        String channelId = channdelIdElement.getTextTrim();
-        if (ObjectUtils.isEmpty(channelId)) {
-            logger.warn("解析Catalog消息时发现缺少 DeviceID");
-            return null;
-        }
-        deviceChannel.setChannelId(channelId);
-        if (event != null && !event.equals(CatalogEvent.ADD) && !event.equals(CatalogEvent.UPDATE)) {
-            // 除了ADD和update情况下需要识别全部内容,
-            return deviceChannel;
-        }
-        Element nameElement = itemDevice.element("Name");
-        if (nameElement != null) {
-            deviceChannel.setName(nameElement.getText());
-        }
-        if(channelId.length() <= 8) {
-            deviceChannel.setHasAudio(false);
-            CivilCodePo parentCode = civilCodeFileConf.getParentCode(channelId);
-            if (parentCode != null) {
-                deviceChannel.setParentId(parentCode.getCode());
-                deviceChannel.setCivilCode(parentCode.getCode());
-            }else {
-                logger.warn("[xml解析] 无法确定行政区划{}的上级行政区划", channelId);
-            }
-            deviceChannel.setStatus(true);
-            return deviceChannel;
-        }else {
-            if(channelId.length() != 20) {
-                logger.warn("[xml解析] 失败,编号不符合国标28181定义: {}", channelId);
-                return null;
-            }
-
-            int code = Integer.parseInt(channelId.substring(10, 13));
-            if (code == 136 || code == 137 || code == 138) {
-                deviceChannel.setHasAudio(true);
-            }else {
-                deviceChannel.setHasAudio(false);
-            }
-            // 设备厂商
-            String manufacturer = getText(itemDevice, "Manufacturer");
-            // 设备型号
-            String model = getText(itemDevice, "Model");
-            // 设备归属
-            String owner = getText(itemDevice, "Owner");
-            // 行政区域
-            String civilCode = getText(itemDevice, "CivilCode");
-            // 虚拟组织所属的业务分组ID,业务分组根据特定的业务需求制定,一个业务分组包含一组特定的虚拟组织
-            String businessGroupID = getText(itemDevice, "BusinessGroupID");
-            // 父设备/区域/系统ID
-            String parentID = getText(itemDevice, "ParentID");
-            if (parentID != null && parentID.equalsIgnoreCase("null")) {
-                parentID = null;
-            }
-            // 注册方式(必选)缺省为1;1:符合IETFRFC3261标准的认证注册模式;2:基于口令的双向认证注册模式;3:基于数字证书的双向认证注册模式
-            String registerWay = getText(itemDevice, "RegisterWay");
-            // 保密属性(必选)缺省为0;0:不涉密,1:涉密
-            String secrecy = getText(itemDevice, "Secrecy");
-            // 安装地址
-            String address = getText(itemDevice, "Address");
-
-            switch (code){
-                case 200:
-                    // 系统目录
-                    if (!ObjectUtils.isEmpty(manufacturer)) {
-                        deviceChannel.setManufacture(manufacturer);
-                    }
-                    if (!ObjectUtils.isEmpty(model)) {
-                        deviceChannel.setModel(model);
-                    }
-                    if (!ObjectUtils.isEmpty(owner)) {
-                        deviceChannel.setOwner(owner);
-                    }
-                    if (!ObjectUtils.isEmpty(civilCode)) {
-                        deviceChannel.setCivilCode(civilCode);
-                        deviceChannel.setParentId(civilCode);
-                    }else {
-                        if (!ObjectUtils.isEmpty(parentID)) {
-                            deviceChannel.setParentId(parentID);
-                        }
-                    }
-                    if (!ObjectUtils.isEmpty(address)) {
-                        deviceChannel.setAddress(address);
-                    }
-                    deviceChannel.setStatus(true);
-                    if (!ObjectUtils.isEmpty(registerWay)) {
-                        try {
-                            deviceChannel.setRegisterWay(Integer.parseInt(registerWay));
-                        }catch (NumberFormatException exception) {
-                            logger.warn("[xml解析] 从通道数据获取registerWay失败: {}", registerWay);
-                        }
-                    }
-                    if (!ObjectUtils.isEmpty(secrecy)) {
-                        deviceChannel.setSecrecy(secrecy);
-                    }
-                    return deviceChannel;
-                case 215:
-                    // 业务分组
-                    deviceChannel.setStatus(true);
-                    if (!ObjectUtils.isEmpty(parentID)) {
-                        if (!parentID.trim().equalsIgnoreCase(device.getDeviceId())) {
-                            deviceChannel.setParentId(parentID);
-                        }
-                    }else {
-                        logger.warn("[xml解析] 业务分组数据中缺少关键信息->ParentId");
-                        if (!ObjectUtils.isEmpty(civilCode)) {
-                            deviceChannel.setCivilCode(civilCode);
-                        }
-                    }
-                    break;
-                case 216:
-                    // 虚拟组织
-                    deviceChannel.setStatus(true);
-                    if (!ObjectUtils.isEmpty(businessGroupID)) {
-                        deviceChannel.setBusinessGroupId(businessGroupID);
-                    }
-
-                    if (!ObjectUtils.isEmpty(parentID)) {
-                        if (parentID.contains("/")) {
-                            String[] parentIdArray = parentID.split("/");
-                            parentID = parentIdArray[parentIdArray.length - 1];
-                        }
-                        deviceChannel.setParentId(parentID);
-                    }else {
-                        if (!ObjectUtils.isEmpty(businessGroupID)) {
-                            deviceChannel.setParentId(businessGroupID);
-                        }
-                    }
-                    break;
-                default:
-                    // 设备目录
-                    if (!ObjectUtils.isEmpty(manufacturer)) {
-                        deviceChannel.setManufacture(manufacturer);
-                    }
-                    if (!ObjectUtils.isEmpty(model)) {
-                        deviceChannel.setModel(model);
-                    }
-                    if (!ObjectUtils.isEmpty(owner)) {
-                        deviceChannel.setOwner(owner);
-                    }
-                    if (!ObjectUtils.isEmpty(civilCode)
-                            && civilCode.length() <= 8
-                            && NumberUtils.isParsable(civilCode)
-                            && civilCode.length()%2 == 0
-                    ) {
-                        deviceChannel.setCivilCode(civilCode);
-                    }
-                    if (!ObjectUtils.isEmpty(businessGroupID)) {
-                        deviceChannel.setBusinessGroupId(businessGroupID);
-                    }
-
-                    // 警区
-                    String block = getText(itemDevice, "Block");
-                    if (!ObjectUtils.isEmpty(block)) {
-                        deviceChannel.setBlock(block);
-                    }
-                    if (!ObjectUtils.isEmpty(address)) {
-                        deviceChannel.setAddress(address);
-                    }
-
-                    if (!ObjectUtils.isEmpty(secrecy)) {
-                        deviceChannel.setSecrecy(secrecy);
-                    }
-
-                    // 当为设备时,是否有子设备(必选)1有,0没有
-                    String parental = getText(itemDevice, "Parental");
-                    if (!ObjectUtils.isEmpty(parental)) {
-                        try {
-                            // 由于海康会错误的发送65535作为这里的取值,所以这里除非是0否则认为是1
-                            if (!ObjectUtils.isEmpty(parental) && parental.length() == 1 && Integer.parseInt(parental) == 0) {
-                                deviceChannel.setParental(0);
-                            }else {
-                                deviceChannel.setParental(1);
-                            }
-                        }catch (NumberFormatException e) {
-                            logger.warn("[xml解析] 从通道数据获取 parental失败: {}", parental);
-                        }
-                    }
-                    // 父设备/区域/系统ID
-
-                    if (!ObjectUtils.isEmpty(parentID) ) {
-                        if (parentID.contains("/")) {
-                            String[] parentIdArray = parentID.split("/");
-                            deviceChannel.setParentId(parentIdArray[parentIdArray.length - 1]);
-                        }else {
-                            if (parentID.length()%2 == 0) {
-                                deviceChannel.setParentId(parentID);
-                            }else {
-                                logger.warn("[xml解析] 不规范的parentID:{}, 已舍弃", parentID);
-                            }
-                        }
-                    }else {
-                        if (!ObjectUtils.isEmpty(businessGroupID)) {
-                            deviceChannel.setParentId(businessGroupID);
-                        }else {
-                            if (!ObjectUtils.isEmpty(deviceChannel.getCivilCode())) {
-                                deviceChannel.setParentId(deviceChannel.getCivilCode());
-                            }
-                        }
-                    }
-                    // 注册方式
-                    if (!ObjectUtils.isEmpty(registerWay)) {
-                        try {
-                            int registerWayInt = Integer.parseInt(registerWay);
-                            deviceChannel.setRegisterWay(registerWayInt);
-                        }catch (NumberFormatException exception) {
-                            logger.warn("[xml解析] 从通道数据获取registerWay失败: {}", registerWay);
-                            deviceChannel.setRegisterWay(1);
-                        }
-                    }else {
-                        deviceChannel.setRegisterWay(1);
-                    }
-
-                    // 信令安全模式(可选)缺省为0; 0:不采用;2:S/MIME 签名方式;3:S/MIME加密签名同时采用方式;4:数字摘要方式
-                    String safetyWay = getText(itemDevice, "SafetyWay");
-                    if (!ObjectUtils.isEmpty(safetyWay)) {
-                        try {
-                            deviceChannel.setSafetyWay(Integer.parseInt(safetyWay));
-                        }catch (NumberFormatException e) {
-                            logger.warn("[xml解析] 从通道数据获取 safetyWay失败: {}", safetyWay);
-                        }
-                    }
-
-                    // 证书序列号(有证书的设备必选)
-                    String certNum = getText(itemDevice, "CertNum");
-                    if (!ObjectUtils.isEmpty(certNum)) {
-                        deviceChannel.setCertNum(certNum);
-                    }
-
-                    // 证书有效标识(有证书的设备必选)缺省为0;证书有效标识:0:无效 1:有效
-                    String certifiable = getText(itemDevice, "Certifiable");
-                    if (!ObjectUtils.isEmpty(certifiable)) {
-                        try {
-                            deviceChannel.setCertifiable(Integer.parseInt(certifiable));
-                        }catch (NumberFormatException e) {
-                            logger.warn("[xml解析] 从通道数据获取 Certifiable失败: {}", certifiable);
-                        }
-                    }
-
-                    // 无效原因码(有证书且证书无效的设备必选)
-                    String errCode = getText(itemDevice, "ErrCode");
-                    if (!ObjectUtils.isEmpty(errCode)) {
-                        try {
-                            deviceChannel.setErrCode(Integer.parseInt(errCode));
-                        }catch (NumberFormatException e) {
-                            logger.warn("[xml解析] 从通道数据获取 ErrCode失败: {}", errCode);
-                        }
-                    }
-
-                    // 证书终止有效期(有证书的设备必选)
-                    String endTime = getText(itemDevice, "EndTime");
-                    if (!ObjectUtils.isEmpty(endTime)) {
-                        deviceChannel.setEndTime(endTime);
-                    }
-
-
-                    // 设备/区域/系统IP地址
-                    String ipAddress = getText(itemDevice, "IPAddress");
-                    if (!ObjectUtils.isEmpty(ipAddress)) {
-                        deviceChannel.setIpAddress(ipAddress);
-                    }
-
-                    // 设备/区域/系统端口
-                    String port = getText(itemDevice, "Port");
-                    if (!ObjectUtils.isEmpty(port)) {
-                        try {
-                            deviceChannel.setPort(Integer.parseInt(port));
-                        }catch (NumberFormatException e) {
-                            logger.warn("[xml解析] 从通道数据获取 Port失败: {}", port);
-                        }
-                    }
-
-                    // 设备口令
-                    String password = getText(itemDevice, "Password");
-                    if (!ObjectUtils.isEmpty(password)) {
-                        deviceChannel.setPassword(password);
-                    }
-
-
-                    // 设备状态
-                    String status = getText(itemDevice, "Status");
-                    if (status != null) {
-                        // ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理
-                        if (status.equals("ON") || status.equals("On") || status.equals("ONLINE") || status.equals("OK")) {
-                            deviceChannel.setStatus(true);
-                        }
-                        if (status.equals("OFF") || status.equals("Off") || status.equals("OFFLINE")) {
-                            deviceChannel.setStatus(false);
-                        }
-                    }else {
-                        deviceChannel.setStatus(true);
-                    }
-
-                    // 经度
-                    String longitude = getText(itemDevice, "Longitude");
-                    if (NumericUtil.isDouble(longitude)) {
-                        deviceChannel.setLongitude(Double.parseDouble(longitude));
-                    } else {
-                        deviceChannel.setLongitude(0.00);
-                    }
-
-                    // 纬度
-                    String latitude = getText(itemDevice, "Latitude");
-                    if (NumericUtil.isDouble(latitude)) {
-                        deviceChannel.setLatitude(Double.parseDouble(latitude));
-                    } else {
-                        deviceChannel.setLatitude(0.00);
-                    }
-
-                    deviceChannel.setGpsTime(DateUtil.getNow());
-
-                    // -摄像机类型扩展,标识摄像机类型:1-球机;2-半球;3-固定枪机;4-遥控枪机。当目录项为摄像机时可选
-                    String ptzType = getText(itemDevice, "PTZType");
-                    if (ObjectUtils.isEmpty(ptzType)) {
-                        //兼容INFO中的信息
-                        Element info = itemDevice.element("Info");
-                        String ptzTypeFromInfo = XmlUtil.getText(info, "PTZType");
-                        if(!ObjectUtils.isEmpty(ptzTypeFromInfo)){
-                            try {
-                                deviceChannel.setPTZType(Integer.parseInt(ptzTypeFromInfo));
-                            }catch (NumberFormatException e){
-                                logger.warn("[xml解析] 从通道数据info中获取PTZType失败: {}", ptzTypeFromInfo);
-                            }
-                        }
-                    } else {
-                        try {
-                            deviceChannel.setPTZType(Integer.parseInt(ptzType));
-                        }catch (NumberFormatException e){
-                            logger.warn("[xml解析] 从通道数据中获取PTZType失败: {}", ptzType);
-                        }
-                    }
-
-                    // TODO 摄像机位置类型扩展。
-                    // 1-省际检查站、
-                    // 2-党政机关、
-                    // 3-车站码头、
-                    // 4-中心广场、
-                    // 5-体育场馆、
-                    // 6-商业中心、
-                    // 7-宗教场所、
-                    // 8-校园周边、
-                    // 9-治安复杂区域、
-                    // 10-交通干线。
-                    // String positionType = getText(itemDevice, "PositionType");
-
-                    // TODO 摄像机安装位置室外、室内属性。1-室外、2-室内。
-                    // String roomType = getText(itemDevice, "RoomType");
-                    // TODO 摄像机用途属性
-                    // String useType = getText(itemDevice, "UseType");
-                    // TODO 摄像机补光属性。1-无补光、2-红外补光、3-白光补光
-                    // String supplyLightType = getText(itemDevice, "SupplyLightType");
-                    // TODO 摄像机监视方位属性。1-东、2-西、3-南、4-北、5-东南、6-东北、7-西南、8-西北。
-                    // String directionType = getText(itemDevice, "DirectionType");
-                    // TODO 摄像机支持的分辨率,可有多个分辨率值,各个取值间以“/”分隔。分辨率取值参见附录 F中SDPf字段规定
-                    // String resolution = getText(itemDevice, "Resolution");
-
-                    // TODO 下载倍速范围(可选),各可选参数以“/”分隔,如设备支持1,2,4倍速下载则应写为“1/2/4
-                    // String downloadSpeed = getText(itemDevice, "DownloadSpeed");
-                    // TODO 空域编码能力,取值0:不支持;1:1级增强(1个增强层);2:2级增强(2个增强层);3:3级增强(3个增强层)
-                    // String svcSpaceSupportMode = getText(itemDevice, "SVCSpaceSupportMode");
-                    // TODO 时域编码能力,取值0:不支持;1:1级增强;2:2级增强;3:3级增强
-                    // String svcTimeSupportMode = getText(itemDevice, "SVCTimeSupportMode");
-
-
-                    deviceChannel.setSecrecy(secrecy);
-                    break;
-            }
-        }
-
-        return deviceChannel;
-    }
-
-    /**
-     * 新增方法支持内部嵌套
-     *
-     * @param element xmlElement
-     * @param clazz 结果类
-     * @param <T> 泛型
-     * @return 结果对象
-     * @throws NoSuchMethodException
-     * @throws InvocationTargetException
-     * @throws InstantiationException
-     * @throws IllegalAccessException
-     */
-    public static <T> T loadElement(Element element, Class<T> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
-        Field[] fields = clazz.getDeclaredFields();
-        T t = clazz.getDeclaredConstructor().newInstance();
-        for (Field field : fields) {
-            ReflectionUtils.makeAccessible(field);
-            MessageElement annotation = field.getAnnotation(MessageElement.class);
-            if (annotation == null) {
-                continue;
-            }
-            String value = annotation.value();
-            String subVal = annotation.subVal();
-            Element element1 = element.element(value);
-            if (element1 == null) {
-                continue;
-            }
-            if ("".equals(subVal)) {
-                // 无下级数据
-                Object fieldVal = element1.isTextOnly() ? element1.getText() : loadElement(element1, field.getType());
-                Object o = simpleTypeDeal(field.getType(), fieldVal);
-                ReflectionUtils.setField(field, t,  o);
-            } else {
-                // 存在下级数据
-                ArrayList<Object> list = new ArrayList<>();
-                Type genericType = field.getGenericType();
-                if (!(genericType instanceof ParameterizedType)) {
-                    continue;
-                }
-                Class<?> aClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
-                for (Element element2 : element1.elements(subVal)) {
-                    list.add(loadElement(element2, aClass));
-                }
-                ReflectionUtils.setField(field, t, list);
-            }
-        }
-        return t;
-    }
-
-    /**
-     * 简单类型处理
-     *
-     * @param tClass
-     * @param val
-     * @return
-     */
-    private static Object simpleTypeDeal(Class<?> tClass, Object val) {
-        if (tClass.equals(String.class)) {
-            return val.toString();
-        }
-        if (tClass.equals(Integer.class)) {
-            return Integer.valueOf(val.toString());
-        }
-        if (tClass.equals(Double.class)) {
-            return Double.valueOf(val.toString());
-        }
-        if (tClass.equals(Long.class)) {
-            return Long.valueOf(val.toString());
-        }
-        return val;
-    }
+package com.genersoft.iot.vmp.gb28181.utils;
+
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.common.CivilCodePo;
+import com.genersoft.iot.vmp.conf.CivilCodeFileConf;
+import com.genersoft.iot.vmp.gb28181.bean.Device;
+import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
+import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
+import com.genersoft.iot.vmp.utils.DateUtil;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.dom4j.Attribute;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
+
+import javax.sip.RequestEvent;
+import javax.sip.message.Request;
+import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.*;
+
+/**
+ * 基于dom4j的工具包
+ *
+ *
+ */
+public class XmlUtil {
+    /**
+     * 日志服务
+     */
+    private static Logger logger = LoggerFactory.getLogger(XmlUtil.class);
+
+    /**
+     * 解析XML为Document对象
+     *
+     * @param xml 被解析的XMl
+     *
+     * @return Document
+     */
+    public static Element parseXml(String xml) {
+        Document document = null;
+        //
+        StringReader sr = new StringReader(xml);
+        SAXReader saxReader = new SAXReader();
+        try {
+            document = saxReader.read(sr);
+        } catch (DocumentException e) {
+            logger.error("解析失败", e);
+        }
+        return null == document ? null : document.getRootElement();
+    }
+
+    /**
+     * 获取element对象的text的值
+     *
+     * @param em  节点的对象
+     * @param tag 节点的tag
+     * @return 节点
+     */
+    public static String getText(Element em, String tag) {
+        if (null == em) {
+            return null;
+        }
+        Element e = em.element(tag);
+        //
+        return null == e ? null : e.getText().trim();
+    }
+
+    /**
+     * 递归解析xml节点,适用于 多节点数据
+     *
+     * @param node     node
+     * @param nodeName nodeName
+     * @return List<Map<String, Object>>
+     */
+    public static List<Map<String, Object>> listNodes(Element node, String nodeName) {
+        if (null == node) {
+            return null;
+        }
+        // 初始化返回
+        List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
+        // 首先获取当前节点的所有属性节点
+        List<Attribute> list = node.attributes();
+
+        Map<String, Object> map = null;
+        // 遍历属性节点
+        for (Attribute attribute : list) {
+            if (nodeName.equals(node.getName())) {
+                if (null == map) {
+                    map = new HashMap<String, Object>();
+                    listMap.add(map);
+                }
+                // 取到的节点属性放到map中
+                map.put(attribute.getName(), attribute.getValue());
+            }
+
+        }
+        // 遍历当前节点下的所有节点 ,nodeName 要解析的节点名称
+        // 使用递归
+        Iterator<Element> iterator = node.elementIterator();
+        while (iterator.hasNext()) {
+            Element e = iterator.next();
+            listMap.addAll(listNodes(e, nodeName));
+        }
+        return listMap;
+    }
+
+    /**
+     * xml转json
+     *
+     * @param element
+     * @param json
+     */
+    public static void node2Json(Element element, JSONObject json) {
+        // 如果是属性
+        for (Object o : element.attributes()) {
+            Attribute attr = (Attribute) o;
+            if (!ObjectUtils.isEmpty(attr.getValue())) {
+                json.put("@" + attr.getName(), attr.getValue());
+            }
+        }
+        List<Element> chdEl = element.elements();
+        if (chdEl.isEmpty() && !ObjectUtils.isEmpty(element.getText())) {// 如果没有子元素,只有一个值
+            json.put(element.getName(), element.getText());
+        }
+
+        for (Element e : chdEl) {   // 有子元素
+            if (!e.elements().isEmpty()) {  // 子元素也有子元素
+                JSONObject chdjson = new JSONObject();
+                node2Json(e, chdjson);
+                Object o = json.get(e.getName());
+                if (o != null) {
+                    JSONArray jsona = null;
+                    if (o instanceof JSONObject) {  // 如果此元素已存在,则转为jsonArray
+                        JSONObject jsono = (JSONObject) o;
+                        json.remove(e.getName());
+                        jsona = new JSONArray();
+                        jsona.add(jsono);
+                        jsona.add(chdjson);
+                    }
+                    if (o instanceof JSONArray) {
+                        jsona = (JSONArray) o;
+                        jsona.add(chdjson);
+                    }
+                    json.put(e.getName(), jsona);
+                } else {
+                    if (!chdjson.isEmpty()) {
+                        json.put(e.getName(), chdjson);
+                    }
+                }
+            } else { // 子元素没有子元素
+                for (Object o : element.attributes()) {
+                    Attribute attr = (Attribute) o;
+                    if (!ObjectUtils.isEmpty(attr.getValue())) {
+                        json.put("@" + attr.getName(), attr.getValue());
+                    }
+                }
+                if (!e.getText().isEmpty()) {
+                    json.put(e.getName(), e.getText());
+                }
+            }
+        }
+    }
+    public static  Element getRootElement(RequestEvent evt) throws DocumentException {
+
+        return getRootElement(evt, "gb2312");
+    }
+
+    public static Element getRootElement(RequestEvent evt, String charset) throws DocumentException {
+        Request request = evt.getRequest();
+        return getRootElement(request.getRawContent(), charset);
+    }
+
+    public static Element getRootElement(byte[] content, String charset) throws DocumentException {
+        if (charset == null) {
+            charset = "gb2312";
+        }
+        SAXReader reader = new SAXReader();
+        reader.setEncoding(charset);
+        Document xml = reader.read(new ByteArrayInputStream(content));
+        return xml.getRootElement();
+    }
+
+    private enum ChannelType{
+        CivilCode, BusinessGroup,VirtualOrganization,Other
+    }
+
+    public static DeviceChannel channelContentHandler(Element itemDevice, Device device, String event, CivilCodeFileConf civilCodeFileConf){
+        DeviceChannel deviceChannel = new DeviceChannel();
+        deviceChannel.setDeviceId(device.getDeviceId());
+        Element channdelIdElement = itemDevice.element("DeviceID");
+        if (channdelIdElement == null) {
+            logger.warn("解析Catalog消息时发现缺少 DeviceID");
+            return null;
+        }
+        String channelId = channdelIdElement.getTextTrim();
+        if (ObjectUtils.isEmpty(channelId)) {
+            logger.warn("解析Catalog消息时发现缺少 DeviceID");
+            return null;
+        }
+        deviceChannel.setChannelId(channelId);
+        if (event != null && !event.equals(CatalogEvent.ADD) && !event.equals(CatalogEvent.UPDATE)) {
+            // 除了ADD和update情况下需要识别全部内容,
+            return deviceChannel;
+        }
+        Element nameElement = itemDevice.element("Name");
+        if (nameElement != null) {
+            deviceChannel.setName(nameElement.getText());
+        }
+        if(channelId.length() <= 8) {
+            deviceChannel.setHasAudio(false);
+            CivilCodePo parentCode = civilCodeFileConf.getParentCode(channelId);
+            if (parentCode != null) {
+                deviceChannel.setParentId(parentCode.getCode());
+                deviceChannel.setCivilCode(parentCode.getCode());
+            }else {
+                logger.warn("[xml解析] 无法确定行政区划{}的上级行政区划", channelId);
+            }
+            deviceChannel.setStatus(true);
+            return deviceChannel;
+        }else {
+            if(channelId.length() != 20) {
+                logger.warn("[xml解析] 失败,编号不符合国标28181定义: {}", channelId);
+                return null;
+            }
+
+            int code = Integer.parseInt(channelId.substring(10, 13));
+            if (code == 136 || code == 137 || code == 138) {
+                deviceChannel.setHasAudio(true);
+            }else {
+                deviceChannel.setHasAudio(false);
+            }
+            // 设备厂商
+            String manufacturer = getText(itemDevice, "Manufacturer");
+            // 设备型号
+            String model = getText(itemDevice, "Model");
+            // 设备归属
+            String owner = getText(itemDevice, "Owner");
+            // 行政区域
+            String civilCode = getText(itemDevice, "CivilCode");
+            // 虚拟组织所属的业务分组ID,业务分组根据特定的业务需求制定,一个业务分组包含一组特定的虚拟组织
+            String businessGroupID = getText(itemDevice, "BusinessGroupID");
+            // 父设备/区域/系统ID
+            String parentID = getText(itemDevice, "ParentID");
+            if (parentID != null && parentID.equalsIgnoreCase("null")) {
+                parentID = null;
+            }
+            // 注册方式(必选)缺省为1;1:符合IETFRFC3261标准的认证注册模式;2:基于口令的双向认证注册模式;3:基于数字证书的双向认证注册模式
+            String registerWay = getText(itemDevice, "RegisterWay");
+            // 保密属性(必选)缺省为0;0:不涉密,1:涉密
+            String secrecy = getText(itemDevice, "Secrecy");
+            // 安装地址
+            String address = getText(itemDevice, "Address");
+
+            switch (code){
+                case 200:
+                    // 系统目录
+                    if (!ObjectUtils.isEmpty(manufacturer)) {
+                        deviceChannel.setManufacture(manufacturer);
+                    }
+                    if (!ObjectUtils.isEmpty(model)) {
+                        deviceChannel.setModel(model);
+                    }
+                    if (!ObjectUtils.isEmpty(owner)) {
+                        deviceChannel.setOwner(owner);
+                    }
+                    if (!ObjectUtils.isEmpty(civilCode)) {
+                        deviceChannel.setCivilCode(civilCode);
+                        deviceChannel.setParentId(civilCode);
+                    }else {
+                        if (!ObjectUtils.isEmpty(parentID)) {
+                            deviceChannel.setParentId(parentID);
+                        }
+                    }
+                    if (!ObjectUtils.isEmpty(address)) {
+                        deviceChannel.setAddress(address);
+                    }
+                    deviceChannel.setStatus(true);
+                    if (!ObjectUtils.isEmpty(registerWay)) {
+                        try {
+                            deviceChannel.setRegisterWay(Integer.parseInt(registerWay));
+                        }catch (NumberFormatException exception) {
+                            logger.warn("[xml解析] 从通道数据获取registerWay失败: {}", registerWay);
+                        }
+                    }
+                    if (!ObjectUtils.isEmpty(secrecy)) {
+                        deviceChannel.setSecrecy(secrecy);
+                    }
+                    return deviceChannel;
+                case 215:
+                    // 业务分组
+                    deviceChannel.setStatus(true);
+                    if (!ObjectUtils.isEmpty(parentID)) {
+                        if (!parentID.trim().equalsIgnoreCase(device.getDeviceId())) {
+                            deviceChannel.setParentId(parentID);
+                        }
+                    }else {
+                        logger.warn("[xml解析] 业务分组数据中缺少关键信息->ParentId");
+                        if (!ObjectUtils.isEmpty(civilCode)) {
+                            deviceChannel.setCivilCode(civilCode);
+                        }
+                    }
+                    break;
+                case 216:
+                    // 虚拟组织
+                    deviceChannel.setStatus(true);
+                    if (!ObjectUtils.isEmpty(businessGroupID)) {
+                        deviceChannel.setBusinessGroupId(businessGroupID);
+                    }
+
+                    if (!ObjectUtils.isEmpty(parentID)) {
+                        if (parentID.contains("/")) {
+                            String[] parentIdArray = parentID.split("/");
+                            parentID = parentIdArray[parentIdArray.length - 1];
+                        }
+                        deviceChannel.setParentId(parentID);
+                    }else {
+                        if (!ObjectUtils.isEmpty(businessGroupID)) {
+                            deviceChannel.setParentId(businessGroupID);
+                        }
+                    }
+                    break;
+                default:
+                    // 设备目录
+                    if (!ObjectUtils.isEmpty(manufacturer)) {
+                        deviceChannel.setManufacture(manufacturer);
+                    }
+                    if (!ObjectUtils.isEmpty(model)) {
+                        deviceChannel.setModel(model);
+                    }
+                    if (!ObjectUtils.isEmpty(owner)) {
+                        deviceChannel.setOwner(owner);
+                    }
+                    if (!ObjectUtils.isEmpty(civilCode)
+                            && civilCode.length() <= 8
+                            && NumberUtils.isParsable(civilCode)
+                            && civilCode.length()%2 == 0
+                    ) {
+                        deviceChannel.setCivilCode(civilCode);
+                    }
+                    if (!ObjectUtils.isEmpty(businessGroupID)) {
+                        deviceChannel.setBusinessGroupId(businessGroupID);
+                    }
+
+                    // 警区
+                    String block = getText(itemDevice, "Block");
+                    if (!ObjectUtils.isEmpty(block)) {
+                        deviceChannel.setBlock(block);
+                    }
+                    if (!ObjectUtils.isEmpty(address)) {
+                        deviceChannel.setAddress(address);
+                    }
+
+                    if (!ObjectUtils.isEmpty(secrecy)) {
+                        deviceChannel.setSecrecy(secrecy);
+                    }
+
+                    // 当为设备时,是否有子设备(必选)1有,0没有
+                    String parental = getText(itemDevice, "Parental");
+                    if (!ObjectUtils.isEmpty(parental)) {
+                        try {
+                            // 由于海康会错误的发送65535作为这里的取值,所以这里除非是0否则认为是1
+                            if (!ObjectUtils.isEmpty(parental) && parental.length() == 1 && Integer.parseInt(parental) == 0) {
+                                deviceChannel.setParental(0);
+                            }else {
+                                deviceChannel.setParental(1);
+                            }
+                        }catch (NumberFormatException e) {
+                            logger.warn("[xml解析] 从通道数据获取 parental失败: {}", parental);
+                        }
+                    }
+                    // 父设备/区域/系统ID
+
+                    if (!ObjectUtils.isEmpty(parentID) ) {
+                        if (parentID.contains("/")) {
+                            String[] parentIdArray = parentID.split("/");
+                            deviceChannel.setParentId(parentIdArray[parentIdArray.length - 1]);
+                        }else {
+                            if (parentID.length()%2 == 0) {
+                                deviceChannel.setParentId(parentID);
+                            }else {
+                                logger.warn("[xml解析] 不规范的parentID:{}, 已舍弃", parentID);
+                            }
+                        }
+                    }else {
+                        if (!ObjectUtils.isEmpty(businessGroupID)) {
+                            deviceChannel.setParentId(businessGroupID);
+                        }else {
+                            if (!ObjectUtils.isEmpty(deviceChannel.getCivilCode())) {
+                                deviceChannel.setParentId(deviceChannel.getCivilCode());
+                            }
+                        }
+                    }
+                    // 注册方式
+                    if (!ObjectUtils.isEmpty(registerWay)) {
+                        try {
+                            int registerWayInt = Integer.parseInt(registerWay);
+                            deviceChannel.setRegisterWay(registerWayInt);
+                        }catch (NumberFormatException exception) {
+                            logger.warn("[xml解析] 从通道数据获取registerWay失败: {}", registerWay);
+                            deviceChannel.setRegisterWay(1);
+                        }
+                    }else {
+                        deviceChannel.setRegisterWay(1);
+                    }
+
+                    // 信令安全模式(可选)缺省为0; 0:不采用;2:S/MIME 签名方式;3:S/MIME加密签名同时采用方式;4:数字摘要方式
+                    String safetyWay = getText(itemDevice, "SafetyWay");
+                    if (!ObjectUtils.isEmpty(safetyWay)) {
+                        try {
+                            deviceChannel.setSafetyWay(Integer.parseInt(safetyWay));
+                        }catch (NumberFormatException e) {
+                            logger.warn("[xml解析] 从通道数据获取 safetyWay失败: {}", safetyWay);
+                        }
+                    }
+
+                    // 证书序列号(有证书的设备必选)
+                    String certNum = getText(itemDevice, "CertNum");
+                    if (!ObjectUtils.isEmpty(certNum)) {
+                        deviceChannel.setCertNum(certNum);
+                    }
+
+                    // 证书有效标识(有证书的设备必选)缺省为0;证书有效标识:0:无效 1:有效
+                    String certifiable = getText(itemDevice, "Certifiable");
+                    if (!ObjectUtils.isEmpty(certifiable)) {
+                        try {
+                            deviceChannel.setCertifiable(Integer.parseInt(certifiable));
+                        }catch (NumberFormatException e) {
+                            logger.warn("[xml解析] 从通道数据获取 Certifiable失败: {}", certifiable);
+                        }
+                    }
+
+                    // 无效原因码(有证书且证书无效的设备必选)
+                    String errCode = getText(itemDevice, "ErrCode");
+                    if (!ObjectUtils.isEmpty(errCode)) {
+                        try {
+                            deviceChannel.setErrCode(Integer.parseInt(errCode));
+                        }catch (NumberFormatException e) {
+                            logger.warn("[xml解析] 从通道数据获取 ErrCode失败: {}", errCode);
+                        }
+                    }
+
+                    // 证书终止有效期(有证书的设备必选)
+                    String endTime = getText(itemDevice, "EndTime");
+                    if (!ObjectUtils.isEmpty(endTime)) {
+                        deviceChannel.setEndTime(endTime);
+                    }
+
+
+                    // 设备/区域/系统IP地址
+                    String ipAddress = getText(itemDevice, "IPAddress");
+                    if (!ObjectUtils.isEmpty(ipAddress)) {
+                        deviceChannel.setIpAddress(ipAddress);
+                    }
+
+                    // 设备/区域/系统端口
+                    String port = getText(itemDevice, "Port");
+                    if (!ObjectUtils.isEmpty(port)) {
+                        try {
+                            deviceChannel.setPort(Integer.parseInt(port));
+                        }catch (NumberFormatException e) {
+                            logger.warn("[xml解析] 从通道数据获取 Port失败: {}", port);
+                        }
+                    }
+
+                    // 设备口令
+                    String password = getText(itemDevice, "Password");
+                    if (!ObjectUtils.isEmpty(password)) {
+                        deviceChannel.setPassword(password);
+                    }
+
+
+                    // 设备状态
+                    String status = getText(itemDevice, "Status");
+                    if (status != null) {
+                        // ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理
+                        if (status.equals("ON") || status.equals("On") || status.equals("ONLINE") || status.equals("OK")) {
+                            deviceChannel.setStatus(true);
+                        }
+                        if (status.equals("OFF") || status.equals("Off") || status.equals("OFFLINE")) {
+                            deviceChannel.setStatus(false);
+                        }
+                    }else {
+                        deviceChannel.setStatus(true);
+                    }
+
+                    // 经度
+                    String longitude = getText(itemDevice, "Longitude");
+                    if (NumericUtil.isDouble(longitude)) {
+                        deviceChannel.setLongitude(Double.parseDouble(longitude));
+                    } else {
+                        deviceChannel.setLongitude(0.00);
+                    }
+
+                    // 纬度
+                    String latitude = getText(itemDevice, "Latitude");
+                    if (NumericUtil.isDouble(latitude)) {
+                        deviceChannel.setLatitude(Double.parseDouble(latitude));
+                    } else {
+                        deviceChannel.setLatitude(0.00);
+                    }
+
+                    deviceChannel.setGpsTime(DateUtil.getNow());
+
+                    // -摄像机类型扩展,标识摄像机类型:1-球机;2-半球;3-固定枪机;4-遥控枪机。当目录项为摄像机时可选
+                    String ptzType = getText(itemDevice, "PTZType");
+                    if (ObjectUtils.isEmpty(ptzType)) {
+                        //兼容INFO中的信息
+                        Element info = itemDevice.element("Info");
+                        String ptzTypeFromInfo = XmlUtil.getText(info, "PTZType");
+                        if(!ObjectUtils.isEmpty(ptzTypeFromInfo)){
+                            try {
+                                deviceChannel.setPTZType(Integer.parseInt(ptzTypeFromInfo));
+                            }catch (NumberFormatException e){
+                                logger.warn("[xml解析] 从通道数据info中获取PTZType失败: {}", ptzTypeFromInfo);
+                            }
+                        }
+                    } else {
+                        try {
+                            deviceChannel.setPTZType(Integer.parseInt(ptzType));
+                        }catch (NumberFormatException e){
+                            logger.warn("[xml解析] 从通道数据中获取PTZType失败: {}", ptzType);
+                        }
+                    }
+
+                    // TODO 摄像机位置类型扩展。
+                    // 1-省际检查站、
+                    // 2-党政机关、
+                    // 3-车站码头、
+                    // 4-中心广场、
+                    // 5-体育场馆、
+                    // 6-商业中心、
+                    // 7-宗教场所、
+                    // 8-校园周边、
+                    // 9-治安复杂区域、
+                    // 10-交通干线。
+                    // String positionType = getText(itemDevice, "PositionType");
+
+                    // TODO 摄像机安装位置室外、室内属性。1-室外、2-室内。
+                    // String roomType = getText(itemDevice, "RoomType");
+                    // TODO 摄像机用途属性
+                    // String useType = getText(itemDevice, "UseType");
+                    // TODO 摄像机补光属性。1-无补光、2-红外补光、3-白光补光
+                    // String supplyLightType = getText(itemDevice, "SupplyLightType");
+                    // TODO 摄像机监视方位属性。1-东、2-西、3-南、4-北、5-东南、6-东北、7-西南、8-西北。
+                    // String directionType = getText(itemDevice, "DirectionType");
+                    // TODO 摄像机支持的分辨率,可有多个分辨率值,各个取值间以“/”分隔。分辨率取值参见附录 F中SDPf字段规定
+                    // String resolution = getText(itemDevice, "Resolution");
+
+                    // TODO 下载倍速范围(可选),各可选参数以“/”分隔,如设备支持1,2,4倍速下载则应写为“1/2/4
+                    // String downloadSpeed = getText(itemDevice, "DownloadSpeed");
+                    // TODO 空域编码能力,取值0:不支持;1:1级增强(1个增强层);2:2级增强(2个增强层);3:3级增强(3个增强层)
+                    // String svcSpaceSupportMode = getText(itemDevice, "SVCSpaceSupportMode");
+                    // TODO 时域编码能力,取值0:不支持;1:1级增强;2:2级增强;3:3级增强
+                    // String svcTimeSupportMode = getText(itemDevice, "SVCTimeSupportMode");
+
+
+                    deviceChannel.setSecrecy(secrecy);
+                    break;
+            }
+        }
+
+        return deviceChannel;
+    }
+
+    /**
+     * 新增方法支持内部嵌套
+     *
+     * @param element xmlElement
+     * @param clazz 结果类
+     * @param <T> 泛型
+     * @return 结果对象
+     * @throws NoSuchMethodException
+     * @throws InvocationTargetException
+     * @throws InstantiationException
+     * @throws IllegalAccessException
+     */
+    public static <T> T loadElement(Element element, Class<T> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+        Field[] fields = clazz.getDeclaredFields();
+        T t = clazz.getDeclaredConstructor().newInstance();
+        for (Field field : fields) {
+            ReflectionUtils.makeAccessible(field);
+            MessageElement annotation = field.getAnnotation(MessageElement.class);
+            if (annotation == null) {
+                continue;
+            }
+            String value = annotation.value();
+            String subVal = annotation.subVal();
+            Element element1 = element.element(value);
+            if (element1 == null) {
+                continue;
+            }
+            if ("".equals(subVal)) {
+                // 无下级数据
+                Object fieldVal = element1.isTextOnly() ? element1.getText() : loadElement(element1, field.getType());
+                Object o = simpleTypeDeal(field.getType(), fieldVal);
+                ReflectionUtils.setField(field, t,  o);
+            } else {
+                // 存在下级数据
+                ArrayList<Object> list = new ArrayList<>();
+                Type genericType = field.getGenericType();
+                if (!(genericType instanceof ParameterizedType)) {
+                    continue;
+                }
+                Class<?> aClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
+                for (Element element2 : element1.elements(subVal)) {
+                    list.add(loadElement(element2, aClass));
+                }
+                ReflectionUtils.setField(field, t, list);
+            }
+        }
+        return t;
+    }
+
+    /**
+     * 简单类型处理
+     *
+     * @param tClass
+     * @param val
+     * @return
+     */
+    private static Object simpleTypeDeal(Class<?> tClass, Object val) {
+        if (tClass.equals(String.class)) {
+            return val.toString();
+        }
+        if (tClass.equals(Integer.class)) {
+            return Integer.valueOf(val.toString());
+        }
+        if (tClass.equals(Double.class)) {
+            return Double.valueOf(val.toString());
+        }
+        if (tClass.equals(Long.class)) {
+            return Long.valueOf(val.toString());
+        }
+        return val;
+    }
 }

+ 777 - 777
src/main/java/com/genersoft/iot/vmp/media/zlm/SendRtpPortManager.java

@@ -1,777 +1,777 @@
-package com.genersoft.iot.vmp.media.zlm;
-
-import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONObject;
-import com.genersoft.iot.vmp.common.InviteInfo;
-import com.genersoft.iot.vmp.common.InviteSessionType;
-import com.genersoft.iot.vmp.common.StreamInfo;
-import com.genersoft.iot.vmp.common.VideoManagerConstants;
-import com.genersoft.iot.vmp.conf.UserSetting;
-import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
-import com.genersoft.iot.vmp.gb28181.bean.*;
-import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
-import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
-import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
-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.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
-import com.genersoft.iot.vmp.media.zlm.dto.HookType;
-import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
-import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
-import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
-import com.genersoft.iot.vmp.media.zlm.dto.hook.*;
-import com.genersoft.iot.vmp.service.*;
-import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
-import com.genersoft.iot.vmp.service.bean.SSRCInfo;
-import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
-import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
-import com.genersoft.iot.vmp.utils.DateUtil;
-import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
-import com.genersoft.iot.vmp.vmanager.bean.OtherPsSendInfo;
-import com.genersoft.iot.vmp.vmanager.bean.OtherRtpSendInfo;
-import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
-import org.springframework.util.ObjectUtils;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.web.context.request.async.DeferredResult;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.sip.InvalidArgumentException;
-import javax.sip.SipException;
-import java.text.ParseException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * @description:针对 ZLMediaServer的hook事件监听
- * @author: swwheihei
- * @date: 2020年5月8日 上午10:46:48
- */
-@RestController
-@RequestMapping("/index/hook")
-public class ZLMHttpHookListener {
-
-    private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class);
-
-    @Autowired
-    private SIPCommander cmder;
-
-    @Autowired
-    private SIPCommanderFroPlatform commanderFroPlatform;
-
-    @Autowired
-    private IPlayService playService;
-
-    @Autowired
-    private IVideoManagerStorage storager;
-
-    @Autowired
-    private IRedisCatchStorage redisCatchStorage;
-
-    @Autowired
-    private IInviteStreamService inviteStreamService;
-
-    @Autowired
-    private IDeviceService deviceService;
-
-    @Autowired
-    private IMediaServerService mediaServerService;
-
-    @Autowired
-    private IStreamProxyService streamProxyService;
-
-    @Autowired
-    private DeferredResultHolder resultHolder;
-
-    @Autowired
-    private IMediaService mediaService;
-
-    @Autowired
-    private EventPublisher eventPublisher;
-
-    @Autowired
-    private ZLMMediaListManager zlmMediaListManager;
-
-    @Autowired
-    private ZlmHttpHookSubscribe subscribe;
-
-    @Autowired
-    private UserSetting userSetting;
-
-    @Autowired
-    private IUserService userService;
-
-    @Autowired
-    private VideoStreamSessionManager sessionManager;
-
-    @Autowired
-    private AssistRESTfulUtils assistRESTfulUtils;
-
-    @Autowired
-    private SSRCFactory ssrcFactory;
-
-    @Qualifier("taskExecutor")
-    @Autowired
-    private ThreadPoolTaskExecutor taskExecutor;
-
-    @Autowired
-    private RedisTemplate<Object, Object> redisTemplate;
-
-    /**
-     * 服务器定时上报时间,上报间隔可配置,默认10s上报一次
-     */
-    @ResponseBody
-    
-    @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8")
-    public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) {
-
-
-        taskExecutor.execute(() -> {
-            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive);
-            if (subscribes != null && subscribes.size() > 0) {
-                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
-                    subscribe.response(null, param);
-                }
-            }
-        });
-        mediaServerService.updateMediaServerKeepalive(param.getMediaServerId(), param.getData());
-
-        return HookResult.SUCCESS();
-    }
-
-    /**
-     * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。
-     */
-    @ResponseBody
-    
-    @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8")
-    public HookResult onPlay(@RequestBody OnPlayHookParam param) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("[ZLM HOOK] 播放鉴权:{}->{}" + param.getMediaServerId(), param);
-        }
-        String mediaServerId = param.getMediaServerId();
-
-        taskExecutor.execute(() -> {
-            JSONObject json = (JSONObject) JSON.toJSON(param);
-            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json);
-            if (subscribe != null) {
-                MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
-                if (mediaInfo != null) {
-                    subscribe.response(mediaInfo, param);
-                }
-            }
-        });
-        if (!"rtp".equals(param.getApp())) {
-            Map<String, String> paramMap = urlParamToMap(param.getParams());
-            StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
-            if (streamAuthorityInfo != null && streamAuthorityInfo.getCallId() != null && !streamAuthorityInfo.getCallId().equals(paramMap.get("callId"))) {
-                return new HookResult(401, "Unauthorized");
-            }
-        }
-
-        return HookResult.SUCCESS();
-    }
-
-    /**
-     * rtsp/rtmp/rtp推流鉴权事件。
-     */
-    @ResponseBody
-    @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8")
-    public HookResultForOnPublish onPublish(@RequestBody OnPublishHookParam param) {
-
-        JSONObject json = (JSONObject) JSON.toJSON(param);
-
-        logger.info("[ZLM HOOK]推流鉴权:{}->{}", param.getMediaServerId(), param);
-
-        String mediaServerId = json.getString("mediaServerId");
-        MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
-        if (mediaInfo == null) {
-            return new HookResultForOnPublish(200, "success");
-        }
-        // 推流鉴权的处理
-        if (!"rtp".equals(param.getApp())) {
-            if (userSetting.getPushAuthority()) {
-                // 推流鉴权
-                if (param.getParams() == null) {
-                    logger.info("推流鉴权失败: 缺少必要参数:sign=md5(user表的pushKey)");
-                    return new HookResultForOnPublish(401, "Unauthorized");
-                }
-                Map<String, String> paramMap = urlParamToMap(param.getParams());
-                String sign = paramMap.get("sign");
-                if (sign == null) {
-                    logger.info("推流鉴权失败: 缺少必要参数:sign=md5(user表的pushKey)");
-                    return new HookResultForOnPublish(401, "Unauthorized");
-                }
-                // 推流自定义播放鉴权码
-                String callId = paramMap.get("callId");
-                // 鉴权配置
-                boolean hasAuthority = userService.checkPushAuthority(callId, sign);
-                if (!hasAuthority) {
-                    logger.info("推流鉴权失败: sign 无权限: callId={}. sign={}", callId, sign);
-                    return new HookResultForOnPublish(401, "Unauthorized");
-                }
-                StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
-                streamAuthorityInfo.setCallId(callId);
-                streamAuthorityInfo.setSign(sign);
-                // 鉴权通过
-                redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
-                // 通知assist新的callId
-                if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) {
-                    taskExecutor.execute(() -> {
-                        assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null);
-                    });
-                }
-            }
-        } else {
-            zlmMediaListManager.sendStreamEvent(param.getApp(), param.getStream(), param.getMediaServerId());
-        }
-
-
-        HookResultForOnPublish result = HookResultForOnPublish.SUCCESS();
-        result.setEnable_audio(true);
-        taskExecutor.execute(() -> {
-            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json);
-            if (subscribe != null) {
-                if (mediaInfo != null) {
-                    subscribe.response(mediaInfo, param);
-                } else {
-                    new HookResultForOnPublish(1, "zlm not register");
-                }
-            }
-        });
-
-        // 是否录像
-        if ("rtp".equals(param.getApp())) {
-            result.setEnable_mp4(userSetting.getRecordSip());
-        } else {
-            result.setEnable_mp4(userSetting.isRecordPushLive());
-        }
-        // 替换流地址
-        if ("rtp".equals(param.getApp()) && !mediaInfo.isRtpEnable()) {
-            String ssrc = String.format("%010d", Long.parseLong(param.getStream(), 16));;
-            InviteInfo inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc);
-            if (inviteInfo != null) {
-                result.setStream_replace(inviteInfo.getStream());
-                logger.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", param.getStream(), inviteInfo.getStream());
-            }
-        }
-        List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream());
-        if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
-            String deviceId = ssrcTransactionForAll.get(0).getDeviceId();
-            String channelId = ssrcTransactionForAll.get(0).getChannelId();
-            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
-            if (deviceChannel != null) {
-                result.setEnable_audio(deviceChannel.isHasAudio());
-            }
-            // 如果是录像下载就设置视频间隔十秒
-            if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) {
-                result.setMp4_max_second(10);
-                result.setEnable_mp4(true);
-            }
-        }
-        if (mediaInfo.getRecordAssistPort() > 0 && userSetting.getRecordPath() == null) {
-            logger.info("推流时发现尚未设置录像路径,从assist服务中读取");
-            JSONObject info = assistRESTfulUtils.getInfo(mediaInfo, null);
-            if (info != null && info.getInteger("code") != null && info.getInteger("code") == 0 ) {
-                JSONObject dataJson = info.getJSONObject("data");
-                if (dataJson != null) {
-                    String recordPath = dataJson.getString("record");
-                    userSetting.setRecordPath(recordPath);
-                    result.setMp4_save_path(recordPath);
-                    // 修改zlm中的录像路径
-                    if (mediaInfo.isAutoConfig()) {
-                        taskExecutor.execute(() -> {
-                            mediaServerService.setZLMConfig(mediaInfo, false);
-                        });
-                    }
-                }
-            }
-        }
-        if (param.getApp().equalsIgnoreCase("rtp")) {
-            String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + param.getStream();
-            OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo)redisTemplate.opsForValue().get(receiveKey);
-
-            String receiveKeyForPS = VideoManagerConstants.WVP_OTHER_RECEIVE_PS_INFO + userSetting.getServerId() + "_" + param.getStream();
-            OtherPsSendInfo otherPsSendInfo = (OtherPsSendInfo)redisTemplate.opsForValue().get(receiveKeyForPS);
-            if (otherRtpSendInfo != null || otherPsSendInfo != null) {
-                result.setEnable_mp4(true);
-            }
-        }
-        logger.info("[ZLM HOOK]推流鉴权 响应:{}->{}->>>>{}", param.getMediaServerId(), param, result);
-        return result;
-    }
-
-
-    /**
-     * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
-     */
-    @ResponseBody
-    @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
-    public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) {
-
-        if (param.isRegist()) {
-            logger.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
-        } else {
-            logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
-        }
-
-
-        JSONObject json = (JSONObject) JSON.toJSON(param);
-        taskExecutor.execute(() -> {
-            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
-            MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
-            if (mediaInfo == null) {
-                logger.info("[ZLM HOOK] 流变化未找到ZLM, {}", param.getMediaServerId());
-                return;
-            }
-            if (subscribe != null) {
-                subscribe.response(mediaInfo, param);
-            }
-
-            List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
-            // TODO 重构此处逻辑
-            boolean isPush = false;
-            if (param.isRegist()) {
-                // 处理流注册的鉴权信息
-                if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
-                        || param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
-                        || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
-                    isPush = true;
-                    StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
-                    if (streamAuthorityInfo == null) {
-                        streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
-                    } else {
-                        streamAuthorityInfo.setOriginType(param.getOriginType());
-                        streamAuthorityInfo.setOriginTypeStr(param.getOriginTypeStr());
-                    }
-                    redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
-                }
-            } else {
-                redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream());
-            }
-
-            if ("rtsp".equals(param.getSchema())) {
-                // 更新流媒体负载信息
-                if (param.isRegist()) {
-                    mediaServerService.addCount(param.getMediaServerId());
-                } else {
-                    mediaServerService.removeCount(param.getMediaServerId());
-                }
-                // 设置拉流代理上线/离线
-                int updateStatusResult = streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream());
-                if (updateStatusResult > 0) {
-
-                }
-
-                if ("rtp".equals(param.getApp()) && !param.isRegist()) {
-                    InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
-                    if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) {
-                        inviteStreamService.removeInviteInfo(inviteInfo);
-                        storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
-                    }
-                } else {
-                    if (!"rtp".equals(param.getApp())) {
-                        String type = OriginType.values()[param.getOriginType()].getType();
-                        if (param.isRegist()) {
-                            StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(
-                                    param.getApp(), param.getStream());
-                            String callId = null;
-                            if (streamAuthorityInfo != null) {
-                                callId = streamAuthorityInfo.getCallId();
-                            }
-                            StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaInfo,
-                                    param.getApp(), param.getStream(), tracks, callId);
-                            param.setStreamInfo(new StreamContent(streamInfoByAppAndStream));
-                            redisCatchStorage.addStream(mediaInfo, type, param.getApp(), param.getStream(), param);
-                            if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
-                                    || param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
-                                    || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
-                                param.setSeverId(userSetting.getServerId());
-                                zlmMediaListManager.addPush(param);
-                            }
-                        } else {
-                            // 兼容流注销时类型从redis记录获取
-                            OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo(
-                                    param.getApp(), param.getStream(), param.getMediaServerId());
-                            if (onStreamChangedHookParam != null) {
-                                type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType();
-                                redisCatchStorage.removeStream(mediaInfo.getId(), type, param.getApp(), param.getStream());
-                            }
-                            GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream());
-                            if (gbStream != null) {
-//									eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF);
-                            }
-                            zlmMediaListManager.removeMedia(param.getApp(), param.getStream());
-                        }
-                        GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream());
-                        if (gbStream != null) {
-                            if (userSetting.isUsePushingAsStatus()) {
-                                eventPublisher.catalogEventPublishForStream(null, gbStream, param.isRegist()?CatalogEvent.ON:CatalogEvent.OFF);
-                            }
-                        }
-                        if (type != null) {
-                            // 发送流变化redis消息
-                            JSONObject jsonObject = new JSONObject();
-                            jsonObject.put("serverId", userSetting.getServerId());
-                            jsonObject.put("app", param.getApp());
-                            jsonObject.put("stream", param.getStream());
-                            jsonObject.put("register", param.isRegist());
-                            jsonObject.put("mediaServerId", param.getMediaServerId());
-                            redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
-                        }
-                    }
-                }
-                if (!param.isRegist()) {
-                    List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
-                    if (sendRtpItems.size() > 0) {
-                        for (SendRtpItem sendRtpItem : sendRtpItems) {
-                            if (sendRtpItem != null && sendRtpItem.getApp().equals(param.getApp())) {
-                                String platformId = sendRtpItem.getPlatformId();
-                                ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
-                                Device device = deviceService.getDevice(platformId);
-
-                                try {
-                                    if (platform != null) {
-                                        commanderFroPlatform.streamByeCmd(platform, sendRtpItem);
-                                        redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(),
-                                                sendRtpItem.getCallId(), sendRtpItem.getStreamId());
-                                    } else {
-                                        cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId());
-                                    }
-                                } catch (SipException | InvalidArgumentException | ParseException |
-                                         SsrcTransactionNotFoundException e) {
-                                    logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        });
-
-        return HookResult.SUCCESS();
-    }
-
-    /**
-     * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。
-     */
-    @ResponseBody
-    @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
-    public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) {
-
-        logger.info("[ZLM HOOK]流无人观看:{}->{}->{}/{}",  param.getMediaServerId(), param.getSchema(),
-                param.getApp(), param.getStream());
-        JSONObject ret = new JSONObject();
-        ret.put("code", 0);
-        // 国标类型的流
-        if ("rtp".equals(param.getApp())) {
-            ret.put("close", userSetting.getStreamOnDemand());
-            // 国标流, 点播/录像回放/录像下载
-            InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
-            // 点播
-            if (inviteInfo != null) {
-                // 录像下载
-                if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) {
-                    ret.put("close", false);
-                    return ret;
-                }
-                // 收到无人观看说明流也没有在往上级推送
-                if (redisCatchStorage.isChannelSendingRTP(inviteInfo.getChannelId())) {
-                    List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId(
-                            inviteInfo.getChannelId());
-                    if (sendRtpItems.size() > 0) {
-                        for (SendRtpItem sendRtpItem : sendRtpItems) {
-                            ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
-                            try {
-                                commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
-                            } catch (SipException | InvalidArgumentException | ParseException e) {
-                                logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
-                            }
-                            redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
-                                    sendRtpItem.getCallId(), sendRtpItem.getStreamId());
-                            if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
-                                MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
-                                        sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
-                                        sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
-                                messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
-                                redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
-                            }
-                        }
-                    }
-                }
-                Device device = deviceService.getDevice(inviteInfo.getDeviceId());
-                if (device != null) {
-                    try {
-                        // 多查询一次防止已经被处理了
-                        InviteInfo info = inviteStreamService.getInviteInfo(inviteInfo.getType(),
-                                inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream());
-                        if (info != null) {
-                            cmder.streamByeCmd(device, inviteInfo.getChannelId(),
-                                    inviteInfo.getStream(), null);
-                        }
-                    } catch (InvalidArgumentException | ParseException | SipException |
-                             SsrcTransactionNotFoundException e) {
-                        logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage());
-                    }
-                }
-
-                inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(),
-                        inviteInfo.getChannelId(), inviteInfo.getStream());
-                storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
-                return ret;
-            }
-        } else {
-            // 非国标流 推流/拉流代理
-            // 拉流代理
-            StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
-            if (streamProxyItem != null) {
-                if (streamProxyItem.isEnableRemoveNoneReader()) {
-                    // 无人观看自动移除
-                    ret.put("close", true);
-                    streamProxyService.del(param.getApp(), param.getStream());
-                    String url = streamProxyItem.getUrl() != null ? streamProxyItem.getUrl() : streamProxyItem.getSrcUrl();
-                    logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", param.getApp(), param.getStream(), url);
-                } else if (streamProxyItem.isEnableDisableNoneReader()) {
-                    // 无人观看停用
-                    ret.put("close", true);
-                    // 修改数据
-                    streamProxyService.stop(param.getApp(), param.getStream());
-                } else {
-                    // 无人观看不做处理
-                    ret.put("close", false);
-                }
-                return ret;
-            }
-            // TODO 推流具有主动性,暂时不做处理
-//			StreamPushItem streamPushItem = streamPushService.getPush(app, streamId);
-//			if (streamPushItem != null) {
-//				// TODO 发送停止
-//
-//			}
-        }
-        return ret;
-    }
-
-    /**
-     * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。
-     */
-    @ResponseBody
-    @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8")
-    public DeferredResult<HookResult> onStreamNotFound(@RequestBody OnStreamNotFoundHookParam param) {
-        logger.info("[ZLM HOOK] 流未找到:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
-
-        DeferredResult<HookResult> defaultResult = new DeferredResult<>();
-
-        MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
-        if (!userSetting.isAutoApplyPlay() || mediaInfo == null) {
-            defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
-            return defaultResult;
-        }
-
-        if ("rtp".equals(param.getApp())) {
-            String[] s = param.getStream().split("_");
-            if ((s.length != 2 && s.length != 4)) {
-                defaultResult.setResult(HookResult.SUCCESS());
-                return defaultResult;
-            }
-            String deviceId = s[0];
-            String channelId = s[1];
-            Device device = redisCatchStorage.getDevice(deviceId);
-            if (device == null) {
-                defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
-                return defaultResult;
-            }
-            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
-            if (deviceChannel == null) {
-                defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
-                return defaultResult;
-            }
-            if (s.length == 2) {
-                logger.info("[ZLM HOOK] 预览流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
-
-                RequestMessage msg = new RequestMessage();
-                String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
-                boolean exist = resultHolder.exist(key, null);
-                msg.setKey(key);
-                String uuid = UUID.randomUUID().toString();
-                msg.setId(uuid);
-                DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
-
-                result.onTimeout(() -> {
-                    logger.info("[ZLM HOOK] 预览流自动点播, 等待超时");
-                    msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时"));
-                    resultHolder.invokeResult(msg);
-                });
-
-                resultHolder.put(key, uuid, result);
-
-                if (!exist) {
-                    playService.play(mediaInfo, deviceId, channelId, null, (code, message, data) -> {
-                        msg.setData(new HookResult(code, message));
-                        resultHolder.invokeResult(msg);
-                    });
-                }
-                return result;
-            }else if(s.length == 4){
-                // 此时为录像回放, 录像回放格式为> 设备ID_通道ID_开始时间_结束时间
-                String startTimeStr = s[2];
-                String endTimeStr = s[3];
-                if (startTimeStr == null || endTimeStr == null || startTimeStr.length() != 14 || endTimeStr.length() != 14) {
-                    defaultResult.setResult(HookResult.SUCCESS());
-                    return defaultResult;
-                }
-                String startTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(startTimeStr);
-                String endTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(endTimeStr);
-                logger.info("[ZLM HOOK] 回放流未找到, 发起自动点播:{}->{}->{}/{}-{}-{}",
-                        param.getMediaServerId(), param.getSchema(),
-                        param.getApp(), param.getStream(),
-                        startTime, endTime
-                );
-                RequestMessage msg = new RequestMessage();
-                String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId;
-                boolean exist = resultHolder.exist(key, null);
-                msg.setKey(key);
-                String uuid = UUID.randomUUID().toString();
-                msg.setId(uuid);
-                DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
-
-                result.onTimeout(() -> {
-                    logger.info("[ZLM HOOK] 回放流自动点播, 等待超时");
-                    // 释放rtpserver
-                    msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时"));
-                    resultHolder.invokeResult(msg);
-                });
-
-                resultHolder.put(key, uuid, result);
-
-                if (!exist) {
-                    SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaInfo, param.getStream(), null,
-                            device.isSsrcCheck(),  true, 0, false, device.getStreamModeForParam());
-                    playService.playBack(mediaInfo, ssrcInfo, deviceId, channelId, startTime, endTime, (code, message, data) -> {
-                        msg.setData(new HookResult(code, message));
-                        resultHolder.invokeResult(msg);
-                    });
-                }
-                return result;
-            }else {
-                defaultResult.setResult(HookResult.SUCCESS());
-                return defaultResult;
-            }
-
-        } else {
-            // 拉流代理
-            StreamProxyItem streamProxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
-            if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnableDisableNoneReader()) {
-                streamProxyService.start(param.getApp(), param.getStream());
-            }
-            DeferredResult<HookResult> result = new DeferredResult<>();
-            result.setResult(HookResult.SUCCESS());
-            return result;
-        }
-    }
-
-    /**
-     * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。
-     */
-    @ResponseBody
-    @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
-    public HookResult onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject) {
-
-        jsonObject.put("ip", request.getRemoteAddr());
-        ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject);
-        zlmServerConfig.setIp(request.getRemoteAddr());
-        logger.info("[ZLM HOOK] zlm 启动 " + zlmServerConfig.getGeneralMediaServerId());
-        taskExecutor.execute(() -> {
-            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_started);
-            if (subscribes != null && subscribes.size() > 0) {
-                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
-                    subscribe.response(null, zlmServerConfig);
-                }
-            }
-            mediaServerService.zlmServerOnline(zlmServerConfig);
-        });
-
-        return HookResult.SUCCESS();
-    }
-
-    /**
-     * 发送rtp(startSendRtp)被动关闭时回调
-     */
-    @ResponseBody
-    @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8")
-    public HookResult onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param) {
-
-        logger.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream());
-
-        // 查找对应的上级推流,发送停止
-        if (!"rtp".equals(param.getApp())) {
-            return HookResult.SUCCESS();
-        }
-        taskExecutor.execute(() -> {
-            List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
-            if (sendRtpItems.size() > 0) {
-                for (SendRtpItem sendRtpItem : sendRtpItems) {
-                    ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
-                    ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
-                    try {
-                        commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
-                    } catch (SipException | InvalidArgumentException | ParseException e) {
-                        logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
-                    }
-                    redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
-                            sendRtpItem.getCallId(), sendRtpItem.getStreamId());
-                }
-            }
-        });
-
-        return HookResult.SUCCESS();
-    }
-
-    /**
-     * rtpServer收流超时
-     */
-    @ResponseBody
-    @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8")
-    public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param) {
-        logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc());
-
-        taskExecutor.execute(() -> {
-            JSONObject json = (JSONObject) JSON.toJSON(param);
-            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout);
-            if (subscribes != null && subscribes.size() > 0) {
-                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
-                    subscribe.response(null, param);
-                }
-            }
-        });
-
-        return HookResult.SUCCESS();
-    }
-
-    private Map<String, String> urlParamToMap(String params) {
-        HashMap<String, String> map = new HashMap<>();
-        if (ObjectUtils.isEmpty(params)) {
-            return map;
-        }
-        String[] paramsArray = params.split("&");
-        if (paramsArray.length == 0) {
-            return map;
-        }
-        for (String param : paramsArray) {
-            String[] paramArray = param.split("=");
-            if (paramArray.length == 2) {
-                map.put(paramArray[0], paramArray[1]);
-            }
-        }
-        return map;
-    }
-}
+package com.genersoft.iot.vmp.media.zlm;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.common.InviteInfo;
+import com.genersoft.iot.vmp.common.InviteSessionType;
+import com.genersoft.iot.vmp.common.StreamInfo;
+import com.genersoft.iot.vmp.common.VideoManagerConstants;
+import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
+import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
+import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
+import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
+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.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
+import com.genersoft.iot.vmp.media.zlm.dto.HookType;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
+import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
+import com.genersoft.iot.vmp.media.zlm.dto.hook.*;
+import com.genersoft.iot.vmp.service.*;
+import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
+import com.genersoft.iot.vmp.service.bean.SSRCInfo;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import com.genersoft.iot.vmp.utils.DateUtil;
+import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import com.genersoft.iot.vmp.vmanager.bean.OtherPsSendInfo;
+import com.genersoft.iot.vmp.vmanager.bean.OtherRtpSendInfo;
+import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.async.DeferredResult;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * @description:针对 ZLMediaServer的hook事件监听
+ * @author: swwheihei
+ * @date: 2020年5月8日 上午10:46:48
+ */
+@RestController
+@RequestMapping("/index/hook")
+public class ZLMHttpHookListener {
+
+    private final static Logger logger = LoggerFactory.getLogger(ZLMHttpHookListener.class);
+
+    @Autowired
+    private SIPCommander cmder;
+
+    @Autowired
+    private SIPCommanderFroPlatform commanderFroPlatform;
+
+    @Autowired
+    private IPlayService playService;
+
+    @Autowired
+    private IVideoManagerStorage storager;
+
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
+    @Autowired
+    private IInviteStreamService inviteStreamService;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @Autowired
+    private IMediaServerService mediaServerService;
+
+    @Autowired
+    private IStreamProxyService streamProxyService;
+
+    @Autowired
+    private DeferredResultHolder resultHolder;
+
+    @Autowired
+    private IMediaService mediaService;
+
+    @Autowired
+    private EventPublisher eventPublisher;
+
+    @Autowired
+    private ZLMMediaListManager zlmMediaListManager;
+
+    @Autowired
+    private ZlmHttpHookSubscribe subscribe;
+
+    @Autowired
+    private UserSetting userSetting;
+
+    @Autowired
+    private IUserService userService;
+
+    @Autowired
+    private VideoStreamSessionManager sessionManager;
+
+    @Autowired
+    private AssistRESTfulUtils assistRESTfulUtils;
+
+    @Autowired
+    private SSRCFactory ssrcFactory;
+
+    @Qualifier("taskExecutor")
+    @Autowired
+    private ThreadPoolTaskExecutor taskExecutor;
+
+    @Autowired
+    private RedisTemplate<Object, Object> redisTemplate;
+
+    /**
+     * 服务器定时上报时间,上报间隔可配置,默认10s上报一次
+     */
+    @ResponseBody
+    
+    @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8")
+    public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) {
+
+
+        taskExecutor.execute(() -> {
+            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_keepalive);
+            if (subscribes != null && subscribes.size() > 0) {
+                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
+                    subscribe.response(null, param);
+                }
+            }
+        });
+        mediaServerService.updateMediaServerKeepalive(param.getMediaServerId(), param.getData());
+
+        return HookResult.SUCCESS();
+    }
+
+    /**
+     * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。
+     */
+    @ResponseBody
+    
+    @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8")
+    public HookResult onPlay(@RequestBody OnPlayHookParam param) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("[ZLM HOOK] 播放鉴权:{}->{}" + param.getMediaServerId(), param);
+        }
+        String mediaServerId = param.getMediaServerId();
+
+        taskExecutor.execute(() -> {
+            JSONObject json = (JSONObject) JSON.toJSON(param);
+            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_play, json);
+            if (subscribe != null) {
+                MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+                if (mediaInfo != null) {
+                    subscribe.response(mediaInfo, param);
+                }
+            }
+        });
+        if (!"rtp".equals(param.getApp())) {
+            Map<String, String> paramMap = urlParamToMap(param.getParams());
+            StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
+            if (streamAuthorityInfo != null && streamAuthorityInfo.getCallId() != null && !streamAuthorityInfo.getCallId().equals(paramMap.get("callId"))) {
+                return new HookResult(401, "Unauthorized");
+            }
+        }
+
+        return HookResult.SUCCESS();
+    }
+
+    /**
+     * rtsp/rtmp/rtp推流鉴权事件。
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_publish", produces = "application/json;charset=UTF-8")
+    public HookResultForOnPublish onPublish(@RequestBody OnPublishHookParam param) {
+
+        JSONObject json = (JSONObject) JSON.toJSON(param);
+
+        logger.info("[ZLM HOOK]推流鉴权:{}->{}", param.getMediaServerId(), param);
+
+        String mediaServerId = json.getString("mediaServerId");
+        MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
+        if (mediaInfo == null) {
+            return new HookResultForOnPublish(200, "success");
+        }
+        // 推流鉴权的处理
+        if (!"rtp".equals(param.getApp())) {
+            if (userSetting.getPushAuthority()) {
+                // 推流鉴权
+                if (param.getParams() == null) {
+                    logger.info("推流鉴权失败: 缺少必要参数:sign=md5(user表的pushKey)");
+                    return new HookResultForOnPublish(401, "Unauthorized");
+                }
+                Map<String, String> paramMap = urlParamToMap(param.getParams());
+                String sign = paramMap.get("sign");
+                if (sign == null) {
+                    logger.info("推流鉴权失败: 缺少必要参数:sign=md5(user表的pushKey)");
+                    return new HookResultForOnPublish(401, "Unauthorized");
+                }
+                // 推流自定义播放鉴权码
+                String callId = paramMap.get("callId");
+                // 鉴权配置
+                boolean hasAuthority = userService.checkPushAuthority(callId, sign);
+                if (!hasAuthority) {
+                    logger.info("推流鉴权失败: sign 无权限: callId={}. sign={}", callId, sign);
+                    return new HookResultForOnPublish(401, "Unauthorized");
+                }
+                StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
+                streamAuthorityInfo.setCallId(callId);
+                streamAuthorityInfo.setSign(sign);
+                // 鉴权通过
+                redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
+                // 通知assist新的callId
+                if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) {
+                    taskExecutor.execute(() -> {
+                        assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null);
+                    });
+                }
+            }
+        } else {
+            zlmMediaListManager.sendStreamEvent(param.getApp(), param.getStream(), param.getMediaServerId());
+        }
+
+
+        HookResultForOnPublish result = HookResultForOnPublish.SUCCESS();
+        result.setEnable_audio(true);
+        taskExecutor.execute(() -> {
+            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_publish, json);
+            if (subscribe != null) {
+                if (mediaInfo != null) {
+                    subscribe.response(mediaInfo, param);
+                } else {
+                    new HookResultForOnPublish(1, "zlm not register");
+                }
+            }
+        });
+
+        // 是否录像
+        if ("rtp".equals(param.getApp())) {
+            result.setEnable_mp4(userSetting.getRecordSip());
+        } else {
+            result.setEnable_mp4(userSetting.isRecordPushLive());
+        }
+        // 替换流地址
+        if ("rtp".equals(param.getApp()) && !mediaInfo.isRtpEnable()) {
+            String ssrc = String.format("%010d", Long.parseLong(param.getStream(), 16));;
+            InviteInfo inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc);
+            if (inviteInfo != null) {
+                result.setStream_replace(inviteInfo.getStream());
+                logger.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", param.getStream(), inviteInfo.getStream());
+            }
+        }
+        List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream());
+        if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
+            String deviceId = ssrcTransactionForAll.get(0).getDeviceId();
+            String channelId = ssrcTransactionForAll.get(0).getChannelId();
+            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
+            if (deviceChannel != null) {
+                result.setEnable_audio(deviceChannel.isHasAudio());
+            }
+            // 如果是录像下载就设置视频间隔十秒
+            if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) {
+                result.setMp4_max_second(10);
+                result.setEnable_mp4(true);
+            }
+        }
+        if (mediaInfo.getRecordAssistPort() > 0 && userSetting.getRecordPath() == null) {
+            logger.info("推流时发现尚未设置录像路径,从assist服务中读取");
+            JSONObject info = assistRESTfulUtils.getInfo(mediaInfo, null);
+            if (info != null && info.getInteger("code") != null && info.getInteger("code") == 0 ) {
+                JSONObject dataJson = info.getJSONObject("data");
+                if (dataJson != null) {
+                    String recordPath = dataJson.getString("record");
+                    userSetting.setRecordPath(recordPath);
+                    result.setMp4_save_path(recordPath);
+                    // 修改zlm中的录像路径
+                    if (mediaInfo.isAutoConfig()) {
+                        taskExecutor.execute(() -> {
+                            mediaServerService.setZLMConfig(mediaInfo, false);
+                        });
+                    }
+                }
+            }
+        }
+        if (param.getApp().equalsIgnoreCase("rtp")) {
+            String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + param.getStream();
+            OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo)redisTemplate.opsForValue().get(receiveKey);
+
+            String receiveKeyForPS = VideoManagerConstants.WVP_OTHER_RECEIVE_PS_INFO + userSetting.getServerId() + "_" + param.getStream();
+            OtherPsSendInfo otherPsSendInfo = (OtherPsSendInfo)redisTemplate.opsForValue().get(receiveKeyForPS);
+            if (otherRtpSendInfo != null || otherPsSendInfo != null) {
+                result.setEnable_mp4(true);
+            }
+        }
+        logger.info("[ZLM HOOK]推流鉴权 响应:{}->{}->>>>{}", param.getMediaServerId(), param, result);
+        return result;
+    }
+
+
+    /**
+     * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
+    public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) {
+
+        if (param.isRegist()) {
+            logger.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
+        } else {
+            logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
+        }
+
+
+        JSONObject json = (JSONObject) JSON.toJSON(param);
+        taskExecutor.execute(() -> {
+            ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
+            MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
+            if (mediaInfo == null) {
+                logger.info("[ZLM HOOK] 流变化未找到ZLM, {}", param.getMediaServerId());
+                return;
+            }
+            if (subscribe != null) {
+                subscribe.response(mediaInfo, param);
+            }
+
+            List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
+            // TODO 重构此处逻辑
+            boolean isPush = false;
+            if (param.isRegist()) {
+                // 处理流注册的鉴权信息
+                if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
+                        || param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
+                        || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
+                    isPush = true;
+                    StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
+                    if (streamAuthorityInfo == null) {
+                        streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param);
+                    } else {
+                        streamAuthorityInfo.setOriginType(param.getOriginType());
+                        streamAuthorityInfo.setOriginTypeStr(param.getOriginTypeStr());
+                    }
+                    redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
+                }
+            } else {
+                redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream());
+            }
+
+            if ("rtsp".equals(param.getSchema())) {
+                // 更新流媒体负载信息
+                if (param.isRegist()) {
+                    mediaServerService.addCount(param.getMediaServerId());
+                } else {
+                    mediaServerService.removeCount(param.getMediaServerId());
+                }
+                // 设置拉流代理上线/离线
+                int updateStatusResult = streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream());
+                if (updateStatusResult > 0) {
+
+                }
+
+                if ("rtp".equals(param.getApp()) && !param.isRegist()) {
+                    InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
+                    if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) {
+                        inviteStreamService.removeInviteInfo(inviteInfo);
+                        storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
+                    }
+                } else {
+                    if (!"rtp".equals(param.getApp())) {
+                        String type = OriginType.values()[param.getOriginType()].getType();
+                        if (param.isRegist()) {
+                            StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(
+                                    param.getApp(), param.getStream());
+                            String callId = null;
+                            if (streamAuthorityInfo != null) {
+                                callId = streamAuthorityInfo.getCallId();
+                            }
+                            StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaInfo,
+                                    param.getApp(), param.getStream(), tracks, callId);
+                            param.setStreamInfo(new StreamContent(streamInfoByAppAndStream));
+                            redisCatchStorage.addStream(mediaInfo, type, param.getApp(), param.getStream(), param);
+                            if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
+                                    || param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
+                                    || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
+                                param.setSeverId(userSetting.getServerId());
+                                zlmMediaListManager.addPush(param);
+                            }
+                        } else {
+                            // 兼容流注销时类型从redis记录获取
+                            OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo(
+                                    param.getApp(), param.getStream(), param.getMediaServerId());
+                            if (onStreamChangedHookParam != null) {
+                                type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType();
+                                redisCatchStorage.removeStream(mediaInfo.getId(), type, param.getApp(), param.getStream());
+                            }
+                            GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream());
+                            if (gbStream != null) {
+//									eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF);
+                            }
+                            zlmMediaListManager.removeMedia(param.getApp(), param.getStream());
+                        }
+                        GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream());
+                        if (gbStream != null) {
+                            if (userSetting.isUsePushingAsStatus()) {
+                                eventPublisher.catalogEventPublishForStream(null, gbStream, param.isRegist()?CatalogEvent.ON:CatalogEvent.OFF);
+                            }
+                        }
+                        if (type != null) {
+                            // 发送流变化redis消息
+                            JSONObject jsonObject = new JSONObject();
+                            jsonObject.put("serverId", userSetting.getServerId());
+                            jsonObject.put("app", param.getApp());
+                            jsonObject.put("stream", param.getStream());
+                            jsonObject.put("register", param.isRegist());
+                            jsonObject.put("mediaServerId", param.getMediaServerId());
+                            redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
+                        }
+                    }
+                }
+                if (!param.isRegist()) {
+                    List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
+                    if (sendRtpItems.size() > 0) {
+                        for (SendRtpItem sendRtpItem : sendRtpItems) {
+                            if (sendRtpItem != null && sendRtpItem.getApp().equals(param.getApp())) {
+                                String platformId = sendRtpItem.getPlatformId();
+                                ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId);
+                                Device device = deviceService.getDevice(platformId);
+
+                                try {
+                                    if (platform != null) {
+                                        commanderFroPlatform.streamByeCmd(platform, sendRtpItem);
+                                        redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(),
+                                                sendRtpItem.getCallId(), sendRtpItem.getStreamId());
+                                    } else {
+                                        cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId());
+                                    }
+                                } catch (SipException | InvalidArgumentException | ParseException |
+                                         SsrcTransactionNotFoundException e) {
+                                    logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        });
+
+        return HookResult.SUCCESS();
+    }
+
+    /**
+     * 流无人观看时事件,用户可以通过此事件选择是否关闭无人看的流。
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8")
+    public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) {
+
+        logger.info("[ZLM HOOK]流无人观看:{}->{}->{}/{}",  param.getMediaServerId(), param.getSchema(),
+                param.getApp(), param.getStream());
+        JSONObject ret = new JSONObject();
+        ret.put("code", 0);
+        // 国标类型的流
+        if ("rtp".equals(param.getApp())) {
+            ret.put("close", userSetting.getStreamOnDemand());
+            // 国标流, 点播/录像回放/录像下载
+            InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
+            // 点播
+            if (inviteInfo != null) {
+                // 录像下载
+                if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) {
+                    ret.put("close", false);
+                    return ret;
+                }
+                // 收到无人观看说明流也没有在往上级推送
+                if (redisCatchStorage.isChannelSendingRTP(inviteInfo.getChannelId())) {
+                    List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId(
+                            inviteInfo.getChannelId());
+                    if (sendRtpItems.size() > 0) {
+                        for (SendRtpItem sendRtpItem : sendRtpItems) {
+                            ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
+                            try {
+                                commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+                            }
+                            redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
+                                    sendRtpItem.getCallId(), sendRtpItem.getStreamId());
+                            if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
+                                MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
+                                        sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
+                                        sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
+                                messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
+                                redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
+                            }
+                        }
+                    }
+                }
+                Device device = deviceService.getDevice(inviteInfo.getDeviceId());
+                if (device != null) {
+                    try {
+                        // 多查询一次防止已经被处理了
+                        InviteInfo info = inviteStreamService.getInviteInfo(inviteInfo.getType(),
+                                inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream());
+                        if (info != null) {
+                            cmder.streamByeCmd(device, inviteInfo.getChannelId(),
+                                    inviteInfo.getStream(), null);
+                        }
+                    } catch (InvalidArgumentException | ParseException | SipException |
+                             SsrcTransactionNotFoundException e) {
+                        logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage());
+                    }
+                }
+
+                inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(),
+                        inviteInfo.getChannelId(), inviteInfo.getStream());
+                storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
+                return ret;
+            }
+        } else {
+            // 非国标流 推流/拉流代理
+            // 拉流代理
+            StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
+            if (streamProxyItem != null) {
+                if (streamProxyItem.isEnableRemoveNoneReader()) {
+                    // 无人观看自动移除
+                    ret.put("close", true);
+                    streamProxyService.del(param.getApp(), param.getStream());
+                    String url = streamProxyItem.getUrl() != null ? streamProxyItem.getUrl() : streamProxyItem.getSrcUrl();
+                    logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", param.getApp(), param.getStream(), url);
+                } else if (streamProxyItem.isEnableDisableNoneReader()) {
+                    // 无人观看停用
+                    ret.put("close", true);
+                    // 修改数据
+                    streamProxyService.stop(param.getApp(), param.getStream());
+                } else {
+                    // 无人观看不做处理
+                    ret.put("close", false);
+                }
+                return ret;
+            }
+            // TODO 推流具有主动性,暂时不做处理
+//			StreamPushItem streamPushItem = streamPushService.getPush(app, streamId);
+//			if (streamPushItem != null) {
+//				// TODO 发送停止
+//
+//			}
+        }
+        return ret;
+    }
+
+    /**
+     * 流未找到事件,用户可以在此事件触发时,立即去拉流,这样可以实现按需拉流;此事件对回复不敏感。
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_stream_not_found", produces = "application/json;charset=UTF-8")
+    public DeferredResult<HookResult> onStreamNotFound(@RequestBody OnStreamNotFoundHookParam param) {
+        logger.info("[ZLM HOOK] 流未找到:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
+
+        DeferredResult<HookResult> defaultResult = new DeferredResult<>();
+
+        MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
+        if (!userSetting.isAutoApplyPlay() || mediaInfo == null) {
+            defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
+            return defaultResult;
+        }
+
+        if ("rtp".equals(param.getApp())) {
+            String[] s = param.getStream().split("_");
+            if ((s.length != 2 && s.length != 4)) {
+                defaultResult.setResult(HookResult.SUCCESS());
+                return defaultResult;
+            }
+            String deviceId = s[0];
+            String channelId = s[1];
+            Device device = redisCatchStorage.getDevice(deviceId);
+            if (device == null) {
+                defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
+                return defaultResult;
+            }
+            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
+            if (deviceChannel == null) {
+                defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
+                return defaultResult;
+            }
+            if (s.length == 2) {
+                logger.info("[ZLM HOOK] 预览流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
+
+                RequestMessage msg = new RequestMessage();
+                String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
+                boolean exist = resultHolder.exist(key, null);
+                msg.setKey(key);
+                String uuid = UUID.randomUUID().toString();
+                msg.setId(uuid);
+                DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
+
+                result.onTimeout(() -> {
+                    logger.info("[ZLM HOOK] 预览流自动点播, 等待超时");
+                    msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时"));
+                    resultHolder.invokeResult(msg);
+                });
+
+                resultHolder.put(key, uuid, result);
+
+                if (!exist) {
+                    playService.play(mediaInfo, deviceId, channelId, null, (code, message, data) -> {
+                        msg.setData(new HookResult(code, message));
+                        resultHolder.invokeResult(msg);
+                    });
+                }
+                return result;
+            }else if(s.length == 4){
+                // 此时为录像回放, 录像回放格式为> 设备ID_通道ID_开始时间_结束时间
+                String startTimeStr = s[2];
+                String endTimeStr = s[3];
+                if (startTimeStr == null || endTimeStr == null || startTimeStr.length() != 14 || endTimeStr.length() != 14) {
+                    defaultResult.setResult(HookResult.SUCCESS());
+                    return defaultResult;
+                }
+                String startTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(startTimeStr);
+                String endTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(endTimeStr);
+                logger.info("[ZLM HOOK] 回放流未找到, 发起自动点播:{}->{}->{}/{}-{}-{}",
+                        param.getMediaServerId(), param.getSchema(),
+                        param.getApp(), param.getStream(),
+                        startTime, endTime
+                );
+                RequestMessage msg = new RequestMessage();
+                String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId;
+                boolean exist = resultHolder.exist(key, null);
+                msg.setKey(key);
+                String uuid = UUID.randomUUID().toString();
+                msg.setId(uuid);
+                DeferredResult<HookResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
+
+                result.onTimeout(() -> {
+                    logger.info("[ZLM HOOK] 回放流自动点播, 等待超时");
+                    // 释放rtpserver
+                    msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时"));
+                    resultHolder.invokeResult(msg);
+                });
+
+                resultHolder.put(key, uuid, result);
+
+                if (!exist) {
+                    SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaInfo, param.getStream(), null,
+                            device.isSsrcCheck(),  true, 0, false, device.getStreamModeForParam());
+                    playService.playBack(mediaInfo, ssrcInfo, deviceId, channelId, startTime, endTime, (code, message, data) -> {
+                        msg.setData(new HookResult(code, message));
+                        resultHolder.invokeResult(msg);
+                    });
+                }
+                return result;
+            }else {
+                defaultResult.setResult(HookResult.SUCCESS());
+                return defaultResult;
+            }
+
+        } else {
+            // 拉流代理
+            StreamProxyItem streamProxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream());
+            if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnableDisableNoneReader()) {
+                streamProxyService.start(param.getApp(), param.getStream());
+            }
+            DeferredResult<HookResult> result = new DeferredResult<>();
+            result.setResult(HookResult.SUCCESS());
+            return result;
+        }
+    }
+
+    /**
+     * 服务器启动事件,可以用于监听服务器崩溃重启;此事件对回复不敏感。
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_server_started", produces = "application/json;charset=UTF-8")
+    public HookResult onServerStarted(HttpServletRequest request, @RequestBody JSONObject jsonObject) {
+
+        jsonObject.put("ip", request.getRemoteAddr());
+        ZLMServerConfig zlmServerConfig = JSON.to(ZLMServerConfig.class, jsonObject);
+        zlmServerConfig.setIp(request.getRemoteAddr());
+        logger.info("[ZLM HOOK] zlm 启动 " + zlmServerConfig.getGeneralMediaServerId());
+        taskExecutor.execute(() -> {
+            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_server_started);
+            if (subscribes != null && subscribes.size() > 0) {
+                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
+                    subscribe.response(null, zlmServerConfig);
+                }
+            }
+            mediaServerService.zlmServerOnline(zlmServerConfig);
+        });
+
+        return HookResult.SUCCESS();
+    }
+
+    /**
+     * 发送rtp(startSendRtp)被动关闭时回调
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_send_rtp_stopped", produces = "application/json;charset=UTF-8")
+    public HookResult onSendRtpStopped(HttpServletRequest request, @RequestBody OnSendRtpStoppedHookParam param) {
+
+        logger.info("[ZLM HOOK] rtp发送关闭:{}->{}/{}", param.getMediaServerId(), param.getApp(), param.getStream());
+
+        // 查找对应的上级推流,发送停止
+        if (!"rtp".equals(param.getApp())) {
+            return HookResult.SUCCESS();
+        }
+        taskExecutor.execute(() -> {
+            List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream());
+            if (sendRtpItems.size() > 0) {
+                for (SendRtpItem sendRtpItem : sendRtpItems) {
+                    ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
+                    ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
+                    try {
+                        commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
+                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                        logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
+                    }
+                    redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
+                            sendRtpItem.getCallId(), sendRtpItem.getStreamId());
+                }
+            }
+        });
+
+        return HookResult.SUCCESS();
+    }
+
+    /**
+     * rtpServer收流超时
+     */
+    @ResponseBody
+    @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8")
+    public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param) {
+        logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc());
+
+        taskExecutor.execute(() -> {
+            JSONObject json = (JSONObject) JSON.toJSON(param);
+            List<ZlmHttpHookSubscribe.Event> subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout);
+            if (subscribes != null && subscribes.size() > 0) {
+                for (ZlmHttpHookSubscribe.Event subscribe : subscribes) {
+                    subscribe.response(null, param);
+                }
+            }
+        });
+
+        return HookResult.SUCCESS();
+    }
+
+    private Map<String, String> urlParamToMap(String params) {
+        HashMap<String, String> map = new HashMap<>();
+        if (ObjectUtils.isEmpty(params)) {
+            return map;
+        }
+        String[] paramsArray = params.split("&");
+        if (paramsArray.length == 0) {
+            return map;
+        }
+        for (String param : paramsArray) {
+            String[] paramArray = param.split("=");
+            if (paramArray.length == 2) {
+                map.put(paramArray[0], paramArray[1]);
+            }
+        }
+        return map;
+    }
+}

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

@@ -42,7 +42,7 @@ public class ZLMServerFactory {
      * @param tcpMode 0/null udp 模式,1 tcp 被动模式, 2 tcp 主动模式。
      * @return
      */
-    public int createRTPServer(MediaServerItem mediaServerItem, String streamId, int ssrc, Integer port, Boolean reUsePort, Integer tcpMode) {
+    public int createRTPServer(MediaServerItem mediaServerItem, String streamId, long ssrc, Integer port, Boolean reUsePort, Integer tcpMode) {
         int result = -1;
         // 查询此rtp server 是否已经存在
         JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);

+ 11 - 0
src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java

@@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.media.zlm.dto.ServerKeepaliveData;
 import com.genersoft.iot.vmp.service.bean.MediaServerLoad;
 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
+import com.genersoft.iot.vmp.vmanager.bean.RecordFile;
 
 import java.util.List;
 
@@ -93,4 +94,14 @@ public interface IMediaServerService {
      * @return
      */
     MediaServerLoad getLoad(MediaServerItem mediaServerItem);
+
+    /**
+     * 按时间查找录像文件
+     */
+    List<RecordFile> getRecords(String app, String stream, String startTime, String endTime, List<MediaServerItem> mediaServerItems);
+
+    /**
+     * 查找存在录像文件的时间
+     */
+    List<String> getRecordDates(String app, String stream, int year, int month, List<MediaServerItem> mediaServerItems);
 }

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

@@ -210,7 +210,7 @@ public class DeviceServiceImpl implements IDeviceService {
         redisCatchStorage.updateDevice(device);
         deviceMapper.update(device);
         //进行通道离线
-        deviceChannelMapper.offlineByDeviceId(deviceId);
+//        deviceChannelMapper.offlineByDeviceId(deviceId);
         // 离线释放所有ssrc
         List<SsrcTransaction> ssrcTransactions = streamSession.getSsrcTransactionForAll(deviceId, null, null, null);
         if (ssrcTransactions != null && ssrcTransactions.size() > 0) {

+ 99 - 2
src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java

@@ -24,23 +24,30 @@ import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.utils.JsonUtil;
 import com.genersoft.iot.vmp.utils.redis.RedisUtil;
 import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
+import com.genersoft.iot.vmp.vmanager.bean.RecordFile;
 import okhttp3.OkHttpClient;
 import okhttp3.Request;
 import okhttp3.Response;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.TransactionDefinition;
 import org.springframework.transaction.TransactionStatus;
+import org.springframework.util.Assert;
 import org.springframework.util.ObjectUtils;
 
 import java.io.File;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 
 /**
  * 媒体服务器节点管理
@@ -104,6 +111,11 @@ public class MediaServerServiceImpl implements IMediaServerService {
     @Autowired
     private RedisTemplate<Object, Object> redisTemplate;
 
+    @Qualifier("taskExecutor")
+    @Autowired
+    private ThreadPoolTaskExecutor taskExecutor;
+
+
 
     /**
      * 初始化
@@ -149,7 +161,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
         }
 
         if (streamId == null) {
-            streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase();
+            streamId = String.format("%08x", Long.parseLong(ssrc)).toUpperCase();
         }
         int ssrcCheckParam = 0;
         if (ssrcCheck && tcpMode > 1) {
@@ -158,7 +170,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
         }
         int rtpServerPort;
         if (mediaServerItem.isRtpEnable()) {
-            rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, (ssrcCheck && tcpMode == 0)?Integer.parseInt(ssrc):0, port, reUsePort, tcpMode);
+            rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, (ssrcCheck && tcpMode == 0) ? Long.parseLong(ssrc) : 0, port, reUsePort, tcpMode);
         } else {
             rtpServerPort = mediaServerItem.getRtpProxyPort();
         }
@@ -749,4 +761,89 @@ public class MediaServerServiceImpl implements IMediaServerService {
         return result;
     }
 
+    @Override
+    public List<RecordFile> getRecords(String app, String stream, String startTime, String endTime, List<MediaServerItem> mediaServerItems) {
+        Assert.notNull(app, "app不存在");
+        Assert.notNull(stream, "stream不存在");
+        Assert.notNull(startTime, "startTime不存在");
+        Assert.notNull(endTime, "endTime不存在");
+        Assert.notEmpty(mediaServerItems, "流媒体列表为空");
+
+        CompletableFuture[] completableFutures = new CompletableFuture[mediaServerItems.size()];
+        for (int i = 0; i < mediaServerItems.size(); i++) {
+            completableFutures[i] = getRecordFilesForOne(app, stream, startTime, endTime, mediaServerItems.get(i));
+        }
+        List<RecordFile> result = new ArrayList<>();
+        for (int i = 0; i < completableFutures.length; i++) {
+            try {
+                List<RecordFile> list = (List<RecordFile>) completableFutures[i].get();
+                if (!list.isEmpty()) {
+                    for (int g = 0; g < list.size(); g++) {
+                        list.get(g).setMediaServerId(mediaServerItems.get(i).getId());
+                    }
+                    result.addAll(list);
+                }
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            } catch (ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        Comparator<RecordFile> comparator = Comparator.comparing(RecordFile::getFileName);
+        result.sort(comparator);
+        return result;
+    }
+
+    @Override
+    public List<String> getRecordDates(String app, String stream, int year, int month, List<MediaServerItem> mediaServerItems) {
+        Assert.notNull(app, "app不存在");
+        Assert.notNull(stream, "stream不存在");
+        Assert.notEmpty(mediaServerItems, "流媒体列表为空");
+        CompletableFuture[] completableFutures = new CompletableFuture[mediaServerItems.size()];
+
+        for (int i = 0; i < mediaServerItems.size(); i++) {
+            completableFutures[i] = getRecordDatesForOne(app, stream, year, month, mediaServerItems.get(i));
+        }
+        List<String> result = new ArrayList<>();
+        CompletableFuture.allOf(completableFutures).join();
+        for (CompletableFuture completableFuture : completableFutures) {
+            try {
+                List<String> list = (List<String>) completableFuture.get();
+                result.addAll(list);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            } catch (ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        Collections.sort(result);
+        return result;
+    }
+
+    @Async
+    public CompletableFuture<List<String>> getRecordDatesForOne(String app, String stream, int year, int month, MediaServerItem mediaServerItem) {
+        JSONObject fileListJson = assistRESTfulUtils.getDateList(mediaServerItem, app, stream, year, month);
+        if (fileListJson != null && !fileListJson.isEmpty()) {
+            if (fileListJson.getString("code") != null && fileListJson.getInteger("code") == 0) {
+                JSONArray data = fileListJson.getJSONArray("data");
+                return CompletableFuture.completedFuture(data.toJavaList(String.class));
+            }
+        }
+        return CompletableFuture.completedFuture(new ArrayList<>());
+    }
+
+    @Async
+    public CompletableFuture<List<RecordFile>> getRecordFilesForOne(String app, String stream, String startTime, String endTime, MediaServerItem mediaServerItem) {
+        JSONObject fileListJson = assistRESTfulUtils.getFileList(mediaServerItem, 1, 100000000, app, stream, startTime, endTime);
+        if (fileListJson != null && !fileListJson.isEmpty()) {
+            if (fileListJson.getString("code") != null && fileListJson.getInteger("code") == 0) {
+                JSONObject data = fileListJson.getJSONObject("data");
+                JSONArray list = data.getJSONArray("list");
+                if (list != null) {
+                    return CompletableFuture.completedFuture(list.toJavaList(RecordFile.class));
+                }
+            }
+        }
+        return CompletableFuture.completedFuture(new ArrayList<>());
+    }
 }

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

@@ -1,6 +1,8 @@
 package com.genersoft.iot.vmp.utils;
 
 
+import org.apache.commons.lang3.ObjectUtils;
+
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -109,6 +111,9 @@ public class DateUtil {
     }
 
     public static long getDifferenceForNow(String keepaliveTime) {
+        if (ObjectUtils.isEmpty(keepaliveTime)) {
+            return 0;
+        }
         Instant beforeInstant = Instant.from(formatter.parse(keepaliveTime));
         return ChronoUnit.MILLIS.between(beforeInstant, Instant.now());
     }

+ 50 - 50
src/main/java/com/genersoft/iot/vmp/utils/GitUtil.java

@@ -1,50 +1,50 @@
-package com.genersoft.iot.vmp.utils;
-
-import org.springframework.beans.BeansException;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-import org.springframework.stereotype.Component;
-
-/**    
- * @description:spring bean获取工厂,获取spring中的已初始化的bean
- * @author: swwheihei
- * @date:   2019年6月25日 下午4:51:52   
- * 
- */
-@Component
-public class SpringBeanFactory implements ApplicationContextAware {
-
-	// Spring应用上下文环境
-    private static ApplicationContext applicationContext;
-    
-    /**
-     * 实现ApplicationContextAware接口的回调方法,设置上下文环境
-     */
-    @Override
-    public void setApplicationContext(ApplicationContext applicationContext)
-            throws BeansException {
-    	SpringBeanFactory.applicationContext = applicationContext;
-    }
-
-    public static ApplicationContext getApplicationContext() {
-        return applicationContext;
-    }
-
-    /**
-     * 获取对象 这里重写了bean方法,起主要作用
-     */
-    public static  <T> T getBean(String beanId) throws BeansException {
-        if (applicationContext == null) {
-            return null;
-        }
-        return (T) applicationContext.getBean(beanId);
-    }
-
-    /**
-     * 获取当前环境
-     */
-    public static String getActiveProfile() {
-        return applicationContext.getEnvironment().getActiveProfiles()[0];
-    }
-
-}
+package com.genersoft.iot.vmp.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**    
+ * @description:spring bean获取工厂,获取spring中的已初始化的bean
+ * @author: swwheihei
+ * @date:   2019年6月25日 下午4:51:52   
+ * 
+ */
+@Component
+public class SpringBeanFactory implements ApplicationContextAware {
+
+	// Spring应用上下文环境
+    private static ApplicationContext applicationContext;
+    
+    /**
+     * 实现ApplicationContextAware接口的回调方法,设置上下文环境
+     */
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext)
+            throws BeansException {
+    	SpringBeanFactory.applicationContext = applicationContext;
+    }
+
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+
+    /**
+     * 获取对象 这里重写了bean方法,起主要作用
+     */
+    public static  <T> T getBean(String beanId) throws BeansException {
+        if (applicationContext == null) {
+            return null;
+        }
+        return (T) applicationContext.getBean(beanId);
+    }
+
+    /**
+     * 获取当前环境
+     */
+    public static String getActiveProfile() {
+        return applicationContext.getEnvironment().getActiveProfiles()[0];
+    }
+
+}

+ 45 - 45
src/main/java/com/genersoft/iot/vmp/utils/SystemInfoUtils.java

@@ -1,45 +1,45 @@
-package com.genersoft.iot.vmp.utils.redis;
-
-import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONReader;
-import com.alibaba.fastjson2.JSONWriter;
-import org.springframework.data.redis.serializer.RedisSerializer;
-import org.springframework.data.redis.serializer.SerializationException;
-
-import java.nio.charset.Charset;
-
-/**    
- * @description:使用fastjson实现redis的序列化   
- * @author: swwheihei
- * @date:   2020年5月6日 下午8:40:11     
- */
-public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
-
-	public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-	 
-    private Class<T> clazz;
- 
-    public FastJsonRedisSerializer(Class<T> clazz) {
-        super();
-        this.clazz = clazz;
-    }
- 
-    @Override
-    public byte[] serialize(T t) throws SerializationException {
-        if (t == null) {
-            return new byte[0];
-        }
-        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName, JSONWriter.Feature.WritePairAsJavaBean).getBytes(DEFAULT_CHARSET);
-    }
- 
-    @Override
-    public T deserialize(byte[] bytes) throws SerializationException {
-        if (bytes == null || bytes.length <= 0) {
-            return null;
-        }
-        String str = new String(bytes, DEFAULT_CHARSET);
-        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
-    }
-
-
-}
+package com.genersoft.iot.vmp.utils.redis;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+
+import java.nio.charset.Charset;
+
+/**    
+ * @description:使用fastjson实现redis的序列化   
+ * @author: swwheihei
+ * @date:   2020年5月6日 下午8:40:11     
+ */
+public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
+
+	public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+	 
+    private Class<T> clazz;
+ 
+    public FastJsonRedisSerializer(Class<T> clazz) {
+        super();
+        this.clazz = clazz;
+    }
+ 
+    @Override
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName, JSONWriter.Feature.WritePairAsJavaBean).getBytes(DEFAULT_CHARSET);
+    }
+ 
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length <= 0) {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
+    }
+
+
+}

+ 47 - 47
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java

@@ -1,47 +1,47 @@
-package com.genersoft.iot.vmp.utils.redis;
-
-import com.google.common.collect.Lists;
-import org.springframework.data.redis.core.Cursor;
-import org.springframework.data.redis.core.RedisCallback;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.ScanOptions;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Redis工具类
- *
- * @author swwheihei
- * @date 2020年5月6日 下午8:27:29
- */
-@SuppressWarnings(value = {"rawtypes", "unchecked"})
-public class RedisUtil {
-
-    /**
-     * 模糊查询
-     *
-     * @param query 查询参数
-     * @return
-     */
-    public static List<Object> scan(RedisTemplate redisTemplate, String query) {
-
-        Set<String> resultKeys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
-            ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + query + "*").count(1000).build();
-            Cursor<byte[]> scan = connection.scan(scanOptions);
-            Set<String> keys = new HashSet<>();
-            while (scan.hasNext()) {
-                byte[] next = scan.next();
-                keys.add(new String(next));
-            }
-            return keys;
-        });
-
-        return Lists.newArrayList(resultKeys);
-    }
-}
-
-
-
+package com.genersoft.iot.vmp.utils.redis;
+
+import com.google.common.collect.Lists;
+import org.springframework.data.redis.core.Cursor;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ScanOptions;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Redis工具类
+ *
+ * @author swwheihei
+ * @date 2020年5月6日 下午8:27:29
+ */
+@SuppressWarnings(value = {"rawtypes", "unchecked"})
+public class RedisUtil {
+
+    /**
+     * 模糊查询
+     *
+     * @param query 查询参数
+     * @return
+     */
+    public static List<Object> scan(RedisTemplate redisTemplate, String query) {
+
+        Set<String> resultKeys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
+            ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + query + "*").count(1000).build();
+            Cursor<byte[]> scan = connection.scan(scanOptions);
+            Set<String> keys = new HashSet<>();
+            while (scan.hasNext()) {
+                byte[] next = scan.next();
+                keys.add(new String(next));
+            }
+            return keys;
+        });
+
+        return Lists.newArrayList(resultKeys);
+    }
+}
+
+
+

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil2.java

@@ -91,10 +91,10 @@ public class PsController {
         if (isSend != null && isSend && callId == null) {
             throw new ControllerException(ErrorCode.ERROR100.getCode(),"isSend为true时,CallID不能为空");
         }
-        int ssrcInt = 0;
+        long ssrcInt = 0;
         if (ssrc != null) {
             try {
-                ssrcInt = Integer.parseInt(ssrc);
+                ssrcInt = Long.parseLong(ssrc);
             }catch (NumberFormatException e) {
                 throw new ControllerException(ErrorCode.ERROR100.getCode(),"ssrc格式错误");
             }

+ 2 - 2
src/main/java/com/genersoft/iot/vmp/vmanager/record/RecordController.java

@@ -91,10 +91,10 @@ public class RtpController {
         if (isSend != null && isSend && callId == null) {
             throw new ControllerException(ErrorCode.ERROR100.getCode(),"isSend为true时,CallID不能为空");
         }
-        int ssrcInt = 0;
+        long ssrcInt = 0;
         if (ssrc != null) {
             try {
-                ssrcInt = Integer.parseInt(ssrc);
+                ssrcInt = Long.parseLong(ssrc);
             }catch (NumberFormatException e) {
                 throw new ControllerException(ErrorCode.ERROR100.getCode(),"ssrc格式错误");
             }

+ 2 - 4
src/main/java/com/genersoft/iot/vmp/vmanager/server/ServerController.java

@@ -47,19 +47,17 @@
         :total="total">
       </el-pagination>
     </div>
-    <cloud-record-detail ref="cloudRecordDetail" v-if="recordDetail" :recordFile="chooseRecord" :mediaServerId="mediaServerId" :mediaServerPath="mediaServerPath" ></cloud-record-detail>
 
   </div>
 </template>
 
 <script>
 	import uiHeader from '../layout/UiHeader.vue'
-	import cloudRecordDetail from './CloudRecordDetail.vue'
   import MediaServer from './service/MediaServer'
 	export default {
 		name: 'app',
 		components: {
-			uiHeader, cloudRecordDetail
+      uiHeader
 		},
 		data() {
 			return {
@@ -178,7 +176,7 @@
         // }).catch(function (error) {
         //   console.log(error);
         // });
-
+        this.$router.push(`/cloudRecordDetail/${row.app}/${row.stream}`)
       },
       deleteRecord(){
 			  // TODO

+ 110 - 51
web_src/src/components/CloudRecordDetail.vue

@@ -1,33 +1,62 @@
 <template>
-	<div id="recordDetail">
-		<el-container>
+  <div id="recordDetail" style="width: 100%">
+    <div class="page-header" style="margin-bottom: 0">
+      <div class="page-title">
+        <el-page-header @back="backToList" content="云端录像"></el-page-header>
+      </div>
+
+      <div class="page-header-btn" v-if="!this.$route.params.mediaServerId" style="padding-right: 1rem">
+        <!--        节点选择:-->
+        <!--        <el-select size="mini" @change="chooseMediaChange" style="width: 16rem; margin-right: 1rem;" v-model="mediaServerId" placeholder="请选择" >-->
+        <!--          <el-option-->
+        <!--              key="undefined"-->
+        <!--              label="全部"-->
+        <!--              value="undefined">-->
+        <!--          </el-option>-->
+        <!--          <el-option-->
+        <!--              v-for="item in mediaServerList"-->
+        <!--              :key="item"-->
+        <!--              :label="item"-->
+        <!--              :value="item">-->
+        <!--          </el-option>-->
+        <!--        </el-select>-->
+        <b>节点:</b> {{ mediaServerId }}
+      </div>
+      <div v-if="this.$route.params.mediaServerId" style="margin-right: 1rem;">
+        <span>流媒体:{{ this.$route.params.mediaServerId }}</span>
+      </div>
+    </div>
+    <el-container>
       <el-aside width="260px">
         <div class="record-list-box-box">
           <div style="margin-top: 20px">
-            <el-date-picker size="mini"  style="width: 160px" v-model="chooseDate" :picker-options="pickerOptions" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="dateChange()"></el-date-picker>
-            <el-button size="mini" type="primary" icon="fa fa-cloud-download" style="margin: auto; margin-left: 12px " title="裁剪合并" @click="drawerOpen"></el-button>
+            <el-date-picker size="mini" v-model="chooseDate" :picker-options="pickerOptions" type="date"
+                            value-format="yyyy-MM-dd" placeholder="日期" @change="dateChange()"></el-date-picker>
+            <!--            <el-button :disabled="!mediaServerId" size="mini" type="primary" icon="fa fa-cloud-download" style="margin: auto; margin-left: 12px " title="裁剪合并" @click="drawerOpen"></el-button>-->
           </div>
           <div class="record-list-box" :style="recordListStyle">
             <ul v-if="detailFiles.length >0" class="infinite-list record-list" v-infinite-scroll="infiniteScroll" >
               <li v-for="(item,index) in detailFiles" :key="index" class="infinite-list-item record-list-item" >
-                <el-tag v-if="choosedFile != item" @click="chooseFile(item)">
+                <el-tag v-if="choosedFile !== item.filename" @click="chooseFile(item)">
                   <i class="el-icon-video-camera"  ></i>
-                  {{ item.substring(0,17)}}
+                  {{ item.fileName.substring(0, 17) }}
                 </el-tag>
-                <el-tag type="danger" v-if="choosedFile == item">
+                <el-tag type="danger" v-if="choosedFile === item.filename">
                   <i class="el-icon-video-camera"  ></i>
-                  {{ item.substring(0,17)}}
+                  {{ item.fileName.substring(0, 17) }}
                 </el-tag>
-                <a class="el-icon-download" style="color: #409EFF;font-weight: 600;margin-left: 10px;" :href="`${getFileBasePath()}/download.html?url=download/${recordFile.app}/${recordFile.stream}/${chooseDate}/${item}`" target="_blank" />
+                <a class="el-icon-download" style="color: #409EFF;font-weight: 600;margin-left: 10px;"
+                   :href="`${getFileBasePath(item)}/download.html?url=download/${app}/${stream}/${chooseDate}/${item.fileName}`"
+                   target="_blank"/>
               </li>
             </ul>
           </div>
-          <div v-if="detailFiles.length ==0" class="record-list-no-val" >暂无数据</div>
+          <div v-if="detailFiles.length === 0" class="record-list-no-val">暂无数据</div>
         </div>
 
 
       </el-aside>
-			<el-main style="padding: 22px">
+      <el-main style="padding: 22px">
         <div class="playBox" :style="playerStyle">
           <player ref="recordVideoPlayer" :videoUrl="videoUrl" :height="true" style="width: 100%" ></player>
         </div>
@@ -48,8 +77,8 @@
           </div>
         </div>
 
-			</el-main>
-		</el-container>
+      </el-main>
+    </el-container>
     <el-drawer
       title="录像下载"
       :visible.sync="drawer"
@@ -76,7 +105,8 @@
               <li class="task-list-item" v-for="(item, index) in taskListEnded" :key="index">
                 <div class="task-list-item-box" style="height: 2rem;line-height: 2rem;">
                   <span>{{ item.startTime.substr(10) }}-{{item.endTime.substr(10)}}</span>
-                  <a class="el-icon-download download-btn" :href="getFileBasePath()  + '/download.html?url=download/' + item.recordFile" target="_blank">
+                  <a class="el-icon-download download-btn" :href="getFileBasePath()  + '/download.html?url=download/' "
+                     target="_blank">
                   </a>
                 </div>
               </li>
@@ -113,11 +143,16 @@
 		components: {
 			uiHeader, player
 		},
-    props: ['recordFile', 'mediaServerId', 'dateFiles'],
+    // props: [ 'mediaServerId',],
 		data() {
 			return {
+        app: this.$route.params.app,
+        stream: this.$route.params.stream,
+        mediaServerId: this.$route.params.mediaServerId,
 			  dateFilesObj: [],
+        mediaServerList: [],
 			  detailFiles: [],
+        loading: false,
         chooseDate: null,
         videoUrl: null,
         choosedFile: null,
@@ -195,6 +230,9 @@
 		mounted() {
       this.recordListStyle.height = this.winHeight + "px";
       this.playerStyle["height"] = this.winHeight + "px";
+      console.log(this.app)
+      console.log(this.stream)
+      console.log(this.mediaServerId)
       // 查询当年有视频的日期
       this.getDateInYear(()=>{
         if (Object.values(this.dateFilesObj).length > 0){
@@ -216,7 +254,8 @@
         let chooseFullDate = new Date(this.chooseDate +" " + this.timeFormat);
         if (chooseFullDate.getFullYear() !== this.queryDate.getFullYear()
           || chooseFullDate.getMonth() !== this.queryDate.getMonth()){
-          // this.getDateInYear()
+          this.queryDate = chooseFullDate;
+          this.getDateInYear()
         }
         this.queryRecordDetails(()=>{
           if (this.detailFiles.length > 0){
@@ -242,48 +281,65 @@
         }
       },
       queryRecordDetails: function (callback){
-        let that = this;
-        that.$axios({
+        this.$axios({
           method: 'get',
-          url:`/record_proxy/${that.mediaServerId}/api/record/file/list`,
+          url: `/api/cloud/record/list`,
           params: {
-            app: that.recordFile.app,
-            stream: that.recordFile.stream,
-            startTime: that.chooseDate + " 00:00:00",
-            endTime: that.chooseDate + " 23:59:59",
-            page: that.currentPage,
-            count: that.count
+            app: this.app,
+            stream: this.stream,
+            startTime: this.chooseDate + " 00:00:00",
+            endTime: this.chooseDate + " 23:59:59",
+            page: this.currentPage,
+            count: this.count,
+            mediaServerId: this.mediaServerId
           }
-        }).then(function (res) {
+        }).then((res) => {
           if (res.data.code === 0) {
-            that.total = res.data.data.total;
-            that.detailFiles = that.detailFiles.concat(res.data.data.list);
+            this.total = res.data.data.total;
+            this.detailFiles = this.detailFiles.concat(res.data.data.list);
+            let temp = new Set()
+            for (let i = 0; i < this.detailFiles.length; i++) {
+              temp.add(this.detailFiles[i].mediaServerId)
+            }
+            this.mediaServerList = Array.from(temp)
+            if (this.mediaServerList.length === 1) {
+              this.mediaServerId = this.mediaServerList[0]
+            }
           }
-          that.loading = false;
+          this.loading = false;
           if (callback) callback();
-        }).catch(function (error) {
+        }).catch((error) => {
           console.log(error);
-          that.loading = false;
+          this.loading = false;
         });
       },
       chooseFile(file){
-        this.choosedFile = file;
 			  if (file == null) {
           this.videoUrl = "";
+          this.choosedFile = "";
         }else {
-          this.videoUrl = `${this.getFileBasePath()}/download/${this.recordFile.app}/${this.recordFile.stream}/${this.chooseDate}/${this.choosedFile}`
-
+          this.choosedFile = file.fileName;
+          this.videoUrl = `${this.getFileBasePath(file)}/download/${this.app}/${this.stream}/${this.chooseDate}/${this.choosedFile}`
           console.log(this.videoUrl)
         }
 
       },
+      backToList() {
+        this.$router.back()
+      },
+      chooseMediaChange() {
+
+      },
+      getRecordList() {
 
-      getFileBasePath(){
+      },
+
+      getFileBasePath(item) {
         let basePath = ""
         if (axios.defaults.baseURL.startsWith("http")) {
-          basePath = `${axios.defaults.baseURL}/record_proxy/${this.mediaServerId}`
+          basePath = `${axios.defaults.baseURL}/record_proxy/${item.mediaServerId}`
         }else {
-          basePath = `${window.location.origin}${axios.defaults.baseURL}/record_proxy/${this.mediaServerId}`
+          basePath = `${window.location.origin}${axios.defaults.baseURL}/record_proxy/${item.mediaServerId}`
         }
         return basePath;
       },
@@ -316,7 +372,7 @@
       },
       getTimeForFile(file){
         console.log(file)
-        let timeStr = file.substring(0,17);
+        let timeStr = file.fileName.substring(0, 17);
         if(timeStr.indexOf("~") > 0){
           timeStr = timeStr.replaceAll("-",":")
         }
@@ -370,27 +426,30 @@
         });
       },
       getDateInYear(callback){
-        let that = this;
-        that.dateFilesObj = {};
+        this.dateFilesObj = {};
         this.$axios({
           method: 'get',
-          url:`/record_proxy/${that.mediaServerId}/api/record/date/list`,
+          url: `/api/cloud/record/date/list`,
           params: {
-            app: that.recordFile.app,
-            stream: that.recordFile.stream
+            app: this.app,
+            stream: this.stream,
+            year: this.queryDate.getFullYear(),
+            month: this.queryDate.getMonth() + 1,
+            mediaServerId: this.mediaServerId,
           }
-        }).then(function (res) {
+        }).then((res) => {
+          console.log(res)
           if (res.data.code === 0) {
             if (res.data.data.length > 0) {
               for (let i = 0; i < res.data.data.length; i++) {
-                that.dateFilesObj[res.data.data[i]] = res.data.data[i]
+                this.dateFilesObj[res.data.data[i]] = res.data.data[i]
               }
 
-              console.log(that.dateFilesObj)
+              console.log(this.dateFilesObj)
             }
           }
           if(callback)callback();
-        }).catch(function (error) {
+        }).catch((error) => {
           console.log(error);
         });
       },
@@ -414,8 +473,8 @@
       },
       addTask(){
         this.showTaskBox = true;
-        let startTimeStr = this.chooseDate + " " + this.detailFiles[0].substring(0,8);
-        let endTimeStr = this.chooseDate + " " + this.detailFiles[this.detailFiles.length - 1].substring(9,17);
+        let startTimeStr = this.chooseDate + " " + this.detailFiles[0].fileName.substring(0, 8);
+        let endTimeStr = this.chooseDate + " " + this.detailFiles[this.detailFiles.length - 1].fileName.substring(9, 17);
         this.taskTimeRange[0] = new Date(startTimeStr)
         this.taskTimeRange[1] = new Date(endTimeStr)
       },
@@ -425,8 +484,8 @@
           method: 'get',
           url:`/record_proxy/${that.mediaServerId}/api/record/file/download/task/add`,
           params: {
-            app: that.recordFile.app,
-            stream: that.recordFile.stream,
+            app: that.app,
+            stream: that.stream,
             startTime: moment(this.taskTimeRange[0]).format('YYYY-MM-DD HH:mm:ss'),
             endTime: moment(this.taskTimeRange[1]).format('YYYY-MM-DD HH:mm:ss'),
           }

+ 6 - 0
web_src/src/components/DeviceList.vue

@@ -89,6 +89,8 @@
           <el-button size="medium" icon="el-icon-position" type="text" v-if="!!scope.row.gbId"
                      @click="removeFromGB(scope.row)">移出国标
           </el-button>
+          <el-button size="medium" icon="el-icon-cloudy" type="text" @click="queryCloudRecords(scope.row)">云端录像
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -257,6 +259,10 @@ export default {
         console.error(error);
       });
     },
+    queryCloudRecords: function (row) {
+
+      this.$router.push(`/cloudRecordDetail/${row.app}/${row.stream}`)
+    },
     importChannel: function () {
       this.$refs.importChannel.openDialog(() => {
 

+ 6 - 0
web_src/src/components/StreamProxyList.vue

@@ -91,6 +91,8 @@
           <el-button size="medium" icon="el-icon-check" type="text" :loading="scope.row.startBtnLoading" v-if="!scope.row.enable" @click="start(scope.row)">启用</el-button>
           <el-divider v-if="!scope.row.enable" direction="vertical"></el-divider>
           <el-button size="medium" icon="el-icon-delete" type="text" style="color: #f56c6c" @click="deleteStreamProxy(scope.row)">删除</el-button>
+          <el-button size="medium" icon="el-icon-cloudy" type="text" @click="queryCloudRecords(scope.row)">云端录像
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -243,6 +245,10 @@
 				});
 
 			},
+      queryCloudRecords: function (row) {
+
+        this.$router.push(`/cloudRecordDetail/${row.app}/${row.stream}`)
+      },
 			deleteStreamProxy: function(row){
 				let that = this;
         this.$confirm('确定删除此代理吗?', '提示', {

+ 9 - 0
web_src/src/components/UserManager.vue

@@ -105,6 +105,9 @@
             <el-divider v-if="scope.row.subCount > 0 || scope.row.parental === 1" direction="vertical"></el-divider>
             <el-button size="medium" v-bind:disabled="device == null || device.online === 0" icon="el-icon-video-camera" type="text" @click="queryRecords(scope.row)">设备录像
             </el-button>
+            <el-button size="medium" v-bind:disabled="device == null || device.online === 0" icon="el-icon-cloudy"
+                       type="text" @click="queryCloudRecords(scope.row)">云端录像
+            </el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -283,6 +286,12 @@ export default {
 
       this.$router.push(`/gbRecordDetail/${deviceId}/${channelId}`)
     },
+    queryCloudRecords: function (itemData) {
+      let deviceId = this.deviceId;
+      let channelId = itemData.channelId;
+
+      this.$router.push(`/cloudRecordDetail/rtp/${deviceId}_${channelId}`)
+    },
     stopDevicePush: function (itemData) {
       var that = this;
       this.$axios({

+ 11 - 0
web_src/src/components/common/ h265web.vue

@@ -12,6 +12,7 @@ import map from '../components/map.vue'
 import login from '../components/Login.vue'
 import parentPlatformList from '../components/ParentPlatformList.vue'
 import cloudRecord from '../components/CloudRecord.vue'
+import cloudRecordDetail from '../components/CloudRecordDetail.vue'
 import mediaServerManger from '../components/MediaServerManger.vue'
 import web from '../components/setting/Web.vue'
 import sip from '../components/setting/Sip.vue'
@@ -85,6 +86,16 @@ export default new VueRouter({
           name: 'cloudRecord',
           component: cloudRecord,
         },
+        {
+          path: '/cloudRecordDetail/:app/:stream',
+          name: 'cloudRecordDetail',
+          component: cloudRecordDetail,
+        },
+        {
+          path: '/cloudRecordDetail/:mediaServerId/:app/:stream',
+          name: 'cloudRecordDetail',
+          component: cloudRecordDetail,
+        },
         {
           path: '/mediaServerManger',
           name: 'mediaServerManger',