Bladeren bron

Merge branch 'wvp-28181-2.0' into wvp-jwt-token

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
#	src/main/resources/all-application.yml
648540858 2 jaren geleden
bovenliggende
commit
2bf07305aa
30 gewijzigde bestanden met toevoegingen van 319 en 89 verwijderingen
  1. 6 0
      sql/2.6.6-2.6.7更新.sql
  2. 2 0
      src/main/resources/db/migration/V2.6.7_20230201__初始化.sql
  3. 9 0
      src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
  4. 11 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
  5. 11 0
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
  6. 11 2
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java
  7. 19 8
      src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java
  8. 1 2
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
  9. 24 12
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
  10. 1 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
  11. 5 1
      src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java
  12. 9 6
      src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
  13. 67 15
      src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
  14. 39 7
      src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java
  15. 3 0
      src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java
  16. 2 2
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
  17. 12 0
      src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
  18. 7 3
      src/main/java/com/genersoft/iot/vmp/storager/dao/ParentPlatformMapper.java
  19. 7 5
      src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java
  20. 10 0
      src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java
  21. 7 1
      src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java
  22. 2 2
      src/main/resources/all-application.yml
  23. 1 0
      web_src/index.html
  24. 43 17
      web_src/src/components/common/jessibuca.vue
  25. 1 0
      web_src/src/components/dialog/deviceEdit.vue
  26. 5 1
      web_src/src/components/dialog/platformEdit.vue
  27. 1 1
      web_src/static/js/jessibuca/decoder.js
  28. BIN
      web_src/static/js/jessibuca/decoder.wasm
  29. 2 2
      web_src/static/js/jessibuca/jessibuca.d.ts
  30. 1 1
      web_src/static/js/jessibuca/jessibuca.js

+ 6 - 0
sql/2.6.6-2.6.7更新.sql

@@ -0,0 +1,6 @@
+alter table device
+    add asMessageChannel int default 0;
+
+alter table parent_platform
+    add asMessageChannel int default 0;
+

+ 2 - 0
src/main/resources/db/migration/V2.6.7_20230201__初始化.sql

@@ -47,6 +47,7 @@ CREATE TABLE `device` (
                           `mobilePositionSubmissionInterval` int DEFAULT '5',
                           `mobilePositionSubmissionInterval` int DEFAULT '5',
                           `subscribeCycleForAlarm` int DEFAULT NULL,
                           `subscribeCycleForAlarm` int DEFAULT NULL,
                           `ssrcCheck` int DEFAULT '0',
                           `ssrcCheck` int DEFAULT '0',
+                          `asMessageChannel` int DEFAULT '0',
                           `geoCoordSys` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                           `geoCoordSys` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                           `treeType` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                           `treeType` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                           `custom_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
                           `custom_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
@@ -329,6 +330,7 @@ CREATE TABLE `parent_platform` (
                                    `catalogId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                                    `catalogId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                                    `ptz` int DEFAULT NULL,
                                    `ptz` int DEFAULT NULL,
                                    `rtcp` int DEFAULT NULL,
                                    `rtcp` int DEFAULT NULL,
+                                   `asMessageChannel` int DEFAULT '0',
                                    `status` bit(1) DEFAULT NULL,
                                    `status` bit(1) DEFAULT NULL,
                                    `startOfflinePush` int DEFAULT '0',
                                    `startOfflinePush` int DEFAULT '0',
                                    `administrativeDivision` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
                                    `administrativeDivision` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,

+ 9 - 0
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java

@@ -48,6 +48,7 @@ public class UserSetting {
     private Boolean syncChannelOnDeviceOnline = Boolean.FALSE;
     private Boolean syncChannelOnDeviceOnline = Boolean.FALSE;
 
 
     private Boolean sipLog = Boolean.FALSE;
     private Boolean sipLog = Boolean.FALSE;
+    private Boolean sendToPlatformsWhenIdLost = Boolean.FALSE;
 
 
     private String serverId = "000000";
     private String serverId = "000000";
 
 
@@ -228,4 +229,12 @@ public class UserSetting {
     public void setAllowedOrigins(List<String> allowedOrigins) {
     public void setAllowedOrigins(List<String> allowedOrigins) {
         this.allowedOrigins = allowedOrigins;
         this.allowedOrigins = allowedOrigins;
     }
     }
+
+    public Boolean getSendToPlatformsWhenIdLost() {
+        return sendToPlatformsWhenIdLost;
+    }
+
+    public void setSendToPlatformsWhenIdLost(Boolean sendToPlatformsWhenIdLost) {
+        this.sendToPlatformsWhenIdLost = sendToPlatformsWhenIdLost;
+    }
 }
 }

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

@@ -188,6 +188,9 @@ public class Device {
 	@Schema(description = "SIP交互IP(设备访问平台的IP)")
 	@Schema(description = "SIP交互IP(设备访问平台的IP)")
 	private String localIp;
 	private String localIp;
 
 
+	@Schema(description = "是否作为消息通道")
+	private boolean asMessageChannel;
+
 
 
 	public String getDeviceId() {
 	public String getDeviceId() {
 		return deviceId;
 		return deviceId;
@@ -428,4 +431,12 @@ public class Device {
 	public void setKeepaliveIntervalTime(int keepaliveIntervalTime) {
 	public void setKeepaliveIntervalTime(int keepaliveIntervalTime) {
 		this.keepaliveIntervalTime = keepaliveIntervalTime;
 		this.keepaliveIntervalTime = keepaliveIntervalTime;
 	}
 	}
+
+	public boolean isAsMessageChannel() {
+		return asMessageChannel;
+	}
+
+	public void setAsMessageChannel(boolean asMessageChannel) {
+		this.asMessageChannel = asMessageChannel;
+	}
 }
 }

+ 11 - 0
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java

@@ -189,6 +189,9 @@ public class ParentPlatform {
     @Schema(description = "树类型 国标规定了两种树的展现方式 行政区划 CivilCode 和业务分组:BusinessGrou")
     @Schema(description = "树类型 国标规定了两种树的展现方式 行政区划 CivilCode 和业务分组:BusinessGrou")
     private String treeType;
     private String treeType;
 
 
+    @Schema(description = "是否作为消息通道")
+    private boolean asMessageChannel;
+
     public Integer getId() {
     public Integer getId() {
         return id;
         return id;
     }
     }
@@ -428,4 +431,12 @@ public class ParentPlatform {
     public void setTreeType(String treeType) {
     public void setTreeType(String treeType) {
         this.treeType = treeType;
         this.treeType = treeType;
     }
     }
+
+    public boolean isAsMessageChannel() {
+        return asMessageChannel;
+    }
+
+    public void setAsMessageChannel(boolean asMessageChannel) {
+        this.asMessageChannel = asMessageChannel;
+    }
 }
 }

+ 11 - 2
src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java

@@ -1,6 +1,8 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 package com.genersoft.iot.vmp.gb28181.bean;
 
 
 
 
+import io.swagger.v3.oas.annotations.media.Schema;
+
 import java.time.Instant;
 import java.time.Instant;
 import java.util.List;
 import java.util.List;
 
 
@@ -9,22 +11,29 @@ import java.util.List;
  * @author: swwheihei
  * @author: swwheihei
  * @date:   2020年5月8日 下午2:05:56     
  * @date:   2020年5月8日 下午2:05:56     
  */
  */
+@Schema(description = "设备录像查询结果信息")
 public class RecordInfo {
 public class RecordInfo {
 
 
+	@Schema(description = "设备编号")
 	private String deviceId;
 	private String deviceId;
 
 
+	@Schema(description = "通道编号")
 	private String channelId;
 	private String channelId;
 
 
+	@Schema(description = "命令序列号")
 	private String sn;
 	private String sn;
 
 
+	@Schema(description = "设备名称")
 	private String name;
 	private String name;
-	
+
+	@Schema(description = "列表总数")
 	private int sumNum;
 	private int sumNum;
 
 
 	private int count;
 	private int count;
 
 
 	private Instant lastTime;
 	private Instant lastTime;
-	
+
+	@Schema(description = "")
 	private List<RecordItem> recordList;
 	private List<RecordItem> recordList;
 
 
 	public String getDeviceId() {
 	public String getDeviceId() {

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

@@ -2,9 +2,9 @@ package com.genersoft.iot.vmp.gb28181.bean;
 
 
 
 
 import com.genersoft.iot.vmp.utils.DateUtil;
 import com.genersoft.iot.vmp.utils.DateUtil;
+import io.swagger.v3.oas.annotations.media.Schema;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.NotNull;
 
 
-import java.text.ParseException;
 import java.time.Instant;
 import java.time.Instant;
 import java.time.temporal.TemporalAccessor;
 import java.time.temporal.TemporalAccessor;
 
 
@@ -13,26 +13,37 @@ import java.time.temporal.TemporalAccessor;
  * @author: swwheihei
  * @author: swwheihei
  * @date:   2020年5月8日 下午2:06:54     
  * @date:   2020年5月8日 下午2:06:54     
  */
  */
+@Schema(description = "设备录像详情")
 public class RecordItem  implements Comparable<RecordItem>{
 public class RecordItem  implements Comparable<RecordItem>{
 
 
+	@Schema(description = "设备编号")
 	private String deviceId;
 	private String deviceId;
-	
+
+	@Schema(description = "名称")
 	private String name;
 	private String name;
-	
+
+	@Schema(description = "文件路径名 (可选)")
 	private String filePath;
 	private String filePath;
 
 
+	@Schema(description = "录像文件大小,单位:Byte(可选)")
 	private String fileSize;
 	private String fileSize;
 
 
+	@Schema(description = "录像地址(可选)")
 	private String address;
 	private String address;
-	
+
+	@Schema(description = "录像开始时间(可选)")
 	private String startTime;
 	private String startTime;
-	
+
+	@Schema(description = "录像结束时间(可选)")
 	private String endTime;
 	private String endTime;
-	
+
+	@Schema(description = "保密属性(必选)缺省为0;0:不涉密,1:涉密")
 	private int secrecy;
 	private int secrecy;
-	
+
+	@Schema(description = "录像产生类型(可选)time或alarm 或 manua")
 	private String type;
 	private String type;
-	
+
+	@Schema(description = "录像触发者ID(可选)")
 	private String recorderId;
 	private String recorderId;
 
 
 	public String getDeviceId() {
 	public String getDeviceId() {

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

@@ -122,7 +122,7 @@ public interface ISIPCommander {
 	 */ 
 	 */ 
 	void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
 	void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
 						   String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
 						   String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
-						   SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
+						   SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 
 
 	/**
 	/**
 	 * 视频流停止
 	 * 视频流停止
@@ -221,7 +221,6 @@ public interface ISIPCommander {
 	 *
 	 *
 	 * @param device      视频设备
 	 * @param device      视频设备
 	 * @param channelId      通道id,非通道则是设备本身
 	 * @param channelId      通道id,非通道则是设备本身
-	 * @param frontCmd     上级平台的指令,如果存在则直接下发
 	 * @param enabled     看守位使能:1 = 开启,0 = 关闭
 	 * @param enabled     看守位使能:1 = 开启,0 = 关闭
 	 * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
 	 * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
 	 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
 	 * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255

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

@@ -470,8 +470,9 @@ public class SIPCommander implements ISIPCommander {
      */
      */
     @Override
     @Override
     public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
     public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
-                                  String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
-                                  SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
+                                  String startTime, String endTime, int downloadSpeed,
+                                  InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
+                                  SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
 
 
         logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
         logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
         String sdpIp;
         String sdpIp;
@@ -540,11 +541,14 @@ public class SIPCommander implements ISIPCommander {
         content.append("a=downloadspeed:" + downloadSpeed + "\r\n");
         content.append("a=downloadspeed:" + downloadSpeed + "\r\n");
 
 
         content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
         content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
-        
+        logger.debug("此时请求下载信令的ssrc===>{}",ssrcInfo.getSsrc());
         HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, null, mediaServerItem.getId());
         HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, null, mediaServerItem.getId());
         // 添加订阅
         // 添加订阅
+        CallIdHeader newCallIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport());
+        String callId=newCallIdHeader.getCallId();
         subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
         subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
-            hookEvent.call(new InviteStreamInfo(mediaServerItem, json,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream()));
+            logger.debug("sipc 添加订阅===callId {}",callId);
+            hookEvent.call(new InviteStreamInfo(mediaServerItem, json,callId, "rtp", ssrcInfo.getStream()));
             subscribe.removeSubscribe(hookSubscribe);
             subscribe.removeSubscribe(hookSubscribe);
             hookSubscribe.getContent().put("regist", false);
             hookSubscribe.getContent().put("regist", false);
             hookSubscribe.getContent().put("schema", "rtsp");
             hookSubscribe.getContent().put("schema", "rtsp");
@@ -553,7 +557,7 @@ public class SIPCommander implements ISIPCommander {
                     (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd) -> {
                     (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd) -> {
                         logger.info("[录像]下载结束, 发送BYE");
                         logger.info("[录像]下载结束, 发送BYE");
                         try {
                         try {
-                            streamByeCmd(device, channelId, ssrcInfo.getStream(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId());
+                            streamByeCmd(device, channelId, ssrcInfo.getStream(),callId);
                         } catch (InvalidArgumentException | ParseException | SipException |
                         } catch (InvalidArgumentException | ParseException | SipException |
                                  SsrcTransactionNotFoundException e) {
                                  SsrcTransactionNotFoundException e) {
                             logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage());
                             logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage());
@@ -561,15 +565,24 @@ public class SIPCommander implements ISIPCommander {
                     });
                     });
         });
         });
 
 
-        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()), ssrcInfo.getSsrc());
+        Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,newCallIdHeader, ssrcInfo.getSsrc());
         if (inviteStreamCallback != null) {
         if (inviteStreamCallback != null) {
-            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream()));
+            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,callId, "rtp", ssrcInfo.getStream()));
         }
         }
 
 
-        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent -> {
-            ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
+        sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> {
+            ResponseEvent responseEvent = (ResponseEvent) event.event;
             SIPResponse response = (SIPResponse) responseEvent.getResponse();
             SIPResponse response = (SIPResponse) responseEvent.getResponse();
-            streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download);
+            String contentString =new String(response.getRawContent());
+            int ssrcIndex = contentString.indexOf("y=");
+            String ssrc=ssrcInfo.getSsrc();
+            if (ssrcIndex >= 0) {
+                ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+            }
+            logger.debug("接收到的下载响应ssrc====>{}",ssrcInfo.getSsrc());
+            logger.debug("接收到的下载响应ssrc====>{}",ssrc);
+            streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download);
+            okEvent.response(event);
         });
         });
     }
     }
 
 
@@ -801,7 +814,6 @@ public class SIPCommander implements ISIPCommander {
      *
      *
      * @param device      视频设备
      * @param device      视频设备
      * @param channelId      通道id,非通道则是设备本身
      * @param channelId      通道id,非通道则是设备本身
-     * @param frontCmd     上级平台的指令,如果存在则直接下发
      * @param enabled     看守位使能:1 = 开启,0 = 关闭
      * @param enabled     看守位使能:1 = 开启,0 = 关闭
      * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
      * @param resetTime   自动归位时间间隔,开启看守位时使用,单位:秒(s)
      * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
      * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255
@@ -1376,7 +1388,7 @@ public class SIPCommander implements ISIPCommander {
         if (device == null) {
         if (device == null) {
             return;
             return;
         }
         }
-        logger.info("[发送 报警通知] {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
+        logger.info("[发送报警通知]设备: {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
                 deviceAlarm.getLongitude(), deviceAlarm.getLatitude());
                 deviceAlarm.getLongitude(), deviceAlarm.getLatitude());
 
 
         String characterSet = device.getCharset();
         String characterSet = device.getCharset();

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

@@ -402,7 +402,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
         if (parentPlatform == null) {
         if (parentPlatform == null) {
             return;
             return;
         }
         }
-        logger.info("[发送报警通知] {}/{}->{},{}: {}", parentPlatform.getServerGBId(), deviceAlarm.getChannelId(),
+        logger.info("[发送报警通知]平台: {}/{}->{},{}: {}", parentPlatform.getServerGBId(), deviceAlarm.getChannelId(),
                 deviceAlarm.getLongitude(), deviceAlarm.getLatitude(), JSON.toJSONString(deviceAlarm));
                 deviceAlarm.getLongitude(), deviceAlarm.getLatitude(), JSON.toJSONString(deviceAlarm));
         String characterSet = parentPlatform.getCharacterSet();
         String characterSet = parentPlatform.getCharacterSet();
         StringBuffer deviceStatusXml = new StringBuffer(600);
         StringBuffer deviceStatusXml = new StringBuffer(600);

+ 5 - 1
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java

@@ -163,7 +163,11 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
     public void releaseRequest(String deviceId, String sn,RecordInfo recordInfo){
     public void releaseRequest(String deviceId, String sn,RecordInfo recordInfo){
         String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn;
         String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn;
         // 对数据进行排序
         // 对数据进行排序
-        Collections.sort(recordInfo.getRecordList());
+        if(recordInfo!=null && recordInfo.getRecordList()!=null) {
+            Collections.sort(recordInfo.getRecordList());
+        }else{
+            recordInfo.setRecordList(new ArrayList<>());
+        }
 
 
         RequestMessage msg = new RequestMessage();
         RequestMessage msg = new RequestMessage();
         msg.setKey(key);
         msg.setKey(key);

+ 9 - 6
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java

@@ -256,6 +256,7 @@ public class ZLMHttpHookListener {
         return result;
         return result;
     }
     }
 
 
+
     /**
     /**
      * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
      * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。
      */
      */
@@ -279,9 +280,12 @@ public class ZLMHttpHookListener {
                     subscribe.response(mediaInfo, json);
                     subscribe.response(mediaInfo, json);
                 }
                 }
             }
             }
-            // 流消失移除redis play
+
             List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
             List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
+            // TODO 重构此处逻辑
+
             if (param.isRegist()) {
             if (param.isRegist()) {
+                // 处理流注册的鉴权信息
                 if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
                 if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal()
                         || param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
                         || param.getOriginType() == OriginType.RTSP_PUSH.ordinal()
                         || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
                         || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) {
@@ -300,16 +304,15 @@ public class ZLMHttpHookListener {
             }
             }
 
 
             if ("rtsp".equals(param.getSchema())) {
             if ("rtsp".equals(param.getSchema())) {
+                // 更新流媒体负载信息
                 if (param.isRegist()) {
                 if (param.isRegist()) {
                     mediaServerService.addCount(param.getMediaServerId());
                     mediaServerService.addCount(param.getMediaServerId());
                 } else {
                 } else {
                     mediaServerService.removeCount(param.getMediaServerId());
                     mediaServerService.removeCount(param.getMediaServerId());
                 }
                 }
-                if (param.getOriginType() == OriginType.PULL.ordinal()
-                        || param.getOriginType() == OriginType.FFMPEG_PULL.ordinal()) {
-                    // 设置拉流代理上线/离线
-                    streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream());
-                }
+                // 设置拉流代理上线/离线
+                streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream());
+
                 if ("rtp".equals(param.getApp()) && !param.isRegist()) {
                 if ("rtp".equals(param.getApp()) && !param.isRegist()) {
                     StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(param.getStream());
                     StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(param.getStream());
                     if (streamInfo != null) {
                     if (streamInfo != null) {

+ 67 - 15
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java

@@ -635,23 +635,75 @@ public class PlayServiceImpl implements IPlayService {
             hookCallBack.call(downloadResult);
             hookCallBack.call(downloadResult);
             streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
             streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
         };
         };
-
+        InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> {
+            logger.info("收到订阅消息: " + inviteStreamInfo.getCallId());
+            dynamicTask.stop(downLoadTimeOutTaskKey);
+            StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
+            streamInfo.setStartTime(startTime);
+            streamInfo.setEndTime(endTime);
+            redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
+            downloadResult.setCode(ErrorCode.SUCCESS.getCode());
+            downloadResult.setMsg(ErrorCode.SUCCESS.getMsg());
+            downloadResult.setData(streamInfo);
+            downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
+            downloadResult.setResponse(inviteStreamInfo.getResponse());
+            hookCallBack.call(downloadResult);
+        };
         try {
         try {
             cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack,
             cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack,
-                    inviteStreamInfo -> {
-                        logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
-                        dynamicTask.stop(downLoadTimeOutTaskKey);
-                        StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
-                        streamInfo.setStartTime(startTime);
-                        streamInfo.setEndTime(endTime);
-                        redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
-                        downloadResult.setCode(ErrorCode.SUCCESS.getCode());
-                        downloadResult.setMsg(ErrorCode.SUCCESS.getMsg());
-                        downloadResult.setData(streamInfo);
-                        downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
-                        downloadResult.setResponse(inviteStreamInfo.getResponse());
-                        hookCallBack.call(downloadResult);
-                    }, errorEvent);
+                    hookEvent, errorEvent, eventResult ->
+                    {
+                        if (eventResult.type == SipSubscribe.EventResultType.response) {
+                            ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
+                            String contentString = new String(responseEvent.getResponse().getRawContent());
+                            // 获取ssrc
+                            int ssrcIndex = contentString.indexOf("y=");
+                            // 检查是否有y字段
+                            if (ssrcIndex >= 0) {
+                                //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
+                                String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+                                // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
+                                if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
+                                    return;
+                                }
+                                logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
+                                if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
+                                    logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
+
+                                    if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
+                                        // ssrc 不可用
+                                        // 释放ssrc
+                                        mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                                        streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
+                                        eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用";
+                                        eventResult.statusCode = 400;
+                                        errorEvent.response(eventResult);
+                                        return;
+                                    }
+
+                                    // 单端口模式streamId也有变化,需要重新设置监听
+                                    if (!mediaServerItem.isRtpEnable()) {
+                                        // 添加订阅
+                                        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
+                                        subscribe.removeSubscribe(hookSubscribe);
+                                        hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
+                                        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
+                                            logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
+                                            dynamicTask.stop(downLoadTimeOutTaskKey);
+                                            // hook响应
+                                            onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, hookCallBack);
+                                            hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
+                                        });
+                                    }
+                                    // 关闭rtp server
+                                    mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+                                    // 重新开启ssrc server
+                                    mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort());
+                                }
+                            }
+                        }
+
+                    });
         } catch (InvalidArgumentException | SipException | ParseException e) {
         } catch (InvalidArgumentException | SipException | ParseException e) {
             logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
             logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
 
 

+ 39 - 7
src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java

@@ -1,6 +1,7 @@
 package com.genersoft.iot.vmp.service.redisMsg;
 package com.genersoft.iot.vmp.service.redisMsg;
 
 
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSON;
+import com.genersoft.iot.vmp.conf.UserSetting;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
@@ -44,8 +45,12 @@ public class RedisAlarmMsgListener implements MessageListener {
     @Autowired
     @Autowired
     private ThreadPoolTaskExecutor taskExecutor;
     private ThreadPoolTaskExecutor taskExecutor;
 
 
+    @Autowired
+    private UserSetting userSetting;
+
     @Override
     @Override
     public void onMessage(@NotNull Message message, byte[] bytes) {
     public void onMessage(@NotNull Message message, byte[] bytes) {
+        // 消息示例:  PUBLISH alarm_receive '{ "gbId": "", "alarmSn": 1, "alarmType": "111", "alarmDescription": "222", }'
         logger.info("收到来自REDIS的ALARM通知: {}", new String(message.getBody()));
         logger.info("收到来自REDIS的ALARM通知: {}", new String(message.getBody()));
         boolean isEmpty = taskQueue.isEmpty();
         boolean isEmpty = taskQueue.isEmpty();
         taskQueue.offer(message);
         taskQueue.offer(message);
@@ -74,17 +79,44 @@ public class RedisAlarmMsgListener implements MessageListener {
                         deviceAlarm.setLatitude(0);
                         deviceAlarm.setLatitude(0);
 
 
                         if (ObjectUtils.isEmpty(gbId)) {
                         if (ObjectUtils.isEmpty(gbId)) {
-                            // 发送给所有的上级
-                            List<ParentPlatform> parentPlatforms = storage.queryEnableParentPlatformList(true);
-                            if (parentPlatforms.size() > 0) {
-                                for (ParentPlatform parentPlatform : parentPlatforms) {
+                            if (userSetting.getSendToPlatformsWhenIdLost()) {
+                                // 发送给所有的上级
+                                List<ParentPlatform> parentPlatforms = storage.queryEnableParentPlatformList(true);
+                                if (parentPlatforms.size() > 0) {
+                                    for (ParentPlatform parentPlatform : parentPlatforms) {
+                                        try {
+                                            commanderForPlatform.sendAlarmMessage(parentPlatform, deviceAlarm);
+                                        } catch (SipException | InvalidArgumentException | ParseException e) {
+                                            logger.error("[命令发送失败] 国标级联 发送报警: {}", e.getMessage());
+                                        }
+                                    }
+                                }
+                            }else {
+                                // 获取开启了消息推送的设备和平台
+                                List<ParentPlatform> parentPlatforms = storage.queryEnablePlatformListWithAsMessageChannel();
+                                if (parentPlatforms.size() > 0) {
+                                    for (ParentPlatform parentPlatform : parentPlatforms) {
+                                        try {
+                                            commanderForPlatform.sendAlarmMessage(parentPlatform, deviceAlarm);
+                                        } catch (SipException | InvalidArgumentException | ParseException e) {
+                                            logger.error("[命令发送失败] 国标级联 发送报警: {}", e.getMessage());
+                                        }
+                                    }
+                                }
+
+                            }
+                            // 获取开启了消息推送的设备和平台
+                            List<Device> devices = storage.queryDeviceWithAsMessageChannel();
+                            if (devices.size() > 0) {
+                                for (Device device : devices) {
                                     try {
                                     try {
-                                        commanderForPlatform.sendAlarmMessage(parentPlatform, deviceAlarm);
-                                    } catch (SipException | InvalidArgumentException | ParseException e) {
-                                        logger.error("[命令发送失败] 国标级联 发送报警: {}", e.getMessage());
+                                        commander.sendAlarmMessage(device, deviceAlarm);
+                                    } catch (InvalidArgumentException | SipException | ParseException e) {
+                                        logger.error("[命令发送失败] 发送报警: {}", e.getMessage());
                                     }
                                     }
                                 }
                                 }
                             }
                             }
+
                         }else {
                         }else {
                             Device device = storage.queryVideoDevice(gbId);
                             Device device = storage.queryVideoDevice(gbId);
                             ParentPlatform platform = storage.queryParentPlatByServerGBId(gbId);
                             ParentPlatform platform = storage.queryParentPlatByServerGBId(gbId);

+ 3 - 0
src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java

@@ -378,4 +378,7 @@ public interface IVideoManagerStorage {
 
 
 	List<DeviceChannelExtend> queryChannelsByDeviceId(String serial, List<String> channelIds, Boolean online);
 	List<DeviceChannelExtend> queryChannelsByDeviceId(String serial, List<String> channelIds, Boolean online);
 
 
+	List<ParentPlatform> queryEnablePlatformListWithAsMessageChannel();
+
+	List<Device> queryDeviceWithAsMessageChannel();
 }
 }

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

@@ -344,10 +344,10 @@ public interface DeviceChannelMapper {
             "select * " +
             "select * " +
             "from device_channel " +
             "from device_channel " +
             "where deviceId=#{deviceId}" +
             "where deviceId=#{deviceId}" +
-            " <if test='parentId != null and length != null' > and parentId = #{parentId} or left(channelId, #{parentId.length()}) = #{parentId} and length(channelId)=#{length} </if>" +
+            " <if test='parentId != null and length != null' > and parentId = #{parentId} or left(channelId, LENGTH(#{parentId})) = #{parentId} and length(channelId)=#{length} </if>" +
             " <if test='parentId == null and length != null' > and parentId = #{parentId} or length(channelId)=#{length} </if>" +
             " <if test='parentId == null and length != null' > and parentId = #{parentId} or length(channelId)=#{length} </if>" +
             " <if test='parentId == null and length == null' > and parentId = #{parentId} </if>" +
             " <if test='parentId == null and length == null' > and parentId = #{parentId} </if>" +
-            " <if test='parentId != null and length == null' > and parentId = #{parentId} or left(channelId, #{parentId.length()}) = #{parentId} </if>" +
+            " <if test='parentId != null and length == null' > and parentId = #{parentId} or left(channelId, LENGTH(#{parentId})) = #{parentId} </if>" +
             " </script>"})
             " </script>"})
     List<DeviceChannel> getChannelsWithCivilCodeAndLength(String deviceId, String parentId, Integer length);
     List<DeviceChannel> getChannelsWithCivilCodeAndLength(String deviceId, String parentId, Integer length);
 
 

+ 12 - 0
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java

@@ -39,6 +39,7 @@ public interface DeviceMapper {
             "mobilePositionSubmissionInterval," +
             "mobilePositionSubmissionInterval," +
             "subscribeCycleForAlarm," +
             "subscribeCycleForAlarm," +
             "ssrcCheck," +
             "ssrcCheck," +
+            "asMessageChannel," +
             "geoCoordSys," +
             "geoCoordSys," +
             "treeType," +
             "treeType," +
             "online" +
             "online" +
@@ -70,6 +71,7 @@ public interface DeviceMapper {
                 "mobilePositionSubmissionInterval," +
                 "mobilePositionSubmissionInterval," +
                 "subscribeCycleForAlarm," +
                 "subscribeCycleForAlarm," +
                 "ssrcCheck," +
                 "ssrcCheck," +
+                "asMessageChannel," +
                 "geoCoordSys," +
                 "geoCoordSys," +
                 "treeType," +
                 "treeType," +
                 "online" +
                 "online" +
@@ -98,6 +100,7 @@ public interface DeviceMapper {
                 "#{mobilePositionSubmissionInterval}," +
                 "#{mobilePositionSubmissionInterval}," +
                 "#{subscribeCycleForAlarm}," +
                 "#{subscribeCycleForAlarm}," +
                 "#{ssrcCheck}," +
                 "#{ssrcCheck}," +
+                "#{asMessageChannel}," +
                 "#{geoCoordSys}," +
                 "#{geoCoordSys}," +
                 "#{treeType}," +
                 "#{treeType}," +
                 "#{online}" +
                 "#{online}" +
@@ -152,6 +155,7 @@ public interface DeviceMapper {
             "mobilePositionSubmissionInterval," +
             "mobilePositionSubmissionInterval," +
             "subscribeCycleForAlarm," +
             "subscribeCycleForAlarm," +
             "ssrcCheck," +
             "ssrcCheck," +
+            "asMessageChannel," +
             "geoCoordSys," +
             "geoCoordSys," +
             "treeType," +
             "treeType," +
             "online," +
             "online," +
@@ -192,6 +196,7 @@ public interface DeviceMapper {
             "mobilePositionSubmissionInterval," +
             "mobilePositionSubmissionInterval," +
             "subscribeCycleForAlarm," +
             "subscribeCycleForAlarm," +
             "ssrcCheck," +
             "ssrcCheck," +
+            "asMessageChannel," +
             "geoCoordSys," +
             "geoCoordSys," +
             "treeType," +
             "treeType," +
             "online " +
             "online " +
@@ -222,6 +227,7 @@ public interface DeviceMapper {
             "mobilePositionSubmissionInterval," +
             "mobilePositionSubmissionInterval," +
             "subscribeCycleForAlarm," +
             "subscribeCycleForAlarm," +
             "ssrcCheck," +
             "ssrcCheck," +
+            "asMessageChannel," +
             "geoCoordSys," +
             "geoCoordSys," +
             "treeType," +
             "treeType," +
             "online" +
             "online" +
@@ -243,6 +249,7 @@ public interface DeviceMapper {
             "<if test=\"mobilePositionSubmissionInterval != null\">, mobilePositionSubmissionInterval=#{mobilePositionSubmissionInterval}</if>" +
             "<if test=\"mobilePositionSubmissionInterval != null\">, mobilePositionSubmissionInterval=#{mobilePositionSubmissionInterval}</if>" +
             "<if test=\"subscribeCycleForAlarm != null\">, subscribeCycleForAlarm=#{subscribeCycleForAlarm}</if>" +
             "<if test=\"subscribeCycleForAlarm != null\">, subscribeCycleForAlarm=#{subscribeCycleForAlarm}</if>" +
             "<if test=\"ssrcCheck != null\">, ssrcCheck=#{ssrcCheck}</if>" +
             "<if test=\"ssrcCheck != null\">, ssrcCheck=#{ssrcCheck}</if>" +
+            "<if test=\"asMessageChannel != null\">, asMessageChannel=#{asMessageChannel}</if>" +
             "<if test=\"geoCoordSys != null\">, geoCoordSys=#{geoCoordSys}</if>" +
             "<if test=\"geoCoordSys != null\">, geoCoordSys=#{geoCoordSys}</if>" +
             "<if test=\"treeType != null\">, treeType=#{treeType}</if>" +
             "<if test=\"treeType != null\">, treeType=#{treeType}</if>" +
             "<if test=\"mediaServerId != null\">, mediaServerId=#{mediaServerId}</if>" +
             "<if test=\"mediaServerId != null\">, mediaServerId=#{mediaServerId}</if>" +
@@ -259,6 +266,7 @@ public interface DeviceMapper {
             "updateTime," +
             "updateTime," +
             "charset," +
             "charset," +
             "ssrcCheck," +
             "ssrcCheck," +
+            "asMessageChannel," +
             "geoCoordSys," +
             "geoCoordSys," +
             "treeType," +
             "treeType," +
             "online" +
             "online" +
@@ -271,6 +279,7 @@ public interface DeviceMapper {
             "#{updateTime}," +
             "#{updateTime}," +
             "#{charset}," +
             "#{charset}," +
             "#{ssrcCheck}," +
             "#{ssrcCheck}," +
+            "#{asMessageChannel}," +
             "#{geoCoordSys}," +
             "#{geoCoordSys}," +
             "#{treeType}," +
             "#{treeType}," +
             "#{online}" +
             "#{online}" +
@@ -282,4 +291,7 @@ public interface DeviceMapper {
 
 
     @Select("select * from device")
     @Select("select * from device")
     List<Device> getAll();
     List<Device> getAll();
+
+    @Select("select * from device where  asMessageChannel = 1")
+    List<Device> queryDeviceWithAsMessageChannel();
 }
 }

+ 7 - 3
src/main/java/com/genersoft/iot/vmp/storager/dao/ParentPlatformMapper.java

@@ -15,10 +15,10 @@ import java.util.List;
 public interface ParentPlatformMapper {
 public interface ParentPlatformMapper {
 
 
     @Insert("INSERT INTO parent_platform (enable, name, serverGBId, serverGBDomain, serverIP, serverPort, deviceGBId, deviceIp,  " +
     @Insert("INSERT INTO parent_platform (enable, name, serverGBId, serverGBDomain, serverIP, serverPort, deviceGBId, deviceIp,  " +
-            "            devicePort, username, password, expires, keepTimeout, transport, characterSet, ptz, rtcp, " +
+            "            devicePort, username, password, expires, keepTimeout, transport, characterSet, ptz, rtcp, asMessageChannel, " +
             "            status, startOfflinePush, catalogId, administrativeDivision, catalogGroup, createTime, updateTime, treeType) " +
             "            status, startOfflinePush, catalogId, administrativeDivision, catalogGroup, createTime, updateTime, treeType) " +
             "            VALUES (#{enable}, #{name}, #{serverGBId}, #{serverGBDomain}, #{serverIP}, #{serverPort}, #{deviceGBId}, #{deviceIp}, " +
             "            VALUES (#{enable}, #{name}, #{serverGBId}, #{serverGBDomain}, #{serverIP}, #{serverPort}, #{deviceGBId}, #{deviceIp}, " +
-            "            #{devicePort}, #{username}, #{password}, #{expires}, #{keepTimeout}, #{transport}, #{characterSet}, #{ptz}, #{rtcp}, " +
+            "            #{devicePort}, #{username}, #{password}, #{expires}, #{keepTimeout}, #{transport}, #{characterSet}, #{ptz}, #{rtcp}, #{asMessageChannel}, " +
             "            #{status},  #{startOfflinePush}, #{catalogId}, #{administrativeDivision}, #{catalogGroup}, #{createTime}, #{updateTime}, #{treeType})")
             "            #{status},  #{startOfflinePush}, #{catalogId}, #{administrativeDivision}, #{catalogGroup}, #{createTime}, #{updateTime}, #{treeType})")
     int addParentPlatform(ParentPlatform parentPlatform);
     int addParentPlatform(ParentPlatform parentPlatform);
 
 
@@ -40,6 +40,7 @@ public interface ParentPlatformMapper {
             "characterSet=#{characterSet}, " +
             "characterSet=#{characterSet}, " +
             "ptz=#{ptz}, " +
             "ptz=#{ptz}, " +
             "rtcp=#{rtcp}, " +
             "rtcp=#{rtcp}, " +
+            "asMessageChannel=#{asMessageChannel}, " +
             "status=#{status}, " +
             "status=#{status}, " +
             "startOfflinePush=#{startOfflinePush}, " +
             "startOfflinePush=#{startOfflinePush}, " +
             "catalogGroup=#{catalogGroup}, " +
             "catalogGroup=#{catalogGroup}, " +
@@ -68,9 +69,12 @@ public interface ParentPlatformMapper {
             "FROM parent_platform pp ")
             "FROM parent_platform pp ")
     List<ParentPlatform> getParentPlatformList();
     List<ParentPlatform> getParentPlatformList();
 
 
-    @Select("SELECT * FROM parent_platform WHERE enable=#{enable}")
+    @Select("SELECT * FROM parent_platform WHERE enable=#{enable} ")
     List<ParentPlatform> getEnableParentPlatformList(boolean enable);
     List<ParentPlatform> getEnableParentPlatformList(boolean enable);
 
 
+    @Select("SELECT * FROM parent_platform WHERE enable=1 and asMessageChannel = 1")
+    List<ParentPlatform> queryEnablePlatformListWithAsMessageChannel();
+
     @Select("SELECT * FROM parent_platform WHERE serverGBId=#{platformGbId}")
     @Select("SELECT * FROM parent_platform WHERE serverGBId=#{platformGbId}")
     ParentPlatform getParentPlatByServerGBId(String platformGbId);
     ParentPlatform getParentPlatByServerGBId(String platformGbId);
 
 

+ 7 - 5
src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java

@@ -177,12 +177,14 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
     @Override
     @Override
     public boolean startDownload(StreamInfo stream, String callId) {
     public boolean startDownload(StreamInfo stream, String callId) {
         boolean result;
         boolean result;
+        String key=String.format("%S_%s_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,
+                userSetting.getServerId(), stream.getMediaServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId);
         if (stream.getProgress() == 1) {
         if (stream.getProgress() == 1) {
-            result = RedisUtil.set(String.format("%S_%s_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,
-                    userSetting.getServerId(), stream.getMediaServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream);
+            logger.debug("添加下载缓存==已完成下载=》{}",key);
+            result = RedisUtil.set(key, stream);
         }else {
         }else {
-            result = RedisUtil.set(String.format("%S_%s_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX,
-                    userSetting.getServerId(), stream.getMediaServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream, 60*60);
+            logger.debug("添加下载缓存==未完成下载=》{}",key);
+            result = RedisUtil.set(key, stream, 60*60);
         }
         }
         return result;
         return result;
     }
     }
@@ -617,7 +619,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
                 stream,
                 stream,
                 callId
                 callId
         );
         );
-        List<Object> streamInfoScan = RedisUtil.scan(key);
+        List<Object> streamInfoScan = RedisUtil.scan2(key);
         if (streamInfoScan.size() > 0) {
         if (streamInfoScan.size() > 0) {
             return (StreamInfo) RedisUtil.get((String) streamInfoScan.get(0));
             return (StreamInfo) RedisUtil.get((String) streamInfoScan.get(0));
         }else {
         }else {

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

@@ -524,6 +524,16 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
 		return platformMapper.getEnableParentPlatformList(enable);
 		return platformMapper.getEnableParentPlatformList(enable);
 	}
 	}
 
 
+	@Override
+	public List<ParentPlatform> queryEnablePlatformListWithAsMessageChannel() {
+		return platformMapper.queryEnablePlatformListWithAsMessageChannel();
+	}
+
+	@Override
+	public List<Device> queryDeviceWithAsMessageChannel() {
+		return deviceMapper.queryDeviceWithAsMessageChannel();
+	}
+
 	@Override
 	@Override
 	public void outlineForAllParentPlatform() {
 	public void outlineForAllParentPlatform() {
 		platformMapper.outlineForAllParentPlatform();
 		platformMapper.outlineForAllParentPlatform();

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

@@ -881,7 +881,13 @@ public class RedisUtil {
 
 
         return new ArrayList<>(resultKeys);
         return new ArrayList<>(resultKeys);
     }
     }
-
+    public static List<Object> scan2(String query) {
+        if (redisTemplate == null) {
+            redisTemplate = SpringBeanFactory.getBean("redisTemplate");
+        }
+        Set<String> keys = redisTemplate.keys(query);
+        return new ArrayList<>(keys);
+    }
     //    ============================== 消息发送与订阅 ==============================
     //    ============================== 消息发送与订阅 ==============================
     public static void convertAndSend(String channel, JSONObject msg) {
     public static void convertAndSend(String channel, JSONObject msg) {
         if (redisTemplate == null) {
         if (redisTemplate == null) {

+ 2 - 2
src/main/resources/all-application.yml

@@ -199,8 +199,8 @@ user-settings:
     sip-use-source-ip-as-remote-address: false
     sip-use-source-ip-as-remote-address: false
     # 是否开启sip日志
     # 是否开启sip日志
     sip-log: true
     sip-log: true
-    # 自动数据库升级,保证表结构完整
-    sync-db: true
+    # 消息通道功能-缺少国标ID是否给所有上级发送消息
+    send-to-platforms-when-id-lost: true
     # 跨域配置,配置你访问前端页面的地址即可, 可以配置多个
     # 跨域配置,配置你访问前端页面的地址即可, 可以配置多个
     allowed-origins:
     allowed-origins:
         - http://localhost:8008
         - http://localhost:8008

+ 1 - 0
web_src/index.html

@@ -15,5 +15,6 @@
     <script type="text/javascript" src="./static/js/ZLMRTCClient.js"></script>
     <script type="text/javascript" src="./static/js/ZLMRTCClient.js"></script>
     <script type="text/javascript" src="./static/js/config.js"></script>
     <script type="text/javascript" src="./static/js/config.js"></script>
     <div id="app"></div>
     <div id="app"></div>
+
   </body>
   </body>
 </html>
 </html>

+ 43 - 17
web_src/src/components/common/jessibuca.vue

@@ -1,5 +1,6 @@
 <template>
 <template>
-  <div ref="container" @dblclick="fullscreenSwich" style="width:100%;height:100%;background-color: #000000;margin:0 auto;">
+  <div ref="container" @dblclick="fullscreenSwich"
+       style="width:100%;height:100%;background-color: #000000;margin:0 auto;">
     <div class="buttons-box" id="buttonsBox">
     <div class="buttons-box" id="buttonsBox">
       <div class="buttons-box-left">
       <div class="buttons-box-left">
         <i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick"></i>
         <i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick"></i>
@@ -12,7 +13,7 @@
         <span class="jessibuca-btn">{{ kBps }} kb/s</span>
         <span class="jessibuca-btn">{{ kBps }} kb/s</span>
         <!--          <i class="iconfont icon-file-record1 jessibuca-btn"></i>-->
         <!--          <i class="iconfont icon-file-record1 jessibuca-btn"></i>-->
         <!--          <i class="iconfont icon-xiangqing2 jessibuca-btn" ></i>-->
         <!--          <i class="iconfont icon-xiangqing2 jessibuca-btn" ></i>-->
-        <i class="iconfont icon-camera1196054easyiconnet jessibuca-btn" @click="jessibuca.screenshot('截图','png',0.5)"
+        <i class="iconfont icon-camera1196054easyiconnet jessibuca-btn" @click="screenshot"
            style="font-size: 1rem !important"></i>
            style="font-size: 1rem !important"></i>
         <i class="iconfont icon-shuaxin11 jessibuca-btn" @click="playBtnClick"></i>
         <i class="iconfont icon-shuaxin11 jessibuca-btn" @click="playBtnClick"></i>
         <i v-if="!fullscreen" class="iconfont icon-weibiaoti10 jessibuca-btn" @click="fullscreenSwich"></i>
         <i v-if="!fullscreen" class="iconfont icon-weibiaoti10 jessibuca-btn" @click="fullscreenSwich"></i>
@@ -92,29 +93,49 @@ export default {
       jessibucaPlayer[this._uid] = new window.Jessibuca(Object.assign(
       jessibucaPlayer[this._uid] = new window.Jessibuca(Object.assign(
         {
         {
           container: this.$refs.container,
           container: this.$refs.container,
-          videoBuffer: 0.2, // 最大缓冲时长,单位秒
-          isResize: true,
+          autoWasm: true,
+          background: "",
+          controlAutoHide: false,
+          debug: false,
           decoder: "static/js/jessibuca/decoder.js",
           decoder: "static/js/jessibuca/decoder.js",
-          useMSE: false,
-          showBandwidth: false,
-          isFlv: true,
-          // text: "WVP-PRO",
-          // background: "static/images/zlm-logo.png",
-          loadingText: "加载中",
+          forceNoOffscreen: true,
           hasAudio: typeof (this.hasAudio) == "undefined" ? true : this.hasAudio,
           hasAudio: typeof (this.hasAudio) == "undefined" ? true : this.hasAudio,
-          debug: false,
-          supportDblclickFullscreen: false, // 是否支持屏幕的双击事件,触发全屏,取消全屏事件。
+          hasVideo: true,
+          heartTimeout: 5,
+          heartTimeoutReplay: true,
+          heartTimeoutReplayTimes: 3,
+          hiddenAutoPause: false,
+          hotKey: false,
+          isFlv: false,
+          isFullResize: false,
+          isNotMute: this.isNotMute,
+          isResize: false,
+          keepScreenOn: false,
+          loadingText: "请稍等, 视频加载中......",
+          loadingTimeout: 10,
+          loadingTimeoutReplay: true,
+          loadingTimeoutReplayTimes: 3,
+          openWebglAlignment: false,
           operateBtns: {
           operateBtns: {
             fullscreen: false,
             fullscreen: false,
             screenshot: false,
             screenshot: false,
             play: false,
             play: false,
             audio: false,
             audio: false,
-            recorder: false,
+            record: false
           },
           },
-          record: "record",
-          vod: this.vod,
-          forceNoOffscreen: this.forceNoOffscreen,
-          isNotMute: this.isNotMute,
+          recordType: "webm",
+          rotate: 0,
+          showBandwidth: false,
+          supportDblclickFullscreen: false,
+          timeout: 10,
+          useMSE: location.hostname !== "localhost" && location.protocol !== "https:",
+          useOffscreen: false,
+          useWCS: location.hostname === "localhost" || location.protocol === "https",
+          useWebFullScreen: false,
+          videoBuffer: 0,
+          wasmDecodeAudioSyncVideo: true,
+          wasmDecodeErrorReplay: true,
+          wcsUseVideoRender: true
         },
         },
         options
         options
       ));
       ));
@@ -243,6 +264,11 @@ export default {
       this.err = "";
       this.err = "";
       this.performance = "";
       this.performance = "";
     },
     },
+    screenshot: function () {
+      if (jessibucaPlayer[this._uid]) {
+        jessibucaPlayer[this._uid].screenshot();
+      }
+    },
     mute: function () {
     mute: function () {
       if (jessibucaPlayer[this._uid]) {
       if (jessibucaPlayer[this._uid]) {
         jessibucaPlayer[this._uid].mute();
         jessibucaPlayer[this._uid].mute();

+ 1 - 0
web_src/src/components/dialog/deviceEdit.vue

@@ -66,6 +66,7 @@
           </el-form-item>
           </el-form-item>
           <el-form-item label="其他选项">
           <el-form-item label="其他选项">
             <el-checkbox label="SSRC校验" v-model="form.ssrcCheck" style="float: left"></el-checkbox>
             <el-checkbox label="SSRC校验" v-model="form.ssrcCheck" style="float: left"></el-checkbox>
+            <el-checkbox label="作为消息通道" v-model="form.asMessageChannel" style="float: left"></el-checkbox>
           </el-form-item>
           </el-form-item>
           <el-form-item>
           <el-form-item>
             <div style="float: right;">
             <div style="float: right;">

+ 5 - 1
web_src/src/components/dialog/platformEdit.vue

@@ -96,9 +96,10 @@
               </el-form-item>
               </el-form-item>
               <el-form-item label="其他选项">
               <el-form-item label="其他选项">
                 <el-checkbox label="启用" v-model="platform.enable" @change="checkExpires"></el-checkbox>
                 <el-checkbox label="启用" v-model="platform.enable" @change="checkExpires"></el-checkbox>
-                <el-checkbox label="云台控制" v-model="platform.ptz"></el-checkbox>
+<!--                <el-checkbox label="云台控制" v-model="platform.ptz"></el-checkbox>-->
                 <el-checkbox label="拉起离线推流" v-model="platform.startOfflinePush"></el-checkbox>
                 <el-checkbox label="拉起离线推流" v-model="platform.startOfflinePush"></el-checkbox>
                 <el-checkbox label="RTCP保活" v-model="platform.rtcp" @change="rtcpCheckBoxChange"></el-checkbox>
                 <el-checkbox label="RTCP保活" v-model="platform.rtcp" @change="rtcpCheckBoxChange"></el-checkbox>
+                <el-checkbox label="作为消息通道" v-model="platform.asMessageChannel" ></el-checkbox>
               </el-form-item>
               </el-form-item>
               <el-form-item>
               <el-form-item>
                 <el-button type="primary" @click="onSubmit">{{
                 <el-button type="primary" @click="onSubmit">{{
@@ -145,6 +146,7 @@ export default {
         enable: true,
         enable: true,
         ptz: true,
         ptz: true,
         rtcp: false,
         rtcp: false,
+        asMessageChannel: false,
         name: null,
         name: null,
         serverGBId: null,
         serverGBId: null,
         serverGBDomain: null,
         serverGBDomain: null,
@@ -213,6 +215,7 @@ export default {
         this.platform.enable = platform.enable;
         this.platform.enable = platform.enable;
         this.platform.ptz = platform.ptz;
         this.platform.ptz = platform.ptz;
         this.platform.rtcp = platform.rtcp;
         this.platform.rtcp = platform.rtcp;
+        this.platform.rtcpasMessageChannel = platform.asMessageChannel;
         this.platform.name = platform.name;
         this.platform.name = platform.name;
         this.platform.serverGBId = platform.serverGBId;
         this.platform.serverGBId = platform.serverGBId;
         this.platform.serverGBDomain = platform.serverGBDomain;
         this.platform.serverGBDomain = platform.serverGBDomain;
@@ -290,6 +293,7 @@ export default {
         enable: true,
         enable: true,
         ptz: true,
         ptz: true,
         rtcp: false,
         rtcp: false,
+        asMessageChannel: false,
         name: null,
         name: null,
         serverGBId: null,
         serverGBId: null,
         administrativeDivision: null,
         administrativeDivision: null,

File diff suppressed because it is too large
+ 1 - 1
web_src/static/js/jessibuca/decoder.js


BIN
web_src/static/js/jessibuca/decoder.wasm


+ 2 - 2
web_src/static/js/jessibuca/jessibuca.d.ts

@@ -62,7 +62,7 @@ declare namespace Jessibuca {
         /**
         /**
          * 1. 当为`true`的时候:视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全。等同于 `setScaleMode(2)`
          * 1. 当为`true`的时候:视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全。等同于 `setScaleMode(2)`
          */
          */
-        isFullSize?: boolean;
+        isFullResize?: boolean;
         /**
         /**
          * 1. 当为`true`的时候:ws协议不检验是否以.flv为依据,进行协议解析。
          * 1. 当为`true`的时候:ws协议不检验是否以.flv为依据,进行协议解析。
          */
          */
@@ -120,7 +120,7 @@ declare namespace Jessibuca {
         /**
         /**
          * 加载过程中文案
          * 加载过程中文案
          */
          */
-        loadingText?: boolean;
+        loadingText?: string;
         /**
         /**
          * 背景图片
          * 背景图片
          */
          */

File diff suppressed because it is too large
+ 1 - 1
web_src/static/js/jessibuca/jessibuca.js