Commit 15df08964bb459d6d01ec5ef423eae094eac1c72

Authored by ‘sxh’
1 parent 314423bd

新增设备主子码流选择,默认为不开启

Showing 25 changed files with 901 additions and 367 deletions
src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java
1 package com.genersoft.iot.vmp.common; 1 package com.genersoft.iot.vmp.common;
2 2
3 import com.genersoft.iot.vmp.service.bean.SSRCInfo; 3 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
4 5
5 /** 6 /**
6 * 记录每次发送invite消息的状态 7 * 记录每次发送invite消息的状态
@@ -123,4 +124,40 @@ public class InviteInfo { @@ -123,4 +124,40 @@ public class InviteInfo {
123 public void setStreamMode(String streamMode) { 124 public void setStreamMode(String streamMode) {
124 this.streamMode = streamMode; 125 this.streamMode = streamMode;
125 } 126 }
  127 +
  128 +
  129 + /*=========================设备主子码流逻辑START====================*/
  130 + @Schema(description = "是否为子码流(true-是,false-主码流)")
  131 + private boolean subStream;
  132 +
  133 + public boolean isSubStream() {
  134 + return subStream;
  135 + }
  136 +
  137 + public void setSubStream(boolean subStream) {
  138 + this.subStream = subStream;
  139 + }
  140 +
  141 + public static InviteInfo getInviteInfo(String deviceId, String channelId,Boolean isSubStream, String stream, SSRCInfo ssrcInfo,
  142 + String receiveIp, Integer receivePort, String streamMode,
  143 + InviteSessionType type, InviteSessionStatus status) {
  144 + InviteInfo inviteInfo = new InviteInfo();
  145 + inviteInfo.setDeviceId(deviceId);
  146 + inviteInfo.setChannelId(channelId);
  147 + inviteInfo.setStream(stream);
  148 + inviteInfo.setSsrcInfo(ssrcInfo);
  149 + inviteInfo.setReceiveIp(receiveIp);
  150 + inviteInfo.setReceivePort(receivePort);
  151 + inviteInfo.setStreamMode(streamMode);
  152 + inviteInfo.setType(type);
  153 + inviteInfo.setStatus(status);
  154 + if(isSubStream != null){
  155 + inviteInfo.setSubStream(isSubStream);
  156 + }
  157 + return inviteInfo;
  158 + }
  159 + /*=========================设备主子码流逻辑END====================*/
  160 +
  161 +
  162 +
126 } 163 }
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
@@ -528,4 +528,31 @@ public class StreamInfo implements Serializable, Cloneable{ @@ -528,4 +528,31 @@ public class StreamInfo implements Serializable, Cloneable{
528 } 528 }
529 return instance; 529 return instance;
530 } 530 }
  531 +
  532 +
  533 + /*=========================设备主子码流逻辑START====================*/
  534 + @Schema(description = "是否为子码流(true-是,false-主码流)")
  535 + private boolean subStream;
  536 +
  537 + public boolean isSubStream() {
  538 + return subStream;
  539 + }
  540 +
  541 + public void setSubStream(boolean subStream) {
  542 + this.subStream = subStream;
  543 + }
  544 +
  545 + public static String getPlayStream(String deviceId,String channelId,boolean isSubStream){
  546 + String streamId;
  547 + if(isSubStream){
  548 + streamId = String.format("%s_%s_%s","sub",deviceId, channelId);
  549 + }else {
  550 + streamId = String.format("%s_%s_%s","main", deviceId, channelId);
  551 + }
  552 + return streamId;
  553 + }
  554 +
  555 + /*=========================设备主子码流逻辑END====================*/
  556 +
  557 +
531 } 558 }
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
1 package com.genersoft.iot.vmp.conf; 1 package com.genersoft.iot.vmp.conf;
2 2
  3 +import io.swagger.v3.oas.annotations.media.Schema;
3 import org.springframework.core.annotation.Order; 4 import org.springframework.core.annotation.Order;
4 import org.springframework.boot.context.properties.ConfigurationProperties; 5 import org.springframework.boot.context.properties.ConfigurationProperties;
5 import org.springframework.stereotype.Component; 6 import org.springframework.stereotype.Component;
@@ -25,11 +26,11 @@ public class UserSetting { @@ -25,11 +26,11 @@ public class UserSetting {
25 26
26 private int platformPlayTimeout = 60000; 27 private int platformPlayTimeout = 60000;
27 28
28 - private Boolean interfaceAuthentication = Boolean.TRUE; 29 + private Boolean interfaceAuthentication = Boolean.FALSE;
29 30
30 - private Boolean recordPushLive = Boolean.TRUE; 31 + private Boolean recordPushLive = Boolean.FALSE;
31 32
32 - private Boolean recordSip = Boolean.TRUE; 33 + private Boolean recordSip = Boolean.FALSE;
33 34
34 private Boolean logInDatebase = Boolean.TRUE; 35 private Boolean logInDatebase = Boolean.TRUE;
35 36
@@ -62,6 +63,8 @@ public class UserSetting { @@ -62,6 +63,8 @@ public class UserSetting {
62 63
63 private String thirdPartyGBIdReg = "[\\s\\S]*"; 64 private String thirdPartyGBIdReg = "[\\s\\S]*";
64 65
  66 +
  67 +
65 private List<String> interfaceAuthenticationExcludes = new ArrayList<>(); 68 private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
66 69
67 private List<String> allowedOrigins = new ArrayList<>(); 70 private List<String> allowedOrigins = new ArrayList<>();
@@ -295,4 +298,10 @@ public class UserSetting { @@ -295,4 +298,10 @@ public class UserSetting {
295 public void setSqlLog(Boolean sqlLog) { 298 public void setSqlLog(Boolean sqlLog) {
296 this.sqlLog = sqlLog; 299 this.sqlLog = sqlLog;
297 } 300 }
  301 +
  302 +
  303 +
  304 +
  305 +
  306 +
298 } 307 }
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
@@ -195,6 +195,8 @@ public class Device { @@ -195,6 +195,8 @@ public class Device {
195 private SipTransactionInfo sipTransactionInfo; 195 private SipTransactionInfo sipTransactionInfo;
196 196
197 197
  198 +
  199 +
198 public String getDeviceId() { 200 public String getDeviceId() {
199 return deviceId; 201 return deviceId;
200 } 202 }
@@ -461,4 +463,20 @@ public class Device { @@ -461,4 +463,20 @@ public class Device {
461 public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) { 463 public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
462 this.sipTransactionInfo = sipTransactionInfo; 464 this.sipTransactionInfo = sipTransactionInfo;
463 } 465 }
  466 +
  467 + /*======================设备主子码流逻辑START=========================*/
  468 + @Schema(description = "开启主子码流切换的开关(false-不开启,true-开启)")
  469 + private boolean switchPrimarySubStream;
  470 +
  471 + public boolean isSwitchPrimarySubStream() {
  472 + return switchPrimarySubStream;
  473 + }
  474 +
  475 + public void setSwitchPrimarySubStream(boolean switchPrimarySubStream) {
  476 + this.switchPrimarySubStream = switchPrimarySubStream;
  477 + }
  478 +
  479 + /*======================设备主子码流逻辑END=========================*/
  480 +
  481 +
464 } 482 }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
@@ -153,4 +153,30 @@ public class DeferredResultHolder { @@ -153,4 +153,30 @@ public class DeferredResultHolder {
153 map.remove(msg.getKey()); 153 map.remove(msg.getKey());
154 } 154 }
155 } 155 }
  156 +
  157 + /*============================设备主子码流逻辑START========================*/
  158 + public static String getPlayKey(String deviceId,String channelId,boolean deviceSwitchSubStream,boolean isSubStream){
  159 + String key = null;
  160 + if(deviceSwitchSubStream){
  161 + key = CALLBACK_CMD_PLAY + isSubStream + deviceId + channelId;
  162 + }else {
  163 + key = CALLBACK_CMD_PLAY +deviceId + channelId;
  164 + }
  165 + return key;
  166 + }
  167 +
  168 + public static String getSnapKey(String deviceId,String channelId,boolean deviceSwitchSubStream,boolean isSubStream){
  169 + String key = null;
  170 + if(deviceSwitchSubStream){
  171 + key = CALLBACK_CMD_SNAP + isSubStream + deviceId + channelId;
  172 + }else {
  173 + key = CALLBACK_CMD_SNAP +deviceId + channelId;
  174 + }
  175 + return key;
  176 + }
  177 +
  178 +
  179 + /*============================设备主子码流逻辑END========================*/
  180 +
  181 +
156 } 182 }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -98,7 +98,7 @@ public interface ISIPCommander { @@ -98,7 +98,7 @@ public interface ISIPCommander {
98 * @param device 视频设备 98 * @param device 视频设备
99 * @param channelId 预览通道 99 * @param channelId 预览通道
100 */ 100 */
101 - void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; 101 + void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,boolean isSubStream, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
102 102
103 /** 103 /**
104 * 请求回放视频流 104 * 请求回放视频流
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -266,7 +266,7 @@ public class SIPCommander implements ISIPCommander { @@ -266,7 +266,7 @@ public class SIPCommander implements ISIPCommander {
266 * @param errorEvent sip错误订阅 266 * @param errorEvent sip错误订阅
267 */ 267 */
268 @Override 268 @Override
269 - public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, 269 + public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,boolean isSubStream,
270 ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { 270 ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
271 String stream = ssrcInfo.getStream(); 271 String stream = ssrcInfo.getStream();
272 272
@@ -341,6 +341,22 @@ public class SIPCommander implements ISIPCommander { @@ -341,6 +341,22 @@ public class SIPCommander implements ISIPCommander {
341 } 341 }
342 } 342 }
343 343
  344 + if( device.isSwitchPrimarySubStream() ){
  345 + if("TP-LINK".equals(device.getManufacturer())){
  346 + if (isSubStream){
  347 + content.append("a=streamMode:sub\r\n");
  348 + }else {
  349 + content.append("a=streamMode:main\r\n");
  350 + }
  351 + }else {
  352 + if (isSubStream){
  353 + content.append("a=streamprofile:1\r\n");
  354 + }else {
  355 + content.append("a=streamprofile:0\r\n");
  356 + }
  357 + }
  358 + }
  359 +
344 content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc 360 content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
345 // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率 361 // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
346 // content.append("f=v/2/5/25/1/4000a/1/8/1" + "\r\n"); // 未发现支持此特性的设备 362 // content.append("f=v/2/5/25/1/4000a/1/8/1" + "\r\n"); // 未发现支持此特性的设备
@@ -356,7 +372,11 @@ public class SIPCommander implements ISIPCommander { @@ -356,7 +372,11 @@ public class SIPCommander implements ISIPCommander {
356 // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 372 // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
357 ResponseEvent responseEvent = (ResponseEvent) e.event; 373 ResponseEvent responseEvent = (ResponseEvent) e.event;
358 SIPResponse response = (SIPResponse) responseEvent.getResponse(); 374 SIPResponse response = (SIPResponse) responseEvent.getResponse();
359 - streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAY); 375 + if(device.isSwitchPrimarySubStream()){
  376 + streamSession.put(device.getDeviceId(), channelId, "switch-play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAY);
  377 + }else {
  378 + streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAY);
  379 + }
360 okEvent.response(e); 380 okEvent.response(e);
361 }); 381 });
362 } 382 }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
@@ -142,8 +142,13 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In @@ -142,8 +142,13 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
142 // 可能是设备主动停止 142 // 可能是设备主动停止
143 Device device = storager.queryVideoDeviceByChannelId(platformGbId); 143 Device device = storager.queryVideoDeviceByChannelId(platformGbId);
144 if (device != null) { 144 if (device != null) {
145 - storager.stopPlay(device.getDeviceId(), channelId);  
146 - SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null); 145 + SsrcTransaction ssrcTransactionForPlay = null;
  146 + if (device.isSwitchPrimarySubStream() ) {
  147 + ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "switch-play", null);
  148 + } else {
  149 + storager.stopPlay(device.getDeviceId(), channelId);
  150 + ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null);
  151 + }
147 if (ssrcTransactionForPlay != null){ 152 if (ssrcTransactionForPlay != null){
148 if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){ 153 if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){
149 // 释放ssrc 154 // 释放ssrc
@@ -153,10 +158,17 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In @@ -153,10 +158,17 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
153 } 158 }
154 streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream()); 159 streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream());
155 } 160 }
156 - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);  
157 -  
158 - if (inviteInfo != null) { 161 + InviteInfo inviteInfo = null;
  162 + if (device.isSwitchPrimarySubStream() ) {
  163 + String streamType = ssrcTransactionForPlay.getStream().split("_")[0];
  164 + boolean isSubStream = "sub".equals(streamType);
  165 + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream);
  166 + inviteStreamService.removeInviteInfo(inviteInfo.getType(),inviteInfo.getDeviceId(),inviteInfo.getChannelId(),isSubStream,inviteInfo.getStream());
  167 + }else {
  168 + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
159 inviteStreamService.removeInviteInfo(inviteInfo); 169 inviteStreamService.removeInviteInfo(inviteInfo);
  170 + }
  171 + if (inviteInfo != null) {
160 if (inviteInfo.getStreamInfo() != null) { 172 if (inviteInfo.getStreamInfo() != null) {
161 mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream()); 173 mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream());
162 } 174 }
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
@@ -497,7 +497,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements @@ -497,7 +497,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
497 } 497 }
498 sendRtpItem.setStreamId(streamId); 498 sendRtpItem.setStreamId(streamId);
499 redisCatchStorage.updateSendRTPSever(sendRtpItem); 499 redisCatchStorage.updateSendRTPSever(sendRtpItem);
500 - playService.play(mediaServerItem, device.getDeviceId(), channelId, ((code, msg, data) -> { 500 + playService.play(mediaServerItem, device.getDeviceId(), channelId,false, ((code, msg, data) -> {
501 if (code == InviteErrorCode.SUCCESS.getCode()){ 501 if (code == InviteErrorCode.SUCCESS.getCode()){
502 hookEvent.run(code, msg, data); 502 hookEvent.run(code, msg, data);
503 }else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()){ 503 }else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()){
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -345,10 +345,19 @@ public class ZLMHttpHookListener { @@ -345,10 +345,19 @@ public class ZLMHttpHookListener {
345 } 345 }
346 346
347 if ("rtp".equals(param.getApp()) && !param.isRegist()) { 347 if ("rtp".equals(param.getApp()) && !param.isRegist()) {
348 - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());  
349 - if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) {  
350 - inviteStreamService.removeInviteInfo(inviteInfo);  
351 - storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); 348 + if(param.getStream().split("_").length == 3){
  349 + boolean isSubStream = "sub".equals(param.getStream().split("_")[0]);
  350 + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream(), isSubStream);
  351 + if(inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY )){
  352 + inviteStreamService.removeInviteInfo(inviteInfo.getType(),inviteInfo.getDeviceId(),
  353 + inviteInfo.getChannelId(),inviteInfo.isSubStream(),inviteInfo.getStream());
  354 + }
  355 + }else {
  356 + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
  357 + if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) {
  358 + inviteStreamService.removeInviteInfo(inviteInfo);
  359 + storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
  360 + }
352 } 361 }
353 } else { 362 } else {
354 if (!"rtp".equals(param.getApp())) { 363 if (!"rtp".equals(param.getApp())) {
@@ -476,7 +485,16 @@ public class ZLMHttpHookListener { @@ -476,7 +485,16 @@ public class ZLMHttpHookListener {
476 Device device = deviceService.getDevice(inviteInfo.getDeviceId()); 485 Device device = deviceService.getDevice(inviteInfo.getDeviceId());
477 if (device != null) { 486 if (device != null) {
478 try { 487 try {
479 - if (inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()) != null) { 488 + InviteInfo info = null;
  489 + if(device.isSwitchPrimarySubStream()){
  490 + boolean isSubStream = "sub".equals(param.getStream().split("_")[0]);
  491 + info = inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), inviteInfo.getChannelId(),isSubStream, inviteInfo.getStream());
  492 + }else {
  493 + info = inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream());
  494 +
  495 + }
  496 +
  497 + if (info != null) {
480 cmder.streamByeCmd(device, inviteInfo.getChannelId(), 498 cmder.streamByeCmd(device, inviteInfo.getChannelId(),
481 inviteInfo.getStream(), null); 499 inviteInfo.getStream(), null);
482 } 500 }
@@ -486,9 +504,15 @@ public class ZLMHttpHookListener { @@ -486,9 +504,15 @@ public class ZLMHttpHookListener {
486 } 504 }
487 } 505 }
488 506
489 - inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(),  
490 - inviteInfo.getChannelId(), inviteInfo.getStream());  
491 - storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); 507 + if(device.isSwitchPrimarySubStream()){
  508 + boolean isSubStream = "sub".equals(param.getStream().split("_")[0]);
  509 + inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(),
  510 + inviteInfo.getChannelId(),isSubStream, inviteInfo.getStream());
  511 + }else {
  512 + inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(),
  513 + inviteInfo.getChannelId(), inviteInfo.getStream());
  514 + storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
  515 + }
492 return ret; 516 return ret;
493 } 517 }
494 } else { 518 } else {
@@ -541,12 +565,26 @@ public class ZLMHttpHookListener { @@ -541,12 +565,26 @@ public class ZLMHttpHookListener {
541 565
542 if ("rtp".equals(param.getApp())) { 566 if ("rtp".equals(param.getApp())) {
543 String[] s = param.getStream().split("_"); 567 String[] s = param.getStream().split("_");
544 - if (!mediaInfo.isRtpEnable() || s.length != 2) { 568 + if (!mediaInfo.isRtpEnable() ) {
545 defaultResult.setResult(HookResult.SUCCESS()); 569 defaultResult.setResult(HookResult.SUCCESS());
546 return defaultResult; 570 return defaultResult;
  571 + }else if(s.length != 2 && s.length != 3 ){
  572 + defaultResult.setResult(HookResult.SUCCESS());
  573 + return defaultResult;
  574 + }
  575 + String deviceId = null;
  576 + String channelId = null;
  577 + boolean isSubStream = false;
  578 + if (s[0].length() < 20) {
  579 + if ("sub".equals(s[0])) {
  580 + isSubStream = true;
  581 + }
  582 + deviceId = s[1];
  583 + channelId = s[2];
  584 + } else {
  585 + deviceId = s[0];
  586 + channelId = s[1];
547 } 587 }
548 - String deviceId = s[0];  
549 - String channelId = s[1];  
550 Device device = redisCatchStorage.getDevice(deviceId); 588 Device device = redisCatchStorage.getDevice(deviceId);
551 if (device == null) { 589 if (device == null) {
552 defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); 590 defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
@@ -560,7 +598,7 @@ public class ZLMHttpHookListener { @@ -560,7 +598,7 @@ public class ZLMHttpHookListener {
560 logger.info("[ZLM HOOK] 流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); 598 logger.info("[ZLM HOOK] 流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
561 599
562 RequestMessage msg = new RequestMessage(); 600 RequestMessage msg = new RequestMessage();
563 - String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; 601 + String key = DeferredResultHolder.getPlayKey(deviceId, channelId, device.isSwitchPrimarySubStream(), isSubStream);
564 boolean exist = resultHolder.exist(key, null); 602 boolean exist = resultHolder.exist(key, null);
565 msg.setKey(key); 603 msg.setKey(key);
566 String uuid = UUID.randomUUID().toString(); 604 String uuid = UUID.randomUUID().toString();
@@ -578,7 +616,7 @@ public class ZLMHttpHookListener { @@ -578,7 +616,7 @@ public class ZLMHttpHookListener {
578 resultHolder.put(key, uuid, result); 616 resultHolder.put(key, uuid, result);
579 617
580 if (!exist) { 618 if (!exist) {
581 - playService.play(mediaInfo, deviceId, channelId, (code, message, data) -> { 619 + playService.play(mediaInfo, deviceId, channelId,isSubStream, (code, message, data) -> {
582 msg.setData(new HookResult(code, message)); 620 msg.setData(new HookResult(code, message));
583 resultHolder.invokeResult(msg); 621 resultHolder.invokeResult(msg);
584 }); 622 });
src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java
@@ -4,6 +4,8 @@ import com.genersoft.iot.vmp.common.InviteInfo; @@ -4,6 +4,8 @@ import com.genersoft.iot.vmp.common.InviteInfo;
4 import com.genersoft.iot.vmp.common.InviteSessionType; 4 import com.genersoft.iot.vmp.common.InviteSessionType;
5 import com.genersoft.iot.vmp.service.bean.ErrorCallback; 5 import com.genersoft.iot.vmp.service.bean.ErrorCallback;
6 6
  7 +import java.util.List;
  8 +
7 /** 9 /**
8 * 记录国标点播的状态,包括实时预览,下载,录像回放 10 * 记录国标点播的状态,包括实时预览,下载,录像回放
9 */ 11 */
@@ -70,4 +72,50 @@ public interface IInviteStreamService { @@ -70,4 +72,50 @@ public interface IInviteStreamService {
70 * 统计同一个zlm下的国标收流个数 72 * 统计同一个zlm下的国标收流个数
71 */ 73 */
72 int getStreamInfoCount(String mediaServerId); 74 int getStreamInfoCount(String mediaServerId);
  75 +
  76 +
  77 + /*======================设备主子码流逻辑START=========================*/
  78 + /**
  79 + * 获取点播的状态信息
  80 + */
  81 + InviteInfo getInviteInfoByDeviceAndChannel(InviteSessionType type,
  82 + String deviceId,
  83 + String channelId,boolean isSubStream);
  84 +
  85 + void removeInviteInfoByDeviceAndChannel(InviteSessionType inviteSessionType, String deviceId, String channelId,boolean isSubStream);
  86 +
  87 + InviteInfo getInviteInfo(InviteSessionType type,
  88 + String deviceId,
  89 + String channelId,
  90 + boolean isSubStream,
  91 + String stream);
  92 +
  93 + void removeInviteInfo(InviteSessionType type,
  94 + String deviceId,
  95 + String channelId,
  96 + boolean isSubStream,
  97 + String stream);
  98 +
  99 + void once(InviteSessionType type, String deviceId, String channelId,boolean isSubStream, String stream, ErrorCallback<Object> callback);
  100 +
  101 + void call(InviteSessionType type, String deviceId, String channelId,boolean isSubStream, String stream, int code, String msg, Object data);
  102 +
  103 + void updateInviteInfoSub(InviteInfo inviteInfo);
  104 +
  105 + /**
  106 + * 获取点播的状态信息
  107 + */
  108 + InviteInfo getInviteInfoByStream(InviteSessionType type, String stream,boolean isSubStream);
  109 +
  110 + /**
  111 + * 获取点播的状态信息
  112 + */
  113 + List<Object> getInviteInfos(InviteSessionType type,
  114 + String deviceId,
  115 + String channelId,
  116 + String stream);
  117 + /*======================设备主子码流逻辑END=========================*/
  118 +
  119 +
  120 +
73 } 121 }
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
@@ -16,9 +16,9 @@ import java.text.ParseException; @@ -16,9 +16,9 @@ import java.text.ParseException;
16 */ 16 */
17 public interface IPlayService { 17 public interface IPlayService {
18 18
19 - void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, 19 + void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,boolean isSubStream,
20 ErrorCallback<Object> callback); 20 ErrorCallback<Object> callback);
21 - SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, ErrorCallback<Object> callback); 21 + SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId,boolean isSubStream, ErrorCallback<Object> callback);
22 22
23 MediaServerItem getNewMediaServerItem(Device device); 23 MediaServerItem getNewMediaServerItem(Device device);
24 24
@@ -43,5 +43,5 @@ public interface IPlayService { @@ -43,5 +43,5 @@ public interface IPlayService {
43 43
44 void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; 44 void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException;
45 45
46 - void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback); 46 + void getSnap(String deviceId, String channelId, String fileName,boolean isSubStream, ErrorCallback errorCallback);
47 } 47 }
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
1 package com.genersoft.iot.vmp.service.impl; 1 package com.genersoft.iot.vmp.service.impl;
2 2
  3 +import com.genersoft.iot.vmp.common.InviteSessionType;
3 import com.genersoft.iot.vmp.common.VideoManagerConstants; 4 import com.genersoft.iot.vmp.common.VideoManagerConstants;
4 import com.genersoft.iot.vmp.conf.DynamicTask; 5 import com.genersoft.iot.vmp.conf.DynamicTask;
5 import com.genersoft.iot.vmp.conf.UserSetting; 6 import com.genersoft.iot.vmp.conf.UserSetting;
  7 +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
6 import com.genersoft.iot.vmp.gb28181.bean.*; 8 import com.genersoft.iot.vmp.gb28181.bean.*;
7 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; 9 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
8 import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; 10 import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
9 import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; 11 import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
10 import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; 12 import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
11 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; 13 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
  14 +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
12 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; 15 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
13 import com.genersoft.iot.vmp.service.IDeviceChannelService; 16 import com.genersoft.iot.vmp.service.IDeviceChannelService;
14 import com.genersoft.iot.vmp.service.IDeviceService; 17 import com.genersoft.iot.vmp.service.IDeviceService;
@@ -48,6 +51,8 @@ public class DeviceServiceImpl implements IDeviceService { @@ -48,6 +51,8 @@ public class DeviceServiceImpl implements IDeviceService {
48 private final static Logger logger = LoggerFactory.getLogger(DeviceServiceImpl.class); 51 private final static Logger logger = LoggerFactory.getLogger(DeviceServiceImpl.class);
49 52
50 @Autowired 53 @Autowired
  54 + private SIPCommander cmder;
  55 + @Autowired
51 private DynamicTask dynamicTask; 56 private DynamicTask dynamicTask;
52 57
53 @Autowired 58 @Autowired
@@ -130,6 +135,10 @@ public class DeviceServiceImpl implements IDeviceService { @@ -130,6 +135,10 @@ public class DeviceServiceImpl implements IDeviceService {
130 } 135 }
131 sync(device); 136 sync(device);
132 }else { 137 }else {
  138 +
  139 + if (deviceInDb != null) {
  140 + device.setSwitchPrimarySubStream(deviceInDb.isSwitchPrimarySubStream());
  141 + }
133 if(!device.isOnLine()){ 142 if(!device.isOnLine()){
134 device.setOnLine(true); 143 device.setOnLine(true);
135 device.setCreateTime(now); 144 device.setCreateTime(now);
@@ -581,6 +590,20 @@ public class DeviceServiceImpl implements IDeviceService { @@ -581,6 +590,20 @@ public class DeviceServiceImpl implements IDeviceService {
581 logger.warn("更新设备时未找到设备信息"); 590 logger.warn("更新设备时未找到设备信息");
582 return; 591 return;
583 } 592 }
  593 + if(deviceInStore.isSwitchPrimarySubStream() != device.isSwitchPrimarySubStream()){
  594 + //当修改设备的主子码流开关时,需要校验是否存在流,如果存在流则直接关闭
  595 + List<SsrcTransaction> ssrcTransactionForAll = streamSession.getSsrcTransactionForAll(device.getDeviceId(), null, null, null);
  596 + for (SsrcTransaction ssrcTransaction: ssrcTransactionForAll) {
  597 + try {
  598 + cmder.streamByeCmd(device, ssrcTransaction.getChannelId(), ssrcTransaction.getStream(), null, null);
  599 + } catch (InvalidArgumentException | SsrcTransactionNotFoundException | ParseException | SipException e) {
  600 + throw new RuntimeException(e);
  601 + }
  602 + }
  603 + deviceChannelMapper.clearPlay(device.getDeviceId());
  604 + inviteStreamService.clearInviteInfo(device.getDeviceId());
  605 + }
  606 +
584 if (!ObjectUtils.isEmpty(device.getName())) { 607 if (!ObjectUtils.isEmpty(device.getName())) {
585 deviceInStore.setName(device.getName()); 608 deviceInStore.setName(device.getName());
586 } 609 }
src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java
@@ -198,4 +198,164 @@ public class InviteStreamServiceImpl implements IInviteStreamService { @@ -198,4 +198,164 @@ public class InviteStreamServiceImpl implements IInviteStreamService {
198 } 198 }
199 return count; 199 return count;
200 } 200 }
  201 +
  202 + /*======================设备主子码流逻辑START=========================*/
  203 +
  204 + @Override
  205 + public InviteInfo getInviteInfoByDeviceAndChannel(InviteSessionType type, String deviceId, String channelId, boolean isSubStream) {
  206 + return getInviteInfo(type, deviceId, channelId,isSubStream, null);
  207 + }
  208 +
  209 + @Override
  210 + public void removeInviteInfoByDeviceAndChannel(InviteSessionType inviteSessionType, String deviceId, String channelId, boolean isSubStream) {
  211 + removeInviteInfo(inviteSessionType, deviceId, channelId,isSubStream, null);
  212 + }
  213 +
  214 + @Override
  215 + public InviteInfo getInviteInfo(InviteSessionType type, String deviceId, String channelId,boolean isSubStream, String stream) {
  216 + String key = VideoManagerConstants.INVITE_PREFIX +
  217 + "_" + (type != null ? type : "*") +
  218 + "_" + (isSubStream ? "sub" : "main") +
  219 + "_" + (deviceId != null ? deviceId : "*") +
  220 + "_" + (channelId != null ? channelId : "*") +
  221 + "_" + (stream != null ? stream : "*");
  222 + List<Object> scanResult = RedisUtil.scan(redisTemplate, key);
  223 + if (scanResult.size() != 1) {
  224 + return null;
  225 + }
  226 + return (InviteInfo) redisTemplate.opsForValue().get(scanResult.get(0));
  227 + }
  228 +
  229 + @Override
  230 + public void removeInviteInfo(InviteSessionType type, String deviceId, String channelId, boolean isSubStream, String stream) {
  231 + String scanKey = VideoManagerConstants.INVITE_PREFIX +
  232 + "_" + (type != null ? type : "*") +
  233 + "_" + (isSubStream ? "sub" : "main") +
  234 + "_" + (deviceId != null ? deviceId : "*") +
  235 + "_" + (channelId != null ? channelId : "*") +
  236 + "_" + (stream != null ? stream : "*");
  237 + List<Object> scanResult = RedisUtil.scan(redisTemplate, scanKey);
  238 + if (scanResult.size() > 0) {
  239 + for (Object keyObj : scanResult) {
  240 + String key = (String) keyObj;
  241 + InviteInfo inviteInfo = (InviteInfo) redisTemplate.opsForValue().get(key);
  242 + if (inviteInfo == null) {
  243 + continue;
  244 + }
  245 + redisTemplate.delete(key);
  246 + inviteErrorCallbackMap.remove(buildKey(type, deviceId, channelId, inviteInfo.getStream()));
  247 + }
  248 + }
  249 + }
  250 +
  251 + @Override
  252 + public void once(InviteSessionType type, String deviceId, String channelId, boolean isSubStream, String stream, ErrorCallback<Object> callback) {
  253 + String key = buildSubStreamKey(type, deviceId, channelId,isSubStream, stream);
  254 + List<ErrorCallback<Object>> callbacks = inviteErrorCallbackMap.get(key);
  255 + if (callbacks == null) {
  256 + callbacks = new CopyOnWriteArrayList<>();
  257 + inviteErrorCallbackMap.put(key, callbacks);
  258 + }
  259 + callbacks.add(callback);
  260 + }
  261 +
  262 + @Override
  263 + public void call(InviteSessionType type, String deviceId, String channelId, boolean isSubStream, String stream, int code, String msg, Object data) {
  264 + String key = buildSubStreamKey(type, deviceId, channelId,isSubStream, stream);
  265 + List<ErrorCallback<Object>> callbacks = inviteErrorCallbackMap.get(key);
  266 + if (callbacks == null) {
  267 + return;
  268 + }
  269 + for (ErrorCallback<Object> callback : callbacks) {
  270 + callback.run(code, msg, data);
  271 + }
  272 + inviteErrorCallbackMap.remove(key);
  273 + }
  274 +
  275 +
  276 + private String buildSubStreamKey(InviteSessionType type, String deviceId, String channelId, boolean isSubStream, String stream) {
  277 + String key = type + "_" + (isSubStream ? "sub":"main") + "_" + deviceId + "_" + channelId;
  278 + // 如果ssrc为null那么可以实现一个通道只能一次操作,ssrc不为null则可以支持一个通道多次invite
  279 + if (stream != null) {
  280 + key += ("_" + stream);
  281 + }
  282 + return key;
  283 + }
  284 + @Override
  285 + public void updateInviteInfoSub(InviteInfo inviteInfo) {
  286 + if (inviteInfo == null || (inviteInfo.getDeviceId() == null || inviteInfo.getChannelId() == null)) {
  287 + logger.warn("[更新Invite信息],参数不全: {}", JSON.toJSON(inviteInfo));
  288 + return;
  289 + }
  290 + InviteInfo inviteInfoForUpdate = null;
  291 +
  292 + if (InviteSessionStatus.ready == inviteInfo.getStatus()) {
  293 + if (inviteInfo.getDeviceId() == null
  294 + || inviteInfo.getChannelId() == null
  295 + || inviteInfo.getType() == null
  296 + || inviteInfo.getStream() == null
  297 + ) {
  298 + return;
  299 + }
  300 + inviteInfoForUpdate = inviteInfo;
  301 + } else {
  302 + InviteInfo inviteInfoInRedis = getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(),
  303 + inviteInfo.getChannelId(),inviteInfo.isSubStream(), inviteInfo.getStream());
  304 + if (inviteInfoInRedis == null) {
  305 + logger.warn("[更新Invite信息],未从缓存中读取到Invite信息: deviceId: {}, channel: {}, stream: {}",
  306 + inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream());
  307 + return;
  308 + }
  309 + if (inviteInfo.getStreamInfo() != null) {
  310 + inviteInfoInRedis.setStreamInfo(inviteInfo.getStreamInfo());
  311 + }
  312 + if (inviteInfo.getSsrcInfo() != null) {
  313 + inviteInfoInRedis.setSsrcInfo(inviteInfo.getSsrcInfo());
  314 + }
  315 + if (inviteInfo.getStreamMode() != null) {
  316 + inviteInfoInRedis.setStreamMode(inviteInfo.getStreamMode());
  317 + }
  318 + if (inviteInfo.getReceiveIp() != null) {
  319 + inviteInfoInRedis.setReceiveIp(inviteInfo.getReceiveIp());
  320 + }
  321 + if (inviteInfo.getReceivePort() != null) {
  322 + inviteInfoInRedis.setReceivePort(inviteInfo.getReceivePort());
  323 + }
  324 + if (inviteInfo.getStatus() != null) {
  325 + inviteInfoInRedis.setStatus(inviteInfo.getStatus());
  326 + }
  327 +
  328 + inviteInfoForUpdate = inviteInfoInRedis;
  329 +
  330 + }
  331 + String key = VideoManagerConstants.INVITE_PREFIX +
  332 + "_" + inviteInfoForUpdate.getType() +
  333 + "_" + (inviteInfoForUpdate.isSubStream() ? "sub":"main") +
  334 + "_" + inviteInfoForUpdate.getDeviceId() +
  335 + "_" + inviteInfoForUpdate.getChannelId() +
  336 + "_" + inviteInfoForUpdate.getStream();
  337 + redisTemplate.opsForValue().set(key, inviteInfoForUpdate);
  338 + }
  339 +
  340 + @Override
  341 + public InviteInfo getInviteInfoByStream(InviteSessionType type, String stream, boolean isSubStream) {
  342 + return getInviteInfo(type, null, null,isSubStream, stream);
  343 + }
  344 +
  345 + @Override
  346 + public List<Object> getInviteInfos(InviteSessionType type, String deviceId, String channelId, String stream) {
  347 + String key = VideoManagerConstants.INVITE_PREFIX +
  348 + "_" + (type != null ? type : "*") +
  349 + "_" + (deviceId != null ? deviceId : "*") +
  350 + "_" + (channelId != null ? channelId : "*") +
  351 + "_" + (stream != null ? stream : "*");
  352 + List<Object> scanResult = RedisUtil.scan(redisTemplate, key);
  353 + return scanResult;
  354 + }
  355 +
  356 + /*======================设备主子码流逻辑END=========================*/
  357 +
  358 +
  359 +
  360 +
201 } 361 }
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -115,28 +115,43 @@ public class PlayServiceImpl implements IPlayService { @@ -115,28 +115,43 @@ public class PlayServiceImpl implements IPlayService {
115 115
116 116
117 @Override 117 @Override
118 - public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, ErrorCallback<Object> callback) { 118 + public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId,boolean isSubStream, ErrorCallback<Object> callback) {
119 if (mediaServerItem == null) { 119 if (mediaServerItem == null) {
120 throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm"); 120 throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm");
121 } 121 }
122 122
123 Device device = redisCatchStorage.getDevice(deviceId); 123 Device device = redisCatchStorage.getDevice(deviceId);
124 - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);  
125 - 124 + InviteInfo inviteInfo;
  125 + if(device.isSwitchPrimarySubStream()){
  126 + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream);
  127 + }else {
  128 + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
  129 + }
126 if (inviteInfo != null ) { 130 if (inviteInfo != null ) {
127 if (inviteInfo.getStreamInfo() == null) { 131 if (inviteInfo.getStreamInfo() == null) {
128 // 点播发起了但是尚未成功, 仅注册回调等待结果即可 132 // 点播发起了但是尚未成功, 仅注册回调等待结果即可
129 - inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback); 133 + if(device.isSwitchPrimarySubStream()){
  134 + inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId,isSubStream, null, callback);
  135 + }else {
  136 + inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback);
  137 + }
130 return inviteInfo.getSsrcInfo(); 138 return inviteInfo.getSsrcInfo();
131 }else { 139 }else {
132 StreamInfo streamInfo = inviteInfo.getStreamInfo(); 140 StreamInfo streamInfo = inviteInfo.getStreamInfo();
133 String streamId = streamInfo.getStream(); 141 String streamId = streamInfo.getStream();
134 if (streamId == null) { 142 if (streamId == null) {
135 callback.run(InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null); 143 callback.run(InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null);
136 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
137 - InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(),  
138 - "点播失败, redis缓存streamId等于null",  
139 - null); 144 + if(device.isSwitchPrimarySubStream()){
  145 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  146 + InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(),
  147 + "点播失败, redis缓存streamId等于null",
  148 + null);
  149 + }else {
  150 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  151 + InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(),
  152 + "点播失败, redis缓存streamId等于null",
  153 + null);
  154 + }
140 return inviteInfo.getSsrcInfo(); 155 return inviteInfo.getSsrcInfo();
141 } 156 }
142 String mediaServerId = streamInfo.getMediaServerId(); 157 String mediaServerId = streamInfo.getMediaServerId();
@@ -145,41 +160,64 @@ public class PlayServiceImpl implements IPlayService { @@ -145,41 +160,64 @@ public class PlayServiceImpl implements IPlayService {
145 Boolean ready = zlmrtpServerFactory.isStreamReady(mediaInfo, "rtp", streamId); 160 Boolean ready = zlmrtpServerFactory.isStreamReady(mediaInfo, "rtp", streamId);
146 if (ready != null && ready) { 161 if (ready != null && ready) {
147 callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); 162 callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
148 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
149 - InviteErrorCode.SUCCESS.getCode(),  
150 - InviteErrorCode.SUCCESS.getMsg(),  
151 - streamInfo); 163 + if(device.isSwitchPrimarySubStream()){
  164 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  165 + InviteErrorCode.SUCCESS.getCode(),
  166 + InviteErrorCode.SUCCESS.getMsg(),
  167 + streamInfo);
  168 + }else {
  169 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  170 + InviteErrorCode.SUCCESS.getCode(),
  171 + InviteErrorCode.SUCCESS.getMsg(),
  172 + streamInfo);
  173 + }
152 return inviteInfo.getSsrcInfo(); 174 return inviteInfo.getSsrcInfo();
153 }else { 175 }else {
154 // 点播发起了但是尚未成功, 仅注册回调等待结果即可 176 // 点播发起了但是尚未成功, 仅注册回调等待结果即可
155 - inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback);  
156 - storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());  
157 - inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); 177 + if(device.isSwitchPrimarySubStream()) {
  178 + inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback);
  179 + storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
  180 + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
  181 + }else {
  182 + inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId,isSubStream, null, callback);
  183 + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream);
  184 + }
158 } 185 }
159 } 186 }
160 } 187 }
161 188
162 String streamId = null; 189 String streamId = null;
163 if (mediaServerItem.isRtpEnable()) { 190 if (mediaServerItem.isRtpEnable()) {
164 - streamId = String.format("%s_%s", device.getDeviceId(), channelId); 191 + if(device.isSwitchPrimarySubStream()){
  192 + streamId = StreamInfo.getPlayStream(deviceId, channelId, isSubStream);
  193 + }else {
  194 + streamId = String.format("%s_%s", device.getDeviceId(), channelId);
  195 + }
165 } 196 }
166 SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam()); 197 SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam());
167 if (ssrcInfo == null) { 198 if (ssrcInfo == null) {
168 callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null); 199 callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null);
169 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
170 - InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(),  
171 - InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(),  
172 - null); 200 + if(device.isSwitchPrimarySubStream()){
  201 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  202 + InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(),
  203 + InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(),
  204 + null);
  205 + }else {
  206 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  207 + InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(),
  208 + InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(),
  209 + null);
  210 + }
173 return null; 211 return null;
174 } 212 }
175 // TODO 记录点播的状态 213 // TODO 记录点播的状态
176 - play(mediaServerItem, ssrcInfo, device, channelId, callback); 214 + play(mediaServerItem, ssrcInfo, device, channelId,isSubStream, callback);
177 return ssrcInfo; 215 return ssrcInfo;
178 } 216 }
179 217
180 218
181 @Override 219 @Override
182 - public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, 220 + public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,boolean isSubStream,
183 ErrorCallback<Object> callback) { 221 ErrorCallback<Object> callback) {
184 222
185 if (mediaServerItem == null || ssrcInfo == null) { 223 if (mediaServerItem == null || ssrcInfo == null) {
@@ -188,8 +226,11 @@ public class PlayServiceImpl implements IPlayService { @@ -188,8 +226,11 @@ public class PlayServiceImpl implements IPlayService {
188 null); 226 null);
189 return; 227 return;
190 } 228 }
191 - logger.info("[点播开始] deviceId: {}, channelId: {},收流端口:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());  
192 - 229 + if( device.isSwitchPrimarySubStream() ){
  230 + logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId,isSubStream ? "辅码流" : "主码流", ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
  231 + }else {
  232 + logger.info("[点播开始] deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
  233 + }
193 //端口获取失败的ssrcInfo 没有必要发送点播指令 234 //端口获取失败的ssrcInfo 没有必要发送点播指令
194 if (ssrcInfo.getPort() <= 0) { 235 if (ssrcInfo.getPort() <= 0) {
195 logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo); 236 logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
@@ -198,23 +239,50 @@ public class PlayServiceImpl implements IPlayService { @@ -198,23 +239,50 @@ public class PlayServiceImpl implements IPlayService {
198 streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); 239 streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
199 240
200 callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null); 241 callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null);
201 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
202 - InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null); 242 + if(device.isSwitchPrimarySubStream()){
  243 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  244 + InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null);
  245 + }else {
  246 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  247 + InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null);
  248 + }
203 return; 249 return;
204 } 250 }
205 251
206 // 初始化redis中的invite消息状态 252 // 初始化redis中的invite消息状态
207 - InviteInfo inviteInfo = InviteInfo.getinviteInfo(device.getDeviceId(), channelId, ssrcInfo.getStream(), ssrcInfo,  
208 - mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAY,  
209 - InviteSessionStatus.ready);  
210 - inviteStreamService.updateInviteInfo(inviteInfo); 253 + InviteInfo inviteInfo;
  254 +
  255 + if(device.isSwitchPrimarySubStream()){
  256 + // 初始化redis中的invite消息状态
  257 + inviteInfo = InviteInfo.getInviteInfo(device.getDeviceId(), channelId,isSubStream, ssrcInfo.getStream(), ssrcInfo,
  258 + mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAY,
  259 + InviteSessionStatus.ready);
  260 + inviteStreamService.updateInviteInfoSub(inviteInfo);
  261 + }else {
  262 + // 初始化redis中的invite消息状态
  263 + inviteInfo = InviteInfo.getinviteInfo(device.getDeviceId(), channelId, ssrcInfo.getStream(), ssrcInfo,
  264 + mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAY,
  265 + InviteSessionStatus.ready);
  266 + inviteStreamService.updateInviteInfo(inviteInfo);
  267 + }
211 // 超时处理 268 // 超时处理
212 String timeOutTaskKey = UUID.randomUUID().toString(); 269 String timeOutTaskKey = UUID.randomUUID().toString();
213 dynamicTask.startDelay(timeOutTaskKey, () -> { 270 dynamicTask.startDelay(timeOutTaskKey, () -> {
214 // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况 271 // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况
215 - InviteInfo inviteInfoForTimeOut = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); 272 + InviteInfo inviteInfoForTimeOut;
  273 + if(device.isSwitchPrimarySubStream()){
  274 + // 初始化redis中的invite消息状态
  275 + inviteInfoForTimeOut = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream);
  276 + }else {
  277 + // 初始化redis中的invite消息状态
  278 + inviteInfoForTimeOut = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
  279 + }
216 if (inviteInfoForTimeOut == null || inviteInfoForTimeOut.getStreamInfo() == null) { 280 if (inviteInfoForTimeOut == null || inviteInfoForTimeOut.getStreamInfo() == null) {
217 - logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc()); 281 + if( device.isSwitchPrimarySubStream()){
  282 + logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},码流类型:{},端口:{}, SSRC: {}", device.getDeviceId(), channelId,isSubStream ? "辅码流" : "主码流", ssrcInfo.getPort(), ssrcInfo.getSsrc());
  283 + }else {
  284 + logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc());
  285 + }
218 // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 286 // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
219 // InviteInfo inviteInfoForTimeout = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.play, device.getDeviceId(), channelId); 287 // InviteInfo inviteInfoForTimeout = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.play, device.getDeviceId(), channelId);
220 // if (inviteInfoForTimeout == null) { 288 // if (inviteInfoForTimeout == null) {
@@ -226,10 +294,16 @@ public class PlayServiceImpl implements IPlayService { @@ -226,10 +294,16 @@ public class PlayServiceImpl implements IPlayService {
226 // // TODO 发送cancel 294 // // TODO 发送cancel
227 // } 295 // }
228 callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null); 296 callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null);
229 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
230 - InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null); 297 + if( device.isSwitchPrimarySubStream()){
  298 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  299 + InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null);
  300 + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream);
231 301
232 - inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); 302 + }else {
  303 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  304 + InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null);
  305 + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
  306 + }
233 try { 307 try {
234 cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null); 308 cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
235 } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { 309 } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
@@ -247,25 +321,42 @@ public class PlayServiceImpl implements IPlayService { @@ -247,25 +321,42 @@ public class PlayServiceImpl implements IPlayService {
247 }, userSetting.getPlayTimeout()); 321 }, userSetting.getPlayTimeout());
248 322
249 try { 323 try {
250 - cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> { 324 + cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId,isSubStream, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
251 logger.info("收到订阅消息: " + response.toJSONString()); 325 logger.info("收到订阅消息: " + response.toJSONString());
252 dynamicTask.stop(timeOutTaskKey); 326 dynamicTask.stop(timeOutTaskKey);
253 // hook响应 327 // hook响应
254 - StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId); 328 + StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId,isSubStream);
255 if (streamInfo == null){ 329 if (streamInfo == null){
256 callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), 330 callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
257 InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); 331 InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
258 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
259 - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),  
260 - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); 332 + if( device.isSwitchPrimarySubStream()){
  333 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  334 + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
  335 + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
  336 + }else {
  337 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  338 + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
  339 + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
  340 + }
261 return; 341 return;
262 } 342 }
263 callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); 343 callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
264 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
265 - InviteErrorCode.SUCCESS.getCode(),  
266 - InviteErrorCode.SUCCESS.getMsg(),  
267 - streamInfo);  
268 - logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId); 344 + if( device.isSwitchPrimarySubStream()){
  345 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  346 + InviteErrorCode.SUCCESS.getCode(),
  347 + InviteErrorCode.SUCCESS.getMsg(),
  348 + streamInfo);
  349 + }else {
  350 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  351 + InviteErrorCode.SUCCESS.getCode(),
  352 + InviteErrorCode.SUCCESS.getMsg(),
  353 + streamInfo);
  354 + }
  355 + if( device.isSwitchPrimarySubStream() ){
  356 + logger.info("[点播成功] deviceId: {}, channelId: {},码流类型:{}", device.getDeviceId(), channelId,isSubStream ? "辅码流" : "主码流");
  357 + }else {
  358 + logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
  359 + }
269 String streamUrl; 360 String streamUrl;
270 if (mediaServerItemInuse.getRtspPort() != 0) { 361 if (mediaServerItemInuse.getRtspPort() != 0) {
271 streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInuse.getRtspPort(), "rtp", ssrcInfo.getStream()); 362 streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInuse.getRtspPort(), "rtp", ssrcInfo.getStream());
@@ -321,9 +412,15 @@ public class PlayServiceImpl implements IPlayService { @@ -321,9 +412,15 @@ public class PlayServiceImpl implements IPlayService {
321 412
322 callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), 413 callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
323 InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); 414 InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
324 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
325 - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),  
326 - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); 415 + if(device.isSwitchPrimarySubStream()){
  416 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  417 + InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
  418 + InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
  419 + }else {
  420 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  421 + InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
  422 + InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
  423 + }
327 } 424 }
328 } 425 }
329 return; 426 return;
@@ -340,9 +437,15 @@ public class PlayServiceImpl implements IPlayService { @@ -340,9 +437,15 @@ public class PlayServiceImpl implements IPlayService {
340 437
341 callback.run(InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getCode(), 438 callback.run(InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getCode(),
342 InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getMsg(), null); 439 InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getMsg(), null);
343 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
344 - InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getCode(),  
345 - InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getMsg(), null); 440 + if( device.isSwitchPrimarySubStream()){
  441 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  442 + InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getCode(),
  443 + InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getMsg(), null);
  444 + }else {
  445 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  446 + InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getCode(),
  447 + InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getMsg(), null);
  448 + }
346 449
347 return; 450 return;
348 } 451 }
@@ -358,21 +461,34 @@ public class PlayServiceImpl implements IPlayService { @@ -358,21 +461,34 @@ public class PlayServiceImpl implements IPlayService {
358 logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString()); 461 logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
359 dynamicTask.stop(timeOutTaskKey); 462 dynamicTask.stop(timeOutTaskKey);
360 // hook响应 463 // hook响应
361 - StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId); 464 + StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId,isSubStream);
362 if (streamInfo == null){ 465 if (streamInfo == null){
363 callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), 466 callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
364 InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); 467 InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
365 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
366 - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),  
367 - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); 468 + if( device.isSwitchPrimarySubStream()){
  469 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  470 + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
  471 + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
  472 + }else {
  473 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  474 + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
  475 + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
  476 + }
368 return; 477 return;
369 } 478 }
370 callback.run(InviteErrorCode.SUCCESS.getCode(), 479 callback.run(InviteErrorCode.SUCCESS.getCode(),
371 InviteErrorCode.SUCCESS.getMsg(), streamInfo); 480 InviteErrorCode.SUCCESS.getMsg(), streamInfo);
372 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
373 - InviteErrorCode.SUCCESS.getCode(),  
374 - InviteErrorCode.SUCCESS.getMsg(),  
375 - streamInfo); 481 + if( device.isSwitchPrimarySubStream()){
  482 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  483 + InviteErrorCode.SUCCESS.getCode(),
  484 + InviteErrorCode.SUCCESS.getMsg(),
  485 + streamInfo);
  486 + }else {
  487 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  488 + InviteErrorCode.SUCCESS.getCode(),
  489 + InviteErrorCode.SUCCESS.getMsg(),
  490 + streamInfo);
  491 + }
376 }); 492 });
377 return; 493 return;
378 } 494 }
@@ -395,9 +511,15 @@ public class PlayServiceImpl implements IPlayService { @@ -395,9 +511,15 @@ public class PlayServiceImpl implements IPlayService {
395 511
396 callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), 512 callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
397 "下级自定义了ssrc,重新设置收流信息失败", null); 513 "下级自定义了ssrc,重新设置收流信息失败", null);
398 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
399 - InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),  
400 - "下级自定义了ssrc,重新设置收流信息失败", null); 514 + if( device.isSwitchPrimarySubStream()){
  515 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  516 + InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
  517 + "下级自定义了ssrc,重新设置收流信息失败", null);
  518 + }else {
  519 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  520 + InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
  521 + "下级自定义了ssrc,重新设置收流信息失败", null);
  522 + }
401 523
402 }else { 524 }else {
403 ssrcInfo.setSsrc(ssrcInResponse); 525 ssrcInfo.setSsrc(ssrcInResponse);
@@ -408,7 +530,11 @@ public class PlayServiceImpl implements IPlayService { @@ -408,7 +530,11 @@ public class PlayServiceImpl implements IPlayService {
408 logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正"); 530 logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正");
409 } 531 }
410 } 532 }
411 - inviteStreamService.updateInviteInfo(inviteInfo); 533 + if(device.isSwitchPrimarySubStream()){
  534 + inviteStreamService.updateInviteInfoSub(inviteInfo);
  535 + }else {
  536 + inviteStreamService.updateInviteInfo(inviteInfo);
  537 + }
412 }, (event) -> { 538 }, (event) -> {
413 dynamicTask.stop(timeOutTaskKey); 539 dynamicTask.stop(timeOutTaskKey);
414 mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); 540 mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
@@ -419,11 +545,19 @@ public class PlayServiceImpl implements IPlayService { @@ -419,11 +545,19 @@ public class PlayServiceImpl implements IPlayService {
419 545
420 callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_ERROR.getCode(), 546 callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_ERROR.getCode(),
421 String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null); 547 String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
422 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
423 - InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),  
424 - String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null); 548 + if( device.isSwitchPrimarySubStream()){
  549 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  550 + InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
  551 + String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
425 552
426 - inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); 553 + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream);
  554 + }else {
  555 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  556 + InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
  557 + String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
  558 +
  559 + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
  560 + }
427 }); 561 });
428 } catch (InvalidArgumentException | SipException | ParseException e) { 562 } catch (InvalidArgumentException | SipException | ParseException e) {
429 563
@@ -437,27 +571,51 @@ public class PlayServiceImpl implements IPlayService { @@ -437,27 +571,51 @@ public class PlayServiceImpl implements IPlayService {
437 571
438 callback.run(InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(), 572 callback.run(InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(),
439 InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); 573 InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null);
440 - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,  
441 - InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(),  
442 - InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); 574 + if( device.isSwitchPrimarySubStream()){
  575 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
  576 + InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(),
  577 + InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null);
  578 +
  579 + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream);
  580 + }else {
  581 + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
  582 + InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(),
  583 + InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null);
443 584
444 - inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); 585 + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
  586 + }
445 } 587 }
446 } 588 }
447 589
448 - private StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId) {  
449 - StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId); 590 + private StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId,boolean isSubStream) {
  591 + StreamInfo streamInfo = null;
  592 + Device device = redisCatchStorage.getDevice(deviceId);
  593 + if( device.isSwitchPrimarySubStream() ){
  594 + streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId,isSubStream);
  595 + }else {
  596 + streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId);
  597 + }
450 if (streamInfo != null) { 598 if (streamInfo != null) {
451 - DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);  
452 - if (deviceChannel != null) {  
453 - deviceChannel.setStreamId(streamInfo.getStream());  
454 - storager.startPlay(deviceId, channelId, streamInfo.getStream()); 599 + InviteInfo inviteInfo;
  600 + if(device.isSwitchPrimarySubStream()){
  601 + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream);
  602 + }else {
  603 + DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
  604 + if (deviceChannel != null) {
  605 + deviceChannel.setStreamId(streamInfo.getStream());
  606 + storager.startPlay(deviceId, channelId, streamInfo.getStream());
  607 + }
  608 + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
455 } 609 }
456 - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);  
457 if (inviteInfo != null) { 610 if (inviteInfo != null) {
458 inviteInfo.setStatus(InviteSessionStatus.ok); 611 inviteInfo.setStatus(InviteSessionStatus.ok);
459 inviteInfo.setStreamInfo(streamInfo); 612 inviteInfo.setStreamInfo(streamInfo);
460 - inviteStreamService.updateInviteInfo(inviteInfo); 613 + if(device.isSwitchPrimarySubStream()){
  614 + inviteStreamService.updateInviteInfoSub(inviteInfo);
  615 + }else {
  616 + inviteStreamService.updateInviteInfo(inviteInfo);
  617 + }
  618 +
461 } 619 }
462 } 620 }
463 return streamInfo; 621 return streamInfo;
@@ -994,6 +1152,7 @@ public class PlayServiceImpl implements IPlayService { @@ -994,6 +1152,7 @@ public class PlayServiceImpl implements IPlayService {
994 return streamInfo; 1152 return streamInfo;
995 } 1153 }
996 1154
  1155 +
997 @Override 1156 @Override
998 public void zlmServerOffline(String mediaServerId) { 1157 public void zlmServerOffline(String mediaServerId) {
999 // 处理正在向上推流的上级平台 1158 // 处理正在向上推流的上级平台
@@ -1130,14 +1289,18 @@ public class PlayServiceImpl implements IPlayService { @@ -1130,14 +1289,18 @@ public class PlayServiceImpl implements IPlayService {
1130 } 1289 }
1131 1290
1132 @Override 1291 @Override
1133 - public void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback) { 1292 + public void getSnap(String deviceId, String channelId, String fileName,boolean isSubStream, ErrorCallback errorCallback) {
1134 Device device = deviceService.getDevice(deviceId); 1293 Device device = deviceService.getDevice(deviceId);
1135 if (device == null) { 1294 if (device == null) {
1136 errorCallback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), null); 1295 errorCallback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), null);
1137 return; 1296 return;
1138 } 1297 }
1139 -  
1140 - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); 1298 + InviteInfo inviteInfo;
  1299 + if(device.isSwitchPrimarySubStream()){
  1300 + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream);
  1301 + }else {
  1302 + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
  1303 + }
1141 if (inviteInfo != null) { 1304 if (inviteInfo != null) {
1142 if (inviteInfo.getStreamInfo() != null) { 1305 if (inviteInfo.getStreamInfo() != null) {
1143 // 已存在线直接截图 1306 // 已存在线直接截图
@@ -1163,11 +1326,11 @@ public class PlayServiceImpl implements IPlayService { @@ -1163,11 +1326,11 @@ public class PlayServiceImpl implements IPlayService {
1163 } 1326 }
1164 1327
1165 MediaServerItem newMediaServerItem = getNewMediaServerItem(device); 1328 MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
1166 - play(newMediaServerItem, deviceId, channelId, (code, msg, data)->{ 1329 + play(newMediaServerItem, deviceId, channelId,isSubStream, (code, msg, data)->{
1167 if (code == InviteErrorCode.SUCCESS.getCode()) { 1330 if (code == InviteErrorCode.SUCCESS.getCode()) {
1168 InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); 1331 InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
1169 if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) { 1332 if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) {
1170 - getSnap(deviceId, channelId, fileName, errorCallback); 1333 + getSnap(deviceId, channelId, fileName,isSubStream, errorCallback);
1171 }else { 1334 }else {
1172 errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); 1335 errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null);
1173 } 1336 }
@@ -1177,4 +1340,17 @@ public class PlayServiceImpl implements IPlayService { @@ -1177,4 +1340,17 @@ public class PlayServiceImpl implements IPlayService {
1177 }); 1340 });
1178 } 1341 }
1179 1342
  1343 +
  1344 + /*======================设备主子码流逻辑START=========================*/
  1345 + public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId,boolean isSubStream) {
  1346 + String streamId = resonse.getString("stream");
  1347 + JSONArray tracks = resonse.getJSONArray("tracks");
  1348 + StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem, "rtp", streamId, tracks, null);
  1349 + streamInfo.setDeviceID(deviceId);
  1350 + streamInfo.setChannelId(channelId);
  1351 + streamInfo.setSubStream(isSubStream);
  1352 + return streamInfo;
  1353 + }
  1354 + /*======================设备主子码流逻辑END=========================*/
  1355 +
1180 } 1356 }
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
@@ -450,4 +450,11 @@ public interface DeviceChannelMapper { @@ -450,4 +450,11 @@ public interface DeviceChannelMapper {
450 450
451 @Select("select count(1) from wvp_device_channel") 451 @Select("select count(1) from wvp_device_channel")
452 int getAllChannelCount(); 452 int getAllChannelCount();
  453 +
  454 +
  455 + /*=================设备主子码流逻辑START==============*/
  456 + @Update(value = {"UPDATE wvp_device_channel SET stream_id=null WHERE device_id=#{deviceId}"})
  457 + void clearPlay(String deviceId);
  458 + /*=================设备主子码流逻辑END==============*/
  459 +
453 } 460 }
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
@@ -43,6 +43,7 @@ public interface DeviceMapper { @@ -43,6 +43,7 @@ public interface DeviceMapper {
43 "tree_type," + 43 "tree_type," +
44 "on_line," + 44 "on_line," +
45 "media_server_id," + 45 "media_server_id," +
  46 + "switch_primary_sub_stream," +
46 "(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+ 47 "(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+
47 " FROM wvp_device WHERE device_id = #{deviceId}") 48 " FROM wvp_device WHERE device_id = #{deviceId}")
48 Device getDeviceByDeviceId(String deviceId); 49 Device getDeviceByDeviceId(String deviceId);
@@ -161,6 +162,7 @@ public interface DeviceMapper { @@ -161,6 +162,7 @@ public interface DeviceMapper {
161 "tree_type,"+ 162 "tree_type,"+
162 "on_line,"+ 163 "on_line,"+
163 "media_server_id,"+ 164 "media_server_id,"+
  165 + "switch_primary_sub_stream switchPrimarySubStream,"+
164 "(SELECT count(0) FROM wvp_device_channel WHERE device_id=de.device_id) as channel_count " + 166 "(SELECT count(0) FROM wvp_device_channel WHERE device_id=de.device_id) as channel_count " +
165 "FROM wvp_device de" + 167 "FROM wvp_device de" +
166 "<if test=\"onLine != null\"> where on_line=${onLine}</if>"+ 168 "<if test=\"onLine != null\"> where on_line=${onLine}</if>"+
@@ -253,6 +255,7 @@ public interface DeviceMapper { @@ -253,6 +255,7 @@ public interface DeviceMapper {
253 "<if test=\"asMessageChannel != null\">, as_message_channel=#{asMessageChannel}</if>" + 255 "<if test=\"asMessageChannel != null\">, as_message_channel=#{asMessageChannel}</if>" +
254 "<if test=\"geoCoordSys != null\">, geo_coord_sys=#{geoCoordSys}</if>" + 256 "<if test=\"geoCoordSys != null\">, geo_coord_sys=#{geoCoordSys}</if>" +
255 "<if test=\"treeType != null\">, tree_type=#{treeType}</if>" + 257 "<if test=\"treeType != null\">, tree_type=#{treeType}</if>" +
  258 + "<if test=\"switchPrimarySubStream != null\">, switch_primary_sub_stream=#{switchPrimarySubStream}</if>" +
256 "<if test=\"mediaServerId != null\">, media_server_id=#{mediaServerId}</if>" + 259 "<if test=\"mediaServerId != null\">, media_server_id=#{mediaServerId}</if>" +
257 "WHERE device_id=#{deviceId}"+ 260 "WHERE device_id=#{deviceId}"+
258 " </script>"}) 261 " </script>"})
@@ -271,7 +274,8 @@ public interface DeviceMapper { @@ -271,7 +274,8 @@ public interface DeviceMapper {
271 "geo_coord_sys,"+ 274 "geo_coord_sys,"+
272 "tree_type,"+ 275 "tree_type,"+
273 "on_line,"+ 276 "on_line,"+
274 - "media_server_id"+ 277 + "media_server_id,"+
  278 + "switch_primary_sub_stream"+
275 ") VALUES (" + 279 ") VALUES (" +
276 "#{deviceId}," + 280 "#{deviceId}," +
277 "#{name}," + 281 "#{name}," +
@@ -285,7 +289,8 @@ public interface DeviceMapper { @@ -285,7 +289,8 @@ public interface DeviceMapper {
285 "#{geoCoordSys}," + 289 "#{geoCoordSys}," +
286 "#{treeType}," + 290 "#{treeType}," +
287 "#{onLine}," + 291 "#{onLine}," +
288 - "#{mediaServerId}" + 292 + "#{mediaServerId}," +
  293 + "#{switchPrimarySubStream}" +
289 ")") 294 ")")
290 void addCustomDevice(Device device); 295 void addCustomDevice(Device device);
291 296
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
@@ -88,16 +88,17 @@ public class PlayController { @@ -88,16 +88,17 @@ public class PlayController {
88 @Operation(summary = "开始点播") 88 @Operation(summary = "开始点播")
89 @Parameter(name = "deviceId", description = "设备国标编号", required = true) 89 @Parameter(name = "deviceId", description = "设备国标编号", required = true)
90 @Parameter(name = "channelId", description = "通道国标编号", required = true) 90 @Parameter(name = "channelId", description = "通道国标编号", required = true)
  91 + @Parameter(name = "isSubStream", description = "是否子码流(true-子码流,false-主码流),默认为false", required = true)
91 @GetMapping("/start/{deviceId}/{channelId}") 92 @GetMapping("/start/{deviceId}/{channelId}")
92 public DeferredResult<WVPResult<StreamContent>> play(HttpServletRequest request, @PathVariable String deviceId, 93 public DeferredResult<WVPResult<StreamContent>> play(HttpServletRequest request, @PathVariable String deviceId,
93 - @PathVariable String channelId) { 94 + @PathVariable String channelId,boolean isSubStream) {
94 95
95 // 获取可用的zlm 96 // 获取可用的zlm
96 Device device = storager.queryVideoDevice(deviceId); 97 Device device = storager.queryVideoDevice(deviceId);
97 MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); 98 MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
98 99
99 RequestMessage requestMessage = new RequestMessage(); 100 RequestMessage requestMessage = new RequestMessage();
100 - String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; 101 + String key = DeferredResultHolder.getPlayKey(deviceId,channelId,device.isSwitchPrimarySubStream(),isSubStream);
101 requestMessage.setKey(key); 102 requestMessage.setKey(key);
102 String uuid = UUID.randomUUID().toString(); 103 String uuid = UUID.randomUUID().toString();
103 requestMessage.setId(uuid); 104 requestMessage.setId(uuid);
@@ -116,7 +117,7 @@ public class PlayController { @@ -116,7 +117,7 @@ public class PlayController {
116 // 录像查询以channelId作为deviceId查询 117 // 录像查询以channelId作为deviceId查询
117 resultHolder.put(key, uuid, result); 118 resultHolder.put(key, uuid, result);
118 119
119 - playService.play(newMediaServerItem, deviceId, channelId, (code, msg, data) -> { 120 + playService.play(newMediaServerItem, deviceId, channelId,isSubStream, (code, msg, data) -> {
120 WVPResult<StreamContent> wvpResult = new WVPResult<>(); 121 WVPResult<StreamContent> wvpResult = new WVPResult<>();
121 if (code == InviteErrorCode.SUCCESS.getCode()) { 122 if (code == InviteErrorCode.SUCCESS.getCode()) {
122 wvpResult.setCode(ErrorCode.SUCCESS.getCode()); 123 wvpResult.setCode(ErrorCode.SUCCESS.getCode());
@@ -142,8 +143,9 @@ public class PlayController { @@ -142,8 +143,9 @@ public class PlayController {
142 @Operation(summary = "停止点播") 143 @Operation(summary = "停止点播")
143 @Parameter(name = "deviceId", description = "设备国标编号", required = true) 144 @Parameter(name = "deviceId", description = "设备国标编号", required = true)
144 @Parameter(name = "channelId", description = "通道国标编号", required = true) 145 @Parameter(name = "channelId", description = "通道国标编号", required = true)
  146 + @Parameter(name = "isSubStream", description = "是否子码流(true-子码流,false-主码流),默认为false", required = true)
145 @GetMapping("/stop/{deviceId}/{channelId}") 147 @GetMapping("/stop/{deviceId}/{channelId}")
146 - public JSONObject playStop(@PathVariable String deviceId, @PathVariable String channelId) { 148 + public JSONObject playStop(@PathVariable String deviceId, @PathVariable String channelId,boolean isSubStream) {
147 149
148 logger.debug(String.format("设备预览/回放停止API调用,streamId:%s_%s", deviceId, channelId )); 150 logger.debug(String.format("设备预览/回放停止API调用,streamId:%s_%s", deviceId, channelId ));
149 151
@@ -156,7 +158,12 @@ public class PlayController { @@ -156,7 +158,12 @@ public class PlayController {
156 throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备[" + deviceId + "]不存在"); 158 throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备[" + deviceId + "]不存在");
157 } 159 }
158 160
159 - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); 161 + InviteInfo inviteInfo =null;
  162 + if(device.isSwitchPrimarySubStream()){
  163 + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream);
  164 + }else {
  165 + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
  166 + }
160 if (inviteInfo == null) { 167 if (inviteInfo == null) {
161 throw new ControllerException(ErrorCode.ERROR100.getCode(), "点播未找到"); 168 throw new ControllerException(ErrorCode.ERROR100.getCode(), "点播未找到");
162 } 169 }
@@ -169,12 +176,17 @@ public class PlayController { @@ -169,12 +176,17 @@ public class PlayController {
169 throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); 176 throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
170 } 177 }
171 } 178 }
172 - inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); 179 + if(device.isSwitchPrimarySubStream()){
  180 + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream);
  181 + }else {
  182 + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
  183 + storager.stopPlay(deviceId, channelId);
  184 + }
173 185
174 - storager.stopPlay(deviceId, channelId);  
175 JSONObject json = new JSONObject(); 186 JSONObject json = new JSONObject();
176 json.put("deviceId", deviceId); 187 json.put("deviceId", deviceId);
177 json.put("channelId", channelId); 188 json.put("channelId", channelId);
  189 + json.put("isSubStream", isSubStream);
178 return json; 190 return json;
179 } 191 }
180 192
@@ -341,14 +353,16 @@ public class PlayController { @@ -341,14 +353,16 @@ public class PlayController {
341 @Operation(summary = "获取截图") 353 @Operation(summary = "获取截图")
342 @Parameter(name = "deviceId", description = "设备国标编号", required = true) 354 @Parameter(name = "deviceId", description = "设备国标编号", required = true)
343 @Parameter(name = "channelId", description = "通道国标编号", required = true) 355 @Parameter(name = "channelId", description = "通道国标编号", required = true)
  356 + @Parameter(name = "isSubStream", description = "是否子码流(true-子码流,false-主码流),默认为false", required = true)
344 @GetMapping("/snap") 357 @GetMapping("/snap")
345 - public DeferredResult<String> getSnap(String deviceId, String channelId) { 358 + public DeferredResult<String> getSnap(String deviceId, String channelId,boolean isSubStream) {
346 if (logger.isDebugEnabled()) { 359 if (logger.isDebugEnabled()) {
347 logger.debug("获取截图: {}/{}", deviceId, channelId); 360 logger.debug("获取截图: {}/{}", deviceId, channelId);
348 } 361 }
349 362
  363 + Device device = storager.queryVideoDevice(deviceId);
350 DeferredResult<String> result = new DeferredResult<>(3 * 1000L); 364 DeferredResult<String> result = new DeferredResult<>(3 * 1000L);
351 - String key = DeferredResultHolder.CALLBACK_CMD_SNAP + deviceId; 365 + String key = DeferredResultHolder.getSnapKey(deviceId,channelId,device.isSwitchPrimarySubStream(),isSubStream);
352 String uuid = UUID.randomUUID().toString(); 366 String uuid = UUID.randomUUID().toString();
353 resultHolder.put(key, uuid, result); 367 resultHolder.put(key, uuid, result);
354 368
@@ -357,7 +371,7 @@ public class PlayController { @@ -357,7 +371,7 @@ public class PlayController {
357 message.setId(uuid); 371 message.setId(uuid);
358 372
359 String fileName = deviceId + "_" + channelId + "_" + DateUtil.getNowForUrl() + "jpg"; 373 String fileName = deviceId + "_" + channelId + "_" + DateUtil.getNowForUrl() + "jpg";
360 - playService.getSnap(deviceId, channelId, fileName, (code, msg, data) -> { 374 + playService.getSnap(deviceId, channelId, fileName,isSubStream, (code, msg, data) -> {
361 if (code == InviteErrorCode.SUCCESS.getCode()) { 375 if (code == InviteErrorCode.SUCCESS.getCode()) {
362 message.setData(data); 376 message.setData(data);
363 }else { 377 }else {
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java
@@ -122,7 +122,7 @@ public class ApiStreamController { @@ -122,7 +122,7 @@ public class ApiStreamController {
122 MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); 122 MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
123 123
124 124
125 - playService.play(newMediaServerItem, serial, code, (errorCode, msg, data) -> { 125 + playService.play(newMediaServerItem, serial, code,false, (errorCode, msg, data) -> {
126 if (errorCode == InviteErrorCode.SUCCESS.getCode()) { 126 if (errorCode == InviteErrorCode.SUCCESS.getCode()) {
127 InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, serial, code); 127 InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, serial, code);
128 if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { 128 if (inviteInfo != null && inviteInfo.getStreamInfo() != null) {
src/main/resources/application-dev.yml
1 spring: 1 spring:
  2 + thymeleaf:
  3 + cache: false
2 # [可选]上传文件大小限制 4 # [可选]上传文件大小限制
3 servlet: 5 servlet:
4 multipart: 6 multipart:
@@ -11,18 +13,18 @@ spring: @@ -11,18 +13,18 @@ spring:
11 # [必须修改] 端口号 13 # [必须修改] 端口号
12 port: 6379 14 port: 6379
13 # [可选] 数据库 DB 15 # [可选] 数据库 DB
14 - database: 6 16 + database: 7
15 # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 17 # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
16 - password: face2020 18 + password:
17 # [可选] 超时时间 19 # [可选] 超时时间
18 timeout: 10000 20 timeout: 10000
19 # mysql数据源 21 # mysql数据源
20 datasource: 22 datasource:
21 type: com.zaxxer.hikari.HikariDataSource 23 type: com.zaxxer.hikari.HikariDataSource
22 driver-class-name: com.mysql.cj.jdbc.Driver 24 driver-class-name: com.mysql.cj.jdbc.Driver
23 - url: jdbc:mysql://127.0.0.1:3306/wvp2?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true 25 + url: jdbc:mysql://127.0.0.1:3306/test_gb-89wulian?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true
24 username: root 26 username: root
25 - password: 123456 27 + password: root
26 hikari: 28 hikari:
27 connection-timeout: 20000 # 是客户端等待连接池连接的最大毫秒数 29 connection-timeout: 20000 # 是客户端等待连接池连接的最大毫秒数
28 initialSize: 10 # 连接池初始化连接数 30 initialSize: 10 # 连接池初始化连接数
@@ -30,11 +32,19 @@ spring: @@ -30,11 +32,19 @@ spring:
30 minimum-idle: 5 # 连接池最小空闲连接数 32 minimum-idle: 5 # 连接池最小空闲连接数
31 idle-timeout: 300000 # 允许连接在连接池中空闲的最长时间(以毫秒为单位) 33 idle-timeout: 300000 # 允许连接在连接池中空闲的最长时间(以毫秒为单位)
32 max-lifetime: 1200000 # 是池中连接关闭后的最长生命周期(以毫秒为单位) 34 max-lifetime: 1200000 # 是池中连接关闭后的最长生命周期(以毫秒为单位)
33 -  
34 -  
35 #[可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口 35 #[可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口
36 server: 36 server:
37 - port: 18080 37 + port: 18978
  38 + # [可选] HTTPS配置, 默认不开启
  39 + ssl:
  40 + # [可选] 是否开启HTTPS访问
  41 + enabled: false
  42 + # [可选] 证书文件路径,放置在resource/目录下即可,修改xxx为文件名
  43 + key-store: classpath:test.monitor.89iot.cn.jks
  44 + # [可选] 证书密码
  45 + key-store-password: gpf64qmw
  46 + # [可选] 证书类型, 默认为jks,根据实际修改
  47 + key-store-type: JKS
38 48
39 # 作为28181服务器的配置 49 # 作为28181服务器的配置
40 sip: 50 sip:
@@ -42,26 +52,36 @@ sip: @@ -42,26 +52,36 @@ sip:
42 # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4 52 # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4
43 # 如果不明白,就使用0.0.0.0,大部分情况都是可以的 53 # 如果不明白,就使用0.0.0.0,大部分情况都是可以的
44 # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。 54 # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。
45 - ip: 192.168.41.16 55 + ip: 192.168.1.18
46 # [可选] 28181服务监听的端口 56 # [可选] 28181服务监听的端口
47 - port: 5060 57 + port: 8116
48 # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) 58 # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
49 # 后两位为行业编码,定义参照附录D.3 59 # 后两位为行业编码,定义参照附录D.3
50 # 3701020049标识山东济南历下区 信息行业接入 60 # 3701020049标识山东济南历下区 信息行业接入
51 # [可选] 61 # [可选]
52 - domain: 4401020049 62 + domain: 4101050000
53 # [可选] 63 # [可选]
54 - id: 44010200492000000001 64 + id: 41010500002000000001
55 # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 65 # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验
56 - password: admin123 66 + password: bajiuwulian1006
  67 + # 是否存储alarm信息
  68 + alarm: true
57 69
58 #zlm 默认服务器配置 70 #zlm 默认服务器配置
59 media: 71 media:
60 - id: FQ3TF8yT83wh5Wvz 72 + id: 89wulian-one
61 # [必须修改] zlm服务器的内网IP 73 # [必须修改] zlm服务器的内网IP
62 - ip: 192.168.41.16 74 + ip: 192.168.1.18
63 # [必须修改] zlm服务器的http.port 75 # [必须修改] zlm服务器的http.port
64 - http-port: 8091 76 + http-port: 80
  77 + # [可选] 返回流地址时的ip,置空使用 media.ip
  78 + stream-ip: 192.168.1.18
  79 + # [可选] wvp在国标信令中使用的ip,此ip为摄像机可以访问到的ip, 置空使用 media.ip
  80 + sdp-ip: 192.168.1.18
  81 + # [可选] zlm服务器的hook所使用的IP, 默认使用sip.ip
  82 + hook-ip: 192.168.1.18
  83 + # [可选] zlm服务器的http.sslport, 置空使用zlm配置文件配置
  84 + http-ssl-port: 443
65 # [可选] zlm服务器的hook.admin_params=secret 85 # [可选] zlm服务器的hook.admin_params=secret
66 secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc 86 secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
67 # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 87 # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试
@@ -69,11 +89,24 @@ media: @@ -69,11 +89,24 @@ media:
69 # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 89 # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输
70 enable: true 90 enable: true
71 # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功 91 # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功
72 - port-range: 30000,30500 # 端口范围 92 + port-range: 50000,50300 # 端口范围
73 # [可选] 国标级联在此范围内选择端口发送媒体流, 93 # [可选] 国标级联在此范围内选择端口发送媒体流,
74 - send-port-range: 30000,30500 # 端口范围 94 + send-port-range: 50000,50300 # 端口范围
75 # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用 95 # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用
76 record-assist-port: 18081 96 record-assist-port: 18081
  97 +# [根据业务需求配置]
  98 +user-settings:
  99 + # 点播/录像回放 等待超时时间,单位:毫秒
  100 + play-timeout: 180000
  101 + # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true
  102 + auto-apply-play: true
  103 + # 设备/通道状态变化时发送消息
  104 + device-status-notify: true
  105 + # 跨域配置,配置你访问前端页面的地址即可, 可以配置多个
  106 + allowed-origins:
  107 + - http://localhost:8080
  108 + - http://127.0.0.1:8080
77 # [可选] 日志配置, 一般不需要改 109 # [可选] 日志配置, 一般不需要改
78 logging: 110 logging:
79 config: classpath:logback-spring-local.xml 111 config: classpath:logback-spring-local.xml
  112 +
src/main/resources/application.yml
@@ -2,4 +2,4 @@ spring: @@ -2,4 +2,4 @@ spring:
2 application: 2 application:
3 name: wvp 3 name: wvp
4 profiles: 4 profiles:
5 - active: local  
6 \ No newline at end of file 5 \ No newline at end of file
  6 + active: dev
7 \ No newline at end of file 7 \ No newline at end of file
web_src/config/index.js
@@ -12,14 +12,14 @@ module.exports = { @@ -12,14 +12,14 @@ module.exports = {
12 assetsPublicPath: '/', 12 assetsPublicPath: '/',
13 proxyTable: { 13 proxyTable: {
14 '/debug': { 14 '/debug': {
15 - target: 'http://localhost:18080', 15 + target: 'http://localhost:18978',
16 changeOrigin: true, 16 changeOrigin: true,
17 pathRewrite: { 17 pathRewrite: {
18 '^/debug': '/' 18 '^/debug': '/'
19 } 19 }
20 }, 20 },
21 '/static/snap': { 21 '/static/snap': {
22 - target: 'http://localhost:18080', 22 + target: 'http://localhost:18978',
23 changeOrigin: true, 23 changeOrigin: true,
24 // pathRewrite: { 24 // pathRewrite: {
25 // '^/static/snap': '/static/snap' 25 // '^/static/snap': '/static/snap'
web_src/package-lock.json
@@ -184,15 +184,19 @@ @@ -184,15 +184,19 @@
184 } 184 }
185 }, 185 },
186 "node_modules/ajv": { 186 "node_modules/ajv": {
187 - "version": "5.5.2",  
188 - "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-5.5.2.tgz?cache=0&sync_timestamp=1600886864349&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-5.5.2.tgz",  
189 - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", 187 + "version": "6.12.6",
  188 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
  189 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
190 "dev": true, 190 "dev": true,
191 "dependencies": { 191 "dependencies": {
192 - "co": "^4.6.0",  
193 - "fast-deep-equal": "^1.0.0", 192 + "fast-deep-equal": "^3.1.1",
194 "fast-json-stable-stringify": "^2.0.0", 193 "fast-json-stable-stringify": "^2.0.0",
195 - "json-schema-traverse": "^0.3.0" 194 + "json-schema-traverse": "^0.4.1",
  195 + "uri-js": "^4.2.2"
  196 + },
  197 + "funding": {
  198 + "type": "github",
  199 + "url": "https://github.com/sponsors/epoberezkin"
196 } 200 }
197 }, 201 },
198 "node_modules/ajv-keywords": { 202 "node_modules/ajv-keywords": {
@@ -2111,8 +2115,8 @@ @@ -2111,8 +2115,8 @@
2111 }, 2115 },
2112 "node_modules/co": { 2116 "node_modules/co": {
2113 "version": "4.6.0", 2117 "version": "4.6.0",
2114 - "resolved": "https://registry.npm.taobao.org/co/download/co-4.6.0.tgz",  
2115 - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", 2118 + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
  2119 + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
2116 "dev": true, 2120 "dev": true,
2117 "engines": { 2121 "engines": {
2118 "iojs": ">= 1.0.0", 2122 "iojs": ">= 1.0.0",
@@ -4620,9 +4624,9 @@ @@ -4620,9 +4624,9 @@
4620 } 4624 }
4621 }, 4625 },
4622 "node_modules/fast-deep-equal": { 4626 "node_modules/fast-deep-equal": {
4623 - "version": "1.1.0",  
4624 - "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-1.1.0.tgz",  
4625 - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", 4627 + "version": "3.1.3",
  4628 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
  4629 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
4626 "dev": true 4630 "dev": true
4627 }, 4631 },
4628 "node_modules/fast-json-stable-stringify": { 4632 "node_modules/fast-json-stable-stringify": {
@@ -4665,30 +4669,6 @@ @@ -4665,30 +4669,6 @@
4665 "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" 4669 "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0"
4666 } 4670 }
4667 }, 4671 },
4668 - "node_modules/file-loader/node_modules/ajv": {  
4669 - "version": "6.12.5",  
4670 - "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.12.5.tgz?cache=0&sync_timestamp=1600886864349&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.12.5.tgz",  
4671 - "integrity": "sha1-GbDouuj0duW6ZmMAOHd1+xoApNo=",  
4672 - "dev": true,  
4673 - "dependencies": {  
4674 - "fast-deep-equal": "^3.1.1",  
4675 - "fast-json-stable-stringify": "^2.0.0",  
4676 - "json-schema-traverse": "^0.4.1",  
4677 - "uri-js": "^4.2.2"  
4678 - }  
4679 - },  
4680 - "node_modules/file-loader/node_modules/fast-deep-equal": {  
4681 - "version": "3.1.3",  
4682 - "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-3.1.3.tgz",  
4683 - "integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=",  
4684 - "dev": true  
4685 - },  
4686 - "node_modules/file-loader/node_modules/json-schema-traverse": {  
4687 - "version": "0.4.1",  
4688 - "resolved": "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz?cache=0&sync_timestamp=1599334207614&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson-schema-traverse%2Fdownload%2Fjson-schema-traverse-0.4.1.tgz",  
4689 - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=",  
4690 - "dev": true  
4691 - },  
4692 "node_modules/file-loader/node_modules/schema-utils": { 4672 "node_modules/file-loader/node_modules/schema-utils": {
4693 "version": "0.4.7", 4673 "version": "0.4.7",
4694 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz", 4674 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz",
@@ -6126,9 +6106,9 @@ @@ -6126,9 +6106,9 @@
6126 "dev": true 6106 "dev": true
6127 }, 6107 },
6128 "node_modules/json-schema-traverse": { 6108 "node_modules/json-schema-traverse": {
6129 - "version": "0.3.1",  
6130 - "resolved": "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.3.1.tgz?cache=0&sync_timestamp=1599334207614&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson-schema-traverse%2Fdownload%2Fjson-schema-traverse-0.3.1.tgz",  
6131 - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", 6109 + "version": "0.4.1",
  6110 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
  6111 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
6132 "dev": true 6112 "dev": true
6133 }, 6113 },
6134 "node_modules/json-stringify-pretty-compact": { 6114 "node_modules/json-stringify-pretty-compact": {
@@ -8770,30 +8750,6 @@ @@ -8770,30 +8750,6 @@
8770 "node": ">= 4" 8750 "node": ">= 4"
8771 } 8751 }
8772 }, 8752 },
8773 - "node_modules/postcss-loader/node_modules/ajv": {  
8774 - "version": "6.12.5",  
8775 - "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.12.5.tgz?cache=0&sync_timestamp=1600886864349&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.12.5.tgz",  
8776 - "integrity": "sha1-GbDouuj0duW6ZmMAOHd1+xoApNo=",  
8777 - "dev": true,  
8778 - "dependencies": {  
8779 - "fast-deep-equal": "^3.1.1",  
8780 - "fast-json-stable-stringify": "^2.0.0",  
8781 - "json-schema-traverse": "^0.4.1",  
8782 - "uri-js": "^4.2.2"  
8783 - }  
8784 - },  
8785 - "node_modules/postcss-loader/node_modules/fast-deep-equal": {  
8786 - "version": "3.1.3",  
8787 - "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-3.1.3.tgz",  
8788 - "integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=",  
8789 - "dev": true  
8790 - },  
8791 - "node_modules/postcss-loader/node_modules/json-schema-traverse": {  
8792 - "version": "0.4.1",  
8793 - "resolved": "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz?cache=0&sync_timestamp=1599334207614&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson-schema-traverse%2Fdownload%2Fjson-schema-traverse-0.4.1.tgz",  
8794 - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=",  
8795 - "dev": true  
8796 - },  
8797 "node_modules/postcss-loader/node_modules/schema-utils": { 8753 "node_modules/postcss-loader/node_modules/schema-utils": {
8798 "version": "0.4.7", 8754 "version": "0.4.7",
8799 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz", 8755 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz",
@@ -11500,6 +11456,30 @@ @@ -11500,6 +11456,30 @@
11500 "node": ">= 4.3 < 5.0.0 || >= 5.10" 11456 "node": ">= 4.3 < 5.0.0 || >= 5.10"
11501 } 11457 }
11502 }, 11458 },
  11459 + "node_modules/schema-utils/node_modules/ajv": {
  11460 + "version": "5.5.2",
  11461 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
  11462 + "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==",
  11463 + "dev": true,
  11464 + "dependencies": {
  11465 + "co": "^4.6.0",
  11466 + "fast-deep-equal": "^1.0.0",
  11467 + "fast-json-stable-stringify": "^2.0.0",
  11468 + "json-schema-traverse": "^0.3.0"
  11469 + }
  11470 + },
  11471 + "node_modules/schema-utils/node_modules/fast-deep-equal": {
  11472 + "version": "1.1.0",
  11473 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
  11474 + "integrity": "sha512-fueX787WZKCV0Is4/T2cyAdM4+x1S3MXXOAhavE1ys/W42SHAPacLTQhucja22QBYrfGw50M2sRiXPtTGv9Ymw==",
  11475 + "dev": true
  11476 + },
  11477 + "node_modules/schema-utils/node_modules/json-schema-traverse": {
  11478 + "version": "0.3.1",
  11479 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
  11480 + "integrity": "sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==",
  11481 + "dev": true
  11482 + },
11503 "node_modules/select": { 11483 "node_modules/select": {
11504 "version": "1.1.2", 11484 "version": "1.1.2",
11505 "resolved": "https://registry.npm.taobao.org/select/download/select-1.1.2.tgz", 11485 "resolved": "https://registry.npm.taobao.org/select/download/select-1.1.2.tgz",
@@ -12721,36 +12701,12 @@ @@ -12721,36 +12701,12 @@
12721 "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" 12701 "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0"
12722 } 12702 }
12723 }, 12703 },
12724 - "node_modules/uglifyjs-webpack-plugin/node_modules/ajv": {  
12725 - "version": "6.12.5",  
12726 - "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.12.5.tgz?cache=0&sync_timestamp=1600886864349&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.12.5.tgz",  
12727 - "integrity": "sha1-GbDouuj0duW6ZmMAOHd1+xoApNo=",  
12728 - "dev": true,  
12729 - "dependencies": {  
12730 - "fast-deep-equal": "^3.1.1",  
12731 - "fast-json-stable-stringify": "^2.0.0",  
12732 - "json-schema-traverse": "^0.4.1",  
12733 - "uri-js": "^4.2.2"  
12734 - }  
12735 - },  
12736 "node_modules/uglifyjs-webpack-plugin/node_modules/commander": { 12704 "node_modules/uglifyjs-webpack-plugin/node_modules/commander": {
12737 "version": "2.13.0", 12705 "version": "2.13.0",
12738 "resolved": "https://registry.npm.taobao.org/commander/download/commander-2.13.0.tgz?cache=0&sync_timestamp=1598576136669&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.13.0.tgz", 12706 "resolved": "https://registry.npm.taobao.org/commander/download/commander-2.13.0.tgz?cache=0&sync_timestamp=1598576136669&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.13.0.tgz",
12739 "integrity": "sha1-aWS8pnaF33wfFDDFhPB9dZeIW5w=", 12707 "integrity": "sha1-aWS8pnaF33wfFDDFhPB9dZeIW5w=",
12740 "dev": true 12708 "dev": true
12741 }, 12709 },
12742 - "node_modules/uglifyjs-webpack-plugin/node_modules/fast-deep-equal": {  
12743 - "version": "3.1.3",  
12744 - "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-3.1.3.tgz",  
12745 - "integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=",  
12746 - "dev": true  
12747 - },  
12748 - "node_modules/uglifyjs-webpack-plugin/node_modules/json-schema-traverse": {  
12749 - "version": "0.4.1",  
12750 - "resolved": "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz?cache=0&sync_timestamp=1599334207614&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson-schema-traverse%2Fdownload%2Fjson-schema-traverse-0.4.1.tgz",  
12751 - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=",  
12752 - "dev": true  
12753 - },  
12754 "node_modules/uglifyjs-webpack-plugin/node_modules/schema-utils": { 12710 "node_modules/uglifyjs-webpack-plugin/node_modules/schema-utils": {
12755 "version": "0.4.7", 12711 "version": "0.4.7",
12756 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz", 12712 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz",
@@ -14082,24 +14038,6 @@ @@ -14082,24 +14038,6 @@
14082 "source-map": "~0.6.1" 14038 "source-map": "~0.6.1"
14083 } 14039 }
14084 }, 14040 },
14085 - "node_modules/webpack/node_modules/ajv": {  
14086 - "version": "6.12.5",  
14087 - "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.12.5.tgz?cache=0&sync_timestamp=1600886864349&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.12.5.tgz",  
14088 - "integrity": "sha1-GbDouuj0duW6ZmMAOHd1+xoApNo=",  
14089 - "dev": true,  
14090 - "dependencies": {  
14091 - "fast-deep-equal": "^3.1.1",  
14092 - "fast-json-stable-stringify": "^2.0.0",  
14093 - "json-schema-traverse": "^0.4.1",  
14094 - "uri-js": "^4.2.2"  
14095 - }  
14096 - },  
14097 - "node_modules/webpack/node_modules/fast-deep-equal": {  
14098 - "version": "3.1.3",  
14099 - "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-3.1.3.tgz",  
14100 - "integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=",  
14101 - "dev": true  
14102 - },  
14103 "node_modules/webpack/node_modules/has-flag": { 14041 "node_modules/webpack/node_modules/has-flag": {
14104 "version": "2.0.0", 14042 "version": "2.0.0",
14105 "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz", 14043 "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz",
@@ -14109,12 +14047,6 @@ @@ -14109,12 +14047,6 @@
14109 "node": ">=0.10.0" 14047 "node": ">=0.10.0"
14110 } 14048 }
14111 }, 14049 },
14112 - "node_modules/webpack/node_modules/json-schema-traverse": {  
14113 - "version": "0.4.1",  
14114 - "resolved": "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz?cache=0&sync_timestamp=1599334207614&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson-schema-traverse%2Fdownload%2Fjson-schema-traverse-0.4.1.tgz",  
14115 - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=",  
14116 - "dev": true  
14117 - },  
14118 "node_modules/webpack/node_modules/source-map": { 14050 "node_modules/webpack/node_modules/source-map": {
14119 "version": "0.5.7", 14051 "version": "0.5.7",
14120 "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", 14052 "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz",
@@ -14551,15 +14483,15 @@ @@ -14551,15 +14483,15 @@
14551 } 14483 }
14552 }, 14484 },
14553 "ajv": { 14485 "ajv": {
14554 - "version": "5.5.2",  
14555 - "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-5.5.2.tgz?cache=0&sync_timestamp=1600886864349&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-5.5.2.tgz",  
14556 - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", 14486 + "version": "6.12.6",
  14487 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
  14488 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
14557 "dev": true, 14489 "dev": true,
14558 "requires": { 14490 "requires": {
14559 - "co": "^4.6.0",  
14560 - "fast-deep-equal": "^1.0.0", 14491 + "fast-deep-equal": "^3.1.1",
14561 "fast-json-stable-stringify": "^2.0.0", 14492 "fast-json-stable-stringify": "^2.0.0",
14562 - "json-schema-traverse": "^0.3.0" 14493 + "json-schema-traverse": "^0.4.1",
  14494 + "uri-js": "^4.2.2"
14563 } 14495 }
14564 }, 14496 },
14565 "ajv-keywords": { 14497 "ajv-keywords": {
@@ -16303,8 +16235,8 @@ @@ -16303,8 +16235,8 @@
16303 }, 16235 },
16304 "co": { 16236 "co": {
16305 "version": "4.6.0", 16237 "version": "4.6.0",
16306 - "resolved": "https://registry.npm.taobao.org/co/download/co-4.6.0.tgz",  
16307 - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", 16238 + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
  16239 + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
16308 "dev": true 16240 "dev": true
16309 }, 16241 },
16310 "coa": { 16242 "coa": {
@@ -18423,9 +18355,9 @@ @@ -18423,9 +18355,9 @@
18423 } 18355 }
18424 }, 18356 },
18425 "fast-deep-equal": { 18357 "fast-deep-equal": {
18426 - "version": "1.1.0",  
18427 - "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-1.1.0.tgz",  
18428 - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", 18358 + "version": "3.1.3",
  18359 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
  18360 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
18429 "dev": true 18361 "dev": true
18430 }, 18362 },
18431 "fast-json-stable-stringify": { 18363 "fast-json-stable-stringify": {
@@ -18459,30 +18391,6 @@ @@ -18459,30 +18391,6 @@
18459 "schema-utils": "^0.4.5" 18391 "schema-utils": "^0.4.5"
18460 }, 18392 },
18461 "dependencies": { 18393 "dependencies": {
18462 - "ajv": {  
18463 - "version": "6.12.5",  
18464 - "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.12.5.tgz?cache=0&sync_timestamp=1600886864349&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.12.5.tgz",  
18465 - "integrity": "sha1-GbDouuj0duW6ZmMAOHd1+xoApNo=",  
18466 - "dev": true,  
18467 - "requires": {  
18468 - "fast-deep-equal": "^3.1.1",  
18469 - "fast-json-stable-stringify": "^2.0.0",  
18470 - "json-schema-traverse": "^0.4.1",  
18471 - "uri-js": "^4.2.2"  
18472 - }  
18473 - },  
18474 - "fast-deep-equal": {  
18475 - "version": "3.1.3",  
18476 - "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-3.1.3.tgz",  
18477 - "integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=",  
18478 - "dev": true  
18479 - },  
18480 - "json-schema-traverse": {  
18481 - "version": "0.4.1",  
18482 - "resolved": "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz?cache=0&sync_timestamp=1599334207614&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson-schema-traverse%2Fdownload%2Fjson-schema-traverse-0.4.1.tgz",  
18483 - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=",  
18484 - "dev": true  
18485 - },  
18486 "schema-utils": { 18394 "schema-utils": {
18487 "version": "0.4.7", 18395 "version": "0.4.7",
18488 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz", 18396 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz",
@@ -19648,9 +19556,9 @@ @@ -19648,9 +19556,9 @@
19648 "dev": true 19556 "dev": true
19649 }, 19557 },
19650 "json-schema-traverse": { 19558 "json-schema-traverse": {
19651 - "version": "0.3.1",  
19652 - "resolved": "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.3.1.tgz?cache=0&sync_timestamp=1599334207614&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson-schema-traverse%2Fdownload%2Fjson-schema-traverse-0.3.1.tgz",  
19653 - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", 19559 + "version": "0.4.1",
  19560 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
  19561 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
19654 "dev": true 19562 "dev": true
19655 }, 19563 },
19656 "json-stringify-pretty-compact": { 19564 "json-stringify-pretty-compact": {
@@ -21822,30 +21730,6 @@ @@ -21822,30 +21730,6 @@
21822 "schema-utils": "^0.4.0" 21730 "schema-utils": "^0.4.0"
21823 }, 21731 },
21824 "dependencies": { 21732 "dependencies": {
21825 - "ajv": {  
21826 - "version": "6.12.5",  
21827 - "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.12.5.tgz?cache=0&sync_timestamp=1600886864349&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.12.5.tgz",  
21828 - "integrity": "sha1-GbDouuj0duW6ZmMAOHd1+xoApNo=",  
21829 - "dev": true,  
21830 - "requires": {  
21831 - "fast-deep-equal": "^3.1.1",  
21832 - "fast-json-stable-stringify": "^2.0.0",  
21833 - "json-schema-traverse": "^0.4.1",  
21834 - "uri-js": "^4.2.2"  
21835 - }  
21836 - },  
21837 - "fast-deep-equal": {  
21838 - "version": "3.1.3",  
21839 - "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-3.1.3.tgz",  
21840 - "integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=",  
21841 - "dev": true  
21842 - },  
21843 - "json-schema-traverse": {  
21844 - "version": "0.4.1",  
21845 - "resolved": "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz?cache=0&sync_timestamp=1599334207614&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson-schema-traverse%2Fdownload%2Fjson-schema-traverse-0.4.1.tgz",  
21846 - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=",  
21847 - "dev": true  
21848 - },  
21849 "schema-utils": { 21733 "schema-utils": {
21850 "version": "0.4.7", 21734 "version": "0.4.7",
21851 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz", 21735 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz",
@@ -24097,6 +23981,32 @@ @@ -24097,6 +23981,32 @@
24097 "dev": true, 23981 "dev": true,
24098 "requires": { 23982 "requires": {
24099 "ajv": "^5.0.0" 23983 "ajv": "^5.0.0"
  23984 + },
  23985 + "dependencies": {
  23986 + "ajv": {
  23987 + "version": "5.5.2",
  23988 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
  23989 + "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==",
  23990 + "dev": true,
  23991 + "requires": {
  23992 + "co": "^4.6.0",
  23993 + "fast-deep-equal": "^1.0.0",
  23994 + "fast-json-stable-stringify": "^2.0.0",
  23995 + "json-schema-traverse": "^0.3.0"
  23996 + }
  23997 + },
  23998 + "fast-deep-equal": {
  23999 + "version": "1.1.0",
  24000 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
  24001 + "integrity": "sha512-fueX787WZKCV0Is4/T2cyAdM4+x1S3MXXOAhavE1ys/W42SHAPacLTQhucja22QBYrfGw50M2sRiXPtTGv9Ymw==",
  24002 + "dev": true
  24003 + },
  24004 + "json-schema-traverse": {
  24005 + "version": "0.3.1",
  24006 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
  24007 + "integrity": "sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==",
  24008 + "dev": true
  24009 + }
24100 } 24010 }
24101 }, 24011 },
24102 "select": { 24012 "select": {
@@ -25116,36 +25026,12 @@ @@ -25116,36 +25026,12 @@
25116 "worker-farm": "^1.5.2" 25026 "worker-farm": "^1.5.2"
25117 }, 25027 },
25118 "dependencies": { 25028 "dependencies": {
25119 - "ajv": {  
25120 - "version": "6.12.5",  
25121 - "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.12.5.tgz?cache=0&sync_timestamp=1600886864349&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.12.5.tgz",  
25122 - "integrity": "sha1-GbDouuj0duW6ZmMAOHd1+xoApNo=",  
25123 - "dev": true,  
25124 - "requires": {  
25125 - "fast-deep-equal": "^3.1.1",  
25126 - "fast-json-stable-stringify": "^2.0.0",  
25127 - "json-schema-traverse": "^0.4.1",  
25128 - "uri-js": "^4.2.2"  
25129 - }  
25130 - },  
25131 "commander": { 25029 "commander": {
25132 "version": "2.13.0", 25030 "version": "2.13.0",
25133 "resolved": "https://registry.npm.taobao.org/commander/download/commander-2.13.0.tgz?cache=0&sync_timestamp=1598576136669&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.13.0.tgz", 25031 "resolved": "https://registry.npm.taobao.org/commander/download/commander-2.13.0.tgz?cache=0&sync_timestamp=1598576136669&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.13.0.tgz",
25134 "integrity": "sha1-aWS8pnaF33wfFDDFhPB9dZeIW5w=", 25032 "integrity": "sha1-aWS8pnaF33wfFDDFhPB9dZeIW5w=",
25135 "dev": true 25033 "dev": true
25136 }, 25034 },
25137 - "fast-deep-equal": {  
25138 - "version": "3.1.3",  
25139 - "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-3.1.3.tgz",  
25140 - "integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=",  
25141 - "dev": true  
25142 - },  
25143 - "json-schema-traverse": {  
25144 - "version": "0.4.1",  
25145 - "resolved": "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz?cache=0&sync_timestamp=1599334207614&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson-schema-traverse%2Fdownload%2Fjson-schema-traverse-0.4.1.tgz",  
25146 - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=",  
25147 - "dev": true  
25148 - },  
25149 "schema-utils": { 25035 "schema-utils": {
25150 "version": "0.4.7", 25036 "version": "0.4.7",
25151 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz", 25037 "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-0.4.7.tgz?cache=0&sync_timestamp=1601922251376&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fschema-utils%2Fdownload%2Fschema-utils-0.4.7.tgz",
@@ -25846,36 +25732,12 @@ @@ -25846,36 +25732,12 @@
25846 "yargs": "^8.0.2" 25732 "yargs": "^8.0.2"
25847 }, 25733 },
25848 "dependencies": { 25734 "dependencies": {
25849 - "ajv": {  
25850 - "version": "6.12.5",  
25851 - "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.12.5.tgz?cache=0&sync_timestamp=1600886864349&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.12.5.tgz",  
25852 - "integrity": "sha1-GbDouuj0duW6ZmMAOHd1+xoApNo=",  
25853 - "dev": true,  
25854 - "requires": {  
25855 - "fast-deep-equal": "^3.1.1",  
25856 - "fast-json-stable-stringify": "^2.0.0",  
25857 - "json-schema-traverse": "^0.4.1",  
25858 - "uri-js": "^4.2.2"  
25859 - }  
25860 - },  
25861 - "fast-deep-equal": {  
25862 - "version": "3.1.3",  
25863 - "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-3.1.3.tgz",  
25864 - "integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=",  
25865 - "dev": true  
25866 - },  
25867 "has-flag": { 25735 "has-flag": {
25868 "version": "2.0.0", 25736 "version": "2.0.0",
25869 "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz", 25737 "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz",
25870 "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", 25738 "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
25871 "dev": true 25739 "dev": true
25872 }, 25740 },
25873 - "json-schema-traverse": {  
25874 - "version": "0.4.1",  
25875 - "resolved": "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz?cache=0&sync_timestamp=1599334207614&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson-schema-traverse%2Fdownload%2Fjson-schema-traverse-0.4.1.tgz",  
25876 - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=",  
25877 - "dev": true  
25878 - },  
25879 "source-map": { 25741 "source-map": {
25880 "version": "0.5.7", 25742 "version": "0.5.7",
25881 "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", 25743 "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz",
web_src/src/components/channelList.vue
@@ -26,6 +26,12 @@ @@ -26,6 +26,12 @@
26 <el-option label="在线" value="true"></el-option> 26 <el-option label="在线" value="true"></el-option>
27 <el-option label="离线" value="false"></el-option> 27 <el-option label="离线" value="false"></el-option>
28 </el-select> 28 </el-select>
  29 + 清晰度:
  30 + <el-select size="mini" style="margin-right: 1rem;" @change="search" v-model="isSubStream" placeholder="请选择"
  31 + default-first-option>
  32 + <el-option label="原画" :value="false"></el-option>
  33 + <el-option label="流畅" :value="true"></el-option>
  34 + </el-select>
29 </div> 35 </div>
30 <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button> 36 <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button>
31 <el-button v-if="showTree" icon="iconfont icon-list" circle size="mini" @click="switchList()"></el-button> 37 <el-button v-if="showTree" icon="iconfont icon-list" circle size="mini" @click="switchList()"></el-button>
@@ -146,6 +152,7 @@ export default { @@ -146,6 +152,7 @@ export default {
146 searchSrt: "", 152 searchSrt: "",
147 channelType: "", 153 channelType: "",
148 online: "", 154 online: "",
  155 + isSubStream: false,
149 winHeight: window.innerHeight - 200, 156 winHeight: window.innerHeight - 200,
150 currentPage: 1, 157 currentPage: 1,
151 count: 15, 158 count: 15,
@@ -237,7 +244,10 @@ export default { @@ -237,7 +244,10 @@ export default {
237 let that = this; 244 let that = this;
238 this.$axios({ 245 this.$axios({
239 method: 'get', 246 method: 'get',
240 - url: '/api/play/start/' + deviceId + '/' + channelId 247 + url: '/api/play/start/' + deviceId + '/' + channelId,
  248 + params:{
  249 + isSubStream: this.isSubStream
  250 + }
241 }).then(function (res) { 251 }).then(function (res) {
242 console.log(res) 252 console.log(res)
243 that.isLoging = false; 253 that.isLoging = false;
@@ -277,7 +287,10 @@ export default { @@ -277,7 +287,10 @@ export default {
277 var that = this; 287 var that = this;
278 this.$axios({ 288 this.$axios({
279 method: 'get', 289 method: 'get',
280 - url: '/api/play/stop/' + this.deviceId + "/" + itemData.channelId 290 + url: '/api/play/stop/' + this.deviceId + "/" + itemData.channelId,
  291 + params:{
  292 + isSubStream: this.isSubStream
  293 + }
281 }).then(function (res) { 294 }).then(function (res) {
282 that.initData(); 295 that.initData();
283 }).catch(function (error) { 296 }).catch(function (error) {
web_src/src/components/dialog/deviceEdit.vue
@@ -64,6 +64,12 @@ @@ -64,6 +64,12 @@
64 <el-form-item v-if="form.subscribeCycleForMobilePosition > 0" label="移动位置报送间隔" prop="subscribeCycleForCatalog" > 64 <el-form-item v-if="form.subscribeCycleForMobilePosition > 0" label="移动位置报送间隔" prop="subscribeCycleForCatalog" >
65 <el-input v-model="form.mobilePositionSubmissionInterval" clearable ></el-input> 65 <el-input v-model="form.mobilePositionSubmissionInterval" clearable ></el-input>
66 </el-form-item> 66 </el-form-item>
  67 + <el-form-item label="主子码流开关" prop="switchPrimarySubStream" >
  68 + <el-select v-model="form.switchPrimarySubStream" style="float: left; width: 100%" >
  69 + <el-option key="true" label="开启" :value="true"></el-option>
  70 + <el-option key="false" label="关闭" :value="false"></el-option>
  71 + </el-select>
  72 + </el-form-item>
67 <el-form-item label="其他选项"> 73 <el-form-item label="其他选项">
68 <el-checkbox label="SSRC校验" v-model="form.ssrcCheck" style="float: left"></el-checkbox> 74 <el-checkbox label="SSRC校验" v-model="form.ssrcCheck" style="float: left"></el-checkbox>
69 <el-checkbox label="作为消息通道" v-model="form.asMessageChannel" style="float: left"></el-checkbox> 75 <el-checkbox label="作为消息通道" v-model="form.asMessageChannel" style="float: left"></el-checkbox>