|  | @@ -18,6 +18,8 @@ import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
 | 
	
		
			
				|  |  |  import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 | 
	
		
			
				|  |  |  import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 | 
	
		
			
				|  |  |  import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
 | 
	
		
			
				|  |  | +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 | 
	
		
			
				|  |  | +import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 | 
	
		
			
				|  |  |  import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 | 
	
		
			
				|  |  |  import com.genersoft.iot.vmp.media.zlm.*;
 | 
	
		
			
				|  |  |  import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
 | 
	
	
		
			
				|  | @@ -43,6 +45,7 @@ import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
 | 
	
		
			
				|  |  |  import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
 | 
	
		
			
				|  |  |  import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
 | 
	
		
			
				|  |  |  import gov.nist.javax.sip.message.SIPResponse;
 | 
	
		
			
				|  |  | +import gov.nist.javax.sip.message.SIPResponse;
 | 
	
		
			
				|  |  |  import org.slf4j.Logger;
 | 
	
		
			
				|  |  |  import org.slf4j.LoggerFactory;
 | 
	
		
			
				|  |  |  import org.springframework.beans.factory.annotation.Autowired;
 | 
	
	
		
			
				|  | @@ -147,15 +150,21 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  |      public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback) {
 | 
	
		
			
				|  |  |          if (mediaServerItem == null) {
 | 
	
		
			
				|  |  | +            logger.warn("[点播] 未找到可用的zlm deviceId: {},channelId:{}", deviceId, channelId);
 | 
	
		
			
				|  |  |              throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm");
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          Device device = redisCatchStorage.getDevice(deviceId);
 | 
	
		
			
				|  |  | +        if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE") && !mediaServerItem.isRtpEnable()) {
 | 
	
		
			
				|  |  | +            logger.warn("[点播] 单端口收流时不支持TCP主动方式收流 deviceId: {},channelId:{}", deviceId, channelId);
 | 
	
		
			
				|  |  | +            throw new ControllerException(ErrorCode.ERROR100.getCode(), "单端口收流时不支持TCP主动方式收流");
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
 | 
	
		
			
				|  |  |          if (inviteInfo != null ) {
 | 
	
		
			
				|  |  |              if (inviteInfo.getStreamInfo() == null) {
 | 
	
		
			
				|  |  |                  // 点播发起了但是尚未成功, 仅注册回调等待结果即可
 | 
	
		
			
				|  |  |                  inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback);
 | 
	
		
			
				|  |  | +                logger.info("[点播开始] 已经请求中,等待结果, deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  |                  return inviteInfo.getSsrcInfo();
 | 
	
		
			
				|  |  |              }else {
 | 
	
		
			
				|  |  |                  StreamInfo streamInfo = inviteInfo.getStreamInfo();
 | 
	
	
		
			
				|  | @@ -178,6 +187,7 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                              InviteErrorCode.SUCCESS.getCode(),
 | 
	
		
			
				|  |  |                              InviteErrorCode.SUCCESS.getMsg(),
 | 
	
		
			
				|  |  |                              streamInfo);
 | 
	
		
			
				|  |  | +                    logger.info("[点播已存在] 直接返回, deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  |                      return inviteInfo.getSsrcInfo();
 | 
	
		
			
				|  |  |                  }else {
 | 
	
		
			
				|  |  |                      // 点播发起了但是尚未成功, 仅注册回调等待结果即可
 | 
	
	
		
			
				|  | @@ -187,11 +197,8 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        String streamId = null;
 | 
	
		
			
				|  |  | -        if (mediaServerItem.isRtpEnable()) {
 | 
	
		
			
				|  |  | -            streamId = String.format("%s_%s", device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(),  false, 0, false, false,device.getStreamModeForParam());
 | 
	
		
			
				|  |  | +        String streamId = String.format("%s_%s", device.getDeviceId(), channelId);;
 | 
	
		
			
				|  |  | +        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(),  false, 0, false, device.getStreamModeForParam());
 | 
	
		
			
				|  |  |          if (ssrcInfo == null) {
 | 
	
		
			
				|  |  |              callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null);
 | 
	
		
			
				|  |  |              inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
	
		
			
				|  | @@ -200,7 +207,6 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                      null);
 | 
	
		
			
				|  |  |              return null;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        // TODO 记录点播的状态
 | 
	
		
			
				|  |  |          play(mediaServerItem, ssrcInfo, device, channelId, callback);
 | 
	
		
			
				|  |  |          return ssrcInfo;
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -357,8 +363,8 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                      null);
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{}, 收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
 | 
	
		
			
				|  |  | -                device.getDeviceId(), channelId, device.isSwitchPrimarySubStream() ? "辅码流" : "主码流", ssrcInfo.getPort(),
 | 
	
		
			
				|  |  | +        logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{}, 收流端口: {}, STREAM:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
 | 
	
		
			
				|  |  | +                device.getDeviceId(), channelId, device.isSwitchPrimarySubStream() ? "辅码流" : "主码流", ssrcInfo.getPort(), ssrcInfo.getStream(),
 | 
	
		
			
				|  |  |                  device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
 | 
	
		
			
				|  |  |          //端口获取失败的ssrcInfo 没有必要发送点播指令
 | 
	
		
			
				|  |  |          if (ssrcInfo.getPort() <= 0) {
 | 
	
	
		
			
				|  | @@ -389,16 +395,6 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                          device.getDeviceId(), channelId, device.isSwitchPrimarySubStream() ? "辅码流" : "主码流",
 | 
	
		
			
				|  |  |                          ssrcInfo.getPort(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
 | 
	
		
			
				|  |  | -//                InviteInfo inviteInfoForTimeout = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.play, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | -//                if (inviteInfoForTimeout == null) {
 | 
	
		
			
				|  |  | -//                    return;
 | 
	
		
			
				|  |  | -//                }
 | 
	
		
			
				|  |  | -//                if (InviteSessionStatus.ok == inviteInfoForTimeout.getStatus() ) {
 | 
	
		
			
				|  |  | -//                    // TODO 发送bye
 | 
	
		
			
				|  |  | -//                }else {
 | 
	
		
			
				|  |  | -//                    // TODO 发送cancel
 | 
	
		
			
				|  |  | -//                }
 | 
	
		
			
				|  |  |                  callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null);
 | 
	
		
			
				|  |  |                  inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  |                          InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null);
 | 
	
	
		
			
				|  | @@ -443,128 +439,10 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                  logger.info("[点播成功] deviceId: {}, channelId:{}, 码流类型:{}", device.getDeviceId(), channelId,
 | 
	
		
			
				|  |  |                          device.isSwitchPrimarySubStream() ? "辅码流" : "主码流");
 | 
	
		
			
				|  |  |                  snapOnPlay(mediaServerItemInuse, device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -            }, (event) -> {
 | 
	
		
			
				|  |  | -                inviteInfo.setStatus(InviteSessionStatus.ok);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                ResponseEvent responseEvent = (ResponseEvent) event.event;
 | 
	
		
			
				|  |  | -                String contentString = new String(responseEvent.getResponse().getRawContent());
 | 
	
		
			
				|  |  | -                // 获取ssrc
 | 
	
		
			
				|  |  | -                int ssrcIndex = contentString.indexOf("y=");
 | 
	
		
			
				|  |  | -                // 检查是否有y字段
 | 
	
		
			
				|  |  | -                if (ssrcIndex >= 0) {
 | 
	
		
			
				|  |  | -                    //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
 | 
	
		
			
				|  |  | -                    String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12).trim();
 | 
	
		
			
				|  |  | -                    // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
 | 
	
		
			
				|  |  | -                    if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
 | 
	
		
			
				|  |  | -                        if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
 | 
	
		
			
				|  |  | -                            String substring = contentString.substring(0, contentString.indexOf("y="));
 | 
	
		
			
				|  |  | -                            try {
 | 
	
		
			
				|  |  | -                                SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
 | 
	
		
			
				|  |  | -                                int port = -1;
 | 
	
		
			
				|  |  | -                                Vector mediaDescriptions = sdp.getMediaDescriptions(true);
 | 
	
		
			
				|  |  | -                                for (Object description : mediaDescriptions) {
 | 
	
		
			
				|  |  | -                                    MediaDescription mediaDescription = (MediaDescription) description;
 | 
	
		
			
				|  |  | -                                    Media media = mediaDescription.getMedia();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                    Vector mediaFormats = media.getMediaFormats(false);
 | 
	
		
			
				|  |  | -                                    if (mediaFormats.contains("96")) {
 | 
	
		
			
				|  |  | -                                        port = media.getMediaPort();
 | 
	
		
			
				|  |  | -                                        break;
 | 
	
		
			
				|  |  | -                                    }
 | 
	
		
			
				|  |  | -                                }
 | 
	
		
			
				|  |  | -                                logger.info("[点播-TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
 | 
	
		
			
				|  |  | -                                JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -                                logger.info("[点播-TCP主动连接对方] 结果: {}", jsonObject);
 | 
	
		
			
				|  |  | -                            } catch (SdpException e) {
 | 
	
		
			
				|  |  | -                                logger.error("[点播-TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
 | 
	
		
			
				|  |  | -                                dynamicTask.stop(timeOutTaskKey);
 | 
	
		
			
				|  |  | -                                mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -                                // 释放ssrc
 | 
	
		
			
				|  |  | -                                mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | -                                        InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | -                                inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | -                                        InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | -                                        InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -                        return;
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                    logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
 | 
	
		
			
				|  |  | -                    if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
 | 
	
		
			
				|  |  | -                        logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
 | 
	
		
			
				|  |  | -                        // 释放ssrc
 | 
	
		
			
				|  |  | -                        mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | -                        // 单端口模式streamId也有变化,重新设置监听即可
 | 
	
		
			
				|  |  | -                        if (!mediaServerItem.isRtpEnable()) {
 | 
	
		
			
				|  |  | -                            // 添加订阅
 | 
	
		
			
				|  |  | -                            HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
 | 
	
		
			
				|  |  | -                            subscribe.removeSubscribe(hookSubscribe);
 | 
	
		
			
				|  |  | -                            String stream = String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase();
 | 
	
		
			
				|  |  | -                            hookSubscribe.getContent().put("stream", stream);
 | 
	
		
			
				|  |  | -                            inviteStreamService.updateInviteInfoForStream(inviteInfo, stream);
 | 
	
		
			
				|  |  | -                            subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> {
 | 
	
		
			
				|  |  | -                                logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + hookParam);
 | 
	
		
			
				|  |  | -                                dynamicTask.stop(timeOutTaskKey);
 | 
	
		
			
				|  |  | -                                // hook响应
 | 
	
		
			
				|  |  | -                                StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, hookParam, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | -                                if (streamInfo == null){
 | 
	
		
			
				|  |  | -                                    callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | -                                            InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | -                                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | -                                            InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | -                                            InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | -                                    return;
 | 
	
		
			
				|  |  | -                                }
 | 
	
		
			
				|  |  | -                                callback.run(InviteErrorCode.SUCCESS.getCode(),
 | 
	
		
			
				|  |  | -                                        InviteErrorCode.SUCCESS.getMsg(), streamInfo);
 | 
	
		
			
				|  |  | -                                inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | -                                        InviteErrorCode.SUCCESS.getCode(),
 | 
	
		
			
				|  |  | -                                        InviteErrorCode.SUCCESS.getMsg(),
 | 
	
		
			
				|  |  | -                                        streamInfo);
 | 
	
		
			
				|  |  | -                                snapOnPlay(mediaServerItemInUse, device.getDeviceId(), channelId, stream);
 | 
	
		
			
				|  |  | -                            });
 | 
	
		
			
				|  |  | -                            return;
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                        // 更新ssrc
 | 
	
		
			
				|  |  | -                        Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse);
 | 
	
		
			
				|  |  | -                        if (!result) {
 | 
	
		
			
				|  |  | -                            try {
 | 
	
		
			
				|  |  | -                                logger.warn("[点播] 更新ssrc失败,停止点播 {}/{}", device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | -                                cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null, null);
 | 
	
		
			
				|  |  | -                            } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
 | 
	
		
			
				|  |  | -                                logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                            dynamicTask.stop(timeOutTaskKey);
 | 
	
		
			
				|  |  | -                            // 释放ssrc
 | 
	
		
			
				|  |  | -                            mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                            streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                            callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
 | 
	
		
			
				|  |  | -                                    "下级自定义了ssrc,重新设置收流信息失败", null);
 | 
	
		
			
				|  |  | -                            inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | -                                    InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
 | 
	
		
			
				|  |  | -                                    "下级自定义了ssrc,重新设置收流信息失败", null);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                        }else {
 | 
	
		
			
				|  |  | -                            if (ssrcInfo.getStream()!= null && !ssrcInfo.getStream().equals(inviteInfo.getStream())) {
 | 
	
		
			
				|  |  | -                                inviteStreamService.removeInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -                            ssrcInfo.setSsrc(ssrcInResponse);
 | 
	
		
			
				|  |  | -                            inviteInfo.setSsrcInfo(ssrcInfo);
 | 
	
		
			
				|  |  | -                            inviteInfo.setStream(ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -                    }else {
 | 
	
		
			
				|  |  | -                        logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正");
 | 
	
		
			
				|  |  | -                    }
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -                inviteStreamService.updateInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | +            }, (eventResult) -> {
 | 
	
		
			
				|  |  | +                // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
 | 
	
		
			
				|  |  | +                InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId,
 | 
	
		
			
				|  |  | +                        timeOutTaskKey, callback, inviteInfo, InviteSessionType.PLAY);
 | 
	
		
			
				|  |  |              }, (event) -> {
 | 
	
		
			
				|  |  |                  dynamicTask.stop(timeOutTaskKey);
 | 
	
		
			
				|  |  |                  mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
 | 
	
	
		
			
				|  | @@ -601,6 +479,47 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    private void tcpActiveHandler(Device device, String channelId, String contentString,
 | 
	
		
			
				|  |  | +                                  MediaServerItem mediaServerItem,
 | 
	
		
			
				|  |  | +                                  String timeOutTaskKey, SSRCInfo ssrcInfo, ErrorCallback<Object> callback){
 | 
	
		
			
				|  |  | +        if (!device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        String substring = contentString.substring(0, contentString.indexOf("y="));
 | 
	
		
			
				|  |  | +        try {
 | 
	
		
			
				|  |  | +            SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
 | 
	
		
			
				|  |  | +            int port = -1;
 | 
	
		
			
				|  |  | +            Vector mediaDescriptions = sdp.getMediaDescriptions(true);
 | 
	
		
			
				|  |  | +            for (Object description : mediaDescriptions) {
 | 
	
		
			
				|  |  | +                MediaDescription mediaDescription = (MediaDescription) description;
 | 
	
		
			
				|  |  | +                Media media = mediaDescription.getMedia();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                Vector mediaFormats = media.getMediaFormats(false);
 | 
	
		
			
				|  |  | +                if (mediaFormats.contains("96")) {
 | 
	
		
			
				|  |  | +                    port = media.getMediaPort();
 | 
	
		
			
				|  |  | +                    break;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            logger.info("[TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
 | 
	
		
			
				|  |  | +            JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | +            logger.info("[TCP主动连接对方] 结果: {}", jsonObject);
 | 
	
		
			
				|  |  | +        } catch (SdpException e) {
 | 
	
		
			
				|  |  | +            logger.error("[TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
 | 
	
		
			
				|  |  | +            dynamicTask.stop(timeOutTaskKey);
 | 
	
		
			
				|  |  | +            mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | +            // 释放ssrc
 | 
	
		
			
				|  |  | +            mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | +                    InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | +            inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                    InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | +                    InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * 点播成功时调用截图.
 | 
	
		
			
				|  |  |       *
 | 
	
	
		
			
				|  | @@ -707,22 +626,23 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                                                            String endTime, ErrorCallback<Object> callback) {
 | 
	
		
			
				|  |  |          Device device = storager.queryVideoDevice(deviceId);
 | 
	
		
			
				|  |  |          if (device == null) {
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | +            logger.warn("[录像回放] 未找到设备 deviceId: {},channelId:{}", deviceId, channelId);
 | 
	
		
			
				|  |  | +            throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备:" + deviceId);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
 | 
	
		
			
				|  |  | -        String stream = null;
 | 
	
		
			
				|  |  | -        if (newMediaServerItem.isRtpEnable()) {
 | 
	
		
			
				|  |  | -            String startTimeStr = startTime.replace("-", "")
 | 
	
		
			
				|  |  | -                    .replace(":", "")
 | 
	
		
			
				|  |  | -                    .replace(" ", "");
 | 
	
		
			
				|  |  | -            System.out.println(startTimeStr);
 | 
	
		
			
				|  |  | -            String endTimeTimeStr = endTime.replace("-", "")
 | 
	
		
			
				|  |  | -                    .replace(":", "")
 | 
	
		
			
				|  |  | -                    .replace(" ", "");
 | 
	
		
			
				|  |  | -            System.out.println(endTimeTimeStr);
 | 
	
		
			
				|  |  | -            stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(),  true, 0, false,false,  device.getStreamModeForParam());
 | 
	
		
			
				|  |  | +        if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE") && ! newMediaServerItem.isRtpEnable()) {
 | 
	
		
			
				|  |  | +            logger.warn("[录像回放] 单端口收流时不支持TCP主动方式收流 deviceId: {},channelId:{}", deviceId, channelId);
 | 
	
		
			
				|  |  | +            throw new ControllerException(ErrorCode.ERROR100.getCode(), "单端口收流时不支持TCP主动方式收流");
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        String startTimeStr = startTime.replace("-", "")
 | 
	
		
			
				|  |  | +                .replace(":", "")
 | 
	
		
			
				|  |  | +                .replace(" ", "");
 | 
	
		
			
				|  |  | +        String endTimeTimeStr = endTime.replace("-", "")
 | 
	
		
			
				|  |  | +                .replace(":", "")
 | 
	
		
			
				|  |  | +                .replace(" ", "");
 | 
	
		
			
				|  |  | +        String stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr;
 | 
	
		
			
				|  |  | +        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(),  true, 0, false, device.getStreamModeForParam());
 | 
	
		
			
				|  |  |          playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, callback);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -795,113 +715,13 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |          try {
 | 
	
		
			
				|  |  |              cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime,
 | 
	
		
			
				|  |  |                      hookEvent, eventResult -> {
 | 
	
		
			
				|  |  | -                        inviteInfo.setStatus(InviteSessionStatus.ok);
 | 
	
		
			
				|  |  | -                        ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
 | 
	
		
			
				|  |  | -                        String contentString = new String(responseEvent.getResponse().getRawContent());
 | 
	
		
			
				|  |  | -                        // 获取ssrc
 | 
	
		
			
				|  |  | -                        int ssrcIndex = contentString.indexOf("y=");
 | 
	
		
			
				|  |  | -                        // 检查是否有y字段
 | 
	
		
			
				|  |  | -                        if (ssrcIndex >= 0) {
 | 
	
		
			
				|  |  | -                            //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
 | 
	
		
			
				|  |  | -                            String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
 | 
	
		
			
				|  |  | -                            // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
 | 
	
		
			
				|  |  | -                            if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
 | 
	
		
			
				|  |  | -                                if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
 | 
	
		
			
				|  |  | -                                    String substring = contentString.substring(0, contentString.indexOf("y="));
 | 
	
		
			
				|  |  | -                                    try {
 | 
	
		
			
				|  |  | -                                        SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
 | 
	
		
			
				|  |  | -                                        int port = -1;
 | 
	
		
			
				|  |  | -                                        Vector mediaDescriptions = sdp.getMediaDescriptions(true);
 | 
	
		
			
				|  |  | -                                        for (Object description : mediaDescriptions) {
 | 
	
		
			
				|  |  | -                                            MediaDescription mediaDescription = (MediaDescription) description;
 | 
	
		
			
				|  |  | -                                            Media media = mediaDescription.getMedia();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                            Vector mediaFormats = media.getMediaFormats(false);
 | 
	
		
			
				|  |  | -                                            if (mediaFormats.contains("96")) {
 | 
	
		
			
				|  |  | -                                                port = media.getMediaPort();
 | 
	
		
			
				|  |  | -                                                break;
 | 
	
		
			
				|  |  | -                                            }
 | 
	
		
			
				|  |  | -                                        }
 | 
	
		
			
				|  |  | -                                        logger.info("[录像回放-TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
 | 
	
		
			
				|  |  | -                                        JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -                                        logger.info("[录像回放-TCP主动连接对方] 结果: {}", jsonObject);
 | 
	
		
			
				|  |  | -                                    } catch (SdpException e) {
 | 
	
		
			
				|  |  | -                                        logger.error("[录像回放-TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
 | 
	
		
			
				|  |  | -                                        dynamicTask.stop(playBackTimeOutTaskKey);
 | 
	
		
			
				|  |  | -                                        mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -                                        // 释放ssrc
 | 
	
		
			
				|  |  | -                                        mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                        streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                        callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | -                                                InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | -                                        inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | -                                                InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | -                                                InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | -                                    }
 | 
	
		
			
				|  |  | -                                }
 | 
	
		
			
				|  |  | -                                return;
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -                            logger.info("[录像回放] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
 | 
	
		
			
				|  |  | -                            if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
 | 
	
		
			
				|  |  | -                                logger.info("[录像回放] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                // 释放ssrc
 | 
	
		
			
				|  |  | -                                mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                // 单端口模式streamId也有变化,需要重新设置监听
 | 
	
		
			
				|  |  | -                                if (!mediaServerItem.isRtpEnable()) {
 | 
	
		
			
				|  |  | -                                    // 添加订阅
 | 
	
		
			
				|  |  | -                                    HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
 | 
	
		
			
				|  |  | -                                    subscribe.removeSubscribe(hookSubscribe);
 | 
	
		
			
				|  |  | -                                    String stream = String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase();
 | 
	
		
			
				|  |  | -                                    hookSubscribe.getContent().put("stream", stream);
 | 
	
		
			
				|  |  | -                                    inviteStreamService.updateInviteInfoForStream(inviteInfo, stream);
 | 
	
		
			
				|  |  | -                                    subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> {
 | 
	
		
			
				|  |  | -                                        logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + hookParam);
 | 
	
		
			
				|  |  | -                                        dynamicTask.stop(playBackTimeOutTaskKey);
 | 
	
		
			
				|  |  | -                                        // hook响应
 | 
	
		
			
				|  |  | -                                        hookEvent.response(mediaServerItemInUse, hookParam);
 | 
	
		
			
				|  |  | -                                    });
 | 
	
		
			
				|  |  | -                                }
 | 
	
		
			
				|  |  | -                                // 更新ssrc
 | 
	
		
			
				|  |  | -                                Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse);
 | 
	
		
			
				|  |  | -                                if (!result) {
 | 
	
		
			
				|  |  | -                                    try {
 | 
	
		
			
				|  |  | -                                        logger.warn("[录像回放] 更新ssrc失败,停止录像回放 {}/{}", device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | -                                        cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null, null);
 | 
	
		
			
				|  |  | -                                    } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
 | 
	
		
			
				|  |  | -                                        logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                    dynamicTask.stop(playBackTimeOutTaskKey);
 | 
	
		
			
				|  |  | -                                    // 释放ssrc
 | 
	
		
			
				|  |  | -                                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                    streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                    callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
 | 
	
		
			
				|  |  | -                                            "下级自定义了ssrc,重新设置收流信息失败", null);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                }else {
 | 
	
		
			
				|  |  | -                                    if (ssrcInfo.getStream()!= null && !ssrcInfo.getStream().equals(inviteInfo.getStream())) {
 | 
	
		
			
				|  |  | -                                        inviteStreamService.removeInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | -                                    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                    ssrcInfo.setSsrc(ssrcInResponse);
 | 
	
		
			
				|  |  | -                                    inviteInfo.setSsrcInfo(ssrcInfo);
 | 
	
		
			
				|  |  | -                                    inviteInfo.setStream(ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -                                }
 | 
	
		
			
				|  |  | -                            }else {
 | 
	
		
			
				|  |  | -                                logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正");
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -                        inviteStreamService.updateInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | +                        // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
 | 
	
		
			
				|  |  | +                        InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId,
 | 
	
		
			
				|  |  | +                                playBackTimeOutTaskKey, callback, inviteInfo, InviteSessionType.PLAYBACK);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                      }, errorEvent);
 | 
	
		
			
				|  |  |          } catch (InvalidArgumentException | SipException | ParseException e) {
 | 
	
		
			
				|  |  | -            logger.error("[命令发送失败] 回放: {}", e.getMessage());
 | 
	
		
			
				|  |  | +            logger.error("[命令发送失败] 录像回放: {}", e.getMessage());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult();
 | 
	
		
			
				|  |  |              eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent;
 | 
	
	
		
			
				|  | @@ -912,6 +732,90 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    private void InviteOKHandler(SipSubscribe.EventResult eventResult, SSRCInfo ssrcInfo, MediaServerItem mediaServerItem,
 | 
	
		
			
				|  |  | +                                 Device device, String channelId, String timeOutTaskKey, ErrorCallback<Object> callback,
 | 
	
		
			
				|  |  | +                                 InviteInfo inviteInfo, InviteSessionType inviteSessionType){
 | 
	
		
			
				|  |  | +        inviteInfo.setStatus(InviteSessionStatus.ok);
 | 
	
		
			
				|  |  | +        ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
 | 
	
		
			
				|  |  | +        String contentString = new String(responseEvent.getResponse().getRawContent());
 | 
	
		
			
				|  |  | +        String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString);
 | 
	
		
			
				|  |  | +        if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
 | 
	
		
			
				|  |  | +            // ssrc 一致
 | 
	
		
			
				|  |  | +            if (mediaServerItem.isRtpEnable()) {
 | 
	
		
			
				|  |  | +                // 多端口
 | 
	
		
			
				|  |  | +                if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
 | 
	
		
			
				|  |  | +                    tcpActiveHandler(device, channelId, contentString, mediaServerItem, timeOutTaskKey, ssrcInfo, callback);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }else {
 | 
	
		
			
				|  |  | +                // 单端口
 | 
	
		
			
				|  |  | +                if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
 | 
	
		
			
				|  |  | +                    logger.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流");
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }else {
 | 
	
		
			
				|  |  | +            logger.info("[Invite 200OK] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
 | 
	
		
			
				|  |  | +            // ssrc 不一致
 | 
	
		
			
				|  |  | +            if (mediaServerItem.isRtpEnable()) {
 | 
	
		
			
				|  |  | +                // 多端口
 | 
	
		
			
				|  |  | +                if (device.isSsrcCheck()) {
 | 
	
		
			
				|  |  | +                    // ssrc检验
 | 
	
		
			
				|  |  | +                    // 更新ssrc
 | 
	
		
			
				|  |  | +                    logger.info("[Invite 200OK] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
 | 
	
		
			
				|  |  | +                    // 释放ssrc
 | 
	
		
			
				|  |  | +                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | +                    Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse);
 | 
	
		
			
				|  |  | +                    if (!result) {
 | 
	
		
			
				|  |  | +                        try {
 | 
	
		
			
				|  |  | +                            logger.warn("[Invite 200OK] 更新ssrc失败,停止点播 {}/{}", device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +                            cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null, null);
 | 
	
		
			
				|  |  | +                        } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
 | 
	
		
			
				|  |  | +                            logger.error("[命令发送失败] 停止播放, 发送BYE: {}", e.getMessage());
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                        dynamicTask.stop(timeOutTaskKey);
 | 
	
		
			
				|  |  | +                        // 释放ssrc
 | 
	
		
			
				|  |  | +                        mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                        streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                        callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
 | 
	
		
			
				|  |  | +                                "下级自定义了ssrc,重新设置收流信息失败", null);
 | 
	
		
			
				|  |  | +                        inviteStreamService.call(inviteSessionType, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                                InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
 | 
	
		
			
				|  |  | +                                "下级自定义了ssrc,重新设置收流信息失败", null);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    }else {
 | 
	
		
			
				|  |  | +                        ssrcInfo.setSsrc(ssrcInResponse);
 | 
	
		
			
				|  |  | +                        inviteInfo.setSsrcInfo(ssrcInfo);
 | 
	
		
			
				|  |  | +                        inviteInfo.setStream(ssrcInfo.getStream());
 | 
	
		
			
				|  |  | +                        if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
 | 
	
		
			
				|  |  | +                            if (mediaServerItem.isRtpEnable()) {
 | 
	
		
			
				|  |  | +                                tcpActiveHandler(device, channelId, contentString, mediaServerItem, timeOutTaskKey, ssrcInfo, callback);
 | 
	
		
			
				|  |  | +                            }else {
 | 
	
		
			
				|  |  | +                                logger.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流");
 | 
	
		
			
				|  |  | +                            }
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                        inviteStreamService.updateInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }else {
 | 
	
		
			
				|  |  | +                if (ssrcInResponse != null) {
 | 
	
		
			
				|  |  | +                    // 单端口
 | 
	
		
			
				|  |  | +                    // 重新订阅流上线
 | 
	
		
			
				|  |  | +                    SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(inviteInfo.getDeviceId(),
 | 
	
		
			
				|  |  | +                            inviteInfo.getChannelId(), null, inviteInfo.getStream());
 | 
	
		
			
				|  |  | +                    streamSession.remove(inviteInfo.getDeviceId(),
 | 
	
		
			
				|  |  | +                            inviteInfo.getChannelId(), inviteInfo.getStream());
 | 
	
		
			
				|  |  | +                    inviteStreamService.updateInviteInfoForSSRC(inviteInfo, ssrcInResponse);
 | 
	
		
			
				|  |  | +                    streamSession.put(device.getDeviceId(), channelId, ssrcTransaction.getCallId(),
 | 
	
		
			
				|  |  | +                            inviteInfo.getStream(), ssrcInResponse, mediaServerItem.getId(), (SIPResponse) responseEvent.getResponse(), inviteSessionType);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  |      public void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback) {
 | 
	
		
			
				|  |  |          Device device = storager.queryVideoDevice(deviceId);
 | 
	
	
		
			
				|  | @@ -925,6 +829,7 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                      null);
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +        // 录像下载不使用固定流地址,固定流地址会导致如果开始时间与结束时间一致时文件错误的叠加在一起
 | 
	
		
			
				|  |  |          SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(),  true, 0, false,false, device.getStreamModeForParam());
 | 
	
		
			
				|  |  |          download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed, callback);
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -993,108 +898,9 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |          try {
 | 
	
		
			
				|  |  |              cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed,
 | 
	
		
			
				|  |  |                      hookEvent, errorEvent, eventResult ->{
 | 
	
		
			
				|  |  | -                        inviteInfo.setStatus(InviteSessionStatus.ok);
 | 
	
		
			
				|  |  | -                        ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
 | 
	
		
			
				|  |  | -                        String contentString = new String(responseEvent.getResponse().getRawContent());
 | 
	
		
			
				|  |  | -                        // 获取ssrc
 | 
	
		
			
				|  |  | -                        int ssrcIndex = contentString.indexOf("y=");
 | 
	
		
			
				|  |  | -                        // 检查是否有y字段
 | 
	
		
			
				|  |  | -                        if (ssrcIndex >= 0) {
 | 
	
		
			
				|  |  | -                            //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
 | 
	
		
			
				|  |  | -                            String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
 | 
	
		
			
				|  |  | -                            // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
 | 
	
		
			
				|  |  | -                            if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
 | 
	
		
			
				|  |  | -                                if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
 | 
	
		
			
				|  |  | -                                    String substring = contentString.substring(0, contentString.indexOf("y="));
 | 
	
		
			
				|  |  | -                                    try {
 | 
	
		
			
				|  |  | -                                        SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
 | 
	
		
			
				|  |  | -                                        int port = -1;
 | 
	
		
			
				|  |  | -                                        Vector mediaDescriptions = sdp.getMediaDescriptions(true);
 | 
	
		
			
				|  |  | -                                        for (Object description : mediaDescriptions) {
 | 
	
		
			
				|  |  | -                                            MediaDescription mediaDescription = (MediaDescription) description;
 | 
	
		
			
				|  |  | -                                            Media media = mediaDescription.getMedia();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                            Vector mediaFormats = media.getMediaFormats(false);
 | 
	
		
			
				|  |  | -                                            if (mediaFormats.contains("96")) {
 | 
	
		
			
				|  |  | -                                                port = media.getMediaPort();
 | 
	
		
			
				|  |  | -                                                break;
 | 
	
		
			
				|  |  | -                                            }
 | 
	
		
			
				|  |  | -                                        }
 | 
	
		
			
				|  |  | -                                        logger.info("[录像下载-TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
 | 
	
		
			
				|  |  | -                                        JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -                                        logger.info("[录像下载-TCP主动连接对方] 结果: {}", jsonObject);
 | 
	
		
			
				|  |  | -                                    } catch (SdpException e) {
 | 
	
		
			
				|  |  | -                                        logger.error("[录像下载-TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
 | 
	
		
			
				|  |  | -                                        dynamicTask.stop(downLoadTimeOutTaskKey);
 | 
	
		
			
				|  |  | -                                        mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -                                        // 释放ssrc
 | 
	
		
			
				|  |  | -                                        mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                        streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                        callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | -                                                InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | -                                        inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | -                                                InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | -                                                InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | -                                    }
 | 
	
		
			
				|  |  | -                                }
 | 
	
		
			
				|  |  | -                                return;
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -                            logger.info("[录像下载] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
 | 
	
		
			
				|  |  | -                            if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
 | 
	
		
			
				|  |  | -                                logger.info("[录像下载] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                // 释放ssrc
 | 
	
		
			
				|  |  | -                                mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                // 单端口模式streamId也有变化,需要重新设置监听
 | 
	
		
			
				|  |  | -                                if (!mediaServerItem.isRtpEnable()) {
 | 
	
		
			
				|  |  | -                                    // 添加订阅
 | 
	
		
			
				|  |  | -                                    HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
 | 
	
		
			
				|  |  | -                                    subscribe.removeSubscribe(hookSubscribe);
 | 
	
		
			
				|  |  | -                                    String stream = String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase();
 | 
	
		
			
				|  |  | -                                    hookSubscribe.getContent().put("stream", stream);
 | 
	
		
			
				|  |  | -                                    inviteStreamService.updateInviteInfoForStream(inviteInfo, stream);
 | 
	
		
			
				|  |  | -                                    subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> {
 | 
	
		
			
				|  |  | -                                        logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + hookParam);
 | 
	
		
			
				|  |  | -                                        dynamicTask.stop(downLoadTimeOutTaskKey);
 | 
	
		
			
				|  |  | -                                        hookEvent.response(mediaServerItemInUse, hookParam);
 | 
	
		
			
				|  |  | -                                    });
 | 
	
		
			
				|  |  | -                                }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                // 更新ssrc
 | 
	
		
			
				|  |  | -                                Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse);
 | 
	
		
			
				|  |  | -                                if (!result) {
 | 
	
		
			
				|  |  | -                                    try {
 | 
	
		
			
				|  |  | -                                        logger.warn("[录像下载] 更新ssrc失败,停止录像回放 {}/{}", device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | -                                        cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null, null);
 | 
	
		
			
				|  |  | -                                    } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
 | 
	
		
			
				|  |  | -                                        logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage());
 | 
	
		
			
				|  |  | -                                    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                    dynamicTask.stop(downLoadTimeOutTaskKey);
 | 
	
		
			
				|  |  | -                                    // 释放ssrc
 | 
	
		
			
				|  |  | -                                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                    streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                    callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
 | 
	
		
			
				|  |  | -                                            "下级自定义了ssrc,重新设置收流信息失败", null);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                                }else {
 | 
	
		
			
				|  |  | -                                    if (ssrcInfo.getStream()!= null && !ssrcInfo.getStream().equals(inviteInfo.getStream())) {
 | 
	
		
			
				|  |  | -                                        inviteStreamService.removeInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | -                                    }
 | 
	
		
			
				|  |  | -                                    ssrcInfo.setSsrc(ssrcInResponse);
 | 
	
		
			
				|  |  | -                                    inviteInfo.setSsrcInfo(ssrcInfo);
 | 
	
		
			
				|  |  | -                                    inviteInfo.setStream(ssrcInfo.getStream());
 | 
	
		
			
				|  |  | -                                }
 | 
	
		
			
				|  |  | -                            }else {
 | 
	
		
			
				|  |  | -                                logger.info("[录像下载] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正");
 | 
	
		
			
				|  |  | -                            }
 | 
	
		
			
				|  |  | -                        }
 | 
	
		
			
				|  |  | -                        inviteStreamService.updateInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | +                        // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
 | 
	
		
			
				|  |  | +                        InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId,
 | 
	
		
			
				|  |  | +                                downLoadTimeOutTaskKey, callback, inviteInfo, InviteSessionType.DOWNLOAD);
 | 
	
		
			
				|  |  |                      });
 | 
	
		
			
				|  |  |          } catch (InvalidArgumentException | SipException | ParseException e) {
 | 
	
		
			
				|  |  |              logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
 |