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 1 package com.genersoft.iot.vmp.common;
2 2  
3 3 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
4 5  
5 6 /**
6 7 * 记录每次发送invite消息的状态
... ... @@ -123,4 +124,40 @@ public class InviteInfo {
123 124 public void setStreamMode(String streamMode) {
124 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 528 }
529 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 1 package com.genersoft.iot.vmp.conf;
2 2  
  3 +import io.swagger.v3.oas.annotations.media.Schema;
3 4 import org.springframework.core.annotation.Order;
4 5 import org.springframework.boot.context.properties.ConfigurationProperties;
5 6 import org.springframework.stereotype.Component;
... ... @@ -25,11 +26,11 @@ public class UserSetting {
25 26  
26 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 35 private Boolean logInDatebase = Boolean.TRUE;
35 36  
... ... @@ -62,6 +63,8 @@ public class UserSetting {
62 63  
63 64 private String thirdPartyGBIdReg = "[\\s\\S]*";
64 65  
  66 +
  67 +
65 68 private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
66 69  
67 70 private List<String> allowedOrigins = new ArrayList<>();
... ... @@ -295,4 +298,10 @@ public class UserSetting {
295 298 public void setSqlLog(Boolean sqlLog) {
296 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 195 private SipTransactionInfo sipTransactionInfo;
196 196  
197 197  
  198 +
  199 +
198 200 public String getDeviceId() {
199 201 return deviceId;
200 202 }
... ... @@ -461,4 +463,20 @@ public class Device {
461 463 public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
462 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 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 98 * @param device 视频设备
99 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 266 * @param errorEvent sip错误订阅
267 267 */
268 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 270 ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
271 271 String stream = ssrcInfo.getStream();
272 272  
... ... @@ -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 360 content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
345 361 // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
346 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 372 // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
357 373 ResponseEvent responseEvent = (ResponseEvent) e.event;
358 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 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 142 // 可能是设备主动停止
143 143 Device device = storager.queryVideoDeviceByChannelId(platformGbId);
144 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 152 if (ssrcTransactionForPlay != null){
148 153 if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){
149 154 // 释放ssrc
... ... @@ -153,10 +158,17 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
153 158 }
154 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 169 inviteStreamService.removeInviteInfo(inviteInfo);
  170 + }
  171 + if (inviteInfo != null) {
160 172 if (inviteInfo.getStreamInfo() != null) {
161 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 497 }
498 498 sendRtpItem.setStreamId(streamId);
499 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 501 if (code == InviteErrorCode.SUCCESS.getCode()){
502 502 hookEvent.run(code, msg, data);
503 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 345 }
346 346  
347 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 362 } else {
354 363 if (!"rtp".equals(param.getApp())) {
... ... @@ -476,7 +485,16 @@ public class ZLMHttpHookListener {
476 485 Device device = deviceService.getDevice(inviteInfo.getDeviceId());
477 486 if (device != null) {
478 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 498 cmder.streamByeCmd(device, inviteInfo.getChannelId(),
481 499 inviteInfo.getStream(), null);
482 500 }
... ... @@ -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 516 return ret;
493 517 }
494 518 } else {
... ... @@ -541,12 +565,26 @@ public class ZLMHttpHookListener {
541 565  
542 566 if ("rtp".equals(param.getApp())) {
543 567 String[] s = param.getStream().split("_");
544   - if (!mediaInfo.isRtpEnable() || s.length != 2) {
  568 + if (!mediaInfo.isRtpEnable() ) {
545 569 defaultResult.setResult(HookResult.SUCCESS());
546 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 588 Device device = redisCatchStorage.getDevice(deviceId);
551 589 if (device == null) {
552 590 defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg()));
... ... @@ -560,7 +598,7 @@ public class ZLMHttpHookListener {
560 598 logger.info("[ZLM HOOK] 流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
561 599  
562 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 602 boolean exist = resultHolder.exist(key, null);
565 603 msg.setKey(key);
566 604 String uuid = UUID.randomUUID().toString();
... ... @@ -578,7 +616,7 @@ public class ZLMHttpHookListener {
578 616 resultHolder.put(key, uuid, result);
579 617  
580 618 if (!exist) {
581   - playService.play(mediaInfo, deviceId, channelId, (code, message, data) -> {
  619 + playService.play(mediaInfo, deviceId, channelId,isSubStream, (code, message, data) -> {
582 620 msg.setData(new HookResult(code, message));
583 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 4 import com.genersoft.iot.vmp.common.InviteSessionType;
5 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 72 * 统计同一个zlm下的国标收流个数
71 73 */
72 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 16 */
17 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 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 23 MediaServerItem getNewMediaServerItem(Device device);
24 24  
... ... @@ -43,5 +43,5 @@ public interface IPlayService {
43 43  
44 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 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.genersoft.iot.vmp.common.InviteSessionType;
3 4 import com.genersoft.iot.vmp.common.VideoManagerConstants;
4 5 import com.genersoft.iot.vmp.conf.DynamicTask;
5 6 import com.genersoft.iot.vmp.conf.UserSetting;
  7 +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
6 8 import com.genersoft.iot.vmp.gb28181.bean.*;
7 9 import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
8 10 import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
9 11 import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
10 12 import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
11 13 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
  14 +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
12 15 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
13 16 import com.genersoft.iot.vmp.service.IDeviceChannelService;
14 17 import com.genersoft.iot.vmp.service.IDeviceService;
... ... @@ -48,6 +51,8 @@ public class DeviceServiceImpl implements IDeviceService {
48 51 private final static Logger logger = LoggerFactory.getLogger(DeviceServiceImpl.class);
49 52  
50 53 @Autowired
  54 + private SIPCommander cmder;
  55 + @Autowired
51 56 private DynamicTask dynamicTask;
52 57  
53 58 @Autowired
... ... @@ -130,6 +135,10 @@ public class DeviceServiceImpl implements IDeviceService {
130 135 }
131 136 sync(device);
132 137 }else {
  138 +
  139 + if (deviceInDb != null) {
  140 + device.setSwitchPrimarySubStream(deviceInDb.isSwitchPrimarySubStream());
  141 + }
133 142 if(!device.isOnLine()){
134 143 device.setOnLine(true);
135 144 device.setCreateTime(now);
... ... @@ -581,6 +590,20 @@ public class DeviceServiceImpl implements IDeviceService {
581 590 logger.warn("更新设备时未找到设备信息");
582 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 607 if (!ObjectUtils.isEmpty(device.getName())) {
585 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 198 }
199 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 115  
116 116  
117 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 119 if (mediaServerItem == null) {
120 120 throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm");
121 121 }
122 122  
123 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 130 if (inviteInfo != null ) {
127 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 138 return inviteInfo.getSsrcInfo();
131 139 }else {
132 140 StreamInfo streamInfo = inviteInfo.getStreamInfo();
133 141 String streamId = streamInfo.getStream();
134 142 if (streamId == null) {
135 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 155 return inviteInfo.getSsrcInfo();
141 156 }
142 157 String mediaServerId = streamInfo.getMediaServerId();
... ... @@ -145,41 +160,64 @@ public class PlayServiceImpl implements IPlayService {
145 160 Boolean ready = zlmrtpServerFactory.isStreamReady(mediaInfo, "rtp", streamId);
146 161 if (ready != null && ready) {
147 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 174 return inviteInfo.getSsrcInfo();
153 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 189 String streamId = null;
163 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 197 SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam());
167 198 if (ssrcInfo == null) {
168 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 211 return null;
174 212 }
175 213 // TODO 记录点播的状态
176   - play(mediaServerItem, ssrcInfo, device, channelId, callback);
  214 + play(mediaServerItem, ssrcInfo, device, channelId,isSubStream, callback);
177 215 return ssrcInfo;
178 216 }
179 217  
180 218  
181 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 221 ErrorCallback<Object> callback) {
184 222  
185 223 if (mediaServerItem == null || ssrcInfo == null) {
... ... @@ -188,8 +226,11 @@ public class PlayServiceImpl implements IPlayService {
188 226 null);
189 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 234 //端口获取失败的ssrcInfo 没有必要发送点播指令
194 235 if (ssrcInfo.getPort() <= 0) {
195 236 logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
... ... @@ -198,23 +239,50 @@ public class PlayServiceImpl implements IPlayService {
198 239 streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
199 240  
200 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 249 return;
204 250 }
205 251  
206 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 269 String timeOutTaskKey = UUID.randomUUID().toString();
213 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 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 286 // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
219 287 // InviteInfo inviteInfoForTimeout = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.play, device.getDeviceId(), channelId);
220 288 // if (inviteInfoForTimeout == null) {
... ... @@ -226,10 +294,16 @@ public class PlayServiceImpl implements IPlayService {
226 294 // // TODO 发送cancel
227 295 // }
228 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 307 try {
234 308 cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
235 309 } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
... ... @@ -247,25 +321,42 @@ public class PlayServiceImpl implements IPlayService {
247 321 }, userSetting.getPlayTimeout());
248 322  
249 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 325 logger.info("收到订阅消息: " + response.toJSONString());
252 326 dynamicTask.stop(timeOutTaskKey);
253 327 // hook响应
254   - StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId);
  328 + StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId,isSubStream);
255 329 if (streamInfo == null){
256 330 callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
257 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 341 return;
262 342 }
263 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 360 String streamUrl;
270 361 if (mediaServerItemInuse.getRtspPort() != 0) {
271 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 412  
322 413 callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
323 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 426 return;
... ... @@ -340,9 +437,15 @@ public class PlayServiceImpl implements IPlayService {
340 437  
341 438 callback.run(InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getCode(),
342 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 450 return;
348 451 }
... ... @@ -358,21 +461,34 @@ public class PlayServiceImpl implements IPlayService {
358 461 logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
359 462 dynamicTask.stop(timeOutTaskKey);
360 463 // hook响应
361   - StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId);
  464 + StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId,isSubStream);
362 465 if (streamInfo == null){
363 466 callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
364 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 477 return;
369 478 }
370 479 callback.run(InviteErrorCode.SUCCESS.getCode(),
371 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 493 return;
378 494 }
... ... @@ -395,9 +511,15 @@ public class PlayServiceImpl implements IPlayService {
395 511  
396 512 callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
397 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 524 }else {
403 525 ssrcInfo.setSsrc(ssrcInResponse);
... ... @@ -408,7 +530,11 @@ public class PlayServiceImpl implements IPlayService {
408 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 538 }, (event) -> {
413 539 dynamicTask.stop(timeOutTaskKey);
414 540 mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
... ... @@ -419,11 +545,19 @@ public class PlayServiceImpl implements IPlayService {
419 545  
420 546 callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_ERROR.getCode(),
421 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 562 } catch (InvalidArgumentException | SipException | ParseException e) {
429 563  
... ... @@ -437,27 +571,51 @@ public class PlayServiceImpl implements IPlayService {
437 571  
438 572 callback.run(InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(),
439 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 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 610 if (inviteInfo != null) {
458 611 inviteInfo.setStatus(InviteSessionStatus.ok);
459 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 621 return streamInfo;
... ... @@ -994,6 +1152,7 @@ public class PlayServiceImpl implements IPlayService {
994 1152 return streamInfo;
995 1153 }
996 1154  
  1155 +
997 1156 @Override
998 1157 public void zlmServerOffline(String mediaServerId) {
999 1158 // 处理正在向上推流的上级平台
... ... @@ -1130,14 +1289,18 @@ public class PlayServiceImpl implements IPlayService {
1130 1289 }
1131 1290  
1132 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 1293 Device device = deviceService.getDevice(deviceId);
1135 1294 if (device == null) {
1136 1295 errorCallback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), null);
1137 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 1304 if (inviteInfo != null) {
1142 1305 if (inviteInfo.getStreamInfo() != null) {
1143 1306 // 已存在线直接截图
... ... @@ -1163,11 +1326,11 @@ public class PlayServiceImpl implements IPlayService {
1163 1326 }
1164 1327  
1165 1328 MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
1166   - play(newMediaServerItem, deviceId, channelId, (code, msg, data)->{
  1329 + play(newMediaServerItem, deviceId, channelId,isSubStream, (code, msg, data)->{
1167 1330 if (code == InviteErrorCode.SUCCESS.getCode()) {
1168 1331 InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
1169 1332 if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) {
1170   - getSnap(deviceId, channelId, fileName, errorCallback);
  1333 + getSnap(deviceId, channelId, fileName,isSubStream, errorCallback);
1171 1334 }else {
1172 1335 errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null);
1173 1336 }
... ... @@ -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 450  
451 451 @Select("select count(1) from wvp_device_channel")
452 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 43 "tree_type," +
44 44 "on_line," +
45 45 "media_server_id," +
  46 + "switch_primary_sub_stream," +
46 47 "(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+
47 48 " FROM wvp_device WHERE device_id = #{deviceId}")
48 49 Device getDeviceByDeviceId(String deviceId);
... ... @@ -161,6 +162,7 @@ public interface DeviceMapper {
161 162 "tree_type,"+
162 163 "on_line,"+
163 164 "media_server_id,"+
  165 + "switch_primary_sub_stream switchPrimarySubStream,"+
164 166 "(SELECT count(0) FROM wvp_device_channel WHERE device_id=de.device_id) as channel_count " +
165 167 "FROM wvp_device de" +
166 168 "<if test=\"onLine != null\"> where on_line=${onLine}</if>"+
... ... @@ -253,6 +255,7 @@ public interface DeviceMapper {
253 255 "<if test=\"asMessageChannel != null\">, as_message_channel=#{asMessageChannel}</if>" +
254 256 "<if test=\"geoCoordSys != null\">, geo_coord_sys=#{geoCoordSys}</if>" +
255 257 "<if test=\"treeType != null\">, tree_type=#{treeType}</if>" +
  258 + "<if test=\"switchPrimarySubStream != null\">, switch_primary_sub_stream=#{switchPrimarySubStream}</if>" +
256 259 "<if test=\"mediaServerId != null\">, media_server_id=#{mediaServerId}</if>" +
257 260 "WHERE device_id=#{deviceId}"+
258 261 " </script>"})
... ... @@ -271,7 +274,8 @@ public interface DeviceMapper {
271 274 "geo_coord_sys,"+
272 275 "tree_type,"+
273 276 "on_line,"+
274   - "media_server_id"+
  277 + "media_server_id,"+
  278 + "switch_primary_sub_stream"+
275 279 ") VALUES (" +
276 280 "#{deviceId}," +
277 281 "#{name}," +
... ... @@ -285,7 +289,8 @@ public interface DeviceMapper {
285 289 "#{geoCoordSys}," +
286 290 "#{treeType}," +
287 291 "#{onLine}," +
288   - "#{mediaServerId}" +
  292 + "#{mediaServerId}," +
  293 + "#{switchPrimarySubStream}" +
289 294 ")")
290 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 88 @Operation(summary = "开始点播")
89 89 @Parameter(name = "deviceId", description = "设备国标编号", required = true)
90 90 @Parameter(name = "channelId", description = "通道国标编号", required = true)
  91 + @Parameter(name = "isSubStream", description = "是否子码流(true-子码流,false-主码流),默认为false", required = true)
91 92 @GetMapping("/start/{deviceId}/{channelId}")
92 93 public DeferredResult<WVPResult<StreamContent>> play(HttpServletRequest request, @PathVariable String deviceId,
93   - @PathVariable String channelId) {
  94 + @PathVariable String channelId,boolean isSubStream) {
94 95  
95 96 // 获取可用的zlm
96 97 Device device = storager.queryVideoDevice(deviceId);
97 98 MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
98 99  
99 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 102 requestMessage.setKey(key);
102 103 String uuid = UUID.randomUUID().toString();
103 104 requestMessage.setId(uuid);
... ... @@ -116,7 +117,7 @@ public class PlayController {
116 117 // 录像查询以channelId作为deviceId查询
117 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 121 WVPResult<StreamContent> wvpResult = new WVPResult<>();
121 122 if (code == InviteErrorCode.SUCCESS.getCode()) {
122 123 wvpResult.setCode(ErrorCode.SUCCESS.getCode());
... ... @@ -142,8 +143,9 @@ public class PlayController {
142 143 @Operation(summary = "停止点播")
143 144 @Parameter(name = "deviceId", description = "设备国标编号", required = true)
144 145 @Parameter(name = "channelId", description = "通道国标编号", required = true)
  146 + @Parameter(name = "isSubStream", description = "是否子码流(true-子码流,false-主码流),默认为false", required = true)
145 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 150 logger.debug(String.format("设备预览/回放停止API调用,streamId:%s_%s", deviceId, channelId ));
149 151  
... ... @@ -156,7 +158,12 @@ public class PlayController {
156 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 167 if (inviteInfo == null) {
161 168 throw new ControllerException(ErrorCode.ERROR100.getCode(), "点播未找到");
162 169 }
... ... @@ -169,12 +176,17 @@ public class PlayController {
169 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 186 JSONObject json = new JSONObject();
176 187 json.put("deviceId", deviceId);
177 188 json.put("channelId", channelId);
  189 + json.put("isSubStream", isSubStream);
178 190 return json;
179 191 }
180 192  
... ... @@ -341,14 +353,16 @@ public class PlayController {
341 353 @Operation(summary = "获取截图")
342 354 @Parameter(name = "deviceId", description = "设备国标编号", required = true)
343 355 @Parameter(name = "channelId", description = "通道国标编号", required = true)
  356 + @Parameter(name = "isSubStream", description = "是否子码流(true-子码流,false-主码流),默认为false", required = true)
344 357 @GetMapping("/snap")
345   - public DeferredResult<String> getSnap(String deviceId, String channelId) {
  358 + public DeferredResult<String> getSnap(String deviceId, String channelId,boolean isSubStream) {
346 359 if (logger.isDebugEnabled()) {
347 360 logger.debug("获取截图: {}/{}", deviceId, channelId);
348 361 }
349 362  
  363 + Device device = storager.queryVideoDevice(deviceId);
350 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 366 String uuid = UUID.randomUUID().toString();
353 367 resultHolder.put(key, uuid, result);
354 368  
... ... @@ -357,7 +371,7 @@ public class PlayController {
357 371 message.setId(uuid);
358 372  
359 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 375 if (code == InviteErrorCode.SUCCESS.getCode()) {
362 376 message.setData(data);
363 377 }else {
... ...
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java
... ... @@ -122,7 +122,7 @@ public class ApiStreamController {
122 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 126 if (errorCode == InviteErrorCode.SUCCESS.getCode()) {
127 127 InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, serial, code);
128 128 if (inviteInfo != null && inviteInfo.getStreamInfo() != null) {
... ...
src/main/resources/application-dev.yml
1 1 spring:
  2 + thymeleaf:
  3 + cache: false
2 4 # [可选]上传文件大小限制
3 5 servlet:
4 6 multipart:
... ... @@ -11,18 +13,18 @@ spring:
11 13 # [必须修改] 端口号
12 14 port: 6379
13 15 # [可选] 数据库 DB
14   - database: 6
  16 + database: 7
15 17 # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
16   - password: face2020
  18 + password:
17 19 # [可选] 超时时间
18 20 timeout: 10000
19 21 # mysql数据源
20 22 datasource:
21 23 type: com.zaxxer.hikari.HikariDataSource
22 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 26 username: root
25   - password: 123456
  27 + password: root
26 28 hikari:
27 29 connection-timeout: 20000 # 是客户端等待连接池连接的最大毫秒数
28 30 initialSize: 10 # 连接池初始化连接数
... ... @@ -30,11 +32,19 @@ spring:
30 32 minimum-idle: 5 # 连接池最小空闲连接数
31 33 idle-timeout: 300000 # 允许连接在连接池中空闲的最长时间(以毫秒为单位)
32 34 max-lifetime: 1200000 # 是池中连接关闭后的最长生命周期(以毫秒为单位)
33   -
34   -
35 35 #[可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口
36 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 49 # 作为28181服务器的配置
40 50 sip:
... ... @@ -42,26 +52,36 @@ sip:
42 52 # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4
43 53 # 如果不明白,就使用0.0.0.0,大部分情况都是可以的
44 54 # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。
45   - ip: 192.168.41.16
  55 + ip: 192.168.1.18
46 56 # [可选] 28181服务监听的端口
47   - port: 5060
  57 + port: 8116
48 58 # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007)
49 59 # 后两位为行业编码,定义参照附录D.3
50 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 70 #zlm 默认服务器配置
59 71 media:
60   - id: FQ3TF8yT83wh5Wvz
  72 + id: 89wulian-one
61 73 # [必须修改] zlm服务器的内网IP
62   - ip: 192.168.41.16
  74 + ip: 192.168.1.18
63 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 85 # [可选] zlm服务器的hook.admin_params=secret
66 86 secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc
67 87 # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试
... ... @@ -69,11 +89,24 @@ media:
69 89 # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输
70 90 enable: true
71 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 95 # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用
76 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 110 logging:
79 111 config: classpath:logback-spring-local.xml
  112 +
... ...
src/main/resources/application.yml
... ... @@ -2,4 +2,4 @@ spring:
2 2 application:
3 3 name: wvp
4 4 profiles:
5   - active: local
6 5 \ No newline at end of file
  6 + active: dev
7 7 \ No newline at end of file
... ...
web_src/config/index.js
... ... @@ -12,14 +12,14 @@ module.exports = {
12 12 assetsPublicPath: '/',
13 13 proxyTable: {
14 14 '/debug': {
15   - target: 'http://localhost:18080',
  15 + target: 'http://localhost:18978',
16 16 changeOrigin: true,
17 17 pathRewrite: {
18 18 '^/debug': '/'
19 19 }
20 20 },
21 21 '/static/snap': {
22   - target: 'http://localhost:18080',
  22 + target: 'http://localhost:18978',
23 23 changeOrigin: true,
24 24 // pathRewrite: {
25 25 // '^/static/snap': '/static/snap'
... ...
web_src/package-lock.json
... ... @@ -184,15 +184,19 @@
184 184 }
185 185 },
186 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 190 "dev": true,
191 191 "dependencies": {
192   - "co": "^4.6.0",
193   - "fast-deep-equal": "^1.0.0",
  192 + "fast-deep-equal": "^3.1.1",
194 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 202 "node_modules/ajv-keywords": {
... ... @@ -2111,8 +2115,8 @@
2111 2115 },
2112 2116 "node_modules/co": {
2113 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 2120 "dev": true,
2117 2121 "engines": {
2118 2122 "iojs": ">= 1.0.0",
... ... @@ -4620,9 +4624,9 @@
4620 4624 }
4621 4625 },
4622 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 4630 "dev": true
4627 4631 },
4628 4632 "node_modules/fast-json-stable-stringify": {
... ... @@ -4665,30 +4669,6 @@
4665 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 4672 "node_modules/file-loader/node_modules/schema-utils": {
4693 4673 "version": "0.4.7",
4694 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 6106 "dev": true
6127 6107 },
6128 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 6112 "dev": true
6133 6113 },
6134 6114 "node_modules/json-stringify-pretty-compact": {
... ... @@ -8770,30 +8750,6 @@
8770 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 8753 "node_modules/postcss-loader/node_modules/schema-utils": {
8798 8754 "version": "0.4.7",
8799 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 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 11483 "node_modules/select": {
11504 11484 "version": "1.1.2",
11505 11485 "resolved": "https://registry.npm.taobao.org/select/download/select-1.1.2.tgz",
... ... @@ -12721,36 +12701,12 @@
12721 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 12704 "node_modules/uglifyjs-webpack-plugin/node_modules/commander": {
12737 12705 "version": "2.13.0",
12738 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 12707 "integrity": "sha1-aWS8pnaF33wfFDDFhPB9dZeIW5w=",
12740 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 12710 "node_modules/uglifyjs-webpack-plugin/node_modules/schema-utils": {
12755 12711 "version": "0.4.7",
12756 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 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 14041 "node_modules/webpack/node_modules/has-flag": {
14104 14042 "version": "2.0.0",
14105 14043 "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz",
... ... @@ -14109,12 +14047,6 @@
14109 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 14050 "node_modules/webpack/node_modules/source-map": {
14119 14051 "version": "0.5.7",
14120 14052 "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz",
... ... @@ -14551,15 +14483,15 @@
14551 14483 }
14552 14484 },
14553 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 14489 "dev": true,
14558 14490 "requires": {
14559   - "co": "^4.6.0",
14560   - "fast-deep-equal": "^1.0.0",
  14491 + "fast-deep-equal": "^3.1.1",
14561 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 14497 "ajv-keywords": {
... ... @@ -16303,8 +16235,8 @@
16303 16235 },
16304 16236 "co": {
16305 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 16240 "dev": true
16309 16241 },
16310 16242 "coa": {
... ... @@ -18423,9 +18355,9 @@
18423 18355 }
18424 18356 },
18425 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 18361 "dev": true
18430 18362 },
18431 18363 "fast-json-stable-stringify": {
... ... @@ -18459,30 +18391,6 @@
18459 18391 "schema-utils": "^0.4.5"
18460 18392 },
18461 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 18394 "schema-utils": {
18487 18395 "version": "0.4.7",
18488 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 19556 "dev": true
19649 19557 },
19650 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 19562 "dev": true
19655 19563 },
19656 19564 "json-stringify-pretty-compact": {
... ... @@ -21822,30 +21730,6 @@
21822 21730 "schema-utils": "^0.4.0"
21823 21731 },
21824 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 21733 "schema-utils": {
21850 21734 "version": "0.4.7",
21851 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 23981 "dev": true,
24098 23982 "requires": {
24099 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 24012 "select": {
... ... @@ -25116,36 +25026,12 @@
25116 25026 "worker-farm": "^1.5.2"
25117 25027 },
25118 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 25029 "commander": {
25132 25030 "version": "2.13.0",
25133 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 25032 "integrity": "sha1-aWS8pnaF33wfFDDFhPB9dZeIW5w=",
25135 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 25035 "schema-utils": {
25150 25036 "version": "0.4.7",
25151 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 25732 "yargs": "^8.0.2"
25847 25733 },
25848 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 25735 "has-flag": {
25868 25736 "version": "2.0.0",
25869 25737 "resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz",
25870 25738 "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
25871 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 25741 "source-map": {
25880 25742 "version": "0.5.7",
25881 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 26 <el-option label="在线" value="true"></el-option>
27 27 <el-option label="离线" value="false"></el-option>
28 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 35 </div>
30 36 <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button>
31 37 <el-button v-if="showTree" icon="iconfont icon-list" circle size="mini" @click="switchList()"></el-button>
... ... @@ -146,6 +152,7 @@ export default {
146 152 searchSrt: "",
147 153 channelType: "",
148 154 online: "",
  155 + isSubStream: false,
149 156 winHeight: window.innerHeight - 200,
150 157 currentPage: 1,
151 158 count: 15,
... ... @@ -237,7 +244,10 @@ export default {
237 244 let that = this;
238 245 this.$axios({
239 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 251 }).then(function (res) {
242 252 console.log(res)
243 253 that.isLoging = false;
... ... @@ -277,7 +287,10 @@ export default {
277 287 var that = this;
278 288 this.$axios({
279 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 294 }).then(function (res) {
282 295 that.initData();
283 296 }).catch(function (error) {
... ...
web_src/src/components/dialog/deviceEdit.vue
... ... @@ -64,6 +64,12 @@
64 64 <el-form-item v-if="form.subscribeCycleForMobilePosition > 0" label="移动位置报送间隔" prop="subscribeCycleForCatalog" >
65 65 <el-input v-model="form.mobilePositionSubmissionInterval" clearable ></el-input>
66 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 73 <el-form-item label="其他选项">
68 74 <el-checkbox label="SSRC校验" v-model="form.ssrcCheck" style="float: left"></el-checkbox>
69 75 <el-checkbox label="作为消息通道" v-model="form.asMessageChannel" style="float: left"></el-checkbox>
... ...