Commit fa62ab9a0143433a5d058ab3229a37e4a9a0f696
Committed by
GitHub
Merge pull request #893 from sxh-netizen/wvp-28181-2.0
新增设备主子码流开关选择,默认为不开启
Showing
25 changed files
with
844 additions
and
203 deletions
sql/2.6.6-2.6.7更新.sql
| @@ -7,6 +7,7 @@ alter table parent_platform | @@ -7,6 +7,7 @@ alter table parent_platform | ||
| 7 | alter table device | 7 | alter table device |
| 8 | add mediaServerId varchar(50) default null; | 8 | add mediaServerId varchar(50) default null; |
| 9 | 9 | ||
| 10 | - | 10 | +ALTER TABLE device |
| 11 | + ADD COLUMN `switchPrimarySubStream` bit(1) NOT NULL DEFAULT b'0' COMMENT '开启主子码流切换的开关(0-不开启,1-开启)现在已知支持设备为 大华、TP——LINK全系设备' AFTER `keepalive_interval_time` | ||
| 11 | 12 | ||
| 12 | 13 |
src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java
| 1 | package com.genersoft.iot.vmp.common; | 1 | package com.genersoft.iot.vmp.common; |
| 2 | 2 | ||
| 3 | import com.genersoft.iot.vmp.service.bean.SSRCInfo; | 3 | import com.genersoft.iot.vmp.service.bean.SSRCInfo; |
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | 5 | ||
| 5 | /** | 6 | /** |
| 6 | * 记录每次发送invite消息的状态 | 7 | * 记录每次发送invite消息的状态 |
| @@ -123,4 +124,40 @@ public class InviteInfo { | @@ -123,4 +124,40 @@ public class InviteInfo { | ||
| 123 | public void setStreamMode(String streamMode) { | 124 | public void setStreamMode(String streamMode) { |
| 124 | this.streamMode = streamMode; | 125 | this.streamMode = streamMode; |
| 125 | } | 126 | } |
| 127 | + | ||
| 128 | + | ||
| 129 | + /*=========================设备主子码流逻辑START====================*/ | ||
| 130 | + @Schema(description = "是否为子码流(true-是,false-主码流)") | ||
| 131 | + private boolean subStream; | ||
| 132 | + | ||
| 133 | + public boolean isSubStream() { | ||
| 134 | + return subStream; | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + public void setSubStream(boolean subStream) { | ||
| 138 | + this.subStream = subStream; | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + public static InviteInfo getInviteInfo(String deviceId, String channelId,Boolean isSubStream, String stream, SSRCInfo ssrcInfo, | ||
| 142 | + String receiveIp, Integer receivePort, String streamMode, | ||
| 143 | + InviteSessionType type, InviteSessionStatus status) { | ||
| 144 | + InviteInfo inviteInfo = new InviteInfo(); | ||
| 145 | + inviteInfo.setDeviceId(deviceId); | ||
| 146 | + inviteInfo.setChannelId(channelId); | ||
| 147 | + inviteInfo.setStream(stream); | ||
| 148 | + inviteInfo.setSsrcInfo(ssrcInfo); | ||
| 149 | + inviteInfo.setReceiveIp(receiveIp); | ||
| 150 | + inviteInfo.setReceivePort(receivePort); | ||
| 151 | + inviteInfo.setStreamMode(streamMode); | ||
| 152 | + inviteInfo.setType(type); | ||
| 153 | + inviteInfo.setStatus(status); | ||
| 154 | + if(isSubStream != null){ | ||
| 155 | + inviteInfo.setSubStream(isSubStream); | ||
| 156 | + } | ||
| 157 | + return inviteInfo; | ||
| 158 | + } | ||
| 159 | + /*=========================设备主子码流逻辑END====================*/ | ||
| 160 | + | ||
| 161 | + | ||
| 162 | + | ||
| 126 | } | 163 | } |
src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java
| @@ -528,4 +528,31 @@ public class StreamInfo implements Serializable, Cloneable{ | @@ -528,4 +528,31 @@ public class StreamInfo implements Serializable, Cloneable{ | ||
| 528 | } | 528 | } |
| 529 | return instance; | 529 | return instance; |
| 530 | } | 530 | } |
| 531 | + | ||
| 532 | + | ||
| 533 | + /*=========================设备主子码流逻辑START====================*/ | ||
| 534 | + @Schema(description = "是否为子码流(true-是,false-主码流)") | ||
| 535 | + private boolean subStream; | ||
| 536 | + | ||
| 537 | + public boolean isSubStream() { | ||
| 538 | + return subStream; | ||
| 539 | + } | ||
| 540 | + | ||
| 541 | + public void setSubStream(boolean subStream) { | ||
| 542 | + this.subStream = subStream; | ||
| 543 | + } | ||
| 544 | + | ||
| 545 | + public static String getPlayStream(String deviceId,String channelId,boolean isSubStream){ | ||
| 546 | + String streamId; | ||
| 547 | + if(isSubStream){ | ||
| 548 | + streamId = String.format("%s_%s_%s","sub",deviceId, channelId); | ||
| 549 | + }else { | ||
| 550 | + streamId = String.format("%s_%s_%s","main", deviceId, channelId); | ||
| 551 | + } | ||
| 552 | + return streamId; | ||
| 553 | + } | ||
| 554 | + | ||
| 555 | + /*=========================设备主子码流逻辑END====================*/ | ||
| 556 | + | ||
| 557 | + | ||
| 531 | } | 558 | } |
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
| 1 | package com.genersoft.iot.vmp.conf; | 1 | package com.genersoft.iot.vmp.conf; |
| 2 | 2 | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 3 | import org.springframework.core.annotation.Order; | 4 | import org.springframework.core.annotation.Order; |
| 4 | import org.springframework.boot.context.properties.ConfigurationProperties; | 5 | import org.springframework.boot.context.properties.ConfigurationProperties; |
| 5 | import org.springframework.stereotype.Component; | 6 | import org.springframework.stereotype.Component; |
| @@ -25,11 +26,11 @@ public class UserSetting { | @@ -25,11 +26,11 @@ public class UserSetting { | ||
| 25 | 26 | ||
| 26 | private int platformPlayTimeout = 60000; | 27 | private int platformPlayTimeout = 60000; |
| 27 | 28 | ||
| 28 | - private Boolean interfaceAuthentication = Boolean.TRUE; | 29 | + private Boolean interfaceAuthentication = Boolean.FALSE; |
| 29 | 30 | ||
| 30 | - private Boolean recordPushLive = Boolean.TRUE; | 31 | + private Boolean recordPushLive = Boolean.FALSE; |
| 31 | 32 | ||
| 32 | - private Boolean recordSip = Boolean.TRUE; | 33 | + private Boolean recordSip = Boolean.FALSE; |
| 33 | 34 | ||
| 34 | private Boolean logInDatebase = Boolean.TRUE; | 35 | private Boolean logInDatebase = Boolean.TRUE; |
| 35 | 36 |
src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java
| @@ -189,6 +189,8 @@ public class Device { | @@ -189,6 +189,8 @@ public class Device { | ||
| 189 | private SipTransactionInfo sipTransactionInfo; | 189 | private SipTransactionInfo sipTransactionInfo; |
| 190 | 190 | ||
| 191 | 191 | ||
| 192 | + | ||
| 193 | + | ||
| 192 | public String getDeviceId() { | 194 | public String getDeviceId() { |
| 193 | return deviceId; | 195 | return deviceId; |
| 194 | } | 196 | } |
| @@ -447,4 +449,20 @@ public class Device { | @@ -447,4 +449,20 @@ public class Device { | ||
| 447 | public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) { | 449 | public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) { |
| 448 | this.sipTransactionInfo = sipTransactionInfo; | 450 | this.sipTransactionInfo = sipTransactionInfo; |
| 449 | } | 451 | } |
| 452 | + | ||
| 453 | + /*======================设备主子码流逻辑START=========================*/ | ||
| 454 | + @Schema(description = "开启主子码流切换的开关(false-不开启,true-开启)") | ||
| 455 | + private boolean switchPrimarySubStream; | ||
| 456 | + | ||
| 457 | + public boolean isSwitchPrimarySubStream() { | ||
| 458 | + return switchPrimarySubStream; | ||
| 459 | + } | ||
| 460 | + | ||
| 461 | + public void setSwitchPrimarySubStream(boolean switchPrimarySubStream) { | ||
| 462 | + this.switchPrimarySubStream = switchPrimarySubStream; | ||
| 463 | + } | ||
| 464 | + | ||
| 465 | + /*======================设备主子码流逻辑END=========================*/ | ||
| 466 | + | ||
| 467 | + | ||
| 450 | } | 468 | } |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
| @@ -155,4 +155,30 @@ public class DeferredResultHolder { | @@ -155,4 +155,30 @@ public class DeferredResultHolder { | ||
| 155 | map.remove(msg.getKey()); | 155 | map.remove(msg.getKey()); |
| 156 | } | 156 | } |
| 157 | } | 157 | } |
| 158 | + | ||
| 159 | + /*============================设备主子码流逻辑START========================*/ | ||
| 160 | + public static String getPlayKey(String deviceId,String channelId,boolean deviceSwitchSubStream,boolean isSubStream){ | ||
| 161 | + String key = null; | ||
| 162 | + if(deviceSwitchSubStream){ | ||
| 163 | + key = CALLBACK_CMD_PLAY + isSubStream + deviceId + channelId; | ||
| 164 | + }else { | ||
| 165 | + key = CALLBACK_CMD_PLAY +deviceId + channelId; | ||
| 166 | + } | ||
| 167 | + return key; | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + public static String getSnapKey(String deviceId,String channelId,boolean deviceSwitchSubStream,boolean isSubStream){ | ||
| 171 | + String key = null; | ||
| 172 | + if(deviceSwitchSubStream){ | ||
| 173 | + key = CALLBACK_CMD_SNAP + isSubStream + deviceId + channelId; | ||
| 174 | + }else { | ||
| 175 | + key = CALLBACK_CMD_SNAP +deviceId + channelId; | ||
| 176 | + } | ||
| 177 | + return key; | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + | ||
| 181 | + /*============================设备主子码流逻辑END========================*/ | ||
| 182 | + | ||
| 183 | + | ||
| 158 | } | 184 | } |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
| @@ -98,7 +98,7 @@ public interface ISIPCommander { | @@ -98,7 +98,7 @@ public interface ISIPCommander { | ||
| 98 | * @param device 视频设备 | 98 | * @param device 视频设备 |
| 99 | * @param channelId 预览通道 | 99 | * @param channelId 预览通道 |
| 100 | */ | 100 | */ |
| 101 | - void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; | 101 | + void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,boolean isSubStream, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; |
| 102 | 102 | ||
| 103 | /** | 103 | /** |
| 104 | * 请求回放视频流 | 104 | * 请求回放视频流 |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
| @@ -266,7 +266,7 @@ public class SIPCommander implements ISIPCommander { | @@ -266,7 +266,7 @@ public class SIPCommander implements ISIPCommander { | ||
| 266 | * @param errorEvent sip错误订阅 | 266 | * @param errorEvent sip错误订阅 |
| 267 | */ | 267 | */ |
| 268 | @Override | 268 | @Override |
| 269 | - public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, | 269 | + public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,boolean isSubStream, |
| 270 | ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { | 270 | ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { |
| 271 | String stream = ssrcInfo.getStream(); | 271 | String stream = ssrcInfo.getStream(); |
| 272 | 272 | ||
| @@ -341,6 +341,22 @@ public class SIPCommander implements ISIPCommander { | @@ -341,6 +341,22 @@ public class SIPCommander implements ISIPCommander { | ||
| 341 | } | 341 | } |
| 342 | } | 342 | } |
| 343 | 343 | ||
| 344 | + if( device.isSwitchPrimarySubStream() ){ | ||
| 345 | + if("TP-LINK".equals(device.getManufacturer())){ | ||
| 346 | + if (isSubStream){ | ||
| 347 | + content.append("a=streamMode:sub\r\n"); | ||
| 348 | + }else { | ||
| 349 | + content.append("a=streamMode:main\r\n"); | ||
| 350 | + } | ||
| 351 | + }else { | ||
| 352 | + if (isSubStream){ | ||
| 353 | + content.append("a=streamprofile:1\r\n"); | ||
| 354 | + }else { | ||
| 355 | + content.append("a=streamprofile:0\r\n"); | ||
| 356 | + } | ||
| 357 | + } | ||
| 358 | + } | ||
| 359 | + | ||
| 344 | content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc | 360 | content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc |
| 345 | // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率 | 361 | // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率 |
| 346 | // content.append("f=v/2/5/25/1/4000a/1/8/1" + "\r\n"); // 未发现支持此特性的设备 | 362 | // content.append("f=v/2/5/25/1/4000a/1/8/1" + "\r\n"); // 未发现支持此特性的设备 |
| @@ -356,7 +372,11 @@ public class SIPCommander implements ISIPCommander { | @@ -356,7 +372,11 @@ public class SIPCommander implements ISIPCommander { | ||
| 356 | // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 | 372 | // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 |
| 357 | ResponseEvent responseEvent = (ResponseEvent) e.event; | 373 | ResponseEvent responseEvent = (ResponseEvent) e.event; |
| 358 | SIPResponse response = (SIPResponse) responseEvent.getResponse(); | 374 | SIPResponse response = (SIPResponse) responseEvent.getResponse(); |
| 359 | - streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAY); | 375 | + if(device.isSwitchPrimarySubStream()){ |
| 376 | + streamSession.put(device.getDeviceId(), channelId, "switch-play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAY); | ||
| 377 | + }else { | ||
| 378 | + streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAY); | ||
| 379 | + } | ||
| 360 | okEvent.response(e); | 380 | okEvent.response(e); |
| 361 | }); | 381 | }); |
| 362 | } | 382 | } |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java
| @@ -142,8 +142,13 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In | @@ -142,8 +142,13 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In | ||
| 142 | // 可能是设备主动停止 | 142 | // 可能是设备主动停止 |
| 143 | Device device = storager.queryVideoDeviceByChannelId(platformGbId); | 143 | Device device = storager.queryVideoDeviceByChannelId(platformGbId); |
| 144 | if (device != null) { | 144 | if (device != null) { |
| 145 | - storager.stopPlay(device.getDeviceId(), channelId); | ||
| 146 | - SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null); | 145 | + SsrcTransaction ssrcTransactionForPlay = null; |
| 146 | + if (device.isSwitchPrimarySubStream() ) { | ||
| 147 | + ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "switch-play", null); | ||
| 148 | + } else { | ||
| 149 | + storager.stopPlay(device.getDeviceId(), channelId); | ||
| 150 | + ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null); | ||
| 151 | + } | ||
| 147 | if (ssrcTransactionForPlay != null){ | 152 | if (ssrcTransactionForPlay != null){ |
| 148 | if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){ | 153 | if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){ |
| 149 | // 释放ssrc | 154 | // 释放ssrc |
| @@ -153,10 +158,17 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In | @@ -153,10 +158,17 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In | ||
| 153 | } | 158 | } |
| 154 | streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream()); | 159 | streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream()); |
| 155 | } | 160 | } |
| 156 | - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); | ||
| 157 | - | ||
| 158 | - if (inviteInfo != null) { | 161 | + InviteInfo inviteInfo = null; |
| 162 | + if (device.isSwitchPrimarySubStream() ) { | ||
| 163 | + String streamType = ssrcTransactionForPlay.getStream().split("_")[0]; | ||
| 164 | + boolean isSubStream = "sub".equals(streamType); | ||
| 165 | + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream); | ||
| 166 | + inviteStreamService.removeInviteInfo(inviteInfo.getType(),inviteInfo.getDeviceId(),inviteInfo.getChannelId(),isSubStream,inviteInfo.getStream()); | ||
| 167 | + }else { | ||
| 168 | + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); | ||
| 159 | inviteStreamService.removeInviteInfo(inviteInfo); | 169 | inviteStreamService.removeInviteInfo(inviteInfo); |
| 170 | + } | ||
| 171 | + if (inviteInfo != null) { | ||
| 160 | if (inviteInfo.getStreamInfo() != null) { | 172 | if (inviteInfo.getStreamInfo() != null) { |
| 161 | mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream()); | 173 | mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream()); |
| 162 | } | 174 | } |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
| @@ -489,7 +489,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | @@ -489,7 +489,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | ||
| 489 | } | 489 | } |
| 490 | sendRtpItem.setStreamId(streamId); | 490 | sendRtpItem.setStreamId(streamId); |
| 491 | redisCatchStorage.updateSendRTPSever(sendRtpItem); | 491 | redisCatchStorage.updateSendRTPSever(sendRtpItem); |
| 492 | - playService.play(mediaServerItem, device.getDeviceId(), channelId, ((code, msg, data) -> { | 492 | + playService.play(mediaServerItem, device.getDeviceId(), channelId,false, ((code, msg, data) -> { |
| 493 | if (code == InviteErrorCode.SUCCESS.getCode()){ | 493 | if (code == InviteErrorCode.SUCCESS.getCode()){ |
| 494 | hookEvent.run(code, msg, data); | 494 | hookEvent.run(code, msg, data); |
| 495 | }else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()){ | 495 | }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
| @@ -289,6 +289,7 @@ public class ZLMHttpHookListener { | @@ -289,6 +289,7 @@ public class ZLMHttpHookListener { | ||
| 289 | @ResponseBody | 289 | @ResponseBody |
| 290 | @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") | 290 | @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") |
| 291 | public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) { | 291 | public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) { |
| 292 | + | ||
| 292 | if (param.isRegist()) { | 293 | if (param.isRegist()) { |
| 293 | logger.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | 294 | logger.info("[ZLM HOOK] 流注册, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); |
| 294 | } else { | 295 | } else { |
| @@ -310,11 +311,13 @@ public class ZLMHttpHookListener { | @@ -310,11 +311,13 @@ public class ZLMHttpHookListener { | ||
| 310 | 311 | ||
| 311 | List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks(); | 312 | List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks(); |
| 312 | // TODO 重构此处逻辑 | 313 | // TODO 重构此处逻辑 |
| 314 | + boolean isPush = false; | ||
| 313 | if (param.isRegist()) { | 315 | if (param.isRegist()) { |
| 314 | // 处理流注册的鉴权信息 | 316 | // 处理流注册的鉴权信息 |
| 315 | if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal() | 317 | if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal() |
| 316 | || param.getOriginType() == OriginType.RTSP_PUSH.ordinal() | 318 | || param.getOriginType() == OriginType.RTSP_PUSH.ordinal() |
| 317 | || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) { | 319 | || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) { |
| 320 | + isPush = true; | ||
| 318 | StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); | 321 | StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); |
| 319 | if (streamAuthorityInfo == null) { | 322 | if (streamAuthorityInfo == null) { |
| 320 | streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); | 323 | streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); |
| @@ -328,7 +331,7 @@ public class ZLMHttpHookListener { | @@ -328,7 +331,7 @@ public class ZLMHttpHookListener { | ||
| 328 | redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream()); | 331 | redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream()); |
| 329 | } | 332 | } |
| 330 | 333 | ||
| 331 | - if ("rtmp".equals(param.getSchema())) { | 334 | + if ("rtsp".equals(param.getSchema())) { |
| 332 | // 更新流媒体负载信息 | 335 | // 更新流媒体负载信息 |
| 333 | if (param.isRegist()) { | 336 | if (param.isRegist()) { |
| 334 | mediaServerService.addCount(param.getMediaServerId()); | 337 | mediaServerService.addCount(param.getMediaServerId()); |
| @@ -342,10 +345,19 @@ public class ZLMHttpHookListener { | @@ -342,10 +345,19 @@ public class ZLMHttpHookListener { | ||
| 342 | } | 345 | } |
| 343 | 346 | ||
| 344 | if ("rtp".equals(param.getApp()) && !param.isRegist()) { | 347 | if ("rtp".equals(param.getApp()) && !param.isRegist()) { |
| 345 | - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); | ||
| 346 | - if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) { | ||
| 347 | - inviteStreamService.removeInviteInfo(inviteInfo); | ||
| 348 | - 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 | + } | ||
| 349 | } | 361 | } |
| 350 | } else { | 362 | } else { |
| 351 | if (!"rtp".equals(param.getApp())) { | 363 | if (!"rtp".equals(param.getApp())) { |
| @@ -360,8 +372,6 @@ public class ZLMHttpHookListener { | @@ -360,8 +372,6 @@ public class ZLMHttpHookListener { | ||
| 360 | StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaInfo, | 372 | StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaInfo, |
| 361 | param.getApp(), param.getStream(), tracks, callId); | 373 | param.getApp(), param.getStream(), tracks, callId); |
| 362 | param.setStreamInfo(new StreamContent(streamInfoByAppAndStream)); | 374 | param.setStreamInfo(new StreamContent(streamInfoByAppAndStream)); |
| 363 | - // 如果是拉流代理产生的,不需要写入推流 | ||
| 364 | - | ||
| 365 | redisCatchStorage.addStream(mediaInfo, type, param.getApp(), param.getStream(), param); | 375 | redisCatchStorage.addStream(mediaInfo, type, param.getApp(), param.getStream(), param); |
| 366 | if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal() | 376 | if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal() |
| 367 | || param.getOriginType() == OriginType.RTMP_PUSH.ordinal() | 377 | || param.getOriginType() == OriginType.RTMP_PUSH.ordinal() |
| @@ -450,6 +460,11 @@ public class ZLMHttpHookListener { | @@ -450,6 +460,11 @@ public class ZLMHttpHookListener { | ||
| 450 | InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); | 460 | InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); |
| 451 | // 点播 | 461 | // 点播 |
| 452 | if (inviteInfo != null) { | 462 | if (inviteInfo != null) { |
| 463 | + // 录像下载 | ||
| 464 | + if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) { | ||
| 465 | + ret.put("close", false); | ||
| 466 | + return ret; | ||
| 467 | + } | ||
| 453 | // 收到无人观看说明流也没有在往上级推送 | 468 | // 收到无人观看说明流也没有在往上级推送 |
| 454 | if (redisCatchStorage.isChannelSendingRTP(inviteInfo.getChannelId())) { | 469 | if (redisCatchStorage.isChannelSendingRTP(inviteInfo.getChannelId())) { |
| 455 | List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId( | 470 | List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId( |
| @@ -467,27 +482,33 @@ public class ZLMHttpHookListener { | @@ -467,27 +482,33 @@ public class ZLMHttpHookListener { | ||
| 467 | } | 482 | } |
| 468 | } | 483 | } |
| 469 | } | 484 | } |
| 485 | + Device device = deviceService.getDevice(inviteInfo.getDeviceId()); | ||
| 486 | + if (device != null) { | ||
| 487 | + try { | ||
| 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()); | ||
| 470 | 494 | ||
| 471 | - if (userSetting.getStreamOnDemand()) { | ||
| 472 | - // 录像下载 | ||
| 473 | - if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) { | ||
| 474 | - ret.put("close", false); | ||
| 475 | - return ret; | ||
| 476 | - } | 495 | + } |
| 477 | 496 | ||
| 478 | - Device device = deviceService.getDevice(inviteInfo.getDeviceId()); | ||
| 479 | - if (device != null) { | ||
| 480 | - try { | ||
| 481 | - if (inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()) != null) { | ||
| 482 | - cmder.streamByeCmd(device, inviteInfo.getChannelId(), | ||
| 483 | - inviteInfo.getStream(), null); | ||
| 484 | - } | ||
| 485 | - } catch (InvalidArgumentException | ParseException | SipException | | ||
| 486 | - SsrcTransactionNotFoundException e) { | ||
| 487 | - logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage()); | 497 | + if (info != null) { |
| 498 | + cmder.streamByeCmd(device, inviteInfo.getChannelId(), | ||
| 499 | + inviteInfo.getStream(), null); | ||
| 488 | } | 500 | } |
| 501 | + } catch (InvalidArgumentException | ParseException | SipException | | ||
| 502 | + SsrcTransactionNotFoundException e) { | ||
| 503 | + logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage()); | ||
| 489 | } | 504 | } |
| 505 | + } | ||
| 490 | 506 | ||
| 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 { | ||
| 491 | inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), | 512 | inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), |
| 492 | inviteInfo.getChannelId(), inviteInfo.getStream()); | 513 | inviteInfo.getChannelId(), inviteInfo.getStream()); |
| 493 | storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); | 514 | storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); |
| @@ -499,7 +520,7 @@ public class ZLMHttpHookListener { | @@ -499,7 +520,7 @@ public class ZLMHttpHookListener { | ||
| 499 | // 拉流代理 | 520 | // 拉流代理 |
| 500 | StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); | 521 | StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); |
| 501 | if (streamProxyItem != null) { | 522 | if (streamProxyItem != null) { |
| 502 | - if (streamProxyItem.isEnableRemoveNoneReader()) { | 523 | + if (streamProxyItem.isEnableDisableNoneReader()) { |
| 503 | // 无人观看自动移除 | 524 | // 无人观看自动移除 |
| 504 | ret.put("close", true); | 525 | ret.put("close", true); |
| 505 | streamProxyService.del(param.getApp(), param.getStream()); | 526 | streamProxyService.del(param.getApp(), param.getStream()); |
| @@ -544,12 +565,26 @@ public class ZLMHttpHookListener { | @@ -544,12 +565,26 @@ public class ZLMHttpHookListener { | ||
| 544 | 565 | ||
| 545 | if ("rtp".equals(param.getApp())) { | 566 | if ("rtp".equals(param.getApp())) { |
| 546 | String[] s = param.getStream().split("_"); | 567 | String[] s = param.getStream().split("_"); |
| 547 | - if (!mediaInfo.isRtpEnable() || s.length != 2) { | 568 | + if (!mediaInfo.isRtpEnable() ) { |
| 569 | + defaultResult.setResult(HookResult.SUCCESS()); | ||
| 570 | + return defaultResult; | ||
| 571 | + }else if(s.length != 2 && s.length != 3 ){ | ||
| 548 | defaultResult.setResult(HookResult.SUCCESS()); | 572 | defaultResult.setResult(HookResult.SUCCESS()); |
| 549 | return defaultResult; | 573 | return defaultResult; |
| 550 | } | 574 | } |
| 551 | - String deviceId = s[0]; | ||
| 552 | - String channelId = s[1]; | 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]; | ||
| 587 | + } | ||
| 553 | Device device = redisCatchStorage.getDevice(deviceId); | 588 | Device device = redisCatchStorage.getDevice(deviceId); |
| 554 | if (device == null) { | 589 | if (device == null) { |
| 555 | defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); | 590 | defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); |
| @@ -563,7 +598,7 @@ public class ZLMHttpHookListener { | @@ -563,7 +598,7 @@ public class ZLMHttpHookListener { | ||
| 563 | logger.info("[ZLM HOOK] 流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | 598 | logger.info("[ZLM HOOK] 流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); |
| 564 | 599 | ||
| 565 | RequestMessage msg = new RequestMessage(); | 600 | RequestMessage msg = new RequestMessage(); |
| 566 | - String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; | 601 | + String key = DeferredResultHolder.getPlayKey(deviceId, channelId, device.isSwitchPrimarySubStream(), isSubStream); |
| 567 | boolean exist = resultHolder.exist(key, null); | 602 | boolean exist = resultHolder.exist(key, null); |
| 568 | msg.setKey(key); | 603 | msg.setKey(key); |
| 569 | String uuid = UUID.randomUUID().toString(); | 604 | String uuid = UUID.randomUUID().toString(); |
| @@ -581,7 +616,7 @@ public class ZLMHttpHookListener { | @@ -581,7 +616,7 @@ public class ZLMHttpHookListener { | ||
| 581 | resultHolder.put(key, uuid, result); | 616 | resultHolder.put(key, uuid, result); |
| 582 | 617 | ||
| 583 | if (!exist) { | 618 | if (!exist) { |
| 584 | - playService.play(mediaInfo, deviceId, channelId, (code, message, data) -> { | 619 | + playService.play(mediaInfo, deviceId, channelId,isSubStream, (code, message, data) -> { |
| 585 | msg.setData(new HookResult(code, message)); | 620 | msg.setData(new HookResult(code, message)); |
| 586 | resultHolder.invokeResult(msg); | 621 | resultHolder.invokeResult(msg); |
| 587 | }); | 622 | }); |
src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java
| @@ -4,6 +4,8 @@ import com.genersoft.iot.vmp.common.InviteInfo; | @@ -4,6 +4,8 @@ import com.genersoft.iot.vmp.common.InviteInfo; | ||
| 4 | import com.genersoft.iot.vmp.common.InviteSessionType; | 4 | import com.genersoft.iot.vmp.common.InviteSessionType; |
| 5 | import com.genersoft.iot.vmp.service.bean.ErrorCallback; | 5 | import com.genersoft.iot.vmp.service.bean.ErrorCallback; |
| 6 | 6 | ||
| 7 | +import java.util.List; | ||
| 8 | + | ||
| 7 | /** | 9 | /** |
| 8 | * 记录国标点播的状态,包括实时预览,下载,录像回放 | 10 | * 记录国标点播的状态,包括实时预览,下载,录像回放 |
| 9 | */ | 11 | */ |
| @@ -70,4 +72,50 @@ public interface IInviteStreamService { | @@ -70,4 +72,50 @@ public interface IInviteStreamService { | ||
| 70 | * 统计同一个zlm下的国标收流个数 | 72 | * 统计同一个zlm下的国标收流个数 |
| 71 | */ | 73 | */ |
| 72 | int getStreamInfoCount(String mediaServerId); | 74 | int getStreamInfoCount(String mediaServerId); |
| 75 | + | ||
| 76 | + | ||
| 77 | + /*======================设备主子码流逻辑START=========================*/ | ||
| 78 | + /** | ||
| 79 | + * 获取点播的状态信息 | ||
| 80 | + */ | ||
| 81 | + InviteInfo getInviteInfoByDeviceAndChannel(InviteSessionType type, | ||
| 82 | + String deviceId, | ||
| 83 | + String channelId,boolean isSubStream); | ||
| 84 | + | ||
| 85 | + void removeInviteInfoByDeviceAndChannel(InviteSessionType inviteSessionType, String deviceId, String channelId,boolean isSubStream); | ||
| 86 | + | ||
| 87 | + InviteInfo getInviteInfo(InviteSessionType type, | ||
| 88 | + String deviceId, | ||
| 89 | + String channelId, | ||
| 90 | + boolean isSubStream, | ||
| 91 | + String stream); | ||
| 92 | + | ||
| 93 | + void removeInviteInfo(InviteSessionType type, | ||
| 94 | + String deviceId, | ||
| 95 | + String channelId, | ||
| 96 | + boolean isSubStream, | ||
| 97 | + String stream); | ||
| 98 | + | ||
| 99 | + void once(InviteSessionType type, String deviceId, String channelId,boolean isSubStream, String stream, ErrorCallback<Object> callback); | ||
| 100 | + | ||
| 101 | + void call(InviteSessionType type, String deviceId, String channelId,boolean isSubStream, String stream, int code, String msg, Object data); | ||
| 102 | + | ||
| 103 | + void updateInviteInfoSub(InviteInfo inviteInfo); | ||
| 104 | + | ||
| 105 | + /** | ||
| 106 | + * 获取点播的状态信息 | ||
| 107 | + */ | ||
| 108 | + InviteInfo getInviteInfoByStream(InviteSessionType type, String stream,boolean isSubStream); | ||
| 109 | + | ||
| 110 | + /** | ||
| 111 | + * 获取点播的状态信息 | ||
| 112 | + */ | ||
| 113 | + List<Object> getInviteInfos(InviteSessionType type, | ||
| 114 | + String deviceId, | ||
| 115 | + String channelId, | ||
| 116 | + String stream); | ||
| 117 | + /*======================设备主子码流逻辑END=========================*/ | ||
| 118 | + | ||
| 119 | + | ||
| 120 | + | ||
| 73 | } | 121 | } |
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
| @@ -16,9 +16,9 @@ import java.text.ParseException; | @@ -16,9 +16,9 @@ import java.text.ParseException; | ||
| 16 | */ | 16 | */ |
| 17 | public interface IPlayService { | 17 | public interface IPlayService { |
| 18 | 18 | ||
| 19 | - void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, | 19 | + void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,boolean isSubStream, |
| 20 | ErrorCallback<Object> callback); | 20 | ErrorCallback<Object> callback); |
| 21 | - SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, ErrorCallback<Object> callback); | 21 | + SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId,boolean isSubStream, ErrorCallback<Object> callback); |
| 22 | 22 | ||
| 23 | MediaServerItem getNewMediaServerItem(Device device); | 23 | MediaServerItem getNewMediaServerItem(Device device); |
| 24 | 24 | ||
| @@ -43,5 +43,5 @@ public interface IPlayService { | @@ -43,5 +43,5 @@ public interface IPlayService { | ||
| 43 | 43 | ||
| 44 | void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; | 44 | void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; |
| 45 | 45 | ||
| 46 | - void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback); | 46 | + void getSnap(String deviceId, String channelId, String fileName,boolean isSubStream, ErrorCallback errorCallback); |
| 47 | } | 47 | } |
src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java
| 1 | package com.genersoft.iot.vmp.service.impl; | 1 | package com.genersoft.iot.vmp.service.impl; |
| 2 | 2 | ||
| 3 | +import com.genersoft.iot.vmp.common.InviteSessionType; | ||
| 3 | import com.genersoft.iot.vmp.common.VideoManagerConstants; | 4 | import com.genersoft.iot.vmp.common.VideoManagerConstants; |
| 4 | import com.genersoft.iot.vmp.conf.DynamicTask; | 5 | import com.genersoft.iot.vmp.conf.DynamicTask; |
| 5 | import com.genersoft.iot.vmp.conf.UserSetting; | 6 | import com.genersoft.iot.vmp.conf.UserSetting; |
| 7 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | ||
| 6 | import com.genersoft.iot.vmp.gb28181.bean.*; | 8 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 7 | import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | 9 | import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; |
| 8 | import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; | 10 | import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; |
| 9 | import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; | 11 | import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; |
| 10 | import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; | 12 | import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; |
| 11 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; | 13 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; |
| 14 | +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; | ||
| 12 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; | 15 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; |
| 13 | import com.genersoft.iot.vmp.service.IDeviceChannelService; | 16 | import com.genersoft.iot.vmp.service.IDeviceChannelService; |
| 14 | import com.genersoft.iot.vmp.service.IDeviceService; | 17 | import com.genersoft.iot.vmp.service.IDeviceService; |
| @@ -48,6 +51,8 @@ public class DeviceServiceImpl implements IDeviceService { | @@ -48,6 +51,8 @@ public class DeviceServiceImpl implements IDeviceService { | ||
| 48 | private final static Logger logger = LoggerFactory.getLogger(DeviceServiceImpl.class); | 51 | private final static Logger logger = LoggerFactory.getLogger(DeviceServiceImpl.class); |
| 49 | 52 | ||
| 50 | @Autowired | 53 | @Autowired |
| 54 | + private SIPCommander cmder; | ||
| 55 | + @Autowired | ||
| 51 | private DynamicTask dynamicTask; | 56 | private DynamicTask dynamicTask; |
| 52 | 57 | ||
| 53 | @Autowired | 58 | @Autowired |
| @@ -131,6 +136,10 @@ public class DeviceServiceImpl implements IDeviceService { | @@ -131,6 +136,10 @@ public class DeviceServiceImpl implements IDeviceService { | ||
| 131 | } | 136 | } |
| 132 | sync(device); | 137 | sync(device); |
| 133 | }else { | 138 | }else { |
| 139 | + | ||
| 140 | + if (deviceInDb != null) { | ||
| 141 | + device.setSwitchPrimarySubStream(deviceInDb.isSwitchPrimarySubStream()); | ||
| 142 | + } | ||
| 134 | if(!device.isOnLine()){ | 143 | if(!device.isOnLine()){ |
| 135 | device.setOnLine(true); | 144 | device.setOnLine(true); |
| 136 | device.setCreateTime(now); | 145 | device.setCreateTime(now); |
| @@ -460,6 +469,22 @@ public class DeviceServiceImpl implements IDeviceService { | @@ -460,6 +469,22 @@ public class DeviceServiceImpl implements IDeviceService { | ||
| 460 | logger.warn("更新设备时未找到设备信息"); | 469 | logger.warn("更新设备时未找到设备信息"); |
| 461 | return; | 470 | return; |
| 462 | } | 471 | } |
| 472 | + if(deviceInStore.isSwitchPrimarySubStream() != device.isSwitchPrimarySubStream()){ | ||
| 473 | + //当修改设备的主子码流开关时,需要校验是否存在流,如果存在流则直接关闭 | ||
| 474 | + List<SsrcTransaction> ssrcTransactionForAll = streamSession.getSsrcTransactionForAll(device.getDeviceId(), null, null, null); | ||
| 475 | + if(ssrcTransactionForAll != null){ | ||
| 476 | + for (SsrcTransaction ssrcTransaction: ssrcTransactionForAll) { | ||
| 477 | + try { | ||
| 478 | + cmder.streamByeCmd(device, ssrcTransaction.getChannelId(), ssrcTransaction.getStream(), null, null); | ||
| 479 | + } catch (InvalidArgumentException | SsrcTransactionNotFoundException | ParseException | SipException e) { | ||
| 480 | + throw new RuntimeException(e); | ||
| 481 | + } | ||
| 482 | + } | ||
| 483 | + } | ||
| 484 | + deviceChannelMapper.clearPlay(device.getDeviceId()); | ||
| 485 | + inviteStreamService.clearInviteInfo(device.getDeviceId()); | ||
| 486 | + } | ||
| 487 | + | ||
| 463 | if (!ObjectUtils.isEmpty(device.getName())) { | 488 | if (!ObjectUtils.isEmpty(device.getName())) { |
| 464 | deviceInStore.setName(device.getName()); | 489 | deviceInStore.setName(device.getName()); |
| 465 | } | 490 | } |
src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java
| @@ -198,4 +198,164 @@ public class InviteStreamServiceImpl implements IInviteStreamService { | @@ -198,4 +198,164 @@ public class InviteStreamServiceImpl implements IInviteStreamService { | ||
| 198 | } | 198 | } |
| 199 | return count; | 199 | return count; |
| 200 | } | 200 | } |
| 201 | + | ||
| 202 | + /*======================设备主子码流逻辑START=========================*/ | ||
| 203 | + | ||
| 204 | + @Override | ||
| 205 | + public InviteInfo getInviteInfoByDeviceAndChannel(InviteSessionType type, String deviceId, String channelId, boolean isSubStream) { | ||
| 206 | + return getInviteInfo(type, deviceId, channelId,isSubStream, null); | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + @Override | ||
| 210 | + public void removeInviteInfoByDeviceAndChannel(InviteSessionType inviteSessionType, String deviceId, String channelId, boolean isSubStream) { | ||
| 211 | + removeInviteInfo(inviteSessionType, deviceId, channelId,isSubStream, null); | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + @Override | ||
| 215 | + public InviteInfo getInviteInfo(InviteSessionType type, String deviceId, String channelId,boolean isSubStream, String stream) { | ||
| 216 | + String key = VideoManagerConstants.INVITE_PREFIX + | ||
| 217 | + "_" + (type != null ? type : "*") + | ||
| 218 | + "_" + (isSubStream ? "sub" : "main") + | ||
| 219 | + "_" + (deviceId != null ? deviceId : "*") + | ||
| 220 | + "_" + (channelId != null ? channelId : "*") + | ||
| 221 | + "_" + (stream != null ? stream : "*"); | ||
| 222 | + List<Object> scanResult = RedisUtil.scan(redisTemplate, key); | ||
| 223 | + if (scanResult.size() != 1) { | ||
| 224 | + return null; | ||
| 225 | + } | ||
| 226 | + return (InviteInfo) redisTemplate.opsForValue().get(scanResult.get(0)); | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + @Override | ||
| 230 | + public void removeInviteInfo(InviteSessionType type, String deviceId, String channelId, boolean isSubStream, String stream) { | ||
| 231 | + String scanKey = VideoManagerConstants.INVITE_PREFIX + | ||
| 232 | + "_" + (type != null ? type : "*") + | ||
| 233 | + "_" + (isSubStream ? "sub" : "main") + | ||
| 234 | + "_" + (deviceId != null ? deviceId : "*") + | ||
| 235 | + "_" + (channelId != null ? channelId : "*") + | ||
| 236 | + "_" + (stream != null ? stream : "*"); | ||
| 237 | + List<Object> scanResult = RedisUtil.scan(redisTemplate, scanKey); | ||
| 238 | + if (scanResult.size() > 0) { | ||
| 239 | + for (Object keyObj : scanResult) { | ||
| 240 | + String key = (String) keyObj; | ||
| 241 | + InviteInfo inviteInfo = (InviteInfo) redisTemplate.opsForValue().get(key); | ||
| 242 | + if (inviteInfo == null) { | ||
| 243 | + continue; | ||
| 244 | + } | ||
| 245 | + redisTemplate.delete(key); | ||
| 246 | + inviteErrorCallbackMap.remove(buildKey(type, deviceId, channelId, inviteInfo.getStream())); | ||
| 247 | + } | ||
| 248 | + } | ||
| 249 | + } | ||
| 250 | + | ||
| 251 | + @Override | ||
| 252 | + public void once(InviteSessionType type, String deviceId, String channelId, boolean isSubStream, String stream, ErrorCallback<Object> callback) { | ||
| 253 | + String key = buildSubStreamKey(type, deviceId, channelId,isSubStream, stream); | ||
| 254 | + List<ErrorCallback<Object>> callbacks = inviteErrorCallbackMap.get(key); | ||
| 255 | + if (callbacks == null) { | ||
| 256 | + callbacks = new CopyOnWriteArrayList<>(); | ||
| 257 | + inviteErrorCallbackMap.put(key, callbacks); | ||
| 258 | + } | ||
| 259 | + callbacks.add(callback); | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + @Override | ||
| 263 | + public void call(InviteSessionType type, String deviceId, String channelId, boolean isSubStream, String stream, int code, String msg, Object data) { | ||
| 264 | + String key = buildSubStreamKey(type, deviceId, channelId,isSubStream, stream); | ||
| 265 | + List<ErrorCallback<Object>> callbacks = inviteErrorCallbackMap.get(key); | ||
| 266 | + if (callbacks == null) { | ||
| 267 | + return; | ||
| 268 | + } | ||
| 269 | + for (ErrorCallback<Object> callback : callbacks) { | ||
| 270 | + callback.run(code, msg, data); | ||
| 271 | + } | ||
| 272 | + inviteErrorCallbackMap.remove(key); | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + | ||
| 276 | + private String buildSubStreamKey(InviteSessionType type, String deviceId, String channelId, boolean isSubStream, String stream) { | ||
| 277 | + String key = type + "_" + (isSubStream ? "sub":"main") + "_" + deviceId + "_" + channelId; | ||
| 278 | + // 如果ssrc为null那么可以实现一个通道只能一次操作,ssrc不为null则可以支持一个通道多次invite | ||
| 279 | + if (stream != null) { | ||
| 280 | + key += ("_" + stream); | ||
| 281 | + } | ||
| 282 | + return key; | ||
| 283 | + } | ||
| 284 | + @Override | ||
| 285 | + public void updateInviteInfoSub(InviteInfo inviteInfo) { | ||
| 286 | + if (inviteInfo == null || (inviteInfo.getDeviceId() == null || inviteInfo.getChannelId() == null)) { | ||
| 287 | + logger.warn("[更新Invite信息],参数不全: {}", JSON.toJSON(inviteInfo)); | ||
| 288 | + return; | ||
| 289 | + } | ||
| 290 | + InviteInfo inviteInfoForUpdate = null; | ||
| 291 | + | ||
| 292 | + if (InviteSessionStatus.ready == inviteInfo.getStatus()) { | ||
| 293 | + if (inviteInfo.getDeviceId() == null | ||
| 294 | + || inviteInfo.getChannelId() == null | ||
| 295 | + || inviteInfo.getType() == null | ||
| 296 | + || inviteInfo.getStream() == null | ||
| 297 | + ) { | ||
| 298 | + return; | ||
| 299 | + } | ||
| 300 | + inviteInfoForUpdate = inviteInfo; | ||
| 301 | + } else { | ||
| 302 | + InviteInfo inviteInfoInRedis = getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), | ||
| 303 | + inviteInfo.getChannelId(),inviteInfo.isSubStream(), inviteInfo.getStream()); | ||
| 304 | + if (inviteInfoInRedis == null) { | ||
| 305 | + logger.warn("[更新Invite信息],未从缓存中读取到Invite信息: deviceId: {}, channel: {}, stream: {}", | ||
| 306 | + inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()); | ||
| 307 | + return; | ||
| 308 | + } | ||
| 309 | + if (inviteInfo.getStreamInfo() != null) { | ||
| 310 | + inviteInfoInRedis.setStreamInfo(inviteInfo.getStreamInfo()); | ||
| 311 | + } | ||
| 312 | + if (inviteInfo.getSsrcInfo() != null) { | ||
| 313 | + inviteInfoInRedis.setSsrcInfo(inviteInfo.getSsrcInfo()); | ||
| 314 | + } | ||
| 315 | + if (inviteInfo.getStreamMode() != null) { | ||
| 316 | + inviteInfoInRedis.setStreamMode(inviteInfo.getStreamMode()); | ||
| 317 | + } | ||
| 318 | + if (inviteInfo.getReceiveIp() != null) { | ||
| 319 | + inviteInfoInRedis.setReceiveIp(inviteInfo.getReceiveIp()); | ||
| 320 | + } | ||
| 321 | + if (inviteInfo.getReceivePort() != null) { | ||
| 322 | + inviteInfoInRedis.setReceivePort(inviteInfo.getReceivePort()); | ||
| 323 | + } | ||
| 324 | + if (inviteInfo.getStatus() != null) { | ||
| 325 | + inviteInfoInRedis.setStatus(inviteInfo.getStatus()); | ||
| 326 | + } | ||
| 327 | + | ||
| 328 | + inviteInfoForUpdate = inviteInfoInRedis; | ||
| 329 | + | ||
| 330 | + } | ||
| 331 | + String key = VideoManagerConstants.INVITE_PREFIX + | ||
| 332 | + "_" + inviteInfoForUpdate.getType() + | ||
| 333 | + "_" + (inviteInfoForUpdate.isSubStream() ? "sub":"main") + | ||
| 334 | + "_" + inviteInfoForUpdate.getDeviceId() + | ||
| 335 | + "_" + inviteInfoForUpdate.getChannelId() + | ||
| 336 | + "_" + inviteInfoForUpdate.getStream(); | ||
| 337 | + redisTemplate.opsForValue().set(key, inviteInfoForUpdate); | ||
| 338 | + } | ||
| 339 | + | ||
| 340 | + @Override | ||
| 341 | + public InviteInfo getInviteInfoByStream(InviteSessionType type, String stream, boolean isSubStream) { | ||
| 342 | + return getInviteInfo(type, null, null,isSubStream, stream); | ||
| 343 | + } | ||
| 344 | + | ||
| 345 | + @Override | ||
| 346 | + public List<Object> getInviteInfos(InviteSessionType type, String deviceId, String channelId, String stream) { | ||
| 347 | + String key = VideoManagerConstants.INVITE_PREFIX + | ||
| 348 | + "_" + (type != null ? type : "*") + | ||
| 349 | + "_" + (deviceId != null ? deviceId : "*") + | ||
| 350 | + "_" + (channelId != null ? channelId : "*") + | ||
| 351 | + "_" + (stream != null ? stream : "*"); | ||
| 352 | + List<Object> scanResult = RedisUtil.scan(redisTemplate, key); | ||
| 353 | + return scanResult; | ||
| 354 | + } | ||
| 355 | + | ||
| 356 | + /*======================设备主子码流逻辑END=========================*/ | ||
| 357 | + | ||
| 358 | + | ||
| 359 | + | ||
| 360 | + | ||
| 201 | } | 361 | } |
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
| @@ -18,7 +18,6 @@ import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | @@ -18,7 +18,6 @@ import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | ||
| 18 | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; | 18 | import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; |
| 19 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; | 19 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; |
| 20 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; | 20 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; |
| 21 | -import com.genersoft.iot.vmp.gb28181.utils.SipUtils; | ||
| 22 | import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; | 21 | import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; |
| 23 | import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; | 22 | import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; |
| 24 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; | 23 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; |
| @@ -116,28 +115,43 @@ public class PlayServiceImpl implements IPlayService { | @@ -116,28 +115,43 @@ public class PlayServiceImpl implements IPlayService { | ||
| 116 | 115 | ||
| 117 | 116 | ||
| 118 | @Override | 117 | @Override |
| 119 | - 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) { |
| 120 | if (mediaServerItem == null) { | 119 | if (mediaServerItem == null) { |
| 121 | throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm"); | 120 | throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm"); |
| 122 | } | 121 | } |
| 123 | 122 | ||
| 124 | Device device = redisCatchStorage.getDevice(deviceId); | 123 | Device device = redisCatchStorage.getDevice(deviceId); |
| 125 | - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); | ||
| 126 | - | 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 | + } | ||
| 127 | if (inviteInfo != null ) { | 130 | if (inviteInfo != null ) { |
| 128 | if (inviteInfo.getStreamInfo() == null) { | 131 | if (inviteInfo.getStreamInfo() == null) { |
| 129 | // 点播发起了但是尚未成功, 仅注册回调等待结果即可 | 132 | // 点播发起了但是尚未成功, 仅注册回调等待结果即可 |
| 130 | - 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 | + } | ||
| 131 | return inviteInfo.getSsrcInfo(); | 138 | return inviteInfo.getSsrcInfo(); |
| 132 | }else { | 139 | }else { |
| 133 | StreamInfo streamInfo = inviteInfo.getStreamInfo(); | 140 | StreamInfo streamInfo = inviteInfo.getStreamInfo(); |
| 134 | String streamId = streamInfo.getStream(); | 141 | String streamId = streamInfo.getStream(); |
| 135 | if (streamId == null) { | 142 | if (streamId == null) { |
| 136 | callback.run(InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null); | 143 | callback.run(InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null); |
| 137 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 138 | - InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), | ||
| 139 | - "点播失败, redis缓存streamId等于null", | ||
| 140 | - 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 | + } | ||
| 141 | return inviteInfo.getSsrcInfo(); | 155 | return inviteInfo.getSsrcInfo(); |
| 142 | } | 156 | } |
| 143 | String mediaServerId = streamInfo.getMediaServerId(); | 157 | String mediaServerId = streamInfo.getMediaServerId(); |
| @@ -146,41 +160,64 @@ public class PlayServiceImpl implements IPlayService { | @@ -146,41 +160,64 @@ public class PlayServiceImpl implements IPlayService { | ||
| 146 | Boolean ready = zlmrtpServerFactory.isStreamReady(mediaInfo, "rtp", streamId); | 160 | Boolean ready = zlmrtpServerFactory.isStreamReady(mediaInfo, "rtp", streamId); |
| 147 | if (ready != null && ready) { | 161 | if (ready != null && ready) { |
| 148 | callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); | 162 | callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); |
| 149 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 150 | - InviteErrorCode.SUCCESS.getCode(), | ||
| 151 | - InviteErrorCode.SUCCESS.getMsg(), | ||
| 152 | - 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 | + } | ||
| 153 | return inviteInfo.getSsrcInfo(); | 174 | return inviteInfo.getSsrcInfo(); |
| 154 | }else { | 175 | }else { |
| 155 | // 点播发起了但是尚未成功, 仅注册回调等待结果即可 | 176 | // 点播发起了但是尚未成功, 仅注册回调等待结果即可 |
| 156 | - inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback); | ||
| 157 | - storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); | ||
| 158 | - 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 | + } | ||
| 159 | } | 185 | } |
| 160 | } | 186 | } |
| 161 | } | 187 | } |
| 162 | 188 | ||
| 163 | String streamId = null; | 189 | String streamId = null; |
| 164 | if (mediaServerItem.isRtpEnable()) { | 190 | if (mediaServerItem.isRtpEnable()) { |
| 165 | - 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 | + } | ||
| 166 | } | 196 | } |
| 167 | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam()); | 197 | SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam()); |
| 168 | if (ssrcInfo == null) { | 198 | if (ssrcInfo == null) { |
| 169 | callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null); | 199 | callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null); |
| 170 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 171 | - InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), | ||
| 172 | - InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), | ||
| 173 | - 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 | + } | ||
| 174 | return null; | 211 | return null; |
| 175 | } | 212 | } |
| 176 | // TODO 记录点播的状态 | 213 | // TODO 记录点播的状态 |
| 177 | - play(mediaServerItem, ssrcInfo, device, channelId, callback); | 214 | + play(mediaServerItem, ssrcInfo, device, channelId,isSubStream, callback); |
| 178 | return ssrcInfo; | 215 | return ssrcInfo; |
| 179 | } | 216 | } |
| 180 | 217 | ||
| 181 | 218 | ||
| 182 | @Override | 219 | @Override |
| 183 | - 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, |
| 184 | ErrorCallback<Object> callback) { | 221 | ErrorCallback<Object> callback) { |
| 185 | 222 | ||
| 186 | if (mediaServerItem == null || ssrcInfo == null) { | 223 | if (mediaServerItem == null || ssrcInfo == null) { |
| @@ -189,21 +226,11 @@ public class PlayServiceImpl implements IPlayService { | @@ -189,21 +226,11 @@ public class PlayServiceImpl implements IPlayService { | ||
| 189 | null); | 226 | null); |
| 190 | return; | 227 | return; |
| 191 | } | 228 | } |
| 192 | - logger.info("\r\n" + | ||
| 193 | - " [点播开始] \r\n" + | ||
| 194 | - "deviceId : {}, \r\n" + | ||
| 195 | - "channelId : {},\r\n" + | ||
| 196 | - "收流端口 : {}, \r\n" + | ||
| 197 | - "收流模式 : {}, \r\n" + | ||
| 198 | - "SSRC : {}, \r\n" + | ||
| 199 | - "SSRC校验 :{}", | ||
| 200 | - device.getDeviceId(), | ||
| 201 | - channelId, | ||
| 202 | - ssrcInfo.getPort(), | ||
| 203 | - device.getStreamMode(), | ||
| 204 | - ssrcInfo.getSsrc(), | ||
| 205 | - device.isSsrcCheck()); | ||
| 206 | - | 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 | + } | ||
| 207 | //端口获取失败的ssrcInfo 没有必要发送点播指令 | 234 | //端口获取失败的ssrcInfo 没有必要发送点播指令 |
| 208 | if (ssrcInfo.getPort() <= 0) { | 235 | if (ssrcInfo.getPort() <= 0) { |
| 209 | logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo); | 236 | logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo); |
| @@ -212,23 +239,50 @@ public class PlayServiceImpl implements IPlayService { | @@ -212,23 +239,50 @@ public class PlayServiceImpl implements IPlayService { | ||
| 212 | streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); | 239 | streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); |
| 213 | 240 | ||
| 214 | callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null); | 241 | callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null); |
| 215 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 216 | - 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 | + } | ||
| 217 | return; | 249 | return; |
| 218 | } | 250 | } |
| 219 | 251 | ||
| 220 | // 初始化redis中的invite消息状态 | 252 | // 初始化redis中的invite消息状态 |
| 221 | - InviteInfo inviteInfo = InviteInfo.getinviteInfo(device.getDeviceId(), channelId, ssrcInfo.getStream(), ssrcInfo, | ||
| 222 | - mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAY, | ||
| 223 | - InviteSessionStatus.ready); | ||
| 224 | - 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 | + } | ||
| 225 | // 超时处理 | 268 | // 超时处理 |
| 226 | String timeOutTaskKey = UUID.randomUUID().toString(); | 269 | String timeOutTaskKey = UUID.randomUUID().toString(); |
| 227 | dynamicTask.startDelay(timeOutTaskKey, () -> { | 270 | dynamicTask.startDelay(timeOutTaskKey, () -> { |
| 228 | // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况 | 271 | // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况 |
| 229 | - 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 | + } | ||
| 230 | if (inviteInfoForTimeOut == null || inviteInfoForTimeOut.getStreamInfo() == null) { | 280 | if (inviteInfoForTimeOut == null || inviteInfoForTimeOut.getStreamInfo() == null) { |
| 231 | - 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 | + } | ||
| 232 | // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 | 286 | // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 |
| 233 | // InviteInfo inviteInfoForTimeout = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.play, device.getDeviceId(), channelId); | 287 | // InviteInfo inviteInfoForTimeout = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.play, device.getDeviceId(), channelId); |
| 234 | // if (inviteInfoForTimeout == null) { | 288 | // if (inviteInfoForTimeout == null) { |
| @@ -240,10 +294,16 @@ public class PlayServiceImpl implements IPlayService { | @@ -240,10 +294,16 @@ public class PlayServiceImpl implements IPlayService { | ||
| 240 | // // TODO 发送cancel | 294 | // // TODO 发送cancel |
| 241 | // } | 295 | // } |
| 242 | callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null); | 296 | callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null); |
| 243 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 244 | - 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); | ||
| 245 | 301 | ||
| 246 | - 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 | + } | ||
| 247 | try { | 307 | try { |
| 248 | cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null); | 308 | cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null); |
| 249 | } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { | 309 | } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { |
| @@ -261,25 +321,42 @@ public class PlayServiceImpl implements IPlayService { | @@ -261,25 +321,42 @@ public class PlayServiceImpl implements IPlayService { | ||
| 261 | }, userSetting.getPlayTimeout()); | 321 | }, userSetting.getPlayTimeout()); |
| 262 | 322 | ||
| 263 | try { | 323 | try { |
| 264 | - cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> { | 324 | + cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId,isSubStream, (MediaServerItem mediaServerItemInuse, JSONObject response) -> { |
| 265 | logger.info("收到订阅消息: " + response.toJSONString()); | 325 | logger.info("收到订阅消息: " + response.toJSONString()); |
| 266 | dynamicTask.stop(timeOutTaskKey); | 326 | dynamicTask.stop(timeOutTaskKey); |
| 267 | // hook响应 | 327 | // hook响应 |
| 268 | - StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId); | 328 | + StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId,isSubStream); |
| 269 | if (streamInfo == null){ | 329 | if (streamInfo == null){ |
| 270 | callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), | 330 | callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), |
| 271 | InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); | 331 | InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); |
| 272 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 273 | - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), | ||
| 274 | - 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 | + } | ||
| 275 | return; | 341 | return; |
| 276 | } | 342 | } |
| 277 | callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); | 343 | callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); |
| 278 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 279 | - InviteErrorCode.SUCCESS.getCode(), | ||
| 280 | - InviteErrorCode.SUCCESS.getMsg(), | ||
| 281 | - streamInfo); | ||
| 282 | - 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 | + } | ||
| 283 | String streamUrl; | 360 | String streamUrl; |
| 284 | if (mediaServerItemInuse.getRtspPort() != 0) { | 361 | if (mediaServerItemInuse.getRtspPort() != 0) { |
| 285 | streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInuse.getRtspPort(), "rtp", ssrcInfo.getStream()); | 362 | streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInuse.getRtspPort(), "rtp", ssrcInfo.getStream()); |
| @@ -298,16 +375,17 @@ public class PlayServiceImpl implements IPlayService { | @@ -298,16 +375,17 @@ public class PlayServiceImpl implements IPlayService { | ||
| 298 | ResponseEvent responseEvent = (ResponseEvent) event.event; | 375 | ResponseEvent responseEvent = (ResponseEvent) event.event; |
| 299 | String contentString = new String(responseEvent.getResponse().getRawContent()); | 376 | String contentString = new String(responseEvent.getResponse().getRawContent()); |
| 300 | // 获取ssrc | 377 | // 获取ssrc |
| 301 | - String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString); | ||
| 302 | - | 378 | + int ssrcIndex = contentString.indexOf("y="); |
| 303 | // 检查是否有y字段 | 379 | // 检查是否有y字段 |
| 304 | - if (ssrcInResponse != null) { | 380 | + if (ssrcIndex >= 0) { |
| 381 | + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容 | ||
| 382 | + String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12).trim(); | ||
| 305 | // 查询到ssrc不一致且开启了ssrc校验则需要针对处理 | 383 | // 查询到ssrc不一致且开启了ssrc校验则需要针对处理 |
| 306 | if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { | 384 | if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { |
| 307 | if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { | 385 | if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { |
| 386 | + String substring = contentString.substring(0, contentString.indexOf("y=")); | ||
| 308 | try { | 387 | try { |
| 309 | - Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString); | ||
| 310 | - SessionDescription sdp = gb28181Sdp.getBaseSdb(); | 388 | + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); |
| 311 | int port = -1; | 389 | int port = -1; |
| 312 | Vector mediaDescriptions = sdp.getMediaDescriptions(true); | 390 | Vector mediaDescriptions = sdp.getMediaDescriptions(true); |
| 313 | for (Object description : mediaDescriptions) { | 391 | for (Object description : mediaDescriptions) { |
| @@ -334,21 +412,24 @@ public class PlayServiceImpl implements IPlayService { | @@ -334,21 +412,24 @@ public class PlayServiceImpl implements IPlayService { | ||
| 334 | 412 | ||
| 335 | callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), | 413 | callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), |
| 336 | InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); | 414 | InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); |
| 337 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 338 | - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), | ||
| 339 | - 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 | + } | ||
| 340 | } | 424 | } |
| 341 | } | 425 | } |
| 342 | return; | 426 | return; |
| 343 | } | 427 | } |
| 344 | logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); | 428 | logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); |
| 345 | - | ||
| 346 | if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { | 429 | if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { |
| 347 | logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); | 430 | logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); |
| 348 | - | ||
| 349 | // 释放ssrc | 431 | // 释放ssrc |
| 350 | mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); | 432 | mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); |
| 351 | - | ||
| 352 | // 单端口模式streamId也有变化,重新设置监听即可 | 433 | // 单端口模式streamId也有变化,重新设置监听即可 |
| 353 | if (!mediaServerItem.isRtpEnable()) { | 434 | if (!mediaServerItem.isRtpEnable()) { |
| 354 | // 添加订阅 | 435 | // 添加订阅 |
| @@ -361,21 +442,34 @@ public class PlayServiceImpl implements IPlayService { | @@ -361,21 +442,34 @@ public class PlayServiceImpl implements IPlayService { | ||
| 361 | logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString()); | 442 | logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString()); |
| 362 | dynamicTask.stop(timeOutTaskKey); | 443 | dynamicTask.stop(timeOutTaskKey); |
| 363 | // hook响应 | 444 | // hook响应 |
| 364 | - StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId); | 445 | + StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId,isSubStream); |
| 365 | if (streamInfo == null){ | 446 | if (streamInfo == null){ |
| 366 | callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), | 447 | callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), |
| 367 | InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); | 448 | InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); |
| 368 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 369 | - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), | ||
| 370 | - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); | 449 | + if( device.isSwitchPrimarySubStream()){ |
| 450 | + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null, | ||
| 451 | + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), | ||
| 452 | + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); | ||
| 453 | + }else { | ||
| 454 | + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 455 | + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), | ||
| 456 | + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); | ||
| 457 | + } | ||
| 371 | return; | 458 | return; |
| 372 | } | 459 | } |
| 373 | callback.run(InviteErrorCode.SUCCESS.getCode(), | 460 | callback.run(InviteErrorCode.SUCCESS.getCode(), |
| 374 | InviteErrorCode.SUCCESS.getMsg(), streamInfo); | 461 | InviteErrorCode.SUCCESS.getMsg(), streamInfo); |
| 375 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 376 | - InviteErrorCode.SUCCESS.getCode(), | ||
| 377 | - InviteErrorCode.SUCCESS.getMsg(), | ||
| 378 | - streamInfo); | 462 | + if( device.isSwitchPrimarySubStream()){ |
| 463 | + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null, | ||
| 464 | + InviteErrorCode.SUCCESS.getCode(), | ||
| 465 | + InviteErrorCode.SUCCESS.getMsg(), | ||
| 466 | + streamInfo); | ||
| 467 | + }else { | ||
| 468 | + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 469 | + InviteErrorCode.SUCCESS.getCode(), | ||
| 470 | + InviteErrorCode.SUCCESS.getMsg(), | ||
| 471 | + streamInfo); | ||
| 472 | + } | ||
| 379 | }); | 473 | }); |
| 380 | return; | 474 | return; |
| 381 | } | 475 | } |
| @@ -391,14 +485,22 @@ public class PlayServiceImpl implements IPlayService { | @@ -391,14 +485,22 @@ public class PlayServiceImpl implements IPlayService { | ||
| 391 | } | 485 | } |
| 392 | 486 | ||
| 393 | dynamicTask.stop(timeOutTaskKey); | 487 | dynamicTask.stop(timeOutTaskKey); |
| 488 | + // 释放ssrc | ||
| 489 | + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); | ||
| 394 | 490 | ||
| 395 | streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); | 491 | streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); |
| 396 | 492 | ||
| 397 | callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), | 493 | callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), |
| 398 | "下级自定义了ssrc,重新设置收流信息失败", null); | 494 | "下级自定义了ssrc,重新设置收流信息失败", null); |
| 399 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 400 | - InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), | ||
| 401 | - "下级自定义了ssrc,重新设置收流信息失败", null); | 495 | + if( device.isSwitchPrimarySubStream()){ |
| 496 | + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null, | ||
| 497 | + InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), | ||
| 498 | + "下级自定义了ssrc,重新设置收流信息失败", null); | ||
| 499 | + }else { | ||
| 500 | + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 501 | + InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), | ||
| 502 | + "下级自定义了ssrc,重新设置收流信息失败", null); | ||
| 503 | + } | ||
| 402 | 504 | ||
| 403 | }else { | 505 | }else { |
| 404 | ssrcInfo.setSsrc(ssrcInResponse); | 506 | ssrcInfo.setSsrc(ssrcInResponse); |
| @@ -409,7 +511,11 @@ public class PlayServiceImpl implements IPlayService { | @@ -409,7 +511,11 @@ public class PlayServiceImpl implements IPlayService { | ||
| 409 | logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正"); | 511 | logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正"); |
| 410 | } | 512 | } |
| 411 | } | 513 | } |
| 412 | - inviteStreamService.updateInviteInfo(inviteInfo); | 514 | + if(device.isSwitchPrimarySubStream()){ |
| 515 | + inviteStreamService.updateInviteInfoSub(inviteInfo); | ||
| 516 | + }else { | ||
| 517 | + inviteStreamService.updateInviteInfo(inviteInfo); | ||
| 518 | + } | ||
| 413 | }, (event) -> { | 519 | }, (event) -> { |
| 414 | dynamicTask.stop(timeOutTaskKey); | 520 | dynamicTask.stop(timeOutTaskKey); |
| 415 | mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); | 521 | mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); |
| @@ -420,11 +526,19 @@ public class PlayServiceImpl implements IPlayService { | @@ -420,11 +526,19 @@ public class PlayServiceImpl implements IPlayService { | ||
| 420 | 526 | ||
| 421 | callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_ERROR.getCode(), | 527 | callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_ERROR.getCode(), |
| 422 | String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null); | 528 | String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null); |
| 423 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 424 | - InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), | ||
| 425 | - String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null); | 529 | + if( device.isSwitchPrimarySubStream()){ |
| 530 | + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null, | ||
| 531 | + InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), | ||
| 532 | + String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null); | ||
| 426 | 533 | ||
| 427 | - inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); | 534 | + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream); |
| 535 | + }else { | ||
| 536 | + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 537 | + InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), | ||
| 538 | + String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null); | ||
| 539 | + | ||
| 540 | + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); | ||
| 541 | + } | ||
| 428 | }); | 542 | }); |
| 429 | } catch (InvalidArgumentException | SipException | ParseException e) { | 543 | } catch (InvalidArgumentException | SipException | ParseException e) { |
| 430 | 544 | ||
| @@ -438,27 +552,51 @@ public class PlayServiceImpl implements IPlayService { | @@ -438,27 +552,51 @@ public class PlayServiceImpl implements IPlayService { | ||
| 438 | 552 | ||
| 439 | callback.run(InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(), | 553 | callback.run(InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(), |
| 440 | InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); | 554 | InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); |
| 441 | - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 442 | - InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(), | ||
| 443 | - InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); | 555 | + if( device.isSwitchPrimarySubStream()){ |
| 556 | + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null, | ||
| 557 | + InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(), | ||
| 558 | + InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); | ||
| 444 | 559 | ||
| 445 | - inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); | 560 | + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream); |
| 561 | + }else { | ||
| 562 | + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, | ||
| 563 | + InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(), | ||
| 564 | + InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); | ||
| 565 | + | ||
| 566 | + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); | ||
| 567 | + } | ||
| 446 | } | 568 | } |
| 447 | } | 569 | } |
| 448 | 570 | ||
| 449 | - private StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId) { | ||
| 450 | - StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId); | 571 | + private StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId,boolean isSubStream) { |
| 572 | + StreamInfo streamInfo = null; | ||
| 573 | + Device device = redisCatchStorage.getDevice(deviceId); | ||
| 574 | + if( device.isSwitchPrimarySubStream() ){ | ||
| 575 | + streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId,isSubStream); | ||
| 576 | + }else { | ||
| 577 | + streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId); | ||
| 578 | + } | ||
| 451 | if (streamInfo != null) { | 579 | if (streamInfo != null) { |
| 452 | - DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); | ||
| 453 | - if (deviceChannel != null) { | ||
| 454 | - deviceChannel.setStreamId(streamInfo.getStream()); | ||
| 455 | - storager.startPlay(deviceId, channelId, streamInfo.getStream()); | 580 | + InviteInfo inviteInfo; |
| 581 | + if(device.isSwitchPrimarySubStream()){ | ||
| 582 | + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream); | ||
| 583 | + }else { | ||
| 584 | + DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); | ||
| 585 | + if (deviceChannel != null) { | ||
| 586 | + deviceChannel.setStreamId(streamInfo.getStream()); | ||
| 587 | + storager.startPlay(deviceId, channelId, streamInfo.getStream()); | ||
| 588 | + } | ||
| 589 | + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); | ||
| 456 | } | 590 | } |
| 457 | - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); | ||
| 458 | if (inviteInfo != null) { | 591 | if (inviteInfo != null) { |
| 459 | inviteInfo.setStatus(InviteSessionStatus.ok); | 592 | inviteInfo.setStatus(InviteSessionStatus.ok); |
| 460 | inviteInfo.setStreamInfo(streamInfo); | 593 | inviteInfo.setStreamInfo(streamInfo); |
| 461 | - inviteStreamService.updateInviteInfo(inviteInfo); | 594 | + if(device.isSwitchPrimarySubStream()){ |
| 595 | + inviteStreamService.updateInviteInfoSub(inviteInfo); | ||
| 596 | + }else { | ||
| 597 | + inviteStreamService.updateInviteInfo(inviteInfo); | ||
| 598 | + } | ||
| 599 | + | ||
| 462 | } | 600 | } |
| 463 | } | 601 | } |
| 464 | return streamInfo; | 602 | return streamInfo; |
| @@ -607,16 +745,17 @@ public class PlayServiceImpl implements IPlayService { | @@ -607,16 +745,17 @@ public class PlayServiceImpl implements IPlayService { | ||
| 607 | ResponseEvent responseEvent = (ResponseEvent) eventResult.event; | 745 | ResponseEvent responseEvent = (ResponseEvent) eventResult.event; |
| 608 | String contentString = new String(responseEvent.getResponse().getRawContent()); | 746 | String contentString = new String(responseEvent.getResponse().getRawContent()); |
| 609 | // 获取ssrc | 747 | // 获取ssrc |
| 610 | - String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString); | ||
| 611 | - | 748 | + int ssrcIndex = contentString.indexOf("y="); |
| 612 | // 检查是否有y字段 | 749 | // 检查是否有y字段 |
| 613 | - if (ssrcInResponse != null) { | 750 | + if (ssrcIndex >= 0) { |
| 751 | + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容 | ||
| 752 | + String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); | ||
| 614 | // 查询到ssrc不一致且开启了ssrc校验则需要针对处理 | 753 | // 查询到ssrc不一致且开启了ssrc校验则需要针对处理 |
| 615 | if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { | 754 | if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { |
| 616 | if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { | 755 | if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { |
| 756 | + String substring = contentString.substring(0, contentString.indexOf("y=")); | ||
| 617 | try { | 757 | try { |
| 618 | - Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString); | ||
| 619 | - SessionDescription sdp = gb28181Sdp.getBaseSdb(); | 758 | + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); |
| 620 | int port = -1; | 759 | int port = -1; |
| 621 | Vector mediaDescriptions = sdp.getMediaDescriptions(true); | 760 | Vector mediaDescriptions = sdp.getMediaDescriptions(true); |
| 622 | for (Object description : mediaDescriptions) { | 761 | for (Object description : mediaDescriptions) { |
| @@ -684,6 +823,8 @@ public class PlayServiceImpl implements IPlayService { | @@ -684,6 +823,8 @@ public class PlayServiceImpl implements IPlayService { | ||
| 684 | } | 823 | } |
| 685 | 824 | ||
| 686 | dynamicTask.stop(playBackTimeOutTaskKey); | 825 | dynamicTask.stop(playBackTimeOutTaskKey); |
| 826 | + // 释放ssrc | ||
| 827 | + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); | ||
| 687 | 828 | ||
| 688 | streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); | 829 | streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); |
| 689 | 830 | ||
| @@ -799,15 +940,17 @@ public class PlayServiceImpl implements IPlayService { | @@ -799,15 +940,17 @@ public class PlayServiceImpl implements IPlayService { | ||
| 799 | ResponseEvent responseEvent = (ResponseEvent) eventResult.event; | 940 | ResponseEvent responseEvent = (ResponseEvent) eventResult.event; |
| 800 | String contentString = new String(responseEvent.getResponse().getRawContent()); | 941 | String contentString = new String(responseEvent.getResponse().getRawContent()); |
| 801 | // 获取ssrc | 942 | // 获取ssrc |
| 802 | - String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString); | 943 | + int ssrcIndex = contentString.indexOf("y="); |
| 803 | // 检查是否有y字段 | 944 | // 检查是否有y字段 |
| 804 | - if (ssrcInResponse != null) { | 945 | + if (ssrcIndex >= 0) { |
| 946 | + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容 | ||
| 947 | + String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); | ||
| 805 | // 查询到ssrc不一致且开启了ssrc校验则需要针对处理 | 948 | // 查询到ssrc不一致且开启了ssrc校验则需要针对处理 |
| 806 | if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { | 949 | if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { |
| 807 | if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { | 950 | if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { |
| 951 | + String substring = contentString.substring(0, contentString.indexOf("y=")); | ||
| 808 | try { | 952 | try { |
| 809 | - Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString); | ||
| 810 | - SessionDescription sdp = gb28181Sdp.getBaseSdb(); | 953 | + SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); |
| 811 | int port = -1; | 954 | int port = -1; |
| 812 | Vector mediaDescriptions = sdp.getMediaDescriptions(true); | 955 | Vector mediaDescriptions = sdp.getMediaDescriptions(true); |
| 813 | for (Object description : mediaDescriptions) { | 956 | for (Object description : mediaDescriptions) { |
| @@ -872,6 +1015,8 @@ public class PlayServiceImpl implements IPlayService { | @@ -872,6 +1015,8 @@ public class PlayServiceImpl implements IPlayService { | ||
| 872 | } | 1015 | } |
| 873 | 1016 | ||
| 874 | dynamicTask.stop(downLoadTimeOutTaskKey); | 1017 | dynamicTask.stop(downLoadTimeOutTaskKey); |
| 1018 | + // 释放ssrc | ||
| 1019 | + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); | ||
| 875 | 1020 | ||
| 876 | streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); | 1021 | streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); |
| 877 | 1022 | ||
| @@ -972,6 +1117,7 @@ public class PlayServiceImpl implements IPlayService { | @@ -972,6 +1117,7 @@ public class PlayServiceImpl implements IPlayService { | ||
| 972 | return streamInfo; | 1117 | return streamInfo; |
| 973 | } | 1118 | } |
| 974 | 1119 | ||
| 1120 | + | ||
| 975 | @Override | 1121 | @Override |
| 976 | public void zlmServerOffline(String mediaServerId) { | 1122 | public void zlmServerOffline(String mediaServerId) { |
| 977 | // 处理正在向上推流的上级平台 | 1123 | // 处理正在向上推流的上级平台 |
| @@ -1108,14 +1254,18 @@ public class PlayServiceImpl implements IPlayService { | @@ -1108,14 +1254,18 @@ public class PlayServiceImpl implements IPlayService { | ||
| 1108 | } | 1254 | } |
| 1109 | 1255 | ||
| 1110 | @Override | 1256 | @Override |
| 1111 | - public void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback) { | 1257 | + public void getSnap(String deviceId, String channelId, String fileName,boolean isSubStream, ErrorCallback errorCallback) { |
| 1112 | Device device = deviceService.getDevice(deviceId); | 1258 | Device device = deviceService.getDevice(deviceId); |
| 1113 | if (device == null) { | 1259 | if (device == null) { |
| 1114 | errorCallback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), null); | 1260 | errorCallback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), null); |
| 1115 | return; | 1261 | return; |
| 1116 | } | 1262 | } |
| 1117 | - | ||
| 1118 | - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); | 1263 | + InviteInfo inviteInfo; |
| 1264 | + if(device.isSwitchPrimarySubStream()){ | ||
| 1265 | + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream); | ||
| 1266 | + }else { | ||
| 1267 | + inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); | ||
| 1268 | + } | ||
| 1119 | if (inviteInfo != null) { | 1269 | if (inviteInfo != null) { |
| 1120 | if (inviteInfo.getStreamInfo() != null) { | 1270 | if (inviteInfo.getStreamInfo() != null) { |
| 1121 | // 已存在线直接截图 | 1271 | // 已存在线直接截图 |
| @@ -1130,10 +1280,9 @@ public class PlayServiceImpl implements IPlayService { | @@ -1130,10 +1280,9 @@ public class PlayServiceImpl implements IPlayService { | ||
| 1130 | // 请求截图 | 1280 | // 请求截图 |
| 1131 | logger.info("[请求截图]: " + fileName); | 1281 | logger.info("[请求截图]: " + fileName); |
| 1132 | zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName); | 1282 | zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName); |
| 1133 | - String filePath = path + File.separator + fileName; | ||
| 1134 | File snapFile = new File(path + File.separator + fileName); | 1283 | File snapFile = new File(path + File.separator + fileName); |
| 1135 | if (snapFile.exists()) { | 1284 | if (snapFile.exists()) { |
| 1136 | - errorCallback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), filePath); | 1285 | + errorCallback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), snapFile.getAbsoluteFile()); |
| 1137 | }else { | 1286 | }else { |
| 1138 | errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); | 1287 | errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); |
| 1139 | } | 1288 | } |
| @@ -1142,11 +1291,11 @@ public class PlayServiceImpl implements IPlayService { | @@ -1142,11 +1291,11 @@ public class PlayServiceImpl implements IPlayService { | ||
| 1142 | } | 1291 | } |
| 1143 | 1292 | ||
| 1144 | MediaServerItem newMediaServerItem = getNewMediaServerItem(device); | 1293 | MediaServerItem newMediaServerItem = getNewMediaServerItem(device); |
| 1145 | - play(newMediaServerItem, deviceId, channelId, (code, msg, data)->{ | 1294 | + play(newMediaServerItem, deviceId, channelId,isSubStream, (code, msg, data)->{ |
| 1146 | if (code == InviteErrorCode.SUCCESS.getCode()) { | 1295 | if (code == InviteErrorCode.SUCCESS.getCode()) { |
| 1147 | InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); | 1296 | InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); |
| 1148 | if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) { | 1297 | if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) { |
| 1149 | - getSnap(deviceId, channelId, fileName, errorCallback); | 1298 | + getSnap(deviceId, channelId, fileName,isSubStream, errorCallback); |
| 1150 | }else { | 1299 | }else { |
| 1151 | errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); | 1300 | errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); |
| 1152 | } | 1301 | } |
| @@ -1156,4 +1305,17 @@ public class PlayServiceImpl implements IPlayService { | @@ -1156,4 +1305,17 @@ public class PlayServiceImpl implements IPlayService { | ||
| 1156 | }); | 1305 | }); |
| 1157 | } | 1306 | } |
| 1158 | 1307 | ||
| 1308 | + | ||
| 1309 | + /*======================设备主子码流逻辑START=========================*/ | ||
| 1310 | + public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId,boolean isSubStream) { | ||
| 1311 | + String streamId = resonse.getString("stream"); | ||
| 1312 | + JSONArray tracks = resonse.getJSONArray("tracks"); | ||
| 1313 | + StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem, "rtp", streamId, tracks, null); | ||
| 1314 | + streamInfo.setDeviceID(deviceId); | ||
| 1315 | + streamInfo.setChannelId(channelId); | ||
| 1316 | + streamInfo.setSubStream(isSubStream); | ||
| 1317 | + return streamInfo; | ||
| 1318 | + } | ||
| 1319 | + /*======================设备主子码流逻辑END=========================*/ | ||
| 1320 | + | ||
| 1159 | } | 1321 | } |
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java
| @@ -451,6 +451,10 @@ public interface DeviceChannelMapper { | @@ -451,6 +451,10 @@ public interface DeviceChannelMapper { | ||
| 451 | @Select("select count(1) from wvp_device_channel") | 451 | @Select("select count(1) from wvp_device_channel") |
| 452 | int getAllChannelCount(); | 452 | int getAllChannelCount(); |
| 453 | 453 | ||
| 454 | + // 设备主子码流逻辑START | ||
| 455 | + @Update(value = {"UPDATE wvp_device_channel SET stream_id=null WHERE device_id=#{deviceId}"}) | ||
| 456 | + void clearPlay(String deviceId); | ||
| 457 | + // 设备主子码流逻辑END | ||
| 454 | @Select(value = {" <script>" + | 458 | @Select(value = {" <script>" + |
| 455 | "select * " + | 459 | "select * " + |
| 456 | "from device_channel " + | 460 | "from device_channel " + |
| @@ -460,4 +464,5 @@ public interface DeviceChannelMapper { | @@ -460,4 +464,5 @@ public interface DeviceChannelMapper { | ||
| 460 | " <if test='onlyCatalog == true '> and parental = 1 </if>" + | 464 | " <if test='onlyCatalog == true '> and parental = 1 </if>" + |
| 461 | " </script>"}) | 465 | " </script>"}) |
| 462 | List<DeviceChannel> getSubChannelsByDeviceId(String deviceId, String parentId, boolean onlyCatalog); | 466 | List<DeviceChannel> getSubChannelsByDeviceId(String deviceId, String parentId, boolean onlyCatalog); |
| 467 | + | ||
| 463 | } | 468 | } |
src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java
| @@ -42,6 +42,7 @@ public interface DeviceMapper { | @@ -42,6 +42,7 @@ public interface DeviceMapper { | ||
| 42 | "geo_coord_sys," + | 42 | "geo_coord_sys," + |
| 43 | "on_line," + | 43 | "on_line," + |
| 44 | "media_server_id," + | 44 | "media_server_id," + |
| 45 | + "switch_primary_sub_stream," + | ||
| 45 | "(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+ | 46 | "(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+ |
| 46 | " FROM wvp_device WHERE device_id = #{deviceId}") | 47 | " FROM wvp_device WHERE device_id = #{deviceId}") |
| 47 | Device getDeviceByDeviceId(String deviceId); | 48 | Device getDeviceByDeviceId(String deviceId); |
| @@ -157,6 +158,7 @@ public interface DeviceMapper { | @@ -157,6 +158,7 @@ public interface DeviceMapper { | ||
| 157 | "geo_coord_sys,"+ | 158 | "geo_coord_sys,"+ |
| 158 | "on_line,"+ | 159 | "on_line,"+ |
| 159 | "media_server_id,"+ | 160 | "media_server_id,"+ |
| 161 | + "switch_primary_sub_stream switchPrimarySubStream,"+ | ||
| 160 | "(SELECT count(0) FROM wvp_device_channel WHERE device_id=de.device_id) as channel_count " + | 162 | "(SELECT count(0) FROM wvp_device_channel WHERE device_id=de.device_id) as channel_count " + |
| 161 | "FROM wvp_device de" + | 163 | "FROM wvp_device de" + |
| 162 | "<if test=\"onLine != null\"> where on_line=${onLine}</if>"+ | 164 | "<if test=\"onLine != null\"> where on_line=${onLine}</if>"+ |
| @@ -246,6 +248,7 @@ public interface DeviceMapper { | @@ -246,6 +248,7 @@ public interface DeviceMapper { | ||
| 246 | "<if test=\"ssrcCheck != null\">, ssrc_check=#{ssrcCheck}</if>" + | 248 | "<if test=\"ssrcCheck != null\">, ssrc_check=#{ssrcCheck}</if>" + |
| 247 | "<if test=\"asMessageChannel != null\">, as_message_channel=#{asMessageChannel}</if>" + | 249 | "<if test=\"asMessageChannel != null\">, as_message_channel=#{asMessageChannel}</if>" + |
| 248 | "<if test=\"geoCoordSys != null\">, geo_coord_sys=#{geoCoordSys}</if>" + | 250 | "<if test=\"geoCoordSys != null\">, geo_coord_sys=#{geoCoordSys}</if>" + |
| 251 | + "<if test=\"switchPrimarySubStream != null\">, switch_primary_sub_stream=#{switchPrimarySubStream}</if>" + | ||
| 249 | "<if test=\"mediaServerId != null\">, media_server_id=#{mediaServerId}</if>" + | 252 | "<if test=\"mediaServerId != null\">, media_server_id=#{mediaServerId}</if>" + |
| 250 | "WHERE device_id=#{deviceId}"+ | 253 | "WHERE device_id=#{deviceId}"+ |
| 251 | " </script>"}) | 254 | " </script>"}) |
| @@ -263,7 +266,8 @@ public interface DeviceMapper { | @@ -263,7 +266,8 @@ public interface DeviceMapper { | ||
| 263 | "as_message_channel,"+ | 266 | "as_message_channel,"+ |
| 264 | "geo_coord_sys,"+ | 267 | "geo_coord_sys,"+ |
| 265 | "on_line,"+ | 268 | "on_line,"+ |
| 266 | - "media_server_id"+ | 269 | + "media_server_id,"+ |
| 270 | + "switch_primary_sub_stream"+ | ||
| 267 | ") VALUES (" + | 271 | ") VALUES (" + |
| 268 | "#{deviceId}," + | 272 | "#{deviceId}," + |
| 269 | "#{name}," + | 273 | "#{name}," + |
| @@ -276,7 +280,8 @@ public interface DeviceMapper { | @@ -276,7 +280,8 @@ public interface DeviceMapper { | ||
| 276 | "#{asMessageChannel}," + | 280 | "#{asMessageChannel}," + |
| 277 | "#{geoCoordSys}," + | 281 | "#{geoCoordSys}," + |
| 278 | "#{onLine}," + | 282 | "#{onLine}," + |
| 279 | - "#{mediaServerId}" + | 283 | + "#{mediaServerId}," + |
| 284 | + "#{switchPrimarySubStream}" + | ||
| 280 | ")") | 285 | ")") |
| 281 | void addCustomDevice(Device device); | 286 | void addCustomDevice(Device device); |
| 282 | 287 |
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
| @@ -26,7 +26,6 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | @@ -26,7 +26,6 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | ||
| 26 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; | 26 | import com.genersoft.iot.vmp.storager.IVideoManagerStorage; |
| 27 | import com.genersoft.iot.vmp.utils.DateUtil; | 27 | import com.genersoft.iot.vmp.utils.DateUtil; |
| 28 | import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; | 28 | import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; |
| 29 | -import com.genersoft.iot.vmp.vmanager.bean.SnapPath; | ||
| 30 | import com.genersoft.iot.vmp.vmanager.bean.StreamContent; | 29 | import com.genersoft.iot.vmp.vmanager.bean.StreamContent; |
| 31 | import com.genersoft.iot.vmp.vmanager.bean.WVPResult; | 30 | import com.genersoft.iot.vmp.vmanager.bean.WVPResult; |
| 32 | import io.swagger.v3.oas.annotations.Operation; | 31 | import io.swagger.v3.oas.annotations.Operation; |
| @@ -41,7 +40,6 @@ import org.springframework.web.context.request.async.DeferredResult; | @@ -41,7 +40,6 @@ import org.springframework.web.context.request.async.DeferredResult; | ||
| 41 | import javax.servlet.http.HttpServletRequest; | 40 | import javax.servlet.http.HttpServletRequest; |
| 42 | import javax.sip.InvalidArgumentException; | 41 | import javax.sip.InvalidArgumentException; |
| 43 | import javax.sip.SipException; | 42 | import javax.sip.SipException; |
| 44 | -import java.io.File; | ||
| 45 | import java.text.ParseException; | 43 | import java.text.ParseException; |
| 46 | import java.util.List; | 44 | import java.util.List; |
| 47 | import java.util.UUID; | 45 | import java.util.UUID; |
| @@ -90,16 +88,17 @@ public class PlayController { | @@ -90,16 +88,17 @@ public class PlayController { | ||
| 90 | @Operation(summary = "开始点播") | 88 | @Operation(summary = "开始点播") |
| 91 | @Parameter(name = "deviceId", description = "设备国标编号", required = true) | 89 | @Parameter(name = "deviceId", description = "设备国标编号", required = true) |
| 92 | @Parameter(name = "channelId", description = "通道国标编号", required = true) | 90 | @Parameter(name = "channelId", description = "通道国标编号", required = true) |
| 91 | + @Parameter(name = "isSubStream", description = "是否子码流(true-子码流,false-主码流),默认为false", required = true) | ||
| 93 | @GetMapping("/start/{deviceId}/{channelId}") | 92 | @GetMapping("/start/{deviceId}/{channelId}") |
| 94 | public DeferredResult<WVPResult<StreamContent>> play(HttpServletRequest request, @PathVariable String deviceId, | 93 | public DeferredResult<WVPResult<StreamContent>> play(HttpServletRequest request, @PathVariable String deviceId, |
| 95 | - @PathVariable String channelId) { | 94 | + @PathVariable String channelId,boolean isSubStream) { |
| 96 | 95 | ||
| 97 | // 获取可用的zlm | 96 | // 获取可用的zlm |
| 98 | Device device = storager.queryVideoDevice(deviceId); | 97 | Device device = storager.queryVideoDevice(deviceId); |
| 99 | MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); | 98 | MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); |
| 100 | 99 | ||
| 101 | RequestMessage requestMessage = new RequestMessage(); | 100 | RequestMessage requestMessage = new RequestMessage(); |
| 102 | - String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; | 101 | + String key = DeferredResultHolder.getPlayKey(deviceId,channelId,device.isSwitchPrimarySubStream(),isSubStream); |
| 103 | requestMessage.setKey(key); | 102 | requestMessage.setKey(key); |
| 104 | String uuid = UUID.randomUUID().toString(); | 103 | String uuid = UUID.randomUUID().toString(); |
| 105 | requestMessage.setId(uuid); | 104 | requestMessage.setId(uuid); |
| @@ -118,7 +117,7 @@ public class PlayController { | @@ -118,7 +117,7 @@ public class PlayController { | ||
| 118 | // 录像查询以channelId作为deviceId查询 | 117 | // 录像查询以channelId作为deviceId查询 |
| 119 | resultHolder.put(key, uuid, result); | 118 | resultHolder.put(key, uuid, result); |
| 120 | 119 | ||
| 121 | - playService.play(newMediaServerItem, deviceId, channelId, (code, msg, data) -> { | 120 | + playService.play(newMediaServerItem, deviceId, channelId,isSubStream, (code, msg, data) -> { |
| 122 | WVPResult<StreamContent> wvpResult = new WVPResult<>(); | 121 | WVPResult<StreamContent> wvpResult = new WVPResult<>(); |
| 123 | if (code == InviteErrorCode.SUCCESS.getCode()) { | 122 | if (code == InviteErrorCode.SUCCESS.getCode()) { |
| 124 | wvpResult.setCode(ErrorCode.SUCCESS.getCode()); | 123 | wvpResult.setCode(ErrorCode.SUCCESS.getCode()); |
| @@ -144,8 +143,9 @@ public class PlayController { | @@ -144,8 +143,9 @@ public class PlayController { | ||
| 144 | @Operation(summary = "停止点播") | 143 | @Operation(summary = "停止点播") |
| 145 | @Parameter(name = "deviceId", description = "设备国标编号", required = true) | 144 | @Parameter(name = "deviceId", description = "设备国标编号", required = true) |
| 146 | @Parameter(name = "channelId", description = "通道国标编号", required = true) | 145 | @Parameter(name = "channelId", description = "通道国标编号", required = true) |
| 146 | + @Parameter(name = "isSubStream", description = "是否子码流(true-子码流,false-主码流),默认为false", required = true) | ||
| 147 | @GetMapping("/stop/{deviceId}/{channelId}") | 147 | @GetMapping("/stop/{deviceId}/{channelId}") |
| 148 | - public JSONObject playStop(@PathVariable String deviceId, @PathVariable String channelId) { | 148 | + public JSONObject playStop(@PathVariable String deviceId, @PathVariable String channelId,boolean isSubStream) { |
| 149 | 149 | ||
| 150 | logger.debug(String.format("设备预览/回放停止API调用,streamId:%s_%s", deviceId, channelId )); | 150 | logger.debug(String.format("设备预览/回放停止API调用,streamId:%s_%s", deviceId, channelId )); |
| 151 | 151 | ||
| @@ -158,7 +158,12 @@ public class PlayController { | @@ -158,7 +158,12 @@ public class PlayController { | ||
| 158 | throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备[" + deviceId + "]不存在"); | 158 | throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备[" + deviceId + "]不存在"); |
| 159 | } | 159 | } |
| 160 | 160 | ||
| 161 | - 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 | + } | ||
| 162 | if (inviteInfo == null) { | 167 | if (inviteInfo == null) { |
| 163 | throw new ControllerException(ErrorCode.ERROR100.getCode(), "点播未找到"); | 168 | throw new ControllerException(ErrorCode.ERROR100.getCode(), "点播未找到"); |
| 164 | } | 169 | } |
| @@ -171,12 +176,17 @@ public class PlayController { | @@ -171,12 +176,17 @@ public class PlayController { | ||
| 171 | throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); | 176 | throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); |
| 172 | } | 177 | } |
| 173 | } | 178 | } |
| 174 | - 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 | + } | ||
| 175 | 185 | ||
| 176 | - storager.stopPlay(deviceId, channelId); | ||
| 177 | JSONObject json = new JSONObject(); | 186 | JSONObject json = new JSONObject(); |
| 178 | json.put("deviceId", deviceId); | 187 | json.put("deviceId", deviceId); |
| 179 | json.put("channelId", channelId); | 188 | json.put("channelId", channelId); |
| 189 | + json.put("isSubStream", isSubStream); | ||
| 180 | return json; | 190 | return json; |
| 181 | } | 191 | } |
| 182 | 192 | ||
| @@ -343,30 +353,27 @@ public class PlayController { | @@ -343,30 +353,27 @@ public class PlayController { | ||
| 343 | @Operation(summary = "获取截图") | 353 | @Operation(summary = "获取截图") |
| 344 | @Parameter(name = "deviceId", description = "设备国标编号", required = true) | 354 | @Parameter(name = "deviceId", description = "设备国标编号", required = true) |
| 345 | @Parameter(name = "channelId", description = "通道国标编号", required = true) | 355 | @Parameter(name = "channelId", description = "通道国标编号", required = true) |
| 356 | + @Parameter(name = "isSubStream", description = "是否子码流(true-子码流,false-主码流),默认为false", required = true) | ||
| 346 | @GetMapping("/snap") | 357 | @GetMapping("/snap") |
| 347 | - public DeferredResult<String> getSnap(HttpServletRequest request, String deviceId, String channelId) { | 358 | + public DeferredResult<String> getSnap(String deviceId, String channelId,boolean isSubStream) { |
| 348 | if (logger.isDebugEnabled()) { | 359 | if (logger.isDebugEnabled()) { |
| 349 | logger.debug("获取截图: {}/{}", deviceId, channelId); | 360 | logger.debug("获取截图: {}/{}", deviceId, channelId); |
| 350 | } | 361 | } |
| 351 | 362 | ||
| 363 | + Device device = storager.queryVideoDevice(deviceId); | ||
| 352 | DeferredResult<String> result = new DeferredResult<>(3 * 1000L); | 364 | DeferredResult<String> result = new DeferredResult<>(3 * 1000L); |
| 353 | - String key = DeferredResultHolder.CALLBACK_CMD_SNAP + deviceId; | 365 | + String key = DeferredResultHolder.getSnapKey(deviceId,channelId,device.isSwitchPrimarySubStream(),isSubStream); |
| 354 | String uuid = UUID.randomUUID().toString(); | 366 | String uuid = UUID.randomUUID().toString(); |
| 355 | resultHolder.put(key, uuid, result); | 367 | resultHolder.put(key, uuid, result); |
| 356 | 368 | ||
| 357 | RequestMessage message = new RequestMessage(); | 369 | RequestMessage message = new RequestMessage(); |
| 358 | message.setKey(key); | 370 | message.setKey(key); |
| 359 | message.setId(uuid); | 371 | message.setId(uuid); |
| 360 | - String nowForUrl = DateUtil.getNowForUrl(); | ||
| 361 | - String fileName = deviceId + "_" + channelId + "_" + nowForUrl + ".jpg"; | ||
| 362 | 372 | ||
| 363 | - playService.getSnap(deviceId, channelId, fileName, (code, msg, data) -> { | 373 | + String fileName = deviceId + "_" + channelId + "_" + DateUtil.getNowForUrl() + "jpg"; |
| 374 | + playService.getSnap(deviceId, channelId, fileName,isSubStream, (code, msg, data) -> { | ||
| 364 | if (code == InviteErrorCode.SUCCESS.getCode()) { | 375 | if (code == InviteErrorCode.SUCCESS.getCode()) { |
| 365 | - File snapFile = new File((String)data); | ||
| 366 | - String fileNameForUrl = deviceId + "/" + channelId + "?mark=" + nowForUrl; | ||
| 367 | - String uri = request.getRequestURL().toString().replace(request.getRequestURI(), "/api/device/query/snap/" + fileNameForUrl); | ||
| 368 | - SnapPath snapPath = SnapPath.getInstance((String) data, snapFile.getAbsolutePath(), uri); | ||
| 369 | - message.setData(snapPath); | 376 | + message.setData(data); |
| 370 | }else { | 377 | }else { |
| 371 | message.setData(WVPResult.fail(code, msg)); | 378 | message.setData(WVPResult.fail(code, msg)); |
| 372 | } | 379 | } |
src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java
| @@ -122,7 +122,7 @@ public class ApiStreamController { | @@ -122,7 +122,7 @@ public class ApiStreamController { | ||
| 122 | MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); | 122 | MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); |
| 123 | 123 | ||
| 124 | 124 | ||
| 125 | - playService.play(newMediaServerItem, serial, code, (errorCode, msg, data) -> { | 125 | + playService.play(newMediaServerItem, serial, code,false, (errorCode, msg, data) -> { |
| 126 | if (errorCode == InviteErrorCode.SUCCESS.getCode()) { | 126 | if (errorCode == InviteErrorCode.SUCCESS.getCode()) { |
| 127 | InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, serial, code); | 127 | InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, serial, code); |
| 128 | if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { | 128 | if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { |
src/main/resources/application-dev.yml
| 1 | spring: | 1 | spring: |
| 2 | + thymeleaf: | ||
| 3 | + cache: false | ||
| 2 | # [可选]上传文件大小限制 | 4 | # [可选]上传文件大小限制 |
| 3 | servlet: | 5 | servlet: |
| 4 | multipart: | 6 | multipart: |
| @@ -11,18 +13,18 @@ spring: | @@ -11,18 +13,18 @@ spring: | ||
| 11 | # [必须修改] 端口号 | 13 | # [必须修改] 端口号 |
| 12 | port: 6379 | 14 | port: 6379 |
| 13 | # [可选] 数据库 DB | 15 | # [可选] 数据库 DB |
| 14 | - database: 6 | 16 | + database: 7 |
| 15 | # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 | 17 | # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 |
| 16 | - password: face2020 | 18 | + password: |
| 17 | # [可选] 超时时间 | 19 | # [可选] 超时时间 |
| 18 | timeout: 10000 | 20 | timeout: 10000 |
| 19 | # mysql数据源 | 21 | # mysql数据源 |
| 20 | datasource: | 22 | datasource: |
| 21 | type: com.zaxxer.hikari.HikariDataSource | 23 | type: com.zaxxer.hikari.HikariDataSource |
| 22 | driver-class-name: com.mysql.cj.jdbc.Driver | 24 | driver-class-name: com.mysql.cj.jdbc.Driver |
| 23 | - url: jdbc:mysql://127.0.0.1:3306/wvp2?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true | 25 | + url: jdbc:mysql://127.0.0.1:3306/test_gb-89wulian?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true |
| 24 | username: root | 26 | username: root |
| 25 | - password: 123456 | 27 | + password: root |
| 26 | hikari: | 28 | hikari: |
| 27 | connection-timeout: 20000 # 是客户端等待连接池连接的最大毫秒数 | 29 | connection-timeout: 20000 # 是客户端等待连接池连接的最大毫秒数 |
| 28 | initialSize: 10 # 连接池初始化连接数 | 30 | initialSize: 10 # 连接池初始化连接数 |
| @@ -30,11 +32,19 @@ spring: | @@ -30,11 +32,19 @@ spring: | ||
| 30 | minimum-idle: 5 # 连接池最小空闲连接数 | 32 | minimum-idle: 5 # 连接池最小空闲连接数 |
| 31 | idle-timeout: 300000 # 允许连接在连接池中空闲的最长时间(以毫秒为单位) | 33 | idle-timeout: 300000 # 允许连接在连接池中空闲的最长时间(以毫秒为单位) |
| 32 | max-lifetime: 1200000 # 是池中连接关闭后的最长生命周期(以毫秒为单位) | 34 | max-lifetime: 1200000 # 是池中连接关闭后的最长生命周期(以毫秒为单位) |
| 33 | - | ||
| 34 | - | ||
| 35 | #[可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口 | 35 | #[可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口 |
| 36 | server: | 36 | server: |
| 37 | - port: 18080 | 37 | + port: 18978 |
| 38 | + # [可选] HTTPS配置, 默认不开启 | ||
| 39 | + ssl: | ||
| 40 | + # [可选] 是否开启HTTPS访问 | ||
| 41 | + enabled: false | ||
| 42 | + # [可选] 证书文件路径,放置在resource/目录下即可,修改xxx为文件名 | ||
| 43 | + key-store: classpath:test.monitor.89iot.cn.jks | ||
| 44 | + # [可选] 证书密码 | ||
| 45 | + key-store-password: gpf64qmw | ||
| 46 | + # [可选] 证书类型, 默认为jks,根据实际修改 | ||
| 47 | + key-store-type: JKS | ||
| 38 | 48 | ||
| 39 | # 作为28181服务器的配置 | 49 | # 作为28181服务器的配置 |
| 40 | sip: | 50 | sip: |
| @@ -42,26 +52,36 @@ sip: | @@ -42,26 +52,36 @@ sip: | ||
| 42 | # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4 | 52 | # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4 |
| 43 | # 如果不明白,就使用0.0.0.0,大部分情况都是可以的 | 53 | # 如果不明白,就使用0.0.0.0,大部分情况都是可以的 |
| 44 | # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。 | 54 | # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。 |
| 45 | - ip: 192.168.41.16 | 55 | + ip: 192.168.1.18 |
| 46 | # [可选] 28181服务监听的端口 | 56 | # [可选] 28181服务监听的端口 |
| 47 | - port: 5060 | 57 | + port: 8116 |
| 48 | # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) | 58 | # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) |
| 49 | # 后两位为行业编码,定义参照附录D.3 | 59 | # 后两位为行业编码,定义参照附录D.3 |
| 50 | # 3701020049标识山东济南历下区 信息行业接入 | 60 | # 3701020049标识山东济南历下区 信息行业接入 |
| 51 | # [可选] | 61 | # [可选] |
| 52 | - domain: 4401020049 | 62 | + domain: 4101050000 |
| 53 | # [可选] | 63 | # [可选] |
| 54 | - id: 44010200492000000001 | 64 | + id: 41010500002000000001 |
| 55 | # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 | 65 | # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 |
| 56 | - password: admin123 | 66 | + password: bajiuwulian1006 |
| 67 | + # 是否存储alarm信息 | ||
| 68 | + alarm: true | ||
| 57 | 69 | ||
| 58 | #zlm 默认服务器配置 | 70 | #zlm 默认服务器配置 |
| 59 | media: | 71 | media: |
| 60 | - id: FQ3TF8yT83wh5Wvz | 72 | + id: 89wulian-one |
| 61 | # [必须修改] zlm服务器的内网IP | 73 | # [必须修改] zlm服务器的内网IP |
| 62 | - ip: 192.168.41.16 | 74 | + ip: 192.168.1.18 |
| 63 | # [必须修改] zlm服务器的http.port | 75 | # [必须修改] zlm服务器的http.port |
| 64 | - http-port: 8091 | 76 | + http-port: 80 |
| 77 | + # [可选] 返回流地址时的ip,置空使用 media.ip | ||
| 78 | + stream-ip: 192.168.1.18 | ||
| 79 | + # [可选] wvp在国标信令中使用的ip,此ip为摄像机可以访问到的ip, 置空使用 media.ip | ||
| 80 | + sdp-ip: 192.168.1.18 | ||
| 81 | + # [可选] zlm服务器的hook所使用的IP, 默认使用sip.ip | ||
| 82 | + hook-ip: 192.168.1.18 | ||
| 83 | + # [可选] zlm服务器的http.sslport, 置空使用zlm配置文件配置 | ||
| 84 | + http-ssl-port: 443 | ||
| 65 | # [可选] zlm服务器的hook.admin_params=secret | 85 | # [可选] zlm服务器的hook.admin_params=secret |
| 66 | secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc | 86 | secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc |
| 67 | # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 | 87 | # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 |
| @@ -69,11 +89,24 @@ media: | @@ -69,11 +89,24 @@ media: | ||
| 69 | # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 | 89 | # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 |
| 70 | enable: true | 90 | enable: true |
| 71 | # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功 | 91 | # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功 |
| 72 | - port-range: 30000,30500 # 端口范围 | 92 | + port-range: 50000,50300 # 端口范围 |
| 73 | # [可选] 国标级联在此范围内选择端口发送媒体流, | 93 | # [可选] 国标级联在此范围内选择端口发送媒体流, |
| 74 | - send-port-range: 30000,30500 # 端口范围 | 94 | + send-port-range: 50000,50300 # 端口范围 |
| 75 | # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用 | 95 | # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用 |
| 76 | record-assist-port: 18081 | 96 | record-assist-port: 18081 |
| 97 | +# [根据业务需求配置] | ||
| 98 | +user-settings: | ||
| 99 | + # 点播/录像回放 等待超时时间,单位:毫秒 | ||
| 100 | + play-timeout: 180000 | ||
| 101 | + # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true | ||
| 102 | + auto-apply-play: true | ||
| 103 | + # 设备/通道状态变化时发送消息 | ||
| 104 | + device-status-notify: true | ||
| 105 | + # 跨域配置,配置你访问前端页面的地址即可, 可以配置多个 | ||
| 106 | + allowed-origins: | ||
| 107 | + - http://localhost:8080 | ||
| 108 | + - http://127.0.0.1:8080 | ||
| 77 | # [可选] 日志配置, 一般不需要改 | 109 | # [可选] 日志配置, 一般不需要改 |
| 78 | logging: | 110 | logging: |
| 79 | config: classpath:logback-spring-local.xml | 111 | config: classpath:logback-spring-local.xml |
| 112 | + |
src/main/resources/application.yml
web_src/config/index.js
| @@ -12,14 +12,14 @@ module.exports = { | @@ -12,14 +12,14 @@ module.exports = { | ||
| 12 | assetsPublicPath: '/', | 12 | assetsPublicPath: '/', |
| 13 | proxyTable: { | 13 | proxyTable: { |
| 14 | '/debug': { | 14 | '/debug': { |
| 15 | - target: 'http://localhost:18080', | 15 | + target: 'http://localhost:18978', |
| 16 | changeOrigin: true, | 16 | changeOrigin: true, |
| 17 | pathRewrite: { | 17 | pathRewrite: { |
| 18 | '^/debug': '/' | 18 | '^/debug': '/' |
| 19 | } | 19 | } |
| 20 | }, | 20 | }, |
| 21 | '/static/snap': { | 21 | '/static/snap': { |
| 22 | - target: 'http://localhost:18080', | 22 | + target: 'http://localhost:18978', |
| 23 | changeOrigin: true, | 23 | changeOrigin: true, |
| 24 | // pathRewrite: { | 24 | // pathRewrite: { |
| 25 | // '^/static/snap': '/static/snap' | 25 | // '^/static/snap': '/static/snap' |
web_src/src/components/channelList.vue
| @@ -26,6 +26,12 @@ | @@ -26,6 +26,12 @@ | ||
| 26 | <el-option label="在线" value="true"></el-option> | 26 | <el-option label="在线" value="true"></el-option> |
| 27 | <el-option label="离线" value="false"></el-option> | 27 | <el-option label="离线" value="false"></el-option> |
| 28 | </el-select> | 28 | </el-select> |
| 29 | + 清晰度: | ||
| 30 | + <el-select size="mini" style="margin-right: 1rem;" @change="search" v-model="isSubStream" placeholder="请选择" | ||
| 31 | + default-first-option> | ||
| 32 | + <el-option label="原画" :value="false"></el-option> | ||
| 33 | + <el-option label="流畅" :value="true"></el-option> | ||
| 34 | + </el-select> | ||
| 29 | </div> | 35 | </div> |
| 30 | <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button> | 36 | <el-button icon="el-icon-refresh-right" circle size="mini" @click="refresh()"></el-button> |
| 31 | <el-button v-if="showTree" icon="iconfont icon-list" circle size="mini" @click="switchList()"></el-button> | 37 | <el-button v-if="showTree" icon="iconfont icon-list" circle size="mini" @click="switchList()"></el-button> |
| @@ -146,6 +152,7 @@ export default { | @@ -146,6 +152,7 @@ export default { | ||
| 146 | searchSrt: "", | 152 | searchSrt: "", |
| 147 | channelType: "", | 153 | channelType: "", |
| 148 | online: "", | 154 | online: "", |
| 155 | + isSubStream: false, | ||
| 149 | winHeight: window.innerHeight - 200, | 156 | winHeight: window.innerHeight - 200, |
| 150 | currentPage: 1, | 157 | currentPage: 1, |
| 151 | count: 15, | 158 | count: 15, |
| @@ -237,7 +244,10 @@ export default { | @@ -237,7 +244,10 @@ export default { | ||
| 237 | let that = this; | 244 | let that = this; |
| 238 | this.$axios({ | 245 | this.$axios({ |
| 239 | method: 'get', | 246 | method: 'get', |
| 240 | - url: '/api/play/start/' + deviceId + '/' + channelId | 247 | + url: '/api/play/start/' + deviceId + '/' + channelId, |
| 248 | + params:{ | ||
| 249 | + isSubStream: this.isSubStream | ||
| 250 | + } | ||
| 241 | }).then(function (res) { | 251 | }).then(function (res) { |
| 242 | console.log(res) | 252 | console.log(res) |
| 243 | that.isLoging = false; | 253 | that.isLoging = false; |
| @@ -277,7 +287,10 @@ export default { | @@ -277,7 +287,10 @@ export default { | ||
| 277 | var that = this; | 287 | var that = this; |
| 278 | this.$axios({ | 288 | this.$axios({ |
| 279 | method: 'get', | 289 | method: 'get', |
| 280 | - url: '/api/play/stop/' + this.deviceId + "/" + itemData.channelId | 290 | + url: '/api/play/stop/' + this.deviceId + "/" + itemData.channelId, |
| 291 | + params:{ | ||
| 292 | + isSubStream: this.isSubStream | ||
| 293 | + } | ||
| 281 | }).then(function (res) { | 294 | }).then(function (res) { |
| 282 | that.initData(); | 295 | that.initData(); |
| 283 | }).catch(function (error) { | 296 | }).catch(function (error) { |
web_src/src/components/dialog/deviceEdit.vue
| @@ -58,6 +58,12 @@ | @@ -58,6 +58,12 @@ | ||
| 58 | <el-form-item v-if="form.subscribeCycleForMobilePosition > 0" label="移动位置报送间隔" prop="subscribeCycleForCatalog" > | 58 | <el-form-item v-if="form.subscribeCycleForMobilePosition > 0" label="移动位置报送间隔" prop="subscribeCycleForCatalog" > |
| 59 | <el-input v-model="form.mobilePositionSubmissionInterval" clearable ></el-input> | 59 | <el-input v-model="form.mobilePositionSubmissionInterval" clearable ></el-input> |
| 60 | </el-form-item> | 60 | </el-form-item> |
| 61 | + <el-form-item label="主子码流开关" prop="switchPrimarySubStream" > | ||
| 62 | + <el-select v-model="form.switchPrimarySubStream" style="float: left; width: 100%" > | ||
| 63 | + <el-option key="true" label="开启" :value="true"></el-option> | ||
| 64 | + <el-option key="false" label="关闭" :value="false"></el-option> | ||
| 65 | + </el-select> | ||
| 66 | + </el-form-item> | ||
| 61 | <el-form-item label="其他选项"> | 67 | <el-form-item label="其他选项"> |
| 62 | <el-checkbox label="SSRC校验" v-model="form.ssrcCheck" style="float: left"></el-checkbox> | 68 | <el-checkbox label="SSRC校验" v-model="form.ssrcCheck" style="float: left"></el-checkbox> |
| 63 | <el-checkbox label="作为消息通道" v-model="form.asMessageChannel" style="float: left"></el-checkbox> | 69 | <el-checkbox label="作为消息通道" v-model="form.asMessageChannel" style="float: left"></el-checkbox> |