From 4b827f3897600e97023ded3df83a2f2551131d53 Mon Sep 17 00:00:00 2001
From: 648540858 <648540858@qq.com>
Date: 星期二, 13 十二月 2022 11:57:07 +0800
Subject: [PATCH] 级联语音对讲部分

---
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java                                                   |    6 
 src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java                                                            |   48 ++
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java |   20 
 src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java                                                    |    4 
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java                                    |   88 ++++
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java                             |   22 
 src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java                                                                 |   28 +
 web_src/src/components/dialog/devicePlayer.vue                                                                                    |    8 
 doc/_sidebar.md                                                                                                                   |    1 
 src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java                                                                         |   10 
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java                                            |   79 +++
 doc/README.md                                                                                                                     |    4 
 src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java                                                            |    2 
 src/main/java/com/genersoft/iot/vmp/service/IPlayService.java                                                                     |    9 
 src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java                                                         |  155 +++++++
 doc/_content/theory/broadcast_cascade.md                                                                                          |   46 ++
 src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java                                                              |    6 
 src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java                                                               |    6 
 src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java                                                             |   50 +-
 src/main/resources/all-application.yml                                                                                            |    4 
 src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java                                                         |   83 ++++
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java                                           |    2 
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java                                  |   59 --
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java                               |    7 
 src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java                                                            |   91 +---
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java                                        |  123 ++++++
 src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java     |  200 ++++++++++
 src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java                                                     |    2 
 28 files changed, 940 insertions(+), 223 deletions(-)

diff --git a/doc/README.md b/doc/README.md
index 0fb5b86..2bbc184 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -52,7 +52,7 @@
     - [X] 鎶ヨ璁㈤槄
     - [X] 鐩綍璁㈤槄
 - [ ] 璇煶骞挎挱
-- [ ] 璇煶瀵硅
+- [ ] 璇煶鍠婅瘽
 
 **浣滀负涓嬬骇骞冲彴**
 - [X] 娉ㄥ唽
@@ -91,7 +91,7 @@
     - [ ] 鎶ヨ璁㈤槄
     - [X] 鐩綍璁㈤槄
 - [ ] 璇煶骞挎挱
-- [ ] 璇煶瀵硅
+- [ ] 璇煶鍠婅瘽
 
    
 
diff --git a/doc/_content/theory/broadcast_cascade.md b/doc/_content/theory/broadcast_cascade.md
new file mode 100644
index 0000000..e59b8c2
--- /dev/null
+++ b/doc/_content/theory/broadcast_cascade.md
@@ -0,0 +1,46 @@
+<!-- 鐐规挱娴佺▼ -->
+
+# 鐐规挱娴佺▼
+> 浠ヤ笅涓篧VP-PRO绾ц仈璇煶鍠婅瘽娴佺▼銆�
+
+```plantuml
+@startuml
+"涓婄骇骞冲彴"  -> "涓嬬骇骞冲彴": 1. 鍙戣捣璇煶鍠婅瘽璇锋眰
+"涓婄骇骞冲彴" <--  "涓嬬骇骞冲彴": 2. 200OK
+"涓婄骇骞冲彴" <- "涓嬬骇骞冲彴": 3. 鍥炲Result OK
+"涓婄骇骞冲彴" -->  "涓嬬骇骞冲彴": 4. 200OK
+
+"涓嬬骇骞冲彴"  -> "璁惧": 5. 鍙戣捣璇煶鍠婅瘽璇锋眰
+"涓嬬骇骞冲彴" <--  "璁惧": 6. 200OK
+"涓嬬骇骞冲彴" <- "璁惧": 7. 鍥炲Result OK
+"涓嬬骇骞冲彴" -->  "璁惧": 8. 200OK
+
+"涓嬬骇骞冲彴"  <- "璁惧": 9. invite(broadcast)
+"涓嬬骇骞冲彴"  --> "璁惧": 10. 100 trying
+"涓嬬骇骞冲彴"  --> "璁惧": 11. 200OK SDP
+"涓嬬骇骞冲彴"  <-- "璁惧": 12. ack
+
+"涓婄骇骞冲彴"  <- "涓嬬骇骞冲彴": 13. invite(broadcast)
+"涓婄骇骞冲彴"  --> "涓嬬骇骞冲彴": 14. 100 trying
+"涓婄骇骞冲彴"  --> "涓嬬骇骞冲彴": 15. 200OK SDP
+"涓婄骇骞冲彴"  <-- "涓嬬骇骞冲彴": 16. ack
+
+"涓婄骇骞冲彴"  -> "涓嬬骇骞冲彴": 17. 鎺ㄩ�丷TP
+"涓嬬骇骞冲彴"  -> "璁惧": 18. 鎺ㄩ�丷TP
+
+@enduml
+```
+
+
+##  娉ㄥ唽娴佺▼鎻忚堪濡備笅:
+1. 鐢ㄦ埛浠庣綉椤垫垨璋冪敤鎺ュ彛鍙戣捣鐐规挱璇锋眰;
+2. WVP-PRO鍚戞憚鍍忔満鍙戦�両nvite娑堟伅,娑堟伅澶村煙涓惡甯� Subject瀛楁,琛ㄦ槑鐐规挱鐨勮棰戞簮ID銆佸彂閫佹柟濯掍綋娴佸簭鍒楀彿銆乑LMediaKit鎺ユ敹娴佷娇鐢ㄧ殑IP銆佺鍙e彿銆�
+   鎺ユ敹绔獟浣撴祦搴忓垪鍙风瓑鍙傛暟,SDP娑堟伅浣撲腑 s瀛楁涓衡�淧lay鈥濅唬琛ㄥ疄鏃剁偣鎾紝y瀛楁鎻忚堪SSRC鍊�,f瀛楁鎻忚堪濯掍綋鍙傛暟銆�
+3. 鎽勫儚鏈哄悜WVP-PRO鍥炲200OK锛屾秷鎭綋涓弿杩颁簡濯掍綋娴佸彂閫佽�呭彂閫佸獟浣撴祦鐨処P銆佺鍙c�佸獟浣撴牸寮忋�丼SRC瀛楁绛夊唴瀹广��
+4. WVP-PRO鍚戣澶囧洖澶岮ck锛� 浼氳瘽寤虹珛鎴愬姛銆�
+5. 璁惧鍚慫LMediaKit鍙戦�佸疄鏃舵祦銆�
+6. ZLMediaKit鍚慦VP-PRO鍙戦�佹祦鏀瑰彉浜嬩欢銆�
+7. WVP-PRO鍚慦EB鐢ㄦ埛鍥炲鎾斁鍦板潃銆�
+8. ZLMediaKit鍚慦VP鍙戦�佹祦鏃犱汉瑙傜湅浜嬩欢銆�
+9. WVP-PRO鍚戣澶囧洖澶岯ye锛� 缁撴潫浼氳瘽銆�
+10. 璁惧鍥炲200OK锛屼細璇濈粨鏉熸垚鍔熴��
diff --git a/doc/_sidebar.md b/doc/_sidebar.md
index 3b10bae..05101c1 100644
--- a/doc/_sidebar.md
+++ b/doc/_sidebar.md
@@ -19,6 +19,7 @@
   * [鏍戝舰缁撴瀯](_content/theory/channel_tree.md)
   * [娉ㄥ唽娴佺▼](_content/theory/register.md)
   * [鐐规挱娴佺▼](_content/theory/play.md)
+  * [绾ц仈璇煶鍠婅瘽娴佺▼](_content/theory/broadcast_cascade.md)
 * **蹇呭鎶�宸�**
   * [鎶撳寘](_content/skill/tcpdump.md)
 
diff --git a/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java b/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
index a2d3054..581ea6f 100644
--- a/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
+++ b/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
@@ -47,6 +47,8 @@
 
     private String thirdPartyGBIdReg = "[\\s\\S]*";
 
+    private String broadcastForPlatform = "UDP";
+
     private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
 
     public Boolean getSavePositionHistory() {
@@ -196,4 +198,12 @@
     public void setSyncChannelOnDeviceOnline(Boolean syncChannelOnDeviceOnline) {
         this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline;
     }
+
+    public String getBroadcastForPlatform() {
+        return broadcastForPlatform;
+    }
+
+    public void setBroadcastForPlatform(String broadcastForPlatform) {
+        this.broadcastForPlatform = broadcastForPlatform;
+    }
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java
index 88db807..d9e89fa 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java
@@ -1,6 +1,8 @@
 package com.genersoft.iot.vmp.gb28181.bean;
 
 
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
 import gov.nist.javax.sip.message.SIPResponse;
 
 /**
@@ -10,10 +12,24 @@
 public class AudioBroadcastCatch {
 
 
-    public AudioBroadcastCatch(String deviceId, String channelId, AudioBroadcastCatchStatus status) {
+    public AudioBroadcastCatch(
+            String deviceId,
+            String channelId,
+            MediaServerItem mediaServerItem,
+            String app,
+            String stream,
+            AudioBroadcastEvent event,
+            AudioBroadcastCatchStatus status,
+            boolean isFromPlatform
+    ) {
         this.deviceId = deviceId;
         this.channelId = channelId;
         this.status = status;
+        this.event = event;
+        this.isFromPlatform = isFromPlatform;
+        this.app = app;
+        this.stream = stream;
+        this.mediaServerItem = mediaServerItem;
     }
 
     public AudioBroadcastCatch() {
@@ -30,6 +46,26 @@
     private String channelId;
 
     /**
+     * 娴佸獟浣撲俊鎭�
+     */
+    private MediaServerItem mediaServerItem;
+
+    /**
+     * 鍏宠仈鐨勬祦APP
+     */
+    private String app;
+
+    /**
+     * 鍏宠仈鐨勬祦STREAM
+     */
+    private String stream;
+
+    /**
+     *  鏄惁鏄骇鑱旇闊冲枈璇�
+     */
+    private boolean isFromPlatform;
+
+    /**
      * 璇煶骞挎挱鐘舵��
      */
     private AudioBroadcastCatchStatus status;
@@ -38,6 +74,11 @@
      * 璇锋眰淇℃伅
      */
     private SipTransactionInfo sipTransactionInfo;
+
+    /**
+     * 璇锋眰缁撴灉鍥炶皟
+     */
+    private AudioBroadcastEvent event;
 
 
     public String getDeviceId() {
@@ -75,4 +116,44 @@
     public void setSipTransactionInfoByRequset(SIPResponse response) {
         this.sipTransactionInfo = new SipTransactionInfo(response, false);
     }
+
+    public AudioBroadcastEvent getEvent() {
+        return event;
+    }
+
+    public void setEvent(AudioBroadcastEvent event) {
+        this.event = event;
+    }
+
+    public String getApp() {
+        return app;
+    }
+
+    public void setApp(String app) {
+        this.app = app;
+    }
+
+    public String getStream() {
+        return stream;
+    }
+
+    public void setStream(String stream) {
+        this.stream = stream;
+    }
+
+    public boolean isFromPlatform() {
+        return isFromPlatform;
+    }
+
+    public void setFromPlatform(boolean fromPlatform) {
+        isFromPlatform = fromPlatform;
+    }
+
+    public MediaServerItem getMediaServerItem() {
+        return mediaServerItem;
+    }
+
+    public void setMediaServerItem(MediaServerItem mediaServerItem) {
+        this.mediaServerItem = mediaServerItem;
+    }
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java
index a3098fb..dde7639 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java
@@ -2,7 +2,7 @@
 
 public enum InviteStreamType {
 
-    PLAY,PLAYBACK,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,TALK
+    PLAY,PLAYBACK,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,BROADCAST,TALK
 
 
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
index b056cc7..262ac55 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
@@ -66,7 +66,7 @@
      * 璁惧绔彛
      */
     @Schema(description = "璁惧绔彛")
-    private String devicePort;
+    private int devicePort;
 
     /**
      * SIP璁よ瘉鐢ㄦ埛鍚�(榛樿浣跨敤璁惧鍥芥爣缂栧彿)
@@ -261,11 +261,11 @@
         this.deviceIp = deviceIp;
     }
 
-    public String getDevicePort() {
+    public int getDevicePort() {
         return devicePort;
     }
 
-    public void setDevicePort(String devicePort) {
+    public void setDevicePort(int devicePort) {
         this.devicePort = devicePort;
     }
 
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
index e7409bf..5cc9cb9 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
@@ -1,6 +1,5 @@
 package com.genersoft.iot.vmp.gb28181.event;
 
-import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.DeviceNotFoundEvent;
 import gov.nist.javax.sip.message.SIPRequest;
 import org.slf4j.Logger;
@@ -87,6 +86,11 @@
         public String callId;
         public EventObject event;
 
+        public EventResult(int statusCode, String msg) {
+            this.statusCode = statusCode;
+            this.msg = msg;
+        }
+
         public EventResult(EventObject event) {
             this.event = event;
             if (event instanceof ResponseEvent) {
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java
index 7186fad..072d0cb 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java
@@ -23,10 +23,6 @@
 
     public static Map<String, AudioBroadcastCatch> data = new ConcurrentHashMap<>();
 
-    public void add(AudioBroadcastCatch audioBroadcastCatch) {
-        this.update(audioBroadcastCatch);
-    }
-
     public void update(AudioBroadcastCatch audioBroadcastCatch) {
         if (SipUtils.isFrontEnd(audioBroadcastCatch.getDeviceId())) {
             data.put(audioBroadcastCatch.getDeviceId(), audioBroadcastCatch);
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
index a351445..43d4186 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java
@@ -49,8 +49,6 @@
 
 	public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM";
 
-	public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST";
-
 	private Map<String, Map<String, DeferredResultEx>> map = new ConcurrentHashMap<>();
 
 
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
index 13a36d7..f0d453b 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
@@ -1,8 +1,12 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd;
 
+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.media.zlm.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
+import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 
 import javax.sip.InvalidArgumentException;
 import javax.sip.SipException;
@@ -14,77 +18,98 @@
 
     /**
      * 鍚戜笂绾у钩鍙版敞鍐�
+     *
      * @param parentPlatform
      * @return
      */
-    void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
-    void register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException;
+    void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
+            throws InvalidArgumentException, ParseException, SipException;
+
+    void register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www,
+                  SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister)
+            throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 鍚戜笂绾у钩鍙版敞閿�
+     *
      * @param parentPlatform
      * @return
      */
-    void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
+    void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
+            throws InvalidArgumentException, ParseException, SipException;
 
 
     /**
      * 鍚戜笂绾у钩鍙戦�佸績璺充俊鎭�
+     *
      * @param parentPlatform
      * @return callId(浣滀负鎺ュ彈鍥炲鐨勫垽瀹�)
      */
-    String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
+    String keepalive(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
+            throws SipException, InvalidArgumentException, ParseException;
 
 
     /**
      * 鍚戜笂绾у洖澶嶉�氶亾淇℃伅
-     * @param channel 閫氶亾淇℃伅
+     *
+     * @param channel        閫氶亾淇℃伅
      * @param parentPlatform 骞冲彴淇℃伅
      * @param sn
      * @param fromTag
      * @param size
      * @return
      */
-    void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException;
-    void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag) throws InvalidArgumentException, ParseException, SipException;
+    void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size)
+            throws SipException, InvalidArgumentException, ParseException;
+
+    void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag)
+            throws InvalidArgumentException, ParseException, SipException;
 
     /**
      * 鍚戜笂绾у洖澶岲eviceInfo鏌ヨ淇℃伅
+     *
      * @param parentPlatform 骞冲彴淇℃伅
      * @param sn
      * @param fromTag
      * @return
      */
-    void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException;
+    void deviceInfoResponse(ParentPlatform parentPlatform, String sn, String fromTag)
+            throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 鍚戜笂绾у洖澶岲eviceStatus鏌ヨ淇℃伅
+     *
      * @param parentPlatform 骞冲彴淇℃伅
      * @param sn
      * @param fromTag
      * @return
      */
-    void deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag) throws SipException, InvalidArgumentException, ParseException;
+    void deviceStatusResponse(ParentPlatform parentPlatform, String sn, String fromTag)
+            throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 鍚戜笂绾у洖澶嶇Щ鍔ㄤ綅缃闃呮秷鎭�
+     *
      * @param parentPlatform 骞冲彴淇℃伅
-     * @param gpsMsgInfo GPS淇℃伅
-     * @param subscribeInfo 璁㈤槄鐩稿叧鐨勪俊鎭�
+     * @param gpsMsgInfo     GPS淇℃伅
+     * @param subscribeInfo  璁㈤槄鐩稿叧鐨勪俊鎭�
      * @return
      */
-    void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
+    void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo)
+            throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
 
     /**
      * 鍚戜笂绾у洖澶嶆姤璀︽秷鎭�
+     *
      * @param parentPlatform 骞冲彴淇℃伅
-     * @param deviceAlarm 鎶ヨ淇℃伅淇℃伅
+     * @param deviceAlarm    鎶ヨ淇℃伅淇℃伅
      * @return
      */
     void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 鍥炲catalog浜嬩欢-澧炲姞/鏇存柊
+     *
      * @param parentPlatform
      * @param deviceChannels
      */
@@ -92,22 +117,28 @@
 
     /**
      * 鍥炲catalog浜嬩欢-鍒犻櫎
+     *
      * @param parentPlatform
      * @param deviceChannels
      */
-    void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
+    void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels,
+                                   SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException,
+            ParseException, NoSuchFieldException, SipException, IllegalAccessException;
 
     /**
      * 鍥炲recordInfo
-     * @param deviceChannel 閫氶亾淇℃伅
+     *
+     * @param deviceChannel  閫氶亾淇℃伅
      * @param parentPlatform 骞冲彴淇℃伅
-     * @param fromTag fromTag
-     * @param recordInfo 褰曞儚淇℃伅
+     * @param fromTag        fromTag
+     * @param recordInfo     褰曞儚淇℃伅
      */
-    void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) throws SipException, InvalidArgumentException, ParseException;
+    void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo)
+            throws SipException, InvalidArgumentException, ParseException;
 
     /**
      * 褰曞儚鎾斁鎺ㄩ�佸畬鎴愭椂鍙戦�丮ediaStatus娑堟伅
+     *
      * @param platform
      * @param sendRtpItem
      * @return
@@ -116,9 +147,19 @@
 
     /**
      * 鍚戝彂璧风偣鎾殑涓婄骇鍥炲bye
+     *
      * @param platform 骞冲彴淇℃伅
-     * @param callId  callId
+     * @param callId   callId
      */
     void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException;
+
     void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException;
+
+    void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
+
+    void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,
+                            SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent,
+                            SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException;
+
+    void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent,  SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
index 0fe11c0..e80f427 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
@@ -4,6 +4,7 @@
 import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
+import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
 import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -14,7 +15,8 @@
 import org.springframework.stereotype.Component;
 import org.springframework.util.DigestUtils;
 
-import javax.sip.*;
+import javax.sip.InvalidArgumentException;
+import javax.sip.PeerUnavailableException;
 import javax.sip.address.Address;
 import javax.sip.address.SipURI;
 import javax.sip.header.*;
@@ -22,7 +24,6 @@
 import javax.validation.constraints.NotNull;
 import java.text.ParseException;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.UUID;
 
 /**
@@ -175,7 +176,7 @@
 		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
 				parentPlatform.getTransport(), viaTag);
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
@@ -212,7 +213,7 @@
 		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
 				parentPlatform.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
@@ -272,7 +273,7 @@
 		SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort());
 		// via
 		ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
-		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(platform.getDeviceIp(), Integer.parseInt(platform.getDevicePort()),
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(),
 				platform.getTransport(), SipUtils.getNewViaTag());
 		viaHeader.setRPort();
 		viaHeaders.add(viaHeader);
@@ -308,4 +309,81 @@
 
 		return request;
 	}
+
+    public Request createInviteRequest(ParentPlatform platform, String channelId, String toString, String viaTag, String fromTag, Object content, String ssrc, CallIdHeader callIdHeader, String transport) throws PeerUnavailableException, ParseException, InvalidArgumentException {
+		Request request = null;
+		//璇锋眰琛�
+		String deviceHostAddress = platform.getDeviceIp() + ":" + platform.getDevicePort();
+		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
+		//via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		HeaderFactory headerFactory = sipLayer.getSipFactory().createHeaderFactory();
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), viaTag);
+		viaHeader.setRPort();
+		viaHeaders.add(viaHeader);
+
+		//from
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //蹇呴』瑕佹湁鏍囪锛屽惁鍒欐棤娉曞垱寤轰細璇濓紝鏃犳硶鍥炲簲ack
+		//to
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null);
+
+		//Forwards
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
+		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+
+		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(platform.getDeviceIp())+":"+ deviceHostAddress));
+		// Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort()));
+		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+		// Subject
+		SubjectHeader subjectHeader = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
+		request.addHeader(subjectHeader);
+		ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
+		request.setContent(content, contentTypeHeader);
+		return request;
+    }
+
+	public Request createByteRequest(ParentPlatform platform, String channelId, SipTransactionInfo transactionInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException {
+		String deviceHostAddress = platform.getDeviceIp() + ":" + platform.getDevicePort();
+		Request request = null;
+		SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
+
+		// via
+		ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
+		ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag());
+		viaHeaders.add(viaHeader);
+		//from
+		SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
+		Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
+		FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.isFromServer()?transactionInfo.getFromTag():transactionInfo.getToTag());
+		//to
+		SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
+		Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
+		ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,transactionInfo.isFromServer()?transactionInfo.getToTag():transactionInfo.getFromTag());
+
+		//Forwards
+		MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
+
+		//ceq
+		CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
+		CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
+		request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
+
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+
+		Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort()));
+		request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
+
+		request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
+
+		return request;
+	}
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
index 29f2bec..425dd93 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -590,12 +590,12 @@
             return;
         }
         if (!mediaServerItem.isRtpEnable()) {
-            // 鍗曠鍙f殏涓嶆敮鎸佽闊冲璁�
-            logger.info("[璇煶瀵硅] 鍗曠鍙f殏涓嶆敮鎸佹鎿嶄綔");
+            // 鍗曠鍙f殏涓嶆敮鎸佽闊冲枈璇�
+            logger.info("[璇煶鍠婅瘽] 鍗曠鍙f殏涓嶆敮鎸佹鎿嶄綔");
             return;
         }
 
-        logger.info("[璇煶瀵硅] {} 鍒嗛厤鐨刏LM涓�: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
+        logger.info("[璇煶鍠婅瘽] {} 鍒嗛厤鐨刏LM涓�: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
         HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
         subscribe.addSubscribe(hookSubscribeForStreamChange, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
             if (event != null) {
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
index 08cc3c3..d0d72bf 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
@@ -1,22 +1,31 @@
 package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
 
 import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.conf.UserSetting;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.SipLayer;
 import com.genersoft.iot.vmp.gb28181.bean.*;
 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider;
 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
+import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
 import com.genersoft.iot.vmp.utils.DateUtil;
 import gov.nist.javax.sip.message.MessageFactoryImpl;
 import gov.nist.javax.sip.message.SIPRequest;
+import gov.nist.javax.sip.message.SIPResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -26,6 +35,7 @@
 import org.springframework.util.ObjectUtils;
 
 import javax.sip.InvalidArgumentException;
+import javax.sip.ResponseEvent;
 import javax.sip.SipException;
 import javax.sip.header.CallIdHeader;
 import javax.sip.header.WWWAuthenticateHeader;
@@ -60,6 +70,16 @@
 
     @Autowired
     private SIPSender sipSender;
+
+    @Autowired
+    private ZlmHttpHookSubscribe subscribe;
+
+    @Autowired
+    private UserSetting userSetting;
+
+
+    @Autowired
+    private VideoStreamSessionManager streamSession;
 
     @Override
     public void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException {
@@ -645,4 +665,107 @@
         }
         sipSender.transmitRequest(platform.getDeviceIp(),byeRequest);
     }
+
+    @Override
+    public void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
+        SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(platform.getServerGBId(), channelId, callId, stream);
+        if (ssrcTransaction == null) {
+            throw new SsrcTransactionNotFoundException(platform.getServerGBId(), channelId, callId, stream);
+        }
+
+        mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
+        mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
+        streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
+
+        Request byteRequest = headerProviderPlatformProvider.createByteRequest(platform, channelId, ssrcTransaction.getSipTransactionInfo());
+        sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), byteRequest, null, okEvent);
+    }
+
+    @Override
+    public void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent,  SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
+        if (platform == null || deviceChannel == null) {
+            return;
+        }
+        String characterSet = platform.getCharacterSet();
+        StringBuffer mediaStatusXml = new StringBuffer(200);
+        mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
+        mediaStatusXml.append("<Notify>\r\n");
+        mediaStatusXml.append("<CmdType>Broadcast</CmdType>\r\n");
+        mediaStatusXml.append("<SN>" + sn + "</SN>\r\n");
+        mediaStatusXml.append("<DeviceID>" + deviceChannel.getChannelId() + "</DeviceID>\r\n");
+        mediaStatusXml.append("<Result>" + (result?"OK":"ERROR") + "</Result>\r\n");
+        mediaStatusXml.append("</Notify>\r\n");
+
+        CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(platform.getDeviceIp(), platform.getTransport());
+
+        SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(platform, mediaStatusXml.toString(),
+               SipUtils.getNewFromTag(), SipUtils.getNewViaTag(), callIdHeader);
+
+        sipSender.transmitRequest(platform.getDeviceIp(),messageRequest, errorEvent, okEvent);
+    }
+
+    @Override
+    public void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,
+                                   SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent,
+                                   SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException {
+        String stream = ssrcInfo.getStream();
+
+        if (platform == null) {
+            return;
+        }
+
+        logger.info("{} 鍒嗛厤鐨刏LM涓�: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
+        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
+        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
+            if (event != null) {
+                event.response(mediaServerItemInUse, json);
+                subscribe.removeSubscribe(hookSubscribe);
+            }
+        });
+        String sdpIp = mediaServerItem.getSdpIp();
+
+        StringBuffer content = new StringBuffer(200);
+        content.append("v=0\r\n");
+        content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
+        content.append("s=Play\r\n");
+        content.append("c=IN IP4 " + sdpIp + "\r\n");
+        content.append("t=0 0\r\n");
+
+        if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
+            content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n");
+        } else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
+            content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n");
+        } else if ("UDP".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
+            content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 8 96\r\n");
+        }
+
+        content.append("a=recvonly\r\n");
+        content.append("a=rtpmap:8 PCMA/8000\r\n");
+        content.append("a=rtpmap:96 PS/90000\r\n");
+        if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
+            content.append("a=setup:passive\r\n");
+            content.append("a=connection:new\r\n");
+        }else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
+            content.append("a=setup:active\r\n");
+            content.append("a=connection:new\r\n");
+        }
+
+        content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
+        CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getTransport());
+
+        Request request = headerProviderPlatformProvider.createInviteRequest(platform, channelId,
+                content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(),
+                callIdHeader ,platform.getTransport());
+        sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), request, (e -> {
+            streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
+            mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+            errorEvent.response(e);
+        }), e -> {
+            // 杩欓噷涓轰緥閬垮厤涓�涓�氶亾鐨勭偣鎾彧鏈変竴涓猚allID杩欎釜鍙傛暟浣跨敤涓�涓浐瀹氬��
+            ResponseEvent responseEvent = (ResponseEvent) e.event;
+            SIPResponse response = (SIPResponse) responseEvent.getResponse();
+            streamSession.put(platform.getServerGBId(), channelId, callIdHeader.getCallId(),  stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play);
+            okEvent.response(e);
+        });
+    }
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
index c5220a9..10b0daa 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
@@ -16,8 +16,6 @@
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
 import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
-import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
-import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRtpServerTimeout;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IDeviceService;
 import com.genersoft.iot.vmp.service.IMediaServerService;
@@ -40,8 +38,6 @@
 import javax.sip.header.HeaderAddress;
 import javax.sip.header.ToHeader;
 import java.text.ParseException;
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * SIP鍛戒护绫诲瀷锛� ACK璇锋眰
@@ -123,68 +119,31 @@
 		MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
 		logger.info("鏀跺埌ACK锛宺tp/{}寮�濮嬪悜涓婄骇鎺ㄦ祦, 鐩爣={}:{}锛孲SRC={}, RTCP={}", sendRtpItem.getStreamId(),
 				sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp());
-		Map<String, Object> param = new HashMap<>(12);
-		param.put("vhost","__defaultVhost__");
-		param.put("app",sendRtpItem.getApp());
-		param.put("stream",sendRtpItem.getStreamId());
-		param.put("ssrc", sendRtpItem.getSsrc());
-		param.put("src_port", sendRtpItem.getLocalPort());
-		param.put("pt", sendRtpItem.getPt());
-		param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
-		param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
-		if (!sendRtpItem.isTcp()) {
-			// udp妯″紡涓嬪紑鍚痳tcp淇濇椿
-			param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
-		}
-
 		if (mediaInfo == null) {
 			RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
 					sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(),
 					sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
 					sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
 			redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> {
-				startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, json, param, callIdHeader);
+				startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, json, callIdHeader);
 			});
-		} else {
-			// 濡傛灉鏄潪涓ユ牸妯″紡锛岄渶瑕佸叧闂鍙e崰鐢�
-			JSONObject startSendRtpStreamResult = null;
-			if (sendRtpItem.getLocalPort() != 0) {
-				HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(sendRtpItem.getSsrc(), null, mediaInfo.getId());
-				hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout);
-				if (zlmrtpServerFactory.releasePort(mediaInfo, sendRtpItem.getSsrc())) {
-					if (sendRtpItem.isTcpActive()) {
-						startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param);
-					}else {
-						param.put("is_udp", is_Udp);
-						param.put("dst_url", sendRtpItem.getIp());
-						param.put("dst_port", sendRtpItem.getPort());
-						startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
-					}
-				}
-			}else {
-				if (sendRtpItem.isTcpActive()) {
-					startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param);
-				}else {
-					param.put("is_udp", is_Udp);
-					param.put("dst_url", sendRtpItem.getIp());
-					param.put("dst_port", sendRtpItem.getPort());
-					startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
-				}
-			}
+		}else {
+			JSONObject startSendRtpStreamResult = zlmrtpServerFactory.startSendRtp(mediaInfo, sendRtpItem);
 			if (startSendRtpStreamResult != null) {
-				startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, param, callIdHeader);
+				startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, callIdHeader);
 			}
 		}
 	}
+
 	private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform,
-										JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) {
+										JSONObject jsonObject, CallIdHeader callIdHeader) {
 		if (jsonObject == null) {
 			logger.error("RTP鎺ㄦ祦澶辫触: 璇锋鏌LM鏈嶅姟");
 		} else if (jsonObject.getInteger("code") == 0) {
 			logger.info("璋冪敤ZLM鎺ㄦ祦鎺ュ彛, 缁撴灉锛� {}",  jsonObject);
-			logger.info("RTP鎺ㄦ祦鎴愬姛[ {}/{} ]锛寋}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
+			logger.info("RTP鎺ㄦ祦鎴愬姛[ {}/{} ]锛寋}->{}:{}, " ,sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getIp(), sendRtpItem.getPort());
 		} else {
-			logger.error("RTP鎺ㄦ祦澶辫触: {}, 鍙傛暟锛歿}",jsonObject.getString("msg"), JSON.toJSONString(param));
+			logger.error("RTP鎺ㄦ祦澶辫触: {}, 鍙傛暟锛歿}",jsonObject.getString("msg"), JSON.toJSONString(sendRtpItem));
 			if (sendRtpItem.isOnlyAudio()) {
 				Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
 				AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
@@ -193,7 +152,7 @@
 						cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
 					} catch (SipException | ParseException | InvalidArgumentException |
 							 SsrcTransactionNotFoundException e) {
-						logger.error("[鍛戒护鍙戦�佸け璐 鍋滄璇煶瀵硅: {}", e.getMessage());
+						logger.error("[鍛戒护鍙戦�佸け璐 鍋滄璇煶鍠婅瘽: {}", e.getMessage());
 					}
 				}
 			}else {
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
index e31995c..6e188a5 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
@@ -201,7 +201,7 @@
 
                 MediaServerItem mediaServerItem = null;
                 StreamPushItem streamPushItem = null;
-                StreamProxyItem proxyByAppAndStream =null;
+                StreamProxyItem proxyByAppAndStream = null;
                 // 涓嶆槸閫氶亾鍙兘鏄洿鎾祦
                 if (channel != null && gbStream == null) {
                     // 閫氶亾瀛樺湪锛屽彂100锛孴RYING
@@ -1001,7 +1001,7 @@
                 String stream = device.getDeviceId() + "_" + audioBroadcastCatch.getChannelId();
 
                 CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
-                sendRtpItem.setPlayType(InviteStreamType.TALK);
+                sendRtpItem.setPlayType(InviteStreamType.BROADCAST);
                 sendRtpItem.setCallId(callIdHeader.getCallId());
                 sendRtpItem.setPlatformId(requesterId);
                 sendRtpItem.setStatus(1);
@@ -1012,6 +1012,7 @@
                 sendRtpItem.setRtcp(false);
                 sendRtpItem.setOnlyAudio(true);
                 redisCatchStorage.updateSendRTPSever(sendRtpItem);
+
 
                 Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream);
                 if (streamReady) {
@@ -1084,7 +1085,7 @@
             audioBroadcastManager.update(audioBroadcastCatch);
 
         } catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) {
-            logger.error("[鍛戒护鍙戦�佸け璐 璇煶瀵硅 鍥炲200OK锛圫DP锛�: {}", e.getMessage());
+            logger.error("[鍛戒护鍙戦�佸け璐 璇煶鍠婅瘽 鍥炲200OK锛圫DP锛�: {}", e.getMessage());
         }
         return sipResponse;
     }
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java
new file mode 100644
index 0000000..353aaf2
--- /dev/null
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java
@@ -0,0 +1,200 @@
+package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
+import com.genersoft.iot.vmp.gb28181.bean.*;
+import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
+import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
+import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
+import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.IDeviceService;
+import com.genersoft.iot.vmp.service.IMediaServerService;
+import com.genersoft.iot.vmp.service.IPlatformService;
+import com.genersoft.iot.vmp.service.IPlayService;
+import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
+import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
+import gov.nist.javax.sip.message.SIPRequest;
+import org.dom4j.Element;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.RequestEvent;
+import javax.sip.SipException;
+import javax.sip.message.Response;
+import java.text.ParseException;
+
+/**
+ * 鐘舵�佷俊鎭�(蹇冭烦)鎶ラ��
+ */
+@Component
+public class BroadcastNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
+
+    private Logger logger = LoggerFactory.getLogger(BroadcastNotifyMessageHandler.class);
+    private final static String cmdType = "Broadcast";
+
+    @Autowired
+    private NotifyMessageHandler notifyMessageHandler;
+
+    @Autowired
+    private IVideoManagerStorage storage;
+
+    @Autowired
+    private ISIPCommanderForPlatform commanderForPlatform;
+
+    @Autowired
+    private IMediaServerService mediaServerService;
+
+    @Autowired
+    private IPlayService playService;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @Autowired
+    private IPlatformService platformService;
+
+    @Autowired
+    private AudioBroadcastManager audioBroadcastManager;
+
+    @Autowired
+    private ZLMRTPServerFactory zlmrtpServerFactory;
+
+    @Autowired
+    private IRedisCatchStorage redisCatchStorage;
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        notifyMessageHandler.addHandler(cmdType, this);
+    }
+
+    @Override
+    public void handForDevice(RequestEvent evt, Device device, Element element) {
+
+    }
+
+    @Override
+    public void handForPlatform(RequestEvent evt, ParentPlatform platform, Element rootElement) {
+        // 鏉ヨ嚜涓婄骇骞冲彴鐨勮闊冲枈璇濊姹�
+        SIPRequest request = (SIPRequest) evt.getRequest();
+        try {
+            Element snElement = rootElement.element("SN");
+            if (snElement == null) {
+                responseAck(request, Response.BAD_REQUEST, "sn must not null");
+                return;
+            }
+            String sn = snElement.getText();
+            Element targetIDElement = rootElement.element("TargetID");
+            if (targetIDElement == null) {
+                responseAck(request, Response.BAD_REQUEST, "TargetID must not null");
+                return;
+            }
+            String targetId = targetIDElement.getText();
+
+
+            logger.info("[鍥芥爣绾ц仈 璇煶鍠婅瘽] platform: {}, channel: {}", platform.getServerGBId(), targetId);
+
+            DeviceChannel deviceChannel = storage.queryChannelInParentPlatform(platform.getServerGBId(), targetId);
+            if (deviceChannel == null) {
+                responseAck(request, Response.NOT_FOUND, "TargetID not found");
+                return;
+            }
+            // 鍚戜笅绾у彂閫佽闊崇殑鍠婅瘽璇锋眰
+            Device device = deviceService.getDevice(deviceChannel.getDeviceId());
+            if (device == null) {
+                responseAck(request, Response.NOT_FOUND, "device not found");
+                return;
+            }
+            responseAck(request, Response.OK);
+
+            // 鏌ョ湅璇煶閫氶亾鏄惁宸茬粡寤虹珛骞朵笖宸茬粡鍦ㄤ娇鐢�
+            if (playService.audioBroadcastInUse(device, targetId)) {
+                commanderForPlatform.broadcastResultCmd(platform, deviceChannel, sn, false,null, null);
+                return;
+            }
+
+            MediaServerItem mediaServerForMinimumLoad = mediaServerService.getMediaServerForMinimumLoad();
+            commanderForPlatform.broadcastResultCmd(platform, deviceChannel, sn, true,  eventResult->{
+                logger.info("[鍥芥爣绾ц仈] 璇煶鍠婅瘽 鍥炲澶辫触 platform锛� {}锛� 閿欒锛歿}/{}", platform.getServerGBId(), eventResult.statusCode, eventResult.msg);
+            }, eventResult->{
+                // 娑堟伅鍙戦�佹垚鍔燂紝 鍚戜笂绾у彂閫乮nvite锛岃幏鍙栨帹娴�
+                try {
+                    platformService.broadcastInvite(platform, deviceChannel.getChannelId(), mediaServerForMinimumLoad,  (mediaServerItem, response)->{
+                        // 涓婄骇骞冲彴鎺ㄦ祦鎴愬姛
+                        String app = response.getString("app");
+                        String stream = response.getString("stream");
+                        AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(device.getDeviceId(), targetId);
+                        if (broadcastCatch != null ) {
+                            if (playService.audioBroadcastInUse(device, targetId)) {
+                                logger.info("[鍥芥爣绾ц仈] 璇煶鍠婅瘽 璁惧姝f鍦ㄤ娇鐢ㄤ腑 platform锛� {}锛� channel: {}",
+                                        platform.getServerGBId(), deviceChannel.getChannelId());
+                                //  鏌ョ湅璇煶閫氶亾宸茬粡寤虹珛涓斿凡缁忓崰鐢� 鍥炲BYE
+                                try {
+                                    platformService.stopBroadcast(platform, deviceChannel.getChannelId(), stream);
+                                } catch (InvalidArgumentException | ParseException | SsrcTransactionNotFoundException |
+                                         SipException e) {
+                                    logger.info("[娑堟伅鍙戦�佸け璐 鍥芥爣绾ц仈 璇煶鍠婅瘽 platform锛� {}锛� channel: {}", platform.getServerGBId(), deviceChannel.getChannelId());
+                                }
+                            }else {
+                                // 鏌ョ湅璇煶閫氶亾宸茬粡寤虹珛浣嗘槸鏈崰鐢�
+                                broadcastCatch.setApp(app);
+                                broadcastCatch.setStream(stream);
+                                broadcastCatch.setMediaServerItem(mediaServerItem);
+                                audioBroadcastManager.update(broadcastCatch);
+                                // 鎺ㄦ祦鍒拌澶�
+                                SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, targetId, stream, null);
+                                if (sendRtpItem == null) {
+                                    logger.warn("[鍥芥爣绾ц仈] 璇煶鍠婅瘽 寮傚父锛屾湭鎵惧埌鍙戞祦淇℃伅锛� channelId: {}, stream: {}", targetId, stream);
+                                    logger.info("[鍥芥爣绾ц仈] 璇煶鍠婅瘽 閲嶆柊寮�濮嬶紝channelId: {}, stream: {}", targetId, stream);
+                                    try {
+                                        playService.audioBroadcastCmd(device, targetId, mediaServerItem, app, stream, 60, true, msg -> {
+                                            logger.info("[璇煶鍠婅瘽] 閫氶亾寤虹珛鎴愬姛, device: {}, channel: {}", device.getDeviceId(), targetId);
+                                        });
+                                    } catch (SipException | InvalidArgumentException | ParseException e) {
+                                        logger.info("[娑堟伅鍙戦�佸け璐 鍥芥爣绾ц仈 璇煶鍠婅瘽 platform锛� {}", platform.getServerGBId());
+                                    }
+                                }else {
+                                    // 鍙戞祦
+                                    JSONObject jsonObject = zlmrtpServerFactory.startSendRtp(mediaServerItem, sendRtpItem);
+                                    if (jsonObject != null && jsonObject.getInteger("code") == 0 ) {
+                                        logger.info("[璇煶鍠婅瘽] 鑷姩鎺ㄦ祦鎴愬姛, device: {}, channel: {}", device.getDeviceId(), targetId);
+                                    }else {
+                                        logger.info("[璇煶鍠婅瘽] 鎺ㄦ祦澶辫触, 缁撴灉锛� {}", jsonObject);
+                                    }
+                                }
+                            }
+                        }else {
+                            try {
+                                playService.audioBroadcastCmd(device, targetId, mediaServerItem, app, stream, 60, true, msg -> {
+                                    logger.info("[璇煶鍠婅瘽] 閫氶亾寤虹珛鎴愬姛, device: {}, channel: {}", device.getDeviceId(), targetId);
+                                });
+                            } catch (SipException | InvalidArgumentException | ParseException e) {
+                                logger.info("[娑堟伅鍙戦�佸け璐 鍥芥爣绾ц仈 璇煶鍠婅瘽 platform锛� {}", platform.getServerGBId());
+                            }
+                        }
+
+                    }, eventResultForBroadcastInvite -> {
+                        // 鏀跺埌閿欒
+                        logger.info("[鍥芥爣绾ц仈-璇煶鍠婅瘽] 涓庝笅绾ч�氶亾寤虹珛澶辫触 device: {}, channel: {}锛� 閿欒锛歿}/{}", device.getDeviceId(),
+                                targetId, eventResultForBroadcastInvite.statusCode, eventResultForBroadcastInvite.msg);
+                    }, (code, msg)->{
+                        // 瓒呮椂
+                        logger.info("[鍥芥爣绾ц仈-璇煶鍠婅瘽] 涓庝笅绾ч�氶亾寤虹珛瓒呮椂 device: {}, channel: {}锛� 閿欒锛歿}/{}", device.getDeviceId(),
+                                targetId, code, msg);
+                    });
+                } catch (SipException | InvalidArgumentException | ParseException e) {
+                    logger.info("[娑堟伅鍙戦�佸け璐 鍥芥爣绾ц仈 璇煶鍠婅瘽 invite娑堟伅 platform锛� {}", platform.getServerGBId());
+                }
+            });
+        } catch (SipException | InvalidArgumentException | ParseException e) {
+            logger.info("[娑堟伅鍙戦�佸け璐 鍥芥爣绾ц仈 璇煶鍠婅瘽 platform锛� {}", platform.getServerGBId());
+        }
+
+    }
+}
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java
index 56fb789..2cf2072 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java
@@ -1,17 +1,14 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
 
-import com.alibaba.fastjson2.JSONObject;
 import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch;
 import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus;
 import com.genersoft.iot.vmp.gb28181.bean.Device;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
 import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
 import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
-import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
-import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
 import gov.nist.javax.sip.message.SIPRequest;
 import org.dom4j.Element;
 import org.slf4j.Logger;
@@ -52,26 +49,13 @@
     public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
         try {
             String channelId = getText(rootElement, "DeviceID");
-            String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId() + channelId;
-
-            // 姝ゅ鏄鏈钩鍙板彂鍑築roadcast鎸囦护鐨勫簲绛�
-            JSONObject json = new JSONObject();
-            XmlUtil.node2Json(rootElement, json);
-            if (logger.isDebugEnabled()) {
-                logger.debug(json.toJSONString());
-            }
-            RequestMessage msg = new RequestMessage();
-            msg.setKey(key);
-            msg.setData(json);
-            deferredResultHolder.invokeAllResult(msg);
-
-
             if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
                 // 鍥炲410
                 responseAck((SIPRequest) evt.getRequest(), Response.GONE);
                 return;
             }
-            logger.info("鏀跺埌璇煶骞挎挱鐨勫洖澶嶏細{}/{}", device.getDeviceId(), channelId );
+            String result = getText(rootElement, "Result");
+            logger.info("鏀跺埌璇煶骞挎挱鐨勫洖澶� {}锛歿}/{}", result, device.getDeviceId(), channelId );
             AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId);
             audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite);
             audioBroadcastManager.update(audioBroadcastCatch);
diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
index d0ba97e..16fa6d8 100644
--- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
+++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
@@ -1,39 +1,24 @@
 package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl;
 
-import com.genersoft.iot.vmp.conf.SipConfig;
 import com.genersoft.iot.vmp.gb28181.SipLayer;
-import com.genersoft.iot.vmp.gb28181.bean.Device;
-import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
-import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
-import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
 import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract;
-import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
-import com.genersoft.iot.vmp.service.IDeviceService;
-import com.genersoft.iot.vmp.utils.GitUtil;
 import gov.nist.javax.sip.ResponseEventExt;
-import gov.nist.javax.sip.SipProviderImpl;
 import gov.nist.javax.sip.message.SIPResponse;
-import gov.nist.javax.sip.stack.SIPClientTransaction;
-import gov.nist.javax.sip.stack.SIPDialog;
-import gov.nist.javax.sip.stack.SIPTransaction;
-import gov.nist.javax.sip.stack.SIPTransactionImpl;
 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.stereotype.Component;
 
 import javax.sdp.SdpFactory;
 import javax.sdp.SdpParseException;
 import javax.sdp.SessionDescription;
-import javax.sip.*;
-import javax.sip.address.Address;
+import javax.sip.InvalidArgumentException;
+import javax.sip.ResponseEvent;
+import javax.sip.SipException;
 import javax.sip.address.SipURI;
-import javax.sip.header.CSeqHeader;
-import javax.sip.header.UserAgentHeader;
 import javax.sip.message.Request;
 import javax.sip.message.Response;
 import java.text.ParseException;
@@ -104,6 +89,7 @@
 				} else {
 					sdp = SdpFactory.getInstance().createSessionDescription(contentString);
 				}
+				// 鏌ョ湅鏄惁鏄潵鑷澶囩殑锛屾鏄洖澶�
 
 				SipURI requestUri = sipLayer.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
 				Request reqAck = headerProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response);
diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
index 88d7e14..51ff7ad 100644
--- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
+++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -287,15 +287,19 @@
 			logger.info("[ZLM HOOK] 娴佹敞閿�, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
 		}
 
+		JSONObject ret = new JSONObject();
+		ret.put("code", 0);
+		ret.put("msg", "success");
 
 		JSONObject json = (JSONObject) JSON.toJSON(param);
+		MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
+		if (mediaInfo == null) {
+			return ret;
+		}
 		taskExecutor.execute(()-> {
 			ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
-			if (subscribe != null) {
-				MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
-				if (mediaInfo != null) {
-					subscribe.response(mediaInfo, json);
-				}
+			if (subscribe != null ) {
+				subscribe.response(mediaInfo, json);
 			}
 			// 娴佹秷澶辩Щ闄edis play
 			List<OnStreamChangedHookParam.MediaTrack> tracks = param.getTracks();
@@ -343,7 +347,7 @@
 					}
 				}
 			}else if ("broadcast".equals(param.getApp())){
-				// 璇煶瀵硅鎺ㄦ祦  stream闇�瑕佹弧瓒虫牸寮廳eviceId_channelId
+				// 璇煶鍠婅瘽鎺ㄦ祦  stream闇�瑕佹弧瓒虫牸寮廳eviceId_channelId
 				if (param.isRegist() && param.getStream().indexOf("_") > 0) {
 					String[] streamArray = param.getStream().split("_");
 					if (streamArray.length == 2) {
@@ -359,53 +363,38 @@
 									if (sendRtpItem == null) {
 										// TODO 鍙兘鏁版嵁閿欒锛岄噸鏂板紑鍚闊抽�氶亾
 									}else {
-										String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
-										MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
-										logger.info("rtp/{}寮�濮嬪悜涓婄骇鎺ㄦ祦, 鐩爣={}:{}锛孲SRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc());
-										Map<String, Object> sendParam = new HashMap<>(12);
-										sendParam.put("vhost","__defaultVhost__");
-										sendParam.put("app",sendRtpItem.getApp());
-										sendParam.put("stream",sendRtpItem.getStreamId());
-										sendParam.put("ssrc", sendRtpItem.getSsrc());
-										sendParam.put("src_port", sendRtpItem.getLocalPort());
-										sendParam.put("pt", sendRtpItem.getPt());
-										sendParam.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
-										sendParam.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
-
-										JSONObject jsonObject;
-										if (sendRtpItem.isTcpActive()) {
-											jsonObject = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, sendParam);
-										} else {
-											sendParam.put("is_udp", is_Udp);
-											sendParam.put("dst_url", sendRtpItem.getIp());
-											sendParam.put("dst_port", sendRtpItem.getPort());
-											jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, sendParam);
-										}
-										if (jsonObject != null && jsonObject.getInteger("code") == 0) {
-											logger.info("[璇煶瀵硅] 鑷姩鎺ㄦ祦鎴愬姛, device: {}, channel: {}", deviceId, channelId);
+										JSONObject jsonObject = zlmrtpServerFactory.startSendRtp(mediaInfo, sendRtpItem);
+										if (jsonObject != null && jsonObject.getInteger("code") == 0 ) {
+											logger.info("[璇煶鍠婅瘽] 鑷姩鎺ㄦ祦鎴愬姛, device: {}, channel: {}", device.getDeviceId(), channelId);
 										}else {
-											logger.info("[璇煶瀵硅] 鎺ㄦ祦澶辫触, 缁撴灉锛� {}", jsonObject);
+											logger.info("[璇煶鍠婅瘽] 鎺ㄦ祦澶辫触, 缁撴灉锛� {}", jsonObject);
 										}
 
 									}
 								}else {
-									// 寮�鍚闊冲璁查�氶亾
+									// 寮�鍚闊冲枈璇濋�氶亾
 									try {
-										playService.audioBroadcastCmd(device, channelId, 60, (msg)->{
-											logger.info("[璇煶瀵硅] 閫氶亾寤虹珛鎴愬姛, device: {}, channel: {}", deviceId, channelId);
+										playService.audioBroadcastCmd(device, channelId, mediaInfo, param.getApp(), param.getStream(),60, false, (msg)->{
+											logger.info("[璇煶鍠婅瘽] 閫氶亾寤虹珛鎴愬姛, device: {}, channel: {}", deviceId, channelId);
 										});
 									} catch (InvalidArgumentException | ParseException | SipException e) {
-										logger.error("[鍛戒护鍙戦�佸け璐 璇煶瀵硅: {}", e.getMessage());
+										logger.error("[鍛戒护鍙戦�佸け璐 璇煶鍠婅瘽: {}", e.getMessage());
 									}
 								}
 
+							}else {
+								logger.info("[璇煶鍠婅瘽] 鎺ㄦ祦鎸囧悜鐨劼烽�氶亾{}鏈壘鍒�", channelId);
 							}
+						}else {
+							logger.info("[璇煶鍠婅瘽] 鎺ㄦ祦鎸囧悜鐨劼疯澶噞}鏈壘鍒�", deviceId);
 						}
+					}else {
+						logger.info("[璇煶鍠婅瘽] 鎺ㄦ祦鏍煎紡鏈夎, 鏍煎紡涓猴細 broadcast/璁惧缂栧彿_閫氶亾缂栧彿 ");
 					}
 				}
 
 			}else if ("talk".equals(param.getApp())){
-				// 璇煶瀵硅鎺ㄦ祦  stream闇�瑕佹弧瓒虫牸寮廳eviceId_channelId
+				// 璇煶鍠婅瘽鎺ㄦ祦  stream闇�瑕佹弧瓒虫牸寮廳eviceId_channelId
 				if (param.isRegist() && param.getStream().indexOf("_") > 0) {
 					String[] streamArray = param.getStream().split("_");
 					if (streamArray.length == 2) {
@@ -421,33 +410,11 @@
 									if (sendRtpItem == null) {
 										// TODO 鍙兘鏁版嵁閿欒锛岄噸鏂板紑鍚闊抽�氶亾
 									}else {
-										MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
 										logger.info("rtp/{}寮�濮嬪悜涓婄骇鎺ㄦ祦, 鐩爣={}:{}锛孲SRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc());
-										Map<String, Object> sendParam = new HashMap<>(12);
-										sendParam.put("vhost","__defaultVhost__");
-										sendParam.put("app",sendRtpItem.getApp());
-										sendParam.put("stream",sendRtpItem.getStreamId());
-										sendParam.put("ssrc", sendRtpItem.getSsrc());
-										sendParam.put("src_port", sendRtpItem.getLocalPort());
-										sendParam.put("pt", sendRtpItem.getPt());
-										sendParam.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
-										sendParam.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
-
-										JSONObject jsonObject;
-										if (sendRtpItem.isTcpActive()) {
-											jsonObject = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, sendParam);
-										} else {
-											sendParam.put("is_udp", sendRtpItem.isTcp() ? "0" : "1");
-											sendParam.put("dst_url", sendRtpItem.getIp());
-											sendParam.put("dst_port", sendRtpItem.getPort());
-											jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaInfo, sendParam);
-										}
-										if (jsonObject != null && jsonObject.getInteger("code") == 0) {
-											logger.info("[璇煶瀵硅] 鑷姩鎺ㄦ祦鎴愬姛, device: {}, channel: {}", deviceId, channelId);
-										}
+										zlmrtpServerFactory.startSendRtp(mediaInfo, sendRtpItem);
 									}
 								}else {
-									// 寮�鍚闊冲璁查�氶亾
+									// 寮�鍚闊冲枈璇濋�氶亾
 									MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
 									playService.talk(mediaServerItem, device, channelId, (mediaServer, jsonObject)->{
 										System.out.println("寮�濮嬫帹娴�");
@@ -549,9 +516,7 @@
 			}
 		}
 
-		JSONObject ret = new JSONObject();
-		ret.put("code", 0);
-		ret.put("msg", "success");
+
 		return ret;
 	}
 	
diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
index 4a6a94a..c7d9966 100644
--- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
+++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
@@ -348,4 +348,52 @@
         return result;
     }
 
+    public JSONObject startSendRtp(MediaServerItem mediaInfo, SendRtpItem sendRtpItem) {
+        String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
+        logger.info("rtp/{}寮�濮嬪悜涓婄骇鎺ㄦ祦, 鐩爣={}:{}锛孲SRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc());
+        Map<String, Object> param = new HashMap<>(12);
+        param.put("vhost","__defaultVhost__");
+        param.put("app",sendRtpItem.getApp());
+        param.put("stream",sendRtpItem.getStreamId());
+        param.put("ssrc", sendRtpItem.getSsrc());
+        param.put("src_port", sendRtpItem.getLocalPort());
+        param.put("pt", sendRtpItem.getPt());
+        param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
+        param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
+        if (!sendRtpItem.isTcp()) {
+            // udp妯″紡涓嬪紑鍚痳tcp淇濇椿
+            param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
+        }
+
+        if (mediaInfo == null) {
+            return null;
+        }
+        // 濡傛灉鏄潪涓ユ牸妯″紡锛岄渶瑕佸叧闂鍙e崰鐢�
+        JSONObject startSendRtpStreamResult = null;
+        if (sendRtpItem.getLocalPort() != 0) {
+            HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(sendRtpItem.getSsrc(), null, mediaInfo.getId());
+            hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout);
+            if (releasePort(mediaInfo, sendRtpItem.getSsrc())) {
+                if (sendRtpItem.isTcpActive()) {
+                    startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param);
+                    System.out.println(JSON.toJSON(param));
+                }else {
+                    param.put("is_udp", is_Udp);
+                    param.put("dst_url", sendRtpItem.getIp());
+                    param.put("dst_port", sendRtpItem.getPort());
+                    startSendRtpStreamResult = startSendRtpStream(mediaInfo, param);
+                }
+            }
+        }else {
+            if (sendRtpItem.isTcpActive()) {
+                startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param);
+            }else {
+                param.put("is_udp", is_Udp);
+                param.put("dst_url", sendRtpItem.getIp());
+                param.put("dst_port", sendRtpItem.getPort());
+                startSendRtpStreamResult = startSendRtpStream(mediaInfo, param);
+            }
+        }
+        return startSendRtpStreamResult;
+    }
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java b/src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java
index 17f8b37..be8a8f5 100644
--- a/src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java
+++ b/src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java
@@ -1,7 +1,16 @@
 package com.genersoft.iot.vmp.service;
 
+import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
+import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
+import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
 import com.github.pagehelper.PageInfo;
+
+import javax.sip.InvalidArgumentException;
+import javax.sip.SipException;
+import java.text.ParseException;
 
 /**
  * 鍥芥爣骞冲彴鐨勪笟鍔$被
@@ -48,4 +57,23 @@
      * @param platformId 骞冲彴
      */
     void sendNotifyMobilePosition(String platformId);
+
+    /**
+     * 鍚戜笂绾у彂閫佽闊冲枈璇濈殑娑堟伅
+     * @param platform 骞冲彴
+     * @param channelId 閫氶亾
+     * @param hookEvent hook浜嬩欢
+     * @param errorEvent 淇′护閿欒浜嬩欢
+     * @param timeoutCallback 瓒呮椂浜嬩欢
+     */
+    void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,  ZlmHttpHookSubscribe.Event hookEvent,
+                         SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException;
+
+    /**
+     * 璇煶鍠婅瘽鍥炲BYE
+     * @param platform 骞冲彴
+     * @param channelId 閫氶亾
+     * @param stream 娴佷俊鎭�
+     */
+    void stopBroadcast(ParentPlatform platform, String channelId, String stream )throws InvalidArgumentException, ParseException, SsrcTransactionNotFoundException, SipException;
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
index 4ab2f4a..3f7e13d 100644
--- a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
+++ b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
@@ -53,10 +53,13 @@
 
     void zlmServerOnline(String mediaServerId);
 
-    AudioBroadcastResult audioBroadcast(Device device, String channelId);
-    void stopAudioBroadcast(String deviceId, String channelId);
+    AudioBroadcastResult audioBroadcastInfo(Device device, String channelId);
 
-    void audioBroadcastCmd(Device device, String channelId, int timeout, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException;
+    boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException;
+
+    boolean audioBroadcastInUse(Device device, String channelId);
+
+    void stopAudioBroadcast(String deviceId, String channelId);
 
     void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException;
 
diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
index fbc507a..5b0f67b 100644
--- a/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
+++ b/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
@@ -1,15 +1,25 @@
 package com.genersoft.iot.vmp.service.impl;
 
+import com.alibaba.fastjson2.JSONObject;
+import com.genersoft.iot.vmp.common.StreamInfo;
 import com.genersoft.iot.vmp.conf.DynamicTask;
 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;
 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
+import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
+import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
 import com.genersoft.iot.vmp.service.IMediaServerService;
 import com.genersoft.iot.vmp.service.IPlatformService;
+import com.genersoft.iot.vmp.service.IPlayService;
 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
+import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
+import com.genersoft.iot.vmp.service.bean.SSRCInfo;
 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
 import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
 import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper;
@@ -21,11 +31,13 @@
 import org.springframework.stereotype.Service;
 
 import javax.sip.InvalidArgumentException;
+import javax.sip.ResponseEvent;
 import javax.sip.SipException;
 import java.text.ParseException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
 /**
  * @author lin
@@ -64,6 +76,16 @@
 
     @Autowired
     private UserSetting userSetting;
+
+    @Autowired
+    private ZlmHttpHookSubscribe subscribe;
+
+    @Autowired
+    private VideoStreamSessionManager streamSession;
+
+
+    @Autowired
+    private IPlayService playService;
 
 
 
@@ -295,4 +317,137 @@
             }
         }
     }
+
+    @Override
+    public void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent,
+                                SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException {
+
+        if (mediaServerItem == null) {
+            logger.info("[鍥芥爣绾ц仈] 璇煶鍠婅瘽鏈壘鍒板彲鐢ㄧ殑zlm. platform: {}", platform.getServerGBId());
+            return;
+        }
+        StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(platform.getServerGBId(), channelId);
+        if (streamInfo != null) {
+            // 濡傛灉zlm涓嶅瓨鍦ㄨ繖涓祦锛屽垯鍒犻櫎鏁版嵁鍗冲彲
+            MediaServerItem mediaServerItemForStreamInfo = mediaServerService.getOne(streamInfo.getMediaServerId());
+            if (mediaServerItemForStreamInfo != null) {
+                Boolean ready = zlmrtpServerFactory.isStreamReady(mediaServerItemForStreamInfo, streamInfo.getApp(), streamInfo.getStream());
+                if (!ready) {
+                    // 閿欒瀛樺湪浜巖edis涓殑鏁版嵁
+                    redisCatchStorage.stopPlay(streamInfo);
+                }else {
+                    // 娴佺‘瀹炲皻鍦ㄦ帹娴侊紝鐩存帴鍥炶皟缁撴灉
+                    JSONObject json = new JSONObject();
+                    json.put("app", streamInfo.getApp());
+                    json.put("stream", streamInfo.getStream());
+                    hookEvent.response(mediaServerItemForStreamInfo, json);
+                    return;
+                }
+            }
+        }
+
+        String streamId = null;
+        if (mediaServerItem.isRtpEnable()) {
+            streamId = String.format("%s_%s", platform.getServerGBId(), channelId);
+        }
+        // 榛樿涓嶈繘琛孲SRC鏍¢獙锛� TODO 鍚庣画鍙敼涓洪厤缃�
+        boolean ssrcCheck = false;
+        SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrcCheck, false);
+        if (ssrcInfo == null || ssrcInfo.getPort() < 0) {
+            logger.info("[鍥芥爣绾ц仈] 鍙戣捣璇煶鍠婅瘽 寮�鍚鍙g洃鍚け璐ワ紝 platform: {}, channel锛� {}", platform.getServerGBId(), channelId);
+            errorEvent.response(new SipSubscribe.EventResult(-1, "绔彛鐩戝惉澶辫触"));
+            return;
+        }
+        logger.info("[鍥芥爣绾ц仈] 鍙戣捣璇煶鍠婅瘽 deviceId: {}, channelId: {},鏀舵祦绔彛锛� {}, 鏀舵祦妯″紡锛歿}, SSRC: {}, SSRC鏍¢獙锛歿}",
+                platform.getServerGBId(), channelId, ssrcInfo.getPort(), userSetting.getBroadcastForPlatform(), ssrcInfo.getSsrc(), ssrcCheck);
+
+        String timeOutTaskKey = UUID.randomUUID().toString();
+        dynamicTask.startDelay(timeOutTaskKey, () -> {
+            // 鎵ц瓒呮椂浠诲姟鏃舵煡璇㈡槸鍚﹀凡缁忔垚鍔燂紝鎴愬姛浜嗗垯涓嶆墽琛岃秴鏃朵换鍔★紝闃叉瓒呮椂浠诲姟鍙栨秷澶辫触鐨勬儏鍐�
+            if (redisCatchStorage.queryPlayByDevice(platform.getServerGBId(), channelId) == null) {
+                logger.info("[鍥芥爣绾ц仈] 鍙戣捣璇煶鍠婅瘽 鏀舵祦瓒呮椂 deviceId: {}, channelId: {}锛岀鍙o細{}, SSRC: {}", platform.getServerGBId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc());
+                // 鐐规挱瓒呮椂鍥炲BYE 鍚屾椂閲婃斁ssrc浠ュ強姝ゆ鐐规挱鐨勮祫婧�
+                try {
+                    commanderForPlatform.streamByeCmd(platform, channelId, ssrcInfo.getStream(), null, null);
+                } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
+                    logger.error("[鐐规挱瓒呮椂]锛� 鍙戦�丅YE澶辫触 {}", e.getMessage());
+                } finally {
+                    timeoutCallback.run(1, "鏀舵祦瓒呮椂");
+                    mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                    mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+                    streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
+                    mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+                }
+            }
+        }, userSetting.getPlayTimeout());
+        commanderForPlatform.broadcastInviteCmd(platform, channelId, mediaServerItem, ssrcInfo, (mediaServerItemForInvite, response)->{
+            dynamicTask.stop(timeOutTaskKey);
+            // hook鍝嶅簲
+            playService.onPublishHandlerForPlay(mediaServerItemForInvite, response, platform.getServerGBId(), channelId);
+            // 鏀跺埌娴�
+            if (hookEvent != null) {
+                hookEvent.response(mediaServerItem, response);
+            }
+        }, event -> {
+            // 鏀跺埌200OK 妫�娴媠src鏄惁鏈夊彉鍖栵紝闃叉涓婄骇鑷畾涔変簡ssrc
+            ResponseEvent responseEvent = (ResponseEvent) event.event;
+            String contentString = new String(responseEvent.getResponse().getRawContent());
+            // 鑾峰彇ssrc
+            int ssrcIndex = contentString.indexOf("y=");
+            // 妫�鏌ユ槸鍚︽湁y瀛楁
+            if (ssrcIndex >= 0) {
+                //ssrc瑙勫畾闀垮害涓�10瀛楄妭锛屼笉鍙栦綑涓嬮暱搴︿互閬垮厤鍚庣画杩樻湁鈥渇=鈥濆瓧娈� TODO 鍚庣画瀵逛笉瑙勮寖鐨勯潪10浣峴src鍏煎
+                String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
+                // 鏌ヨ鍒皊src涓嶄竴鑷翠笖寮�鍚簡ssrc鏍¢獙鍒欓渶瑕侀拡瀵瑰鐞�
+                if (ssrcInfo.getSsrc().equals(ssrcInResponse) || ssrcCheck) {
+                    return;
+                }
+                logger.info("[鐐规挱娑堟伅] 鏀跺埌invite 200, 鍙戠幇涓嬬骇鑷畾涔変簡ssrc: {}", ssrcInResponse);
+                if (!mediaServerItem.isRtpEnable()) {
+                    logger.info("[鐐规挱娑堟伅] SSRC淇 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
+
+                    if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
+                        // ssrc 涓嶅彲鐢�
+                        // 閲婃斁ssrc
+                        mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
+                        streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
+                        event.msg = "涓嬬骇鑷畾涔変簡ssrc,浣嗘槸姝src涓嶅彲鐢�";
+                        event.statusCode = 400;
+                        errorEvent.response(event);
+                        return;
+                    }
+
+                    // 鍗曠鍙fā寮弒treamId涔熸湁鍙樺寲锛岄渶瑕侀噸鏂拌缃洃鍚�
+                    if (!mediaServerItem.isRtpEnable()) {
+                        // 娣诲姞璁㈤槄
+                        HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
+                        subscribe.removeSubscribe(hookSubscribe);
+                        hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
+                        subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
+                            logger.info("[ZLM HOOK] ssrc淇鍚庢敹鍒拌闃呮秷鎭細 " + response.toJSONString());
+                            dynamicTask.stop(timeOutTaskKey);
+                            // hook鍝嶅簲
+                            playService.onPublishHandlerForPlay(mediaServerItemInUse, response, platform.getServerGBId(), channelId);
+                            hookEvent.response(mediaServerItemInUse, response);
+                        });
+                    }
+                    // 鍏抽棴rtp server
+                    mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
+                    // 閲嶆柊寮�鍚痵src server
+                    mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, false, false, ssrcInfo.getPort());
+
+                }
+            }
+        }, eventResult -> {
+            // 鏀跺埌閿欒鍥炲
+            if (errorEvent != null) {
+                errorEvent.response(eventResult);
+            }
+        });
+    }
+
+    @Override
+    public void stopBroadcast(ParentPlatform platform, String channelId, String stream) throws InvalidArgumentException, ParseException, SsrcTransactionNotFoundException, SipException {
+        commanderForPlatform.streamByeCmd(platform, channelId, stream, null, null);
+    }
 }
diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
index 32f0364..5e62981 100644
--- a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
+++ b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
@@ -987,7 +987,7 @@
     }
 
     @Override
-    public AudioBroadcastResult audioBroadcast(Device device, String channelId) {
+    public AudioBroadcastResult audioBroadcastInfo(Device device, String channelId) {
         if (device == null || channelId == null) {
             return null;
         }
@@ -1012,46 +1012,51 @@
     }
 
     @Override
-    public void audioBroadcastCmd(Device device, String channelId, int timeout, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException {
+    public boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException {
         if (device == null || channelId == null) {
-            return;
+            return false;
         }
         logger.info("[璇煶鍠婅瘽] device锛� {}, channel: {}", device.getDeviceId(), channelId);
         DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
         if (deviceChannel == null) {
             logger.warn("寮�鍚闊冲箍鎾殑鏃跺�欐湭鎵惧埌閫氶亾锛� {}", channelId);
             event.call("寮�鍚闊冲箍鎾殑鏃跺�欐湭鎵惧埌閫氶亾");
-            return;
+            return false;
         }
         // 鏌ヨ閫氶亾浣跨敤鐘舵��
-        if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
-            SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
-            if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
-                // 鏌ヨ娴佹槸鍚﹀瓨鍦紝涓嶅瓨鍦ㄥ垯璁や负鏄紓甯哥姸鎬�
-                MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId());
-                Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStreamId());
-                if (streamReady) {
-                    logger.warn("璇煶骞挎挱宸茬粡寮�鍚細 {}", channelId);
-                    event.call("璇煶骞挎挱宸茬粡寮�鍚�");
-                    return;
-                } else {
-                    audioBroadcastManager.del(deviceChannel.getDeviceId(), channelId);
-                    redisCatchStorage.deleteSendRTPServer(device.getDeviceId(), channelId, sendRtpItem.getCallId(), sendRtpItem.getStreamId());
-                }
-            }
+        if (audioBroadcastInUse(device, channelId)) {
+            return false;
         }
 
         // 鍙戦�侀�氱煡
         cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> {
             // 鍙戦�佹垚鍔�
-            AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, AudioBroadcastCatchStatus.Ready);
-            audioBroadcastManager.add(audioBroadcastCatch);
+            AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, mediaServerItem, app, stream, event, AudioBroadcastCatchStatus.Ready, isFromPlatform);
+            audioBroadcastManager.update(audioBroadcastCatch);
         }, eventResultForError -> {
             // 鍙戦�佸け璐�
             logger.error("璇煶骞挎挱鍙戦�佸け璐ワ細 {}:{}", channelId, eventResultForError.msg);
             event.call("璇煶骞挎挱鍙戦�佸け璐�");
             stopAudioBroadcast(device.getDeviceId(), channelId);
         });
+        return true;
+    }
+
+    @Override
+    public boolean audioBroadcastInUse(Device device, String channelId) {
+        if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
+            SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
+            if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
+                // 鏌ヨ娴佹槸鍚﹀瓨鍦紝涓嶅瓨鍦ㄥ垯璁や负鏄紓甯哥姸鎬�
+                MediaServerItem mediaServerServiceOne = mediaServerService.getOne(sendRtpItem.getMediaServerId());
+                Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerServiceOne, sendRtpItem.getApp(), sendRtpItem.getStreamId());
+                if (streamReady) {
+                    logger.warn("璇煶骞挎挱閫氶亾浣跨敤涓細 {}", channelId);
+                    return true;
+                }
+            }
+        }
+        return false;
     }
 
 
@@ -1075,6 +1080,9 @@
                 param.put("stream", sendRtpItem.getStreamId());
                 zlmresTfulUtils.stopSendRtp(mediaInfo, param);
             }
+            if (audioBroadcastCatch.isFromPlatform()) {
+                // TODO 鍚戜笂绾у彂閫丅YE缁撴潫璇煶鍠婅瘽
+            }
 
             audioBroadcastManager.del(deviceId, channelId);
         }
diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
index 19b2adc..0747b9a 100644
--- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
+++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
@@ -267,7 +267,7 @@
 			throw new ControllerException(ErrorCode.ERROR400.getCode(), "鏈壘鍒伴�氶亾锛� " + channelId);
 		}
 
-		return playService.audioBroadcast(device, channelId);
+		return playService.audioBroadcastInfo(device, channelId);
 
 	}
 
diff --git a/src/main/resources/all-application.yml b/src/main/resources/all-application.yml
index ba150fb..92c2acb 100644
--- a/src/main/resources/all-application.yml
+++ b/src/main/resources/all-application.yml
@@ -168,7 +168,7 @@
     # 淇濆瓨绉诲姩浣嶇疆鍘嗗彶杞ㄨ抗锛歵rue:淇濈暀鍘嗗彶鏁版嵁锛宖alse:浠呬繚鐣欐渶鍚庣殑浣嶇疆(榛樿)
     save-position-history: false
     # 鐐规挱绛夊緟瓒呮椂鏃堕棿,鍗曚綅锛氭绉�
-    play-timeout: 3000
+    play-timeout: 18000
     # 涓婄骇鐐规挱绛夊緟瓒呮椂鏃堕棿,鍗曚綅锛氭绉�
     platform-play-timeout: 60000
     # 鏄惁寮�鍚帴鍙i壌鏉�
@@ -195,6 +195,8 @@
     gb-send-stream-strict: false
     # 璁惧涓婄嚎鏃舵槸鍚﹁嚜鍔ㄥ悓姝ラ�氶亾
     sync-channel-on-device-online: false
+    # 鍥芥爣绾ц仈璇煶鍠婅瘽鍙戞祦妯″紡 * UDP:udp浼犺緭 TCP-ACTIVE锛歵cp涓诲姩妯″紡 TCP-PASSIVE锛歵cp琚姩妯″紡
+    broadcast-for-platform: UDP
 
 # 鍏抽棴鍦ㄧ嚎鏂囨。锛堢敓浜х幆澧冨缓璁叧闂級
 springdoc:
diff --git a/web_src/src/components/dialog/devicePlayer.vue b/web_src/src/components/dialog/devicePlayer.vue
index 89b3e07..cd58f41 100644
--- a/web_src/src/components/dialog/devicePlayer.vue
+++ b/web_src/src/components/dialog/devicePlayer.vue
@@ -279,7 +279,7 @@
                     </div>
 
                 </el-tab-pane>
-              <el-tab-pane label="璇煶瀵硅" name="broadcast" >
+              <el-tab-pane label="璇煶鍠婅瘽" name="broadcast" >
                 <div class="trank" style="text-align: center;">
                   <el-button @click="broadcastStatusClick()" :type="getBroadcastStatus()" :disabled="broadcastStatus === -2" circle icon="el-icon-microphone"  style="font-size: 32px; padding: 24px;margin-top: 24px;"/>
                   <p>
@@ -854,7 +854,7 @@
             if (this.broadcastStatus == -1) {
               // 榛樿鐘舵�侊紝 寮�濮�
               this.broadcastStatus = 0
-              // 鍙戣捣璇煶瀵硅
+              // 鍙戣捣璇煶鍠婅瘽
               this.$axios({
                 method: 'get',
                 url: '/api/play/broadcast/' + this.deviceId + '/' + this.channelId + "?timeout=30"
@@ -897,7 +897,7 @@
               let pushKey = res.data.data.pushKey;
               // 鑾峰彇鎺ㄦ祦閴存潈KEY
               url += "&sign=" + crypto.createHash('md5').update(pushKey, "utf8").digest('hex')
-              console.log("寮�濮嬭闊冲璁诧細 " + url)
+              console.log("寮�濮嬭闊冲枈璇濓細 " + url)
               this.broadcastRtc = new ZLMRTCClient.Endpoint({
                 debug: true, // 鏄惁鎵撳嵃鏃ュ織
                 zlmsdpUrl: url, //娴佸湴鍧�
@@ -923,7 +923,7 @@
                 console.error('涓嶆敮鎸亀ebrtc',e)
                 this.$message({
                   showClose: true,
-                  message: '涓嶆敮鎸亀ebrtc, 鏃犳硶杩涜璇煶瀵硅',
+                  message: '涓嶆敮鎸亀ebrtc, 鏃犳硶杩涜璇煶鍠婅瘽',
                   type: 'error'
                 });
                 this.broadcastStatus = -1;

--
Gitblit v1.8.0