|  | @@ -18,7 +18,6 @@ import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 | 
	
		
			
				|  |  |  import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
 | 
	
		
			
				|  |  |  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.media.zlm.AssistRESTfulUtils;
 | 
	
		
			
				|  |  |  import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
 | 
	
		
			
				|  |  |  import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
 | 
	
	
		
			
				|  | @@ -116,28 +115,43 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  | -    public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, ErrorCallback<Object> callback) {
 | 
	
		
			
				|  |  | +    public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId,boolean isSubStream, ErrorCallback<Object> callback) {
 | 
	
		
			
				|  |  |          if (mediaServerItem == null) {
 | 
	
		
			
				|  |  |              throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm");
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          Device device = redisCatchStorage.getDevice(deviceId);
 | 
	
		
			
				|  |  | -        InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +        InviteInfo inviteInfo;
 | 
	
		
			
				|  |  | +        if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +            inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream);
 | 
	
		
			
				|  |  | +        }else {
 | 
	
		
			
				|  |  | +            inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          if (inviteInfo != null ) {
 | 
	
		
			
				|  |  |              if (inviteInfo.getStreamInfo() == null) {
 | 
	
		
			
				|  |  |                  // 点播发起了但是尚未成功, 仅注册回调等待结果即可
 | 
	
		
			
				|  |  | -                inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback);
 | 
	
		
			
				|  |  | +                if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                    inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId,isSubStream, null, callback);
 | 
	
		
			
				|  |  | +                }else {
 | 
	
		
			
				|  |  | +                    inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |                  return inviteInfo.getSsrcInfo();
 | 
	
		
			
				|  |  |              }else {
 | 
	
		
			
				|  |  |                  StreamInfo streamInfo = inviteInfo.getStreamInfo();
 | 
	
		
			
				|  |  |                  String streamId = streamInfo.getStream();
 | 
	
		
			
				|  |  |                  if (streamId == null) {
 | 
	
		
			
				|  |  |                      callback.run(InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null);
 | 
	
		
			
				|  |  | -                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | -                            InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(),
 | 
	
		
			
				|  |  | -                            "点播失败, redis缓存streamId等于null",
 | 
	
		
			
				|  |  | -                            null);
 | 
	
		
			
				|  |  | +                    if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                        inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                                InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(),
 | 
	
		
			
				|  |  | +                                "点播失败, redis缓存streamId等于null",
 | 
	
		
			
				|  |  | +                                null);
 | 
	
		
			
				|  |  | +                    }else {
 | 
	
		
			
				|  |  | +                        inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                                InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(),
 | 
	
		
			
				|  |  | +                                "点播失败, redis缓存streamId等于null",
 | 
	
		
			
				|  |  | +                                null);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  |                      return inviteInfo.getSsrcInfo();
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |                  String mediaServerId = streamInfo.getMediaServerId();
 | 
	
	
		
			
				|  | @@ -146,41 +160,64 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                  Boolean ready = zlmrtpServerFactory.isStreamReady(mediaInfo, "rtp", streamId);
 | 
	
		
			
				|  |  |                  if (ready != null && ready) {
 | 
	
		
			
				|  |  |                      callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
 | 
	
		
			
				|  |  | -                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | -                            InviteErrorCode.SUCCESS.getCode(),
 | 
	
		
			
				|  |  | -                            InviteErrorCode.SUCCESS.getMsg(),
 | 
	
		
			
				|  |  | -                            streamInfo);
 | 
	
		
			
				|  |  | +                    if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                        inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                                InviteErrorCode.SUCCESS.getCode(),
 | 
	
		
			
				|  |  | +                                InviteErrorCode.SUCCESS.getMsg(),
 | 
	
		
			
				|  |  | +                                streamInfo);
 | 
	
		
			
				|  |  | +                    }else {
 | 
	
		
			
				|  |  | +                        inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                                InviteErrorCode.SUCCESS.getCode(),
 | 
	
		
			
				|  |  | +                                InviteErrorCode.SUCCESS.getMsg(),
 | 
	
		
			
				|  |  | +                                streamInfo);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  |                      return inviteInfo.getSsrcInfo();
 | 
	
		
			
				|  |  |                  }else {
 | 
	
		
			
				|  |  |                      // 点播发起了但是尚未成功, 仅注册回调等待结果即可
 | 
	
		
			
				|  |  | -                    inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback);
 | 
	
		
			
				|  |  | -                    storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
 | 
	
		
			
				|  |  | -                    inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
 | 
	
		
			
				|  |  | +                    if(device.isSwitchPrimarySubStream()) {
 | 
	
		
			
				|  |  | +                        inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback);
 | 
	
		
			
				|  |  | +                        storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
 | 
	
		
			
				|  |  | +                        inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
 | 
	
		
			
				|  |  | +                    }else {
 | 
	
		
			
				|  |  | +                        inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId,isSubStream, null, callback);
 | 
	
		
			
				|  |  | +                        inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          String streamId = null;
 | 
	
		
			
				|  |  |          if (mediaServerItem.isRtpEnable()) {
 | 
	
		
			
				|  |  | -            streamId = String.format("%s_%s", device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +            if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                streamId = StreamInfo.getPlayStream(deviceId, channelId, isSubStream);
 | 
	
		
			
				|  |  | +            }else {
 | 
	
		
			
				|  |  | +                streamId = String.format("%s_%s", device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, 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,
 | 
	
		
			
				|  |  | -                    InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(),
 | 
	
		
			
				|  |  | -                    InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(),
 | 
	
		
			
				|  |  | -                    null);
 | 
	
		
			
				|  |  | +            if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                        InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(),
 | 
	
		
			
				|  |  | +                        InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(),
 | 
	
		
			
				|  |  | +                        null);
 | 
	
		
			
				|  |  | +            }else {
 | 
	
		
			
				|  |  | +                inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                        InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(),
 | 
	
		
			
				|  |  | +                        InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(),
 | 
	
		
			
				|  |  | +                        null);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |              return null;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          // TODO 记录点播的状态
 | 
	
		
			
				|  |  | -        play(mediaServerItem, ssrcInfo, device, channelId, callback);
 | 
	
		
			
				|  |  | +        play(mediaServerItem, ssrcInfo, device, channelId,isSubStream, callback);
 | 
	
		
			
				|  |  |          return ssrcInfo;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  | -    public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
 | 
	
		
			
				|  |  | +    public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,boolean isSubStream,
 | 
	
		
			
				|  |  |                       ErrorCallback<Object> callback) {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          if (mediaServerItem == null || ssrcInfo == null) {
 | 
	
	
		
			
				|  | @@ -189,21 +226,11 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                      null);
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        logger.info("\r\n" +
 | 
	
		
			
				|  |  | -                " [点播开始] \r\n" +
 | 
	
		
			
				|  |  | -                "deviceId  : {}, \r\n" +
 | 
	
		
			
				|  |  | -                "channelId : {},\r\n" +
 | 
	
		
			
				|  |  | -                "收流端口    : {}, \r\n" +
 | 
	
		
			
				|  |  | -                "收流模式    : {}, \r\n" +
 | 
	
		
			
				|  |  | -                "SSRC      : {}, \r\n" +
 | 
	
		
			
				|  |  | -                "SSRC校验   :{}",
 | 
	
		
			
				|  |  | -                device.getDeviceId(),
 | 
	
		
			
				|  |  | -                channelId,
 | 
	
		
			
				|  |  | -                ssrcInfo.getPort(),
 | 
	
		
			
				|  |  | -                device.getStreamMode(),
 | 
	
		
			
				|  |  | -                ssrcInfo.getSsrc(),
 | 
	
		
			
				|  |  | -                device.isSsrcCheck());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +        if( device.isSwitchPrimarySubStream() ){
 | 
	
		
			
				|  |  | +            logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId,isSubStream ? "辅码流" : "主码流", ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
 | 
	
		
			
				|  |  | +        }else {
 | 
	
		
			
				|  |  | +            logger.info("[点播开始] deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          //端口获取失败的ssrcInfo 没有必要发送点播指令
 | 
	
		
			
				|  |  |          if (ssrcInfo.getPort() <= 0) {
 | 
	
		
			
				|  |  |              logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
 | 
	
	
		
			
				|  | @@ -212,23 +239,50 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |              streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null);
 | 
	
		
			
				|  |  | -            inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | -                    InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null);
 | 
	
		
			
				|  |  | +            if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                        InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null);
 | 
	
		
			
				|  |  | +            }else {
 | 
	
		
			
				|  |  | +                inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                        InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          // 初始化redis中的invite消息状态
 | 
	
		
			
				|  |  | -        InviteInfo inviteInfo = InviteInfo.getinviteInfo(device.getDeviceId(), channelId, ssrcInfo.getStream(), ssrcInfo,
 | 
	
		
			
				|  |  | -                mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAY,
 | 
	
		
			
				|  |  | -                InviteSessionStatus.ready);
 | 
	
		
			
				|  |  | -        inviteStreamService.updateInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | +        InviteInfo inviteInfo;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +            // 初始化redis中的invite消息状态
 | 
	
		
			
				|  |  | +            inviteInfo = InviteInfo.getInviteInfo(device.getDeviceId(), channelId,isSubStream, ssrcInfo.getStream(), ssrcInfo,
 | 
	
		
			
				|  |  | +                    mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAY,
 | 
	
		
			
				|  |  | +                    InviteSessionStatus.ready);
 | 
	
		
			
				|  |  | +            inviteStreamService.updateInviteInfoSub(inviteInfo);
 | 
	
		
			
				|  |  | +        }else {
 | 
	
		
			
				|  |  | +            // 初始化redis中的invite消息状态
 | 
	
		
			
				|  |  | +            inviteInfo = InviteInfo.getinviteInfo(device.getDeviceId(), channelId, ssrcInfo.getStream(), ssrcInfo,
 | 
	
		
			
				|  |  | +                    mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAY,
 | 
	
		
			
				|  |  | +                    InviteSessionStatus.ready);
 | 
	
		
			
				|  |  | +            inviteStreamService.updateInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          // 超时处理
 | 
	
		
			
				|  |  |          String timeOutTaskKey = UUID.randomUUID().toString();
 | 
	
		
			
				|  |  |          dynamicTask.startDelay(timeOutTaskKey, () -> {
 | 
	
		
			
				|  |  |              // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况
 | 
	
		
			
				|  |  | -            InviteInfo inviteInfoForTimeOut = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +            InviteInfo inviteInfoForTimeOut;
 | 
	
		
			
				|  |  | +            if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                // 初始化redis中的invite消息状态
 | 
	
		
			
				|  |  | +                inviteInfoForTimeOut = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream);
 | 
	
		
			
				|  |  | +            }else {
 | 
	
		
			
				|  |  | +                // 初始化redis中的invite消息状态
 | 
	
		
			
				|  |  | +                inviteInfoForTimeOut = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |              if (inviteInfoForTimeOut == null || inviteInfoForTimeOut.getStreamInfo() == null) {
 | 
	
		
			
				|  |  | -                logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | +                if( device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                    logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},码流类型:{},端口:{}, SSRC: {}", device.getDeviceId(), channelId,isSubStream ? "辅码流" : "主码流", ssrcInfo.getPort(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | +                }else {
 | 
	
		
			
				|  |  | +                    logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |                  // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
 | 
	
		
			
				|  |  |  //                InviteInfo inviteInfoForTimeout = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.play, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  |  //                if (inviteInfoForTimeout == null) {
 | 
	
	
		
			
				|  | @@ -240,10 +294,16 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |  //                    // 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);
 | 
	
		
			
				|  |  | +                if( device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                            InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null);
 | 
	
		
			
				|  |  | +                    inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +                }else {
 | 
	
		
			
				|  |  | +                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                            InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null);
 | 
	
		
			
				|  |  | +                    inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |                  try {
 | 
	
		
			
				|  |  |                      cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
 | 
	
		
			
				|  |  |                  } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
 | 
	
	
		
			
				|  | @@ -261,25 +321,42 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |          }, userSetting.getPlayTimeout());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          try {
 | 
	
		
			
				|  |  | -            cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
 | 
	
		
			
				|  |  | +            cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId,isSubStream, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
 | 
	
		
			
				|  |  |                  logger.info("收到订阅消息: " + response.toJSONString());
 | 
	
		
			
				|  |  |                  dynamicTask.stop(timeOutTaskKey);
 | 
	
		
			
				|  |  |                  // hook响应
 | 
	
		
			
				|  |  | -                StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +                StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId,isSubStream);
 | 
	
		
			
				|  |  |                  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);
 | 
	
		
			
				|  |  | +                    if( device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                        inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                                InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | +                                InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | +                    }else {
 | 
	
		
			
				|  |  | +                        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);
 | 
	
		
			
				|  |  | -                logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +                if( device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                            InviteErrorCode.SUCCESS.getCode(),
 | 
	
		
			
				|  |  | +                            InviteErrorCode.SUCCESS.getMsg(),
 | 
	
		
			
				|  |  | +                            streamInfo);
 | 
	
		
			
				|  |  | +                }else {
 | 
	
		
			
				|  |  | +                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                            InviteErrorCode.SUCCESS.getCode(),
 | 
	
		
			
				|  |  | +                            InviteErrorCode.SUCCESS.getMsg(),
 | 
	
		
			
				|  |  | +                            streamInfo);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                if( device.isSwitchPrimarySubStream() ){
 | 
	
		
			
				|  |  | +                    logger.info("[点播成功] deviceId: {}, channelId: {},码流类型:{}", device.getDeviceId(), channelId,isSubStream ? "辅码流" : "主码流");
 | 
	
		
			
				|  |  | +                }else {
 | 
	
		
			
				|  |  | +                    logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |                  String streamUrl;
 | 
	
		
			
				|  |  |                  if (mediaServerItemInuse.getRtspPort() != 0) {
 | 
	
		
			
				|  |  |                      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 {
 | 
	
		
			
				|  |  |                  ResponseEvent responseEvent = (ResponseEvent) event.event;
 | 
	
		
			
				|  |  |                  String contentString = new String(responseEvent.getResponse().getRawContent());
 | 
	
		
			
				|  |  |                  // 获取ssrc
 | 
	
		
			
				|  |  | -                String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +                int ssrcIndex = contentString.indexOf("y=");
 | 
	
		
			
				|  |  |                  // 检查是否有y字段
 | 
	
		
			
				|  |  | -                if (ssrcInResponse != null) {
 | 
	
		
			
				|  |  | +                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 {
 | 
	
		
			
				|  |  | -                                Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
 | 
	
		
			
				|  |  | -                                SessionDescription sdp = gb28181Sdp.getBaseSdb();
 | 
	
		
			
				|  |  | +                                SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
 | 
	
		
			
				|  |  |                                  int port = -1;
 | 
	
		
			
				|  |  |                                  Vector mediaDescriptions = sdp.getMediaDescriptions(true);
 | 
	
		
			
				|  |  |                                  for (Object description : mediaDescriptions) {
 | 
	
	
		
			
				|  | @@ -334,21 +412,24 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                                  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);
 | 
	
		
			
				|  |  | +                                if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                                            InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | +                                            InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | +                                }else {
 | 
	
		
			
				|  |  | +                                    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()) {
 | 
	
		
			
				|  |  |                              // 添加订阅
 | 
	
	
		
			
				|  | @@ -361,21 +442,34 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                                  logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
 | 
	
		
			
				|  |  |                                  dynamicTask.stop(timeOutTaskKey);
 | 
	
		
			
				|  |  |                                  // hook响应
 | 
	
		
			
				|  |  | -                                StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +                                StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId,isSubStream);
 | 
	
		
			
				|  |  |                                  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);
 | 
	
		
			
				|  |  | +                                    if( device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                                        inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                                                InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
 | 
	
		
			
				|  |  | +                                                InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
 | 
	
		
			
				|  |  | +                                    }else {
 | 
	
		
			
				|  |  | +                                        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);
 | 
	
		
			
				|  |  | +                                if( device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                                            InviteErrorCode.SUCCESS.getCode(),
 | 
	
		
			
				|  |  | +                                            InviteErrorCode.SUCCESS.getMsg(),
 | 
	
		
			
				|  |  | +                                            streamInfo);
 | 
	
		
			
				|  |  | +                                }else {
 | 
	
		
			
				|  |  | +                                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                                            InviteErrorCode.SUCCESS.getCode(),
 | 
	
		
			
				|  |  | +                                            InviteErrorCode.SUCCESS.getMsg(),
 | 
	
		
			
				|  |  | +                                            streamInfo);
 | 
	
		
			
				|  |  | +                                }
 | 
	
		
			
				|  |  |                              });
 | 
	
		
			
				|  |  |                              return;
 | 
	
		
			
				|  |  |                          }
 | 
	
	
		
			
				|  | @@ -391,14 +485,22 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                              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);
 | 
	
		
			
				|  |  | +                            if( device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                                inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                                        InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
 | 
	
		
			
				|  |  | +                                        "下级自定义了ssrc,重新设置收流信息失败", null);
 | 
	
		
			
				|  |  | +                            }else {
 | 
	
		
			
				|  |  | +                                inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                                        InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
 | 
	
		
			
				|  |  | +                                        "下级自定义了ssrc,重新设置收流信息失败", null);
 | 
	
		
			
				|  |  | +                            }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                          }else {
 | 
	
		
			
				|  |  |                              ssrcInfo.setSsrc(ssrcInResponse);
 | 
	
	
		
			
				|  | @@ -409,7 +511,11 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                          logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正");
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  | -                inviteStreamService.updateInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | +                if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                    inviteStreamService.updateInviteInfoSub(inviteInfo);
 | 
	
		
			
				|  |  | +                }else {
 | 
	
		
			
				|  |  | +                    inviteStreamService.updateInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |              }, (event) -> {
 | 
	
		
			
				|  |  |                  dynamicTask.stop(timeOutTaskKey);
 | 
	
		
			
				|  |  |                  mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
 | 
	
	
		
			
				|  | @@ -420,11 +526,19 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_ERROR.getCode(),
 | 
	
		
			
				|  |  |                          String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
 | 
	
		
			
				|  |  | -                inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | -                        InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
 | 
	
		
			
				|  |  | -                        String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
 | 
	
		
			
				|  |  | +                if( device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                            InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
 | 
	
		
			
				|  |  | +                            String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +                    inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream);
 | 
	
		
			
				|  |  | +                }else {
 | 
	
		
			
				|  |  | +                    inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                            InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
 | 
	
		
			
				|  |  | +                            String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |              });
 | 
	
		
			
				|  |  |          } catch (InvalidArgumentException | SipException | ParseException e) {
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -438,27 +552,51 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              callback.run(InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(),
 | 
	
		
			
				|  |  |                      InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null);
 | 
	
		
			
				|  |  | -            inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | -                    InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(),
 | 
	
		
			
				|  |  | -                    InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null);
 | 
	
		
			
				|  |  | +            if( device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream, null,
 | 
	
		
			
				|  |  | +                        InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(),
 | 
	
		
			
				|  |  | +                        InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +                inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId,isSubStream);
 | 
	
		
			
				|  |  | +            }else {
 | 
	
		
			
				|  |  | +                inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
 | 
	
		
			
				|  |  | +                        InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(),
 | 
	
		
			
				|  |  | +                        InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId) {
 | 
	
		
			
				|  |  | -        StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId);
 | 
	
		
			
				|  |  | +    private StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId,boolean isSubStream) {
 | 
	
		
			
				|  |  | +        StreamInfo streamInfo = null;
 | 
	
		
			
				|  |  | +        Device device = redisCatchStorage.getDevice(deviceId);
 | 
	
		
			
				|  |  | +        if( device.isSwitchPrimarySubStream() ){
 | 
	
		
			
				|  |  | +            streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId,isSubStream);
 | 
	
		
			
				|  |  | +        }else {
 | 
	
		
			
				|  |  | +            streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          if (streamInfo != null) {
 | 
	
		
			
				|  |  | -            DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
 | 
	
		
			
				|  |  | -            if (deviceChannel != null) {
 | 
	
		
			
				|  |  | -                deviceChannel.setStreamId(streamInfo.getStream());
 | 
	
		
			
				|  |  | -                storager.startPlay(deviceId, channelId, streamInfo.getStream());
 | 
	
		
			
				|  |  | +            InviteInfo inviteInfo;
 | 
	
		
			
				|  |  | +            if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream);
 | 
	
		
			
				|  |  | +            }else {
 | 
	
		
			
				|  |  | +                DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
 | 
	
		
			
				|  |  | +                if (deviceChannel != null) {
 | 
	
		
			
				|  |  | +                    deviceChannel.setStreamId(streamInfo.getStream());
 | 
	
		
			
				|  |  | +                    storager.startPlay(deviceId, channelId, streamInfo.getStream());
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | -            InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
 | 
	
		
			
				|  |  |              if (inviteInfo != null) {
 | 
	
		
			
				|  |  |                  inviteInfo.setStatus(InviteSessionStatus.ok);
 | 
	
		
			
				|  |  |                  inviteInfo.setStreamInfo(streamInfo);
 | 
	
		
			
				|  |  | -                inviteStreamService.updateInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | +                if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +                    inviteStreamService.updateInviteInfoSub(inviteInfo);
 | 
	
		
			
				|  |  | +                }else {
 | 
	
		
			
				|  |  | +                    inviteStreamService.updateInviteInfo(inviteInfo);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          return streamInfo;
 | 
	
	
		
			
				|  | @@ -607,16 +745,17 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                          ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
 | 
	
		
			
				|  |  |                          String contentString = new String(responseEvent.getResponse().getRawContent());
 | 
	
		
			
				|  |  |                          // 获取ssrc
 | 
	
		
			
				|  |  | -                        String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +                        int ssrcIndex = contentString.indexOf("y=");
 | 
	
		
			
				|  |  |                          // 检查是否有y字段
 | 
	
		
			
				|  |  | -                        if (ssrcInResponse != null) {
 | 
	
		
			
				|  |  | +                        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 {
 | 
	
		
			
				|  |  | -                                        Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
 | 
	
		
			
				|  |  | -                                        SessionDescription sdp = gb28181Sdp.getBaseSdb();
 | 
	
		
			
				|  |  | +                                        SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
 | 
	
		
			
				|  |  |                                          int port = -1;
 | 
	
		
			
				|  |  |                                          Vector mediaDescriptions = sdp.getMediaDescriptions(true);
 | 
	
		
			
				|  |  |                                          for (Object description : mediaDescriptions) {
 | 
	
	
		
			
				|  | @@ -684,6 +823,8 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                                      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                                      dynamicTask.stop(playBackTimeOutTaskKey);
 | 
	
		
			
				|  |  | +                                    // 释放ssrc
 | 
	
		
			
				|  |  | +                                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                                      streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -799,15 +940,17 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                          ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
 | 
	
		
			
				|  |  |                          String contentString = new String(responseEvent.getResponse().getRawContent());
 | 
	
		
			
				|  |  |                          // 获取ssrc
 | 
	
		
			
				|  |  | -                        String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString);
 | 
	
		
			
				|  |  | +                        int ssrcIndex = contentString.indexOf("y=");
 | 
	
		
			
				|  |  |                          // 检查是否有y字段
 | 
	
		
			
				|  |  | -                        if (ssrcInResponse != null) {
 | 
	
		
			
				|  |  | +                        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 {
 | 
	
		
			
				|  |  | -                                        Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
 | 
	
		
			
				|  |  | -                                        SessionDescription sdp = gb28181Sdp.getBaseSdb();
 | 
	
		
			
				|  |  | +                                        SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
 | 
	
		
			
				|  |  |                                          int port = -1;
 | 
	
		
			
				|  |  |                                          Vector mediaDescriptions = sdp.getMediaDescriptions(true);
 | 
	
		
			
				|  |  |                                          for (Object description : mediaDescriptions) {
 | 
	
	
		
			
				|  | @@ -872,6 +1015,8 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                                      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                                      dynamicTask.stop(downLoadTimeOutTaskKey);
 | 
	
		
			
				|  |  | +                                    // 释放ssrc
 | 
	
		
			
				|  |  | +                                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                                      streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -972,6 +1117,7 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |          return streamInfo;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  |      public void zlmServerOffline(String mediaServerId) {
 | 
	
		
			
				|  |  |          // 处理正在向上推流的上级平台
 | 
	
	
		
			
				|  | @@ -1108,14 +1254,18 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  | -    public void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback) {
 | 
	
		
			
				|  |  | +    public void getSnap(String deviceId, String channelId, String fileName,boolean isSubStream, ErrorCallback errorCallback) {
 | 
	
		
			
				|  |  |          Device device = deviceService.getDevice(deviceId);
 | 
	
		
			
				|  |  |          if (device == null) {
 | 
	
		
			
				|  |  |              errorCallback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), null);
 | 
	
		
			
				|  |  |              return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
 | 
	
		
			
				|  |  | +        InviteInfo inviteInfo;
 | 
	
		
			
				|  |  | +        if(device.isSwitchPrimarySubStream()){
 | 
	
		
			
				|  |  | +             inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId,isSubStream);
 | 
	
		
			
				|  |  | +        }else {
 | 
	
		
			
				|  |  | +            inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          if (inviteInfo != null) {
 | 
	
		
			
				|  |  |              if (inviteInfo.getStreamInfo() != null) {
 | 
	
		
			
				|  |  |                  // 已存在线直接截图
 | 
	
	
		
			
				|  | @@ -1130,10 +1280,9 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |                  // 请求截图
 | 
	
		
			
				|  |  |                  logger.info("[请求截图]: " + fileName);
 | 
	
		
			
				|  |  |                  zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
 | 
	
		
			
				|  |  | -                String filePath = path + File.separator + fileName;
 | 
	
		
			
				|  |  |                  File snapFile = new File(path + File.separator + fileName);
 | 
	
		
			
				|  |  |                  if (snapFile.exists()) {
 | 
	
		
			
				|  |  | -                    errorCallback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), filePath);
 | 
	
		
			
				|  |  | +                    errorCallback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), snapFile.getAbsoluteFile());
 | 
	
		
			
				|  |  |                  }else {
 | 
	
		
			
				|  |  |                      errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null);
 | 
	
		
			
				|  |  |                  }
 | 
	
	
		
			
				|  | @@ -1142,11 +1291,11 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
 | 
	
		
			
				|  |  | -        play(newMediaServerItem, deviceId, channelId, (code, msg, data)->{
 | 
	
		
			
				|  |  | +        play(newMediaServerItem, deviceId, channelId,isSubStream, (code, msg, data)->{
 | 
	
		
			
				|  |  |             if (code == InviteErrorCode.SUCCESS.getCode()) {
 | 
	
		
			
				|  |  |                 InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);
 | 
	
		
			
				|  |  |                 if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) {
 | 
	
		
			
				|  |  | -                   getSnap(deviceId, channelId, fileName, errorCallback);
 | 
	
		
			
				|  |  | +                   getSnap(deviceId, channelId, fileName,isSubStream, errorCallback);
 | 
	
		
			
				|  |  |                 }else {
 | 
	
		
			
				|  |  |                     errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null);
 | 
	
		
			
				|  |  |                 }
 | 
	
	
		
			
				|  | @@ -1156,4 +1305,17 @@ public class PlayServiceImpl implements IPlayService {
 | 
	
		
			
				|  |  |          });
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /*======================设备主子码流逻辑START=========================*/
 | 
	
		
			
				|  |  | +    public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId,boolean isSubStream) {
 | 
	
		
			
				|  |  | +        String streamId = resonse.getString("stream");
 | 
	
		
			
				|  |  | +        JSONArray tracks = resonse.getJSONArray("tracks");
 | 
	
		
			
				|  |  | +        StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem, "rtp", streamId, tracks, null);
 | 
	
		
			
				|  |  | +        streamInfo.setDeviceID(deviceId);
 | 
	
		
			
				|  |  | +        streamInfo.setChannelId(channelId);
 | 
	
		
			
				|  |  | +        streamInfo.setSubStream(isSubStream);
 | 
	
		
			
				|  |  | +        return streamInfo;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    /*======================设备主子码流逻辑END=========================*/
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  }
 |