648540858
2022-09-23 cd117ed22825b8f442e0f2281678f4549be3e109
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -5,6 +5,7 @@
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
@@ -22,17 +23,20 @@
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.GitUtil;
import gov.nist.javax.sip.SIPConstants;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.SipStackImpl;
import gov.nist.javax.sip.message.MessageFactoryImpl;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import gov.nist.javax.sip.stack.SIPClientTransaction;
import gov.nist.javax.sip.stack.SIPClientTransactionImpl;
import gov.nist.javax.sip.stack.SIPDialog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
@@ -41,6 +45,7 @@
import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.util.HashSet;
@@ -80,12 +85,6 @@
   private VideoStreamSessionManager streamSession;
   @Autowired
   private IVideoManagerStorage storager;
   @Autowired
   private IRedisCatchStorage redisCatchStorage;
   @Autowired
   private UserSetting userSetting;
   @Autowired
@@ -97,9 +96,6 @@
   @Autowired
   private IMediaServerService mediaServerService;
   @Autowired
   private DynamicTask dynamicTask;
   /**
    * 云台方向放控制,使用配置文件中的默认镜头移动速度
@@ -110,8 +106,8 @@
     * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
    */
   @Override
   public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) {
      return ptzCmd(device, channelId, leftRight, upDown, 0, sipConfig.getPtzSpeed(), 0);
    public void ptzdirectCmd(Device device, String channelId, int leftRight, int upDown) throws InvalidArgumentException, ParseException, SipException {
        ptzCmd(device, channelId, leftRight, upDown, 0, sipConfig.getPtzSpeed(), 0);
   }
   /**
@@ -124,8 +120,8 @@
     * @param moveSpeed  镜头移动速度
    */
   @Override
   public boolean ptzdirectCmd(Device device, String channelId, int leftRight, int upDown, int moveSpeed) {
      return ptzCmd(device, channelId, leftRight, upDown, 0, moveSpeed, 0);
    public void ptzdirectCmd(Device device, String channelId, int leftRight, int upDown, int moveSpeed) throws InvalidArgumentException, ParseException, SipException {
        ptzCmd(device, channelId, leftRight, upDown, 0, moveSpeed, 0);
   }
   /**
@@ -136,8 +132,8 @@
     * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
    */  
   @Override
   public boolean ptzZoomCmd(Device device, String channelId, int inOut) {
      return ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getPtzSpeed());
    public void ptzZoomCmd(Device device, String channelId, int inOut) throws InvalidArgumentException, ParseException, SipException {
        ptzCmd(device, channelId, 0, 0, inOut, 0, sipConfig.getPtzSpeed());
   }
   /**
@@ -149,50 +145,8 @@
     * @param zoomSpeed  镜头缩放速度
    */ 
   @Override
   public boolean ptzZoomCmd(Device device, String channelId, int inOut, int zoomSpeed) {
      return ptzCmd(device, channelId, 0, 0, inOut, 0, zoomSpeed);
   }
   /**
   * 云台指令码计算
   *
    * @param leftRight  镜头左移右移 0:停止 1:左移 2:右移
    * @param upDown     镜头上移下移 0:停止 1:上移 2:下移
    * @param inOut      镜头放大缩小 0:停止 1:缩小 2:放大
    * @param moveSpeed  镜头移动速度 默认 0XFF (0-255)
    * @param zoomSpeed  镜头缩放速度 默认 0X1 (0-255)
    */
    public static String cmdString(int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) {
      int cmdCode = 0;
      if (leftRight == 2) {
         cmdCode|=0x01;      // 右移
      } else if(leftRight == 1) {
         cmdCode|=0x02;      // 左移
      }
      if (upDown == 2) {
         cmdCode|=0x04;      // 下移
      } else if(upDown == 1) {
         cmdCode|=0x08;      // 上移
      }
      if (inOut == 2) {
         cmdCode |= 0x10;   // 放大
      } else if(inOut == 1) {
         cmdCode |= 0x20;   // 缩小
      }
      StringBuilder builder = new StringBuilder("A50F01");
      String strTmp;
      strTmp = String.format("%02X", cmdCode);
      builder.append(strTmp, 0, 2);
      strTmp = String.format("%02X", moveSpeed);
      builder.append(strTmp, 0, 2);
      builder.append(strTmp, 0, 2);
      strTmp = String.format("%X", zoomSpeed);
      builder.append(strTmp, 0, 1).append("0");
      //计算校验码
      int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + moveSpeed + moveSpeed + (zoomSpeed /*<< 4*/ & 0XF0)) % 0X100;
      strTmp = String.format("%02X", checkCode);
      builder.append(strTmp, 0, 2);
      return builder.toString();
    public void ptzZoomCmd(Device device, String channelId, int inOut, int zoomSpeed) throws InvalidArgumentException, ParseException, SipException {
        ptzCmd(device, channelId, 0, 0, inOut, 0, zoomSpeed);
}
   /**
@@ -233,10 +187,9 @@
     * @param zoomSpeed   镜头缩放速度
    */
   @Override
   public boolean ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
         int zoomSpeed) {
      try {
         String cmdStr= cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
    public void ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
                       int zoomSpeed) throws InvalidArgumentException, SipException, ParseException {
        String cmdStr = SipUtils.cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
         StringBuffer ptzXml = new StringBuffer(200);
         String charset = device.getCharset();
         ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -255,12 +208,7 @@
         Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
         
         transmitRequest(device, request);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
      return false;
        transmitRequest(device.getTransport(), request);
   }
   /**
@@ -274,10 +222,9 @@
     * @param combineCode2   组合码2
    */
   @Override
   public boolean frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) {
      try {
    public void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException {
         String cmdStr= frontEndCmdString(cmdCode, parameter1, parameter2, combineCode2);
         logger.debug("控制字符串:" + cmdStr);
         StringBuffer ptzXml = new StringBuffer(200);
         String charset = device.getCharset();
         ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -295,24 +242,21 @@
         CallIdHeader callIdHeader = device.getTransport().equalsIgnoreCase("TCP") ? tcpSipProvider.getNewCallId()
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
      return false;
        SIPRequest request = (SIPRequest) headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
        transmitRequest(device.getTransport(), request);
   }
   /**
    * 前端控制指令(用于转发上级指令)
     *
    * @param device      控制设备
    * @param channelId      预览通道
    * @param cmdString      前端控制指令串
    */
   @Override
   public boolean fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) {
      try {
    public void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer ptzXml = new StringBuffer(200);
         String charset = device.getCharset();
         ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -331,16 +275,13 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent, okEvent);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
      return false;
        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
   }
   
    /**
    *    请求预览视频流
     *
     * @param device  视频设备
     * @param channelId  预览通道
     * @param event hook订阅
@@ -348,13 +289,12 @@
   */
   @Override
   public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
                       ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
                              ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
      String stream = ssrcInfo.getStream();
      try {
         if (device == null) {
            return;
         }
//         String streamMode = device.getStreamMode().toUpperCase();
         logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
         HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
@@ -426,29 +366,17 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(), callIdHeader);
         transmitRequest(device, request, (e -> {
        transmitRequest(device.getTransport(), request, (e -> {
            streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
            mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
            errorEvent.response(e);
         }), e ->{
            // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
            streamSession.put(device.getDeviceId(), channelId ,"play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), ((ResponseEvent)e.event).getClientTransaction(), VideoStreamSessionManager.SessionType.play);
            Dialog sipDialog = null;
            if (e.dialog == null) {
               SIPClientTransaction clientTransaction = (SIPClientTransaction)((ResponseEvent)e.event).getClientTransaction();
               sipDialog = new SIPDialog(clientTransaction, clientTransaction.getLastResponse());
            }else {
               sipDialog = e.dialog;
            }
            streamSession.put(device.getDeviceId(), channelId ,"play", sipDialog);
            ResponseEvent responseEvent = (ResponseEvent) e.event;
            SIPResponse response = (SIPResponse) responseEvent.getResponse();
            streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play);
            okEvent.response(e);
         });
      } catch ( SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
   }
   
   /**
@@ -462,8 +390,8 @@
   @Override
   public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
                          String startTime, String endTime, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
                          SipSubscribe.Event okEvent,SipSubscribe.Event errorEvent) {
      try {
                                  SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
@@ -539,17 +467,14 @@
               });
           Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader, ssrcInfo.getSsrc());
           transmitRequest(device, request, errorEvent, event -> {
        transmitRequest(device.getTransport(), request, errorEvent, event -> {
            ResponseEvent responseEvent = (ResponseEvent) event.event;
              streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.playback);
            streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), event.dialog);
            SIPResponse response = (SIPResponse) responseEvent.getResponse();
            streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.playback);
            okEvent.response(event);
         });
         if (inviteStreamCallback != null) {
            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
         }
      } catch ( SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
   }
@@ -565,8 +490,8 @@
   @Override
   public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
                          String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent,
                          SipSubscribe.Event errorEvent) {
      try {
                                  SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
         StringBuffer content = new StringBuffer(200);
@@ -643,10 +568,12 @@
                  // 添加流注销的订阅,注销了后向设备发送bye
                  subscribe.addSubscribe(hookSubscribe,
                        (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd)->{
                           ClientTransaction transaction = streamSession.getTransaction(device.getDeviceId(), channelId, ssrcInfo.getStream(), callIdHeader.getCallId());
                           if (transaction != null) {
                              logger.info("[录像]下载结束, 发送BYE");
                              streamByeCmd(device.getDeviceId(), channelId, ssrcInfo.getStream(), callIdHeader.getCallId());
                        try {
                            streamByeCmd(device, channelId, ssrcInfo.getStream(), callIdHeader.getCallId());
                        } catch (InvalidArgumentException | ParseException | SipException |
                                 SsrcTransactionNotFoundException e) {
                            logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage());
                           }
                        });
               });
@@ -655,112 +582,37 @@
         if (inviteStreamCallback != null) {
            inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null, callIdHeader.getCallId(), "rtp", ssrcInfo.getStream()));
         }
           transmitRequest(device, request, errorEvent, okEvent->{
        transmitRequest(device.getTransport(), request, errorEvent, okEvent -> {
            ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
            streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), responseEvent.getClientTransaction(), VideoStreamSessionManager.SessionType.download);
            streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), okEvent.dialog);
            SIPResponse response = (SIPResponse) responseEvent.getResponse();
            streamSession.put(device.getDeviceId(), channelId, callIdHeader.getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download);
         });
      } catch ( SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
   }
   /**
    * 视频流停止, 不使用回调
    */
   @Override
   public void streamByeCmd(String deviceId, String channelId, String stream, String callId) {
      streamByeCmd(deviceId, channelId, stream, callId, null);
    public void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException {
        streamByeCmd(device, channelId, stream, callId, null);
   }
   /**
    * 视频流停止
    */
   @Override
   public void streamByeCmd(String deviceId, String channelId, String stream, String callId, SipSubscribe.Event okEvent) {
      try {
         SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, callId, stream);
         ClientTransaction transaction = streamSession.getTransaction(deviceId, channelId, stream, callId);
    public void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callId, stream);
        if (ssrcTransaction == null) {
            throw new SsrcTransactionNotFoundException(device.getDeviceId(), channelId, callId, stream);
        }
         if (transaction == null ) {
            logger.warn("[ {} -> {}]停止视频流的时候发现事务已丢失", deviceId, channelId);
            SipSubscribe.EventResult<Object> eventResult = new SipSubscribe.EventResult<>();
            if (okEvent != null) {
               okEvent.response(eventResult);
            }
            return;
         }
         SIPDialog dialog;
         if (callId != null) {
            dialog = streamSession.getDialogByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), callId);
         }else {
            if (stream == null && ssrcTransaction == null && ssrcTransaction.getStream() == null) {
               return;
            }
            dialog = streamSession.getDialogByStream(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
         }
         mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
         mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
         streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
         if (dialog == null) {
            logger.warn("[ {} -> {}]停止视频流的时候发现对话已丢失", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
            return;
         }
         SipStack sipStack = udpSipProvider.getSipStack();
         SIPDialog sipDialog = ((SipStackImpl) sipStack).putDialog(dialog);
         if (dialog != sipDialog) {
            dialog = sipDialog;
         }else {
            dialog.setSipProvider(udpSipProvider);
            try {
               Field sipStackField = SIPDialog.class.getDeclaredField("sipStack");
               sipStackField.setAccessible(true);
               sipStackField.set(dialog, sipStack);
               Field eventListenersField = SIPDialog.class.getDeclaredField("eventListeners");
               eventListenersField.setAccessible(true);
               eventListenersField.set(dialog, new HashSet<>());
            } catch (NoSuchFieldException | IllegalAccessException e) {
               e.printStackTrace();
            }
         }
         Request byeRequest = dialog.createRequest(Request.BYE);
         SipURI byeURI = (SipURI) byeRequest.getRequestURI();
         SIPRequest request = (SIPRequest)transaction.getRequest();
         byeURI.setHost(request.getRemoteAddress().getHostAddress());
         byeURI.setPort(request.getRemotePort());
         byeURI.setUser(channelId);
         ViaHeader viaHeader = (ViaHeader) byeRequest.getHeader(ViaHeader.NAME);
         String protocol = viaHeader.getTransport().toUpperCase();
         viaHeader.setRPort();
         // 增加Contact header
         Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getIp()+":"+sipConfig.getPort()));
         byeRequest.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
         UserAgentHeader userAgentHeader = SipUtils.createUserAgentHeader(sipFactory, gitUtil);
         byeRequest.addHeader(userAgentHeader);
         ClientTransaction clientTransaction = null;
         if("TCP".equals(protocol)) {
            clientTransaction = tcpSipProvider.getNewClientTransaction(byeRequest);
         } else if("UDP".equals(protocol)) {
            clientTransaction = udpSipProvider.getNewClientTransaction(byeRequest);
         }
         CallIdHeader callIdHeader = (CallIdHeader) byeRequest.getHeader(CallIdHeader.NAME);
         if (okEvent != null) {
            sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), okEvent);
         }
         CSeqHeader cSeqHeader = (CSeqHeader)byeRequest.getHeader(CSeqHeader.NAME);
         cSeqHeader.setSeqNumber(redisCatchStorage.getCSEQ());
         dialog.sendRequest(clientTransaction);
      } catch (SipException | ParseException e) {
         e.printStackTrace();
      } catch (InvalidArgumentException e) {
         throw new RuntimeException(e);
      }
        Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
        transmitRequest(device.getTransport(), byteRequest, null, okEvent);
   }
   /**
@@ -770,9 +622,7 @@
    * @param channelId  预览通道
    */
   @Override
   public boolean audioBroadcastCmd(Device device, String channelId) {
      // 改为新的实现
      return false;
    public void audioBroadcastCmd(Device device, String channelId) {
   }
   /**
@@ -781,8 +631,8 @@
    * @param device  视频设备
    */
   @Override
   public boolean audioBroadcastCmd(Device device) {
      try {
    public void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer broadcastXml = new StringBuffer(200);
         String charset = device.getCharset();
         broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -797,16 +647,13 @@
               : udpSipProvider.getNewCallId();
                        
         Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
        transmitRequest(device.getTransport(), request);
      } 
      return false;
   }
   @Override
   public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) {
      try {
    public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer broadcastXml = new StringBuffer(200);
         String charset = device.getCharset();
         broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -821,10 +668,8 @@
               : udpSipProvider.getNewCallId();
                        
         Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
        transmitRequest(device.getTransport(), request, errorEvent);
   } 
   
   
@@ -836,8 +681,7 @@
    * @param recordCmdStr   录像命令:Record / StopRecord
    */  
   @Override
   public boolean recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) {
      try {
    public void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -856,12 +700,7 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
        transmitRequest(device.getTransport(), request, errorEvent);
   }
   /**
@@ -870,8 +709,8 @@
    * @param device   视频设备
    */
   @Override
   public boolean teleBootCmd(Device device) {
      try {
    public void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -886,12 +725,7 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
        transmitRequest(device.getTransport(), request);
   }
   
   /**
@@ -901,8 +735,8 @@
    * @param guardCmdStr   "SetGuard"/"ResetGuard"
    */
   @Override
   public boolean guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) {
      try {
    public void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -917,12 +751,7 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
        transmitRequest(device.getTransport(), request, errorEvent);
   }
   /**
@@ -931,8 +760,8 @@
    * @param device  视频设备
    */  
   @Override
   public boolean alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) {
      try {
    public void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -959,12 +788,7 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
        transmitRequest(device.getTransport(), request, errorEvent);
   }
   /**
@@ -974,8 +798,8 @@
    * @param channelId  预览通道
    */ 
   @Override
   public boolean iFrameCmd(Device device, String channelId) {
      try {
    public void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -994,12 +818,7 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
        transmitRequest(device.getTransport(), request);
   }
   /**
@@ -1011,8 +830,8 @@
    * @param presetIndex   调用预置位编号,开启看守位时使用,取值范围0~255
    */  
   @Override
   public boolean homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) {
      try {
    public void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1047,12 +866,7 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
        transmitRequest(device.getTransport(), request, errorEvent);
   }
   /**
@@ -1061,9 +875,8 @@
    * @param device  视频设备
    */  
   @Override
   public boolean deviceConfigCmd(Device device) {
    public void deviceConfigCmd(Device device) {
      // TODO Auto-generated method stub
      return false;
   }
   /**
@@ -1077,9 +890,9 @@
    * @param heartBeatCount   心跳超时次数(可选)
    */  
   @Override
   public boolean deviceBasicConfigCmd(Device device, String channelId, String name, String expiration,
                              String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) {
      try {
    public void deviceBasicConfigCmd(Device device, String channelId, String name, String expiration,
                                     String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1117,12 +930,7 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
        transmitRequest(device.getTransport(), request, errorEvent);
   }
   /**
@@ -1131,8 +939,8 @@
    * @param device 视频设备
    */  
   @Override
   public boolean deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) {
      try {
    public void deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         String charset = device.getCharset();
         StringBuffer catalogXml = new StringBuffer(200);
         catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1147,13 +955,7 @@
         Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
        transmitRequest(device.getTransport(), request, errorEvent);
   }
   /**
@@ -1162,8 +964,8 @@
    * @param device 视频设备
    */  
   @Override
   public boolean deviceInfoQuery(Device device) {
      try {
    public void deviceInfoQuery(Device device) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer catalogXml = new StringBuffer(200);
         String charset = device.getCharset();
         catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1178,13 +980,8 @@
         Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request);
        transmitRequest(device.getTransport(), request);
         
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
      return true;
   }
   /**
@@ -1193,8 +990,8 @@
    * @param device 视频设备
    */ 
   @Override
   public boolean catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) {
      try {
    public void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException {
         StringBuffer catalogXml = new StringBuffer(200);
         String charset = device.getCharset();
         catalogXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1209,12 +1006,7 @@
         Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(),  SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
      return true;
        transmitRequest(device.getTransport(), request, errorEvent);
   }
   /**
@@ -1225,14 +1017,14 @@
    * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss
    */  
   @Override
   public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
    public void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
      if (secrecy == null) {
         secrecy = 0;
      }
      if (type == null) {
         type = "all";
      }
      try {
         StringBuffer recordInfoXml = new StringBuffer(200);
         String charset = device.getCharset();
         recordInfoXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1261,12 +1053,7 @@
         Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(),
               SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent, okEvent);
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
      return true;
        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
   }
   /**
@@ -1282,9 +1069,9 @@
    * @return            true = 命令发送成功
    */
   @Override
   public boolean alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType,
                         String startTime, String endTime, SipSubscribe.Event errorEvent) {
      try {
    public void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, String alarmType,
                               String startTime, String endTime, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1316,12 +1103,7 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
        transmitRequest(device.getTransport(), request, errorEvent);
   }
   /**
@@ -1332,8 +1114,8 @@
    * @param configType   配置类型:
    */
   @Override
   public boolean deviceConfigQuery(Device device, String channelId, String configType,  SipSubscribe.Event errorEvent) {
      try {
    public void deviceConfigQuery(Device device, String channelId, String configType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1352,12 +1134,7 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
        transmitRequest(device.getTransport(), request, errorEvent);
   }
   /**
@@ -1366,8 +1143,8 @@
    * @param device 视频设备
    */  
   @Override
   public boolean presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) {
      try {
    public void presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1385,12 +1162,7 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
        transmitRequest(device.getTransport(), request, errorEvent);
   }
   /**
@@ -1399,8 +1171,8 @@
    * @param device 视频设备
    */  
   @Override
   public boolean mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) {
      try {
    public void mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer mobilePostitionXml = new StringBuffer(200);
         String charset = device.getCharset();
         mobilePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1416,13 +1188,8 @@
         Request request = headerProvider.createMessageRequest(device, mobilePostitionXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request, errorEvent);
        transmitRequest(device.getTransport(), request, errorEvent);
         
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
         return false;
      }
      return true;
   }
   /**
@@ -1432,8 +1199,8 @@
    * @return         true = 命令发送成功
    */
   @Override
   public SIPRequest mobilePositionSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) {
      try {
    public SIPRequest mobilePositionSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer subscribePostitionXml = new StringBuffer(200);
         String charset = device.getCharset();
         subscribePostitionXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1456,14 +1223,8 @@
         }
         SIPRequest request = (SIPRequest)headerProvider.createSubscribeRequest(device, subscribePostitionXml.toString(), requestOld, device.getSubscribeCycleForMobilePosition(), "presence" ,callIdHeader); //Position;id=" + tm.substring(tm.length() - 4));
         transmitRequest(device, request, errorEvent, okEvent);
        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
         return request;
      } catch ( NumberFormatException | ParseException | InvalidArgumentException   | SipException e) {
         e.printStackTrace();
         return null;
      }
   }
   /**
@@ -1480,8 +1241,8 @@
    * @return            true = 命令发送成功
    */
   @Override
   public boolean alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) {
      try {
    public void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1513,19 +1274,13 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "presence" , callIdHeader);
         transmitRequest(device, request);
        transmitRequest(device.getTransport(), request);
         return true;
      } catch ( NumberFormatException | ParseException | InvalidArgumentException   | SipException e) {
         e.printStackTrace();
         return false;
      }
   }
   @Override
   public SIPRequest catalogSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) {
      try {
    public SIPRequest catalogSubscribe(Device device, SIPRequest requestOld, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer cmdXml = new StringBuffer(200);
         String charset = device.getCharset();
         cmdXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1547,18 +1302,13 @@
         // 有效时间默认为60秒以上
         SIPRequest request = (SIPRequest)headerProvider.createSubscribeRequest(device, cmdXml.toString(), requestOld,  device.getSubscribeCycleForCatalog(), "Catalog" ,
               callIdHeader);
         transmitRequest(device, request, errorEvent, okEvent);
        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
         return request;
      } catch ( NumberFormatException | ParseException | InvalidArgumentException   | SipException e) {
         e.printStackTrace();
         return null;
      }
   }
   @Override
   public boolean dragZoomCmd(Device device, String channelId, String cmdString) {
      try {
    public void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException {
         StringBuffer dragXml = new StringBuffer(200);
         String charset = device.getCharset();
         dragXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
@@ -1576,30 +1326,23 @@
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, dragXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
         logger.debug("拉框信令: " + request.toString());
         transmitRequest(device, request);
         return true;
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
      return false;
        transmitRequest(device.getTransport(), request);
   }
   private ClientTransaction transmitRequest(Device device, Request request) throws SipException {
      return transmitRequest(device, request, null, null);
    @Override
    public void transmitRequest(String transport, Request request) throws SipException, ParseException {
        transmitRequest(transport, request, null, null);
   }
   private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent) throws SipException {
      return transmitRequest(device, request, errorEvent, null);
    @Override
    public void transmitRequest(String transport, Request request, SipSubscribe.Event errorEvent) throws SipException, ParseException {
        transmitRequest(transport, request, errorEvent, null);
   }
   private ClientTransaction transmitRequest(Device device, Request request, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException {
      ClientTransaction clientTransaction = null;
      if("TCP".equals(device.getTransport())) {
         clientTransaction = tcpSipProvider.getNewClientTransaction(request);
      } else if("UDP".equals(device.getTransport())) {
         clientTransaction = udpSipProvider.getNewClientTransaction(request);
      }
    @Override
    public void transmitRequest(String transport, Request request, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, ParseException {
      if (request.getHeader(UserAgentHeader.NAME) == null) {
         try {
            request.addHeader(SipUtils.createUserAgentHeader(sipFactory, gitUtil));
@@ -1607,6 +1350,7 @@
            logger.error("添加UserAgentHeader失败", e);
         }
      }
      CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
      // 添加错误订阅
      if (errorEvent != null) {
@@ -1624,128 +1368,66 @@
            sipSubscribe.removeErrorSubscribe(eventResult.callId);
         });
      }
      clientTransaction.sendRequest();
      return clientTransaction;
        if ("TCP".equals(transport)) {
            tcpSipProvider.sendRequest(request);
        } else if ("UDP".equals(transport)) {
            udpSipProvider.sendRequest(request);
   }
    }
   /**
    * 回放暂停
    */
   @Override
   public void playPauseCmd(Device device, StreamInfo streamInfo) {
      try {
    public void playPauseCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException {
         StringBuffer content = new StringBuffer(200);
         content.append("PAUSE RTSP/1.0\r\n");
         content.append("CSeq: " + getInfoCseq() + "\r\n");
         content.append("PauseTime: now\r\n");
         Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
         if (request == null) {
            return;
         }
         logger.info(request.toString());
         ClientTransaction clientTransaction = null;
         if ("TCP".equals(device.getTransport())) {
            clientTransaction = tcpSipProvider.getNewClientTransaction(request);
         } else if ("UDP".equals(device.getTransport())) {
            clientTransaction = udpSipProvider.getNewClientTransaction(request);
         }
         if (clientTransaction != null) {
            clientTransaction.sendRequest();
        playbackControlCmd(device, streamInfo, content.toString(), null, null);
         }
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
   }
   /**
    * 回放恢复
    */
   @Override
   public void playResumeCmd(Device device, StreamInfo streamInfo) {
      try {
    public void playResumeCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException {
         StringBuffer content = new StringBuffer(200);
         content.append("PLAY RTSP/1.0\r\n");
         content.append("CSeq: " + getInfoCseq() + "\r\n");
         content.append("Range: npt=now-\r\n");
         Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
         if (request == null) {
            return;
         }
         logger.info(request.toString());
         ClientTransaction clientTransaction = null;
         if ("TCP".equals(device.getTransport())) {
            clientTransaction = tcpSipProvider.getNewClientTransaction(request);
         } else if ("UDP".equals(device.getTransport())) {
            clientTransaction = udpSipProvider.getNewClientTransaction(request);
         }
         clientTransaction.sendRequest();
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
        playbackControlCmd(device, streamInfo, content.toString(), null, null);
   }
   /**
    * 回放拖动播放
    */
   @Override
   public void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) {
      try {
    public void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) throws InvalidArgumentException, ParseException, SipException {
         StringBuffer content = new StringBuffer(200);
         content.append("PLAY RTSP/1.0\r\n");
         content.append("CSeq: " + getInfoCseq() + "\r\n");
         content.append("Range: npt=" + Math.abs(seekTime) + "-\r\n");
         Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
         if (request == null) {
            return;
         }
         logger.info(request.toString());
         ClientTransaction clientTransaction = null;
         if ("TCP".equals(device.getTransport())) {
            clientTransaction = tcpSipProvider.getNewClientTransaction(request);
         } else if ("UDP".equals(device.getTransport())) {
            clientTransaction = udpSipProvider.getNewClientTransaction(request);
         }
         clientTransaction.sendRequest();
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
        playbackControlCmd(device, streamInfo, content.toString(), null, null);
   }
   /**
    * 回放倍速播放
    */
   @Override
   public void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) {
      try {
    public void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException {
         StringBuffer content = new StringBuffer(200);
         content.append("PLAY RTSP/1.0\r\n");
         content.append("CSeq: " + getInfoCseq() + "\r\n");
         content.append("Scale: " + String.format("%.6f",speed) + "\r\n");
         Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
         if (request == null) {
            return;
         }
         logger.info(request.toString());
         ClientTransaction clientTransaction = null;
         if ("TCP".equals(device.getTransport())) {
            clientTransaction = tcpSipProvider.getNewClientTransaction(request);
         } else if ("UDP".equals(device.getTransport())) {
            clientTransaction = udpSipProvider.getNewClientTransaction(request);
         }
         clientTransaction.sendRequest();
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
        playbackControlCmd(device, streamInfo, content.toString(), null, null);
   }
   private int getInfoCseq() {
@@ -1753,49 +1435,31 @@
   }
   
   @Override
   public void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) {
      try {
         Request request = headerProvider.createInfoRequest(device, streamInfo, content);
         if (request == null) {
    public void playbackControlCmd(Device device, StreamInfo streamInfo, String content, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException {
        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), streamInfo.getChannelId(), null, streamInfo.getStream());
        if (ssrcTransaction == null) {
            logger.info("[回放控制]未找到视频流信息,设备:{}, 流ID: {}", device.getDeviceId(), streamInfo.getStream());
            return;
         }
         ClientTransaction clientTransaction = null;
         if ("TCP".equals(device.getTransport())) {
            clientTransaction = tcpSipProvider.getNewClientTransaction(request);
         } else if ("UDP".equals(device.getTransport())) {
            clientTransaction = udpSipProvider.getNewClientTransaction(request);
         }
         CallIdHeader callIdHeader = (CallIdHeader)request.getHeader(CallIdHeader.NAME);
         if(errorEvent != null) {
            sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> {
               errorEvent.response(eventResult);
               sipSubscribe.removeErrorSubscribe(eventResult.callId);
               sipSubscribe.removeOkSubscribe(eventResult.callId);
            }));
        SIPRequest request = headerProvider.createInfoRequest(device, streamInfo.getChannelId(), content.toString(), ssrcTransaction.getSipTransactionInfo());
        if (request == null) {
            logger.info("[回放控制]构建Request信息失败,设备:{}, 流ID: {}", device.getDeviceId(), streamInfo.getStream());
            return;
         }
         
         if(okEvent != null) {
            sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> {
               okEvent.response(eventResult);
               sipSubscribe.removeOkSubscribe(eventResult.callId);
               sipSubscribe.removeErrorSubscribe(eventResult.callId);
            });
         }
         clientTransaction.sendRequest();
      } catch (SipException | ParseException | InvalidArgumentException e) {
         e.printStackTrace();
      }
        transmitRequest(device.getTransport(), request, errorEvent, okEvent);
   }
   @Override
   public boolean sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) {
    public void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException {
      if (device == null) {
         return false;
            return;
      }
      logger.info("[发送 报警通知] {}/{}->{},{}", device.getDeviceId(), deviceAlarm.getChannelId(),
            deviceAlarm.getLongitude(), deviceAlarm.getLatitude());
      try {
         String characterSet = device.getCharset();
         StringBuffer deviceStatusXml = new StringBuffer(600);
         deviceStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
@@ -1817,15 +1481,8 @@
         CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
               : udpSipProvider.getNewCallId();
         Request request = headerProvider.createMessageRequest(device, deviceStatusXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, callIdHeader);
         transmitRequest(device, request);
        transmitRequest(device.getTransport(), request);
      } catch (SipException | ParseException  e) {
         e.printStackTrace();
         return false;
      } catch (InvalidArgumentException e) {
         throw new RuntimeException(e);
      }
      return true;
   }
}