Commit 26bdf2e7768ee5dfc400c3970a5aa129fed49453

Authored by 648540858
2 parents b3c46365 420dbce9

Merge branch '级联' into main-dev

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java
#	src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
#	src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
#	src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
#	src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
#	src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
#	web_src/src/components/dialog/devicePlayer.vue
Showing 28 changed files with 970 additions and 174 deletions
doc/README.md
... ... @@ -52,7 +52,7 @@
52 52 - [X] 报警订阅
53 53 - [X] 目录订阅
54 54 - [ ] 语音广播
55   -- [ ] 语音对讲
  55 +- [ ] 语音喊话
56 56  
57 57 **作为下级平台**
58 58 - [X] 注册
... ... @@ -91,7 +91,7 @@
91 91 - [ ] 报警订阅
92 92 - [X] 目录订阅
93 93 - [ ] 语音广播
94   -- [ ] 语音对讲
  94 +- [ ] 语音喊话
95 95  
96 96  
97 97  
... ...
doc/_content/theory/broadcast_cascade.md 0 → 100644
  1 +<!-- 点播流程 -->
  2 +
  3 +# 点播流程
  4 +> 以下为WVP-PRO级联语音喊话流程。
  5 +
  6 +```plantuml
  7 +@startuml
  8 +"上级平台" -> "下级平台": 1. 发起语音喊话请求
  9 +"上级平台" <-- "下级平台": 2. 200OK
  10 +"上级平台" <- "下级平台": 3. 回复Result OK
  11 +"上级平台" --> "下级平台": 4. 200OK
  12 +
  13 +"下级平台" -> "设备": 5. 发起语音喊话请求
  14 +"下级平台" <-- "设备": 6. 200OK
  15 +"下级平台" <- "设备": 7. 回复Result OK
  16 +"下级平台" --> "设备": 8. 200OK
  17 +
  18 +"下级平台" <- "设备": 9. invite(broadcast)
  19 +"下级平台" --> "设备": 10. 100 trying
  20 +"下级平台" --> "设备": 11. 200OK SDP
  21 +"下级平台" <-- "设备": 12. ack
  22 +
  23 +"上级平台" <- "下级平台": 13. invite(broadcast)
  24 +"上级平台" --> "下级平台": 14. 100 trying
  25 +"上级平台" --> "下级平台": 15. 200OK SDP
  26 +"上级平台" <-- "下级平台": 16. ack
  27 +
  28 +"上级平台" -> "下级平台": 17. 推送RTP
  29 +"下级平台" -> "设备": 18. 推送RTP
  30 +
  31 +@enduml
  32 +```
  33 +
  34 +
  35 +## 注册流程描述如下:
  36 +1. 用户从网页或调用接口发起点播请求;
  37 +2. WVP-PRO向摄像机发送Invite消息,消息头域中携带 Subject字段,表明点播的视频源ID、发送方媒体流序列号、ZLMediaKit接收流使用的IP、端口号、
  38 + 接收端媒体流序列号等参数,SDP消息体中 s字段为“Play”代表实时点播,y字段描述SSRC值,f字段描述媒体参数。
  39 +3. 摄像机向WVP-PRO回复200OK,消息体中描述了媒体流发送者发送媒体流的IP、端口、媒体格式、SSRC字段等内容。
  40 +4. WVP-PRO向设备回复Ack, 会话建立成功。
  41 +5. 设备向ZLMediaKit发送实时流。
  42 +6. ZLMediaKit向WVP-PRO发送流改变事件。
  43 +7. WVP-PRO向WEB用户回复播放地址。
  44 +8. ZLMediaKit向WVP发送流无人观看事件。
  45 +9. WVP-PRO向设备回复Bye, 结束会话。
  46 +10. 设备回复200OK,会话结束成功。
... ...
doc/_sidebar.md
... ... @@ -19,6 +19,7 @@
19 19 * [树形结构](_content/theory/channel_tree.md)
20 20 * [注册流程](_content/theory/register.md)
21 21 * [点播流程](_content/theory/play.md)
  22 + * [级联语音喊话流程](_content/theory/broadcast_cascade.md)
22 23 * **必备技巧**
23 24 * [抓包](_content/skill/tcpdump.md)
24 25  
... ...
src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java
... ... @@ -55,6 +55,8 @@ public class UserSetting {
55 55  
56 56 private String thirdPartyGBIdReg = "[\\s\\S]*";
57 57  
  58 + private String broadcastForPlatform = "UDP";
  59 +
58 60 private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
59 61  
60 62 public Boolean getSavePositionHistory() {
... ... @@ -205,6 +207,14 @@ public class UserSetting {
205 207 this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline;
206 208 }
207 209  
  210 + public String getBroadcastForPlatform() {
  211 + return broadcastForPlatform;
  212 + }
  213 +
  214 + public void setBroadcastForPlatform(String broadcastForPlatform) {
  215 + this.broadcastForPlatform = broadcastForPlatform;
  216 + }
  217 +
208 218 public Boolean getPushStreamAfterAck() {
209 219 return pushStreamAfterAck;
210 220 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java
... ... @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean;
2 2  
3 3  
4 4 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  5 +import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
5 6 import gov.nist.javax.sip.message.SIPResponse;
6 7  
7 8 /**
... ... @@ -11,18 +12,24 @@ import gov.nist.javax.sip.message.SIPResponse;
11 12 public class AudioBroadcastCatch {
12 13  
13 14  
14   - public AudioBroadcastCatch(String deviceId,
15   - String channelId,
16   - AudioBroadcastCatchStatus status,
17   - MediaServerItem mediaServerItem,
18   - String app,
19   - String stream) {
  15 + public AudioBroadcastCatch(
  16 + String deviceId,
  17 + String channelId,
  18 + MediaServerItem mediaServerItem,
  19 + String app,
  20 + String stream,
  21 + AudioBroadcastEvent event,
  22 + AudioBroadcastCatchStatus status,
  23 + boolean isFromPlatform
  24 + ) {
20 25 this.deviceId = deviceId;
21 26 this.channelId = channelId;
22 27 this.status = status;
23   - this.mediaServerItem = mediaServerItem;
  28 + this.event = event;
  29 + this.isFromPlatform = isFromPlatform;
24 30 this.app = app;
25 31 this.stream = stream;
  32 + this.mediaServerItem = mediaServerItem;
26 33 }
27 34  
28 35 public AudioBroadcastCatch() {
... ... @@ -39,21 +46,26 @@ public class AudioBroadcastCatch {
39 46 private String channelId;
40 47  
41 48 /**
42   - * 使用的流媒体
  49 + * 流媒体信息
43 50 */
44 51 private MediaServerItem mediaServerItem;
45 52  
46 53 /**
47   - * 待推送给设备的流应用名
  54 + * 关联的流APP
48 55 */
49 56 private String app;
50 57  
51 58 /**
52   - * 待推送给设备的流ID
  59 + * 关联的流STREAM
53 60 */
54 61 private String stream;
55 62  
56 63 /**
  64 + * 是否是级联语音喊话
  65 + */
  66 + private boolean isFromPlatform;
  67 +
  68 + /**
57 69 * 语音广播状态
58 70 */
59 71 private AudioBroadcastCatchStatus status;
... ... @@ -63,6 +75,11 @@ public class AudioBroadcastCatch {
63 75 */
64 76 private SipTransactionInfo sipTransactionInfo;
65 77  
  78 + /**
  79 + * 请求结果回调
  80 + */
  81 + private AudioBroadcastEvent event;
  82 +
66 83  
67 84 public String getDeviceId() {
68 85 return deviceId;
... ... @@ -123,4 +140,44 @@ public class AudioBroadcastCatch {
123 140 public void setMediaServerItem(MediaServerItem mediaServerItem) {
124 141 this.mediaServerItem = mediaServerItem;
125 142 }
  143 +
  144 + public AudioBroadcastEvent getEvent() {
  145 + return event;
  146 + }
  147 +
  148 + public void setEvent(AudioBroadcastEvent event) {
  149 + this.event = event;
  150 + }
  151 +
  152 + public String getApp() {
  153 + return app;
  154 + }
  155 +
  156 + public void setApp(String app) {
  157 + this.app = app;
  158 + }
  159 +
  160 + public String getStream() {
  161 + return stream;
  162 + }
  163 +
  164 + public void setStream(String stream) {
  165 + this.stream = stream;
  166 + }
  167 +
  168 + public boolean isFromPlatform() {
  169 + return isFromPlatform;
  170 + }
  171 +
  172 + public void setFromPlatform(boolean fromPlatform) {
  173 + isFromPlatform = fromPlatform;
  174 + }
  175 +
  176 + public MediaServerItem getMediaServerItem() {
  177 + return mediaServerItem;
  178 + }
  179 +
  180 + public void setMediaServerItem(MediaServerItem mediaServerItem) {
  181 + this.mediaServerItem = mediaServerItem;
  182 + }
126 183 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java
... ... @@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean;
2 2  
3 3 public enum InviteStreamType {
4 4  
5   - PLAY,PLAYBACK,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,TALK
  5 + PLAY,PLAYBACK,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,BROADCAST,TALK
6 6  
7 7  
8 8 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java
... ... @@ -66,7 +66,7 @@ public class ParentPlatform {
66 66 * 设备端口
67 67 */
68 68 @Schema(description = "设备端口")
69   - private String devicePort;
  69 + private int devicePort;
70 70  
71 71 /**
72 72 * SIP认证用户名(默认使用设备国标编号)
... ... @@ -261,11 +261,11 @@ public class ParentPlatform {
261 261 this.deviceIp = deviceIp;
262 262 }
263 263  
264   - public String getDevicePort() {
  264 + public int getDevicePort() {
265 265 return devicePort;
266 266 }
267 267  
268   - public void setDevicePort(String devicePort) {
  268 + public void setDevicePort(int devicePort) {
269 269 this.devicePort = devicePort;
270 270 }
271 271  
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java
... ... @@ -86,6 +86,11 @@ public class SipSubscribe {
86 86 public String callId;
87 87 public EventObject event;
88 88  
  89 + public EventResult(int statusCode, String msg) {
  90 + this.statusCode = statusCode;
  91 + this.msg = msg;
  92 + }
  93 +
89 94 public EventResult(EventObject event) {
90 95 this.event = event;
91 96 if (event instanceof ResponseEvent) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
1 1 package com.genersoft.iot.vmp.gb28181.transmit.cmd;
2 2  
  3 +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
3 4 import com.genersoft.iot.vmp.gb28181.bean.*;
4 5 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
  6 +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
  7 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
5 8 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
  9 +import com.genersoft.iot.vmp.service.bean.SSRCInfo;
6 10  
7 11 import javax.sip.InvalidArgumentException;
8 12 import javax.sip.SipException;
... ... @@ -14,42 +18,56 @@ public interface ISIPCommanderForPlatform {
14 18  
15 19 /**
16 20 * 向上级平台注册
  21 + *
17 22 * @param parentPlatform
18 23 * @return
19 24 */
20   - void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
21   - void register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister) throws SipException, InvalidArgumentException, ParseException;
  25 + void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
  26 + throws InvalidArgumentException, ParseException, SipException;
  27 +
  28 + void register(ParentPlatform parentPlatform, String callId, WWWAuthenticateHeader www,
  29 + SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent, boolean registerAgain, boolean isRegister)
  30 + throws SipException, InvalidArgumentException, ParseException;
22 31  
23 32 /**
24 33 * 向上级平台注销
  34 + *
25 35 * @param parentPlatform
26 36 * @return
27 37 */
28   - void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException;
  38 + void unregister(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
  39 + throws InvalidArgumentException, ParseException, SipException;
29 40  
30 41  
31 42 /**
32 43 * 向上级平发送心跳信息
  44 + *
33 45 * @param parentPlatform
34 46 * @return callId(作为接受回复的判定)
35 47 */
36   - String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
  48 + String keepalive(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
  49 + throws SipException, InvalidArgumentException, ParseException;
37 50  
38 51  
39 52 /**
40 53 * 向上级回复通道信息
41   - * @param channel 通道信息
  54 + *
  55 + * @param channel 通道信息
42 56 * @param parentPlatform 平台信息
43 57 * @param sn
44 58 * @param fromTag
45 59 * @param size
46 60 * @return
47 61 */
48   - void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException;
49   - void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag) throws InvalidArgumentException, ParseException, SipException;
  62 + void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size)
  63 + throws SipException, InvalidArgumentException, ParseException;
  64 +
  65 + void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag)
  66 + throws InvalidArgumentException, ParseException, SipException;
50 67  
51 68 /**
52 69 * 向上级回复DeviceInfo查询信息
  70 + *
53 71 * @param parentPlatform 平台信息
54 72 * @param sn SN
55 73 * @param fromTag FROM头的tag信息
... ... @@ -59,32 +77,37 @@ public interface ISIPCommanderForPlatform {
59 77  
60 78 /**
61 79 * 向上级回复DeviceStatus查询信息
  80 + *
62 81 * @param parentPlatform 平台信息
63 82 * @param sn
64 83 * @param fromTag
65 84 * @return
66 85 */
67   - void deviceStatusResponse(ParentPlatform parentPlatform,String channelId, String sn, String fromTag,int status) throws SipException, InvalidArgumentException, ParseException;
  86 + void deviceStatusResponse(ParentPlatform parentPlatform,String channelId, String sn, String fromTag,int status) throws SipException, InvalidArgumentException, ParseException ;
68 87  
69 88 /**
70 89 * 向上级回复移动位置订阅消息
  90 + *
71 91 * @param parentPlatform 平台信息
72   - * @param gpsMsgInfo GPS信息
73   - * @param subscribeInfo 订阅相关的信息
  92 + * @param gpsMsgInfo GPS信息
  93 + * @param subscribeInfo 订阅相关的信息
74 94 * @return
75 95 */
76   - void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
  96 + void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo)
  97 + throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
77 98  
78 99 /**
79 100 * 向上级回复报警消息
  101 + *
80 102 * @param parentPlatform 平台信息
81   - * @param deviceAlarm 报警信息信息
  103 + * @param deviceAlarm 报警信息信息
82 104 * @return
83 105 */
84 106 void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException;
85 107  
86 108 /**
87 109 * 回复catalog事件-增加/更新
  110 + *
88 111 * @param parentPlatform
89 112 * @param deviceChannels
90 113 */
... ... @@ -92,22 +115,28 @@ public interface ISIPCommanderForPlatform {
92 115  
93 116 /**
94 117 * 回复catalog事件-删除
  118 + *
95 119 * @param parentPlatform
96 120 * @param deviceChannels
97 121 */
98   - void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
  122 + void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels,
  123 + SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException,
  124 + ParseException, NoSuchFieldException, SipException, IllegalAccessException;
99 125  
100 126 /**
101 127 * 回复recordInfo
102   - * @param deviceChannel 通道信息
  128 + *
  129 + * @param deviceChannel 通道信息
103 130 * @param parentPlatform 平台信息
104   - * @param fromTag fromTag
105   - * @param recordInfo 录像信息
  131 + * @param fromTag fromTag
  132 + * @param recordInfo 录像信息
106 133 */
107   - void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) throws SipException, InvalidArgumentException, ParseException;
  134 + void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo)
  135 + throws SipException, InvalidArgumentException, ParseException;
108 136  
109 137 /**
110 138 * 录像播放推送完成时发送MediaStatus消息
  139 + *
111 140 * @param platform
112 141 * @param sendRtpItem
113 142 * @return
... ... @@ -116,9 +145,19 @@ public interface ISIPCommanderForPlatform {
116 145  
117 146 /**
118 147 * 向发起点播的上级回复bye
  148 + *
119 149 * @param platform 平台信息
120   - * @param callId callId
  150 + * @param callId callId
121 151 */
122 152 void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException;
  153 +
123 154 void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException;
  155 +
  156 + void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
  157 +
  158 + void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,
  159 + SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent,
  160 + SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException;
  161 +
  162 + void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
124 163 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java
... ... @@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.conf.SipConfig;
4 4 import com.genersoft.iot.vmp.gb28181.SipLayer;
5 5 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
6 6 import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
  7 +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
7 8 import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
8 9 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
9 10 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
... ... @@ -14,7 +15,8 @@ import org.springframework.beans.factory.annotation.Autowired;
14 15 import org.springframework.stereotype.Component;
15 16 import org.springframework.util.DigestUtils;
16 17  
17   -import javax.sip.*;
  18 +import javax.sip.InvalidArgumentException;
  19 +import javax.sip.PeerUnavailableException;
18 20 import javax.sip.address.Address;
19 21 import javax.sip.address.SipURI;
20 22 import javax.sip.header.*;
... ... @@ -22,7 +24,6 @@ import javax.sip.message.Request;
22 24 import javax.validation.constraints.NotNull;
23 25 import java.text.ParseException;
24 26 import java.util.ArrayList;
25   -import java.util.List;
26 27 import java.util.UUID;
27 28  
28 29 /**
... ... @@ -175,7 +176,7 @@ public class SIPRequestHeaderPlarformProvider {
175 176 SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
176 177 // via
177 178 ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
178   - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
  179 + ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
179 180 parentPlatform.getTransport(), viaTag);
180 181 viaHeader.setRPort();
181 182 viaHeaders.add(viaHeader);
... ... @@ -212,7 +213,7 @@ public class SIPRequestHeaderPlarformProvider {
212 213 SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
213 214 // via
214 215 ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
215   - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
  216 + ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
216 217 parentPlatform.getTransport(), SipUtils.getNewViaTag());
217 218 viaHeader.setRPort();
218 219 viaHeaders.add(viaHeader);
... ... @@ -272,7 +273,7 @@ public class SIPRequestHeaderPlarformProvider {
272 273 SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort());
273 274 // via
274 275 ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
275   - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(platform.getDeviceIp(), Integer.parseInt(platform.getDevicePort()),
  276 + ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(),
276 277 platform.getTransport(), SipUtils.getNewViaTag());
277 278 viaHeader.setRPort();
278 279 viaHeaders.add(viaHeader);
... ... @@ -308,4 +309,80 @@ public class SIPRequestHeaderPlarformProvider {
308 309  
309 310 return request;
310 311 }
  312 +
  313 + public Request createInviteRequest(ParentPlatform platform, String channelId, String content, String viaTag, String fromTag, String ssrc, CallIdHeader callIdHeader) throws PeerUnavailableException, ParseException, InvalidArgumentException {
  314 + Request request = null;
  315 + //请求行
  316 + String platformHostAddress = platform.getServerIP() + ":" + platform.getServerPort();
  317 + String localHostAddress = sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort();
  318 + SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, platformHostAddress);
  319 + //via
  320 + ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
  321 + ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), viaTag);
  322 + viaHeader.setRPort();
  323 + viaHeaders.add(viaHeader);
  324 +
  325 + //from
  326 + SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getDeviceGBId(), sipConfig.getDomain());
  327 + Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
  328 + FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
  329 + //to
  330 + SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, platformHostAddress);
  331 + Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
  332 + ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null);
  333 +
  334 + //Forwards
  335 + MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
  336 +
  337 + //ceq
  338 + CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
  339 + request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
  340 +
  341 + request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
  342 +
  343 + Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),localHostAddress));
  344 + request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
  345 + // Subject
  346 + SubjectHeader subjectHeader = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
  347 + request.addHeader(subjectHeader);
  348 + ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
  349 + request.setContent(content, contentTypeHeader);
  350 + return request;
  351 + }
  352 +
  353 + public Request createByteRequest(ParentPlatform platform, String channelId, SipTransactionInfo transactionInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException {
  354 + String deviceHostAddress = platform.getDeviceIp() + ":" + platform.getDevicePort();
  355 + Request request = null;
  356 + SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
  357 +
  358 + // via
  359 + ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
  360 + ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag());
  361 + viaHeaders.add(viaHeader);
  362 + //from
  363 + SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
  364 + Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI);
  365 + FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.isAsSender()?transactionInfo.getFromTag():transactionInfo.getToTag());
  366 + //to
  367 + SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress);
  368 + Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI);
  369 + ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,transactionInfo.isAsSender()?transactionInfo.getToTag():transactionInfo.getFromTag());
  370 +
  371 + //Forwards
  372 + MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70);
  373 +
  374 + //ceq
  375 + CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
  376 + CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
  377 + request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
  378 +
  379 + request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
  380 +
  381 + Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort()));
  382 + request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress));
  383 +
  384 + request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil));
  385 +
  386 + return request;
  387 + }
311 388 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
... ... @@ -591,12 +591,12 @@ public class SIPCommander implements ISIPCommander {
591 591 return;
592 592 }
593 593 if (!mediaServerItem.isRtpEnable()) {
594   - // 单端口暂不支持语音对讲
595   - logger.info("[语音对讲] 单端口暂不支持此操作");
  594 + // 单端口暂不支持语音喊话
  595 + logger.info("[语音喊话] 单端口暂不支持此操作");
596 596 return;
597 597 }
598 598  
599   - logger.info("[语音对讲] {} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), sendRtpItem.getPort());
  599 + logger.info("[语音喊话] {} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), sendRtpItem.getPort());
600 600 HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
601 601 subscribe.addSubscribe(hookSubscribeForStreamChange, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
602 602 if (event != null) {
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
1 1 package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
2 2  
3 3 import com.alibaba.fastjson2.JSON;
  4 +import com.alibaba.fastjson2.JSONObject;
4 5 import com.genersoft.iot.vmp.conf.DynamicTask;
  6 +import com.genersoft.iot.vmp.conf.UserSetting;
  7 +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
5 8 import com.genersoft.iot.vmp.gb28181.SipLayer;
6 9 import com.genersoft.iot.vmp.gb28181.bean.*;
7 10 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
  11 +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
8 12 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
9 13 import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
10 14 import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider;
11 15 import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
12 16 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
  17 +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
  18 +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
  19 +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
13 20 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
14 21 import com.genersoft.iot.vmp.service.IMediaServerService;
15 22 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
  23 +import com.genersoft.iot.vmp.service.bean.SSRCInfo;
16 24 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
17 25 import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
18 26 import com.genersoft.iot.vmp.utils.DateUtil;
19 27 import gov.nist.javax.sip.message.MessageFactoryImpl;
20 28 import gov.nist.javax.sip.message.SIPRequest;
  29 +import gov.nist.javax.sip.message.SIPResponse;
21 30 import org.slf4j.Logger;
22 31 import org.slf4j.LoggerFactory;
23 32 import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -27,6 +36,7 @@ import org.springframework.stereotype.Component;
27 36 import org.springframework.util.ObjectUtils;
28 37  
29 38 import javax.sip.InvalidArgumentException;
  39 +import javax.sip.ResponseEvent;
30 40 import javax.sip.SipException;
31 41 import javax.sip.header.CallIdHeader;
32 42 import javax.sip.header.WWWAuthenticateHeader;
... ... @@ -63,6 +73,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
63 73 private SIPSender sipSender;
64 74  
65 75 @Autowired
  76 + private ZlmHttpHookSubscribe subscribe;
  77 +
  78 + @Autowired
  79 + private UserSetting userSetting;
  80 +
  81 +
  82 + @Autowired
  83 + private VideoStreamSessionManager streamSession;
  84 +
  85 + @Autowired
66 86 private DynamicTask dynamicTask;
67 87  
68 88 @Override
... ... @@ -336,6 +356,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
336 356 sipSender.transmitRequest(parentPlatform.getDeviceIp(), request);
337 357 }
338 358  
  359 +
339 360 /**
340 361 * 向上级回复DeviceStatus查询信息
341 362 * @param parentPlatform 平台信息
... ... @@ -702,4 +723,107 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
702 723 }
703 724 sipSender.transmitRequest(platform.getDeviceIp(),byeRequest);
704 725 }
  726 +
  727 + @Override
  728 + public void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
  729 + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(platform.getServerGBId(), channelId, callId, stream);
  730 + if (ssrcTransaction == null) {
  731 + throw new SsrcTransactionNotFoundException(platform.getServerGBId(), channelId, callId, stream);
  732 + }
  733 +
  734 + mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
  735 + mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
  736 + streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
  737 +
  738 + Request byteRequest = headerProviderPlatformProvider.createByteRequest(platform, channelId, ssrcTransaction.getSipTransactionInfo());
  739 + sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), byteRequest, null, okEvent);
  740 + }
  741 +
  742 + @Override
  743 + public void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
  744 + if (platform == null || deviceChannel == null) {
  745 + return;
  746 + }
  747 + String characterSet = platform.getCharacterSet();
  748 + StringBuffer mediaStatusXml = new StringBuffer(200);
  749 + mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
  750 + mediaStatusXml.append("<Response>\r\n");
  751 + mediaStatusXml.append("<CmdType>Broadcast</CmdType>\r\n");
  752 + mediaStatusXml.append("<SN>" + sn + "</SN>\r\n");
  753 + mediaStatusXml.append("<DeviceID>" + deviceChannel.getChannelId() + "</DeviceID>\r\n");
  754 + mediaStatusXml.append("<Result>" + (result?"OK":"ERROR") + "</Result>\r\n");
  755 + mediaStatusXml.append("</Response>\r\n");
  756 +
  757 + CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(platform.getDeviceIp(), platform.getTransport());
  758 +
  759 + SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(platform, mediaStatusXml.toString(),
  760 + SipUtils.getNewFromTag(), SipUtils.getNewViaTag(), callIdHeader);
  761 +
  762 + sipSender.transmitRequest(platform.getDeviceIp(),messageRequest, errorEvent, okEvent);
  763 + }
  764 +
  765 + @Override
  766 + public void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,
  767 + SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent,
  768 + SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException {
  769 + String stream = ssrcInfo.getStream();
  770 +
  771 + if (platform == null) {
  772 + return;
  773 + }
  774 +
  775 + logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
  776 + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
  777 + subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
  778 + if (event != null) {
  779 + event.response(mediaServerItemInUse, json);
  780 + subscribe.removeSubscribe(hookSubscribe);
  781 + }
  782 + });
  783 + String sdpIp = mediaServerItem.getSdpIp();
  784 +
  785 + StringBuffer content = new StringBuffer(200);
  786 + content.append("v=0\r\n");
  787 + content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
  788 + content.append("s=Play\r\n");
  789 + content.append("c=IN IP4 " + sdpIp + "\r\n");
  790 + content.append("t=0 0\r\n");
  791 +
  792 + if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
  793 + content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n");
  794 + } else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
  795 + content.append("m=video " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n");
  796 + } else if ("UDP".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
  797 + content.append("m=video " + ssrcInfo.getPort() + " RTP/AVP 8 96\r\n");
  798 + }
  799 +
  800 + content.append("a=recvonly\r\n");
  801 + content.append("a=rtpmap:8 PCMA/8000\r\n");
  802 + content.append("a=rtpmap:96 PS/90000\r\n");
  803 + if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
  804 + content.append("a=setup:passive\r\n");
  805 + content.append("a=connection:new\r\n");
  806 + }else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
  807 + content.append("a=setup:active\r\n");
  808 + content.append("a=connection:new\r\n");
  809 + }
  810 +
  811 + content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
  812 + CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getTransport());
  813 +
  814 + Request request = headerProviderPlatformProvider.createInviteRequest(platform, channelId,
  815 + content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), ssrcInfo.getSsrc(),
  816 + callIdHeader);
  817 + sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), request, (e -> {
  818 + streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
  819 + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  820 + errorEvent.response(e);
  821 + }), e -> {
  822 + // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
  823 + ResponseEvent responseEvent = (ResponseEvent) e.event;
  824 + SIPResponse response = (SIPResponse) responseEvent.getResponse();
  825 + streamSession.put(platform.getServerGBId(), channelId, callIdHeader.getCallId(), stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play);
  826 + okEvent.response(e);
  827 + });
  828 + }
705 829 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java
... ... @@ -156,7 +156,6 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
156 156 }
157 157 }
158 158 }
159   -
160 159 }
161 160  
162 161 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java
... ... @@ -202,7 +202,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
202 202  
203 203 MediaServerItem mediaServerItem = null;
204 204 StreamPushItem streamPushItem = null;
205   - StreamProxyItem proxyByAppAndStream =null;
  205 + StreamProxyItem proxyByAppAndStream = null;
206 206 // 不是通道可能是直播流
207 207 if (channel != null && gbStream == null) {
208 208 // 通道存在,发100,TRYING
... ... @@ -1018,7 +1018,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
1018 1018  
1019 1019  
1020 1020 CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
1021   - sendRtpItem.setPlayType(InviteStreamType.TALK);
  1021 + sendRtpItem.setPlayType(InviteStreamType.BROADCAST);
1022 1022 sendRtpItem.setCallId(callIdHeader.getCallId());
1023 1023 sendRtpItem.setPlatformId(requesterId);
1024 1024 sendRtpItem.setStatus(1);
... ... @@ -1113,7 +1113,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
1113 1113 }
1114 1114  
1115 1115 } catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) {
1116   - logger.error("[命令发送失败] 语音对讲 回复200OK(SDP): {}", e.getMessage());
  1116 + logger.error("[命令发送失败] 语音喊话 回复200OK(SDP): {}", e.getMessage());
1117 1117 }
1118 1118 return sipResponse;
1119 1119 }
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java 0 → 100644
  1 +package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
  2 +
  3 +import com.alibaba.fastjson2.JSONObject;
  4 +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
  5 +import com.genersoft.iot.vmp.gb28181.bean.*;
  6 +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
  7 +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
  8 +import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
  9 +import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
  10 +import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
  11 +import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
  12 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  13 +import com.genersoft.iot.vmp.service.IDeviceService;
  14 +import com.genersoft.iot.vmp.service.IMediaServerService;
  15 +import com.genersoft.iot.vmp.service.IPlatformService;
  16 +import com.genersoft.iot.vmp.service.IPlayService;
  17 +import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  18 +import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
  19 +import gov.nist.javax.sip.message.SIPRequest;
  20 +import org.dom4j.Element;
  21 +import org.slf4j.Logger;
  22 +import org.slf4j.LoggerFactory;
  23 +import org.springframework.beans.factory.InitializingBean;
  24 +import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.springframework.stereotype.Component;
  26 +
  27 +import javax.sip.InvalidArgumentException;
  28 +import javax.sip.RequestEvent;
  29 +import javax.sip.SipException;
  30 +import javax.sip.message.Response;
  31 +import java.text.ParseException;
  32 +
  33 +/**
  34 + * 状态信息(心跳)报送
  35 + */
  36 +@Component
  37 +public class BroadcastNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
  38 +
  39 + private Logger logger = LoggerFactory.getLogger(BroadcastNotifyMessageHandler.class);
  40 + private final static String cmdType = "Broadcast";
  41 +
  42 + @Autowired
  43 + private NotifyMessageHandler notifyMessageHandler;
  44 +
  45 + @Autowired
  46 + private IVideoManagerStorage storage;
  47 +
  48 + @Autowired
  49 + private ISIPCommanderForPlatform commanderForPlatform;
  50 +
  51 + @Autowired
  52 + private IMediaServerService mediaServerService;
  53 +
  54 + @Autowired
  55 + private IPlayService playService;
  56 +
  57 + @Autowired
  58 + private IDeviceService deviceService;
  59 +
  60 + @Autowired
  61 + private IPlatformService platformService;
  62 +
  63 + @Autowired
  64 + private AudioBroadcastManager audioBroadcastManager;
  65 +
  66 + @Autowired
  67 + private ZLMRTPServerFactory zlmrtpServerFactory;
  68 +
  69 + @Autowired
  70 + private IRedisCatchStorage redisCatchStorage;
  71 +
  72 + @Override
  73 + public void afterPropertiesSet() throws Exception {
  74 + notifyMessageHandler.addHandler(cmdType, this);
  75 + }
  76 +
  77 + @Override
  78 + public void handForDevice(RequestEvent evt, Device device, Element element) {
  79 +
  80 + }
  81 +
  82 + @Override
  83 + public void handForPlatform(RequestEvent evt, ParentPlatform platform, Element rootElement) {
  84 + // 来自上级平台的语音喊话请求
  85 + SIPRequest request = (SIPRequest) evt.getRequest();
  86 + try {
  87 + Element snElement = rootElement.element("SN");
  88 + if (snElement == null) {
  89 + responseAck(request, Response.BAD_REQUEST, "sn must not null");
  90 + return;
  91 + }
  92 + String sn = snElement.getText();
  93 + Element targetIDElement = rootElement.element("TargetID");
  94 + if (targetIDElement == null) {
  95 + responseAck(request, Response.BAD_REQUEST, "TargetID must not null");
  96 + return;
  97 + }
  98 + String targetId = targetIDElement.getText();
  99 +
  100 +
  101 + logger.info("[国标级联 语音喊话] platform: {}, channel: {}", platform.getServerGBId(), targetId);
  102 +
  103 + DeviceChannel deviceChannel = storage.queryChannelInParentPlatform(platform.getServerGBId(), targetId);
  104 + if (deviceChannel == null) {
  105 + responseAck(request, Response.NOT_FOUND, "TargetID not found");
  106 + return;
  107 + }
  108 + // 向下级发送语音的喊话请求
  109 + Device device = deviceService.getDevice(deviceChannel.getDeviceId());
  110 + if (device == null) {
  111 + responseAck(request, Response.NOT_FOUND, "device not found");
  112 + return;
  113 + }
  114 + responseAck(request, Response.OK);
  115 +
  116 + // 查看语音通道是否已经建立并且已经在使用
  117 + if (playService.audioBroadcastInUse(device, targetId)) {
  118 + commanderForPlatform.broadcastResultCmd(platform, deviceChannel, sn, false,null, null);
  119 + return;
  120 + }
  121 +
  122 + MediaServerItem mediaServerForMinimumLoad = mediaServerService.getMediaServerForMinimumLoad(null);
  123 + commanderForPlatform.broadcastResultCmd(platform, deviceChannel, sn, true, eventResult->{
  124 + logger.info("[国标级联] 语音喊话 回复失败 platform: {}, 错误:{}/{}", platform.getServerGBId(), eventResult.statusCode, eventResult.msg);
  125 + }, eventResult->{
  126 + // 消息发送成功, 向上级发送invite,获取推流
  127 + try {
  128 + platformService.broadcastInvite(platform, deviceChannel.getChannelId(), mediaServerForMinimumLoad, (mediaServerItem, response)->{
  129 + // 上级平台推流成功
  130 + String app = response.getString("app");
  131 + String stream = response.getString("stream");
  132 + AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(device.getDeviceId(), targetId);
  133 + if (broadcastCatch != null ) {
  134 + if (playService.audioBroadcastInUse(device, targetId)) {
  135 + logger.info("[国标级联] 语音喊话 设备正正在使用中 platform: {}, channel: {}",
  136 + platform.getServerGBId(), deviceChannel.getChannelId());
  137 + // 查看语音通道已经建立且已经占用 回复BYE
  138 + try {
  139 + platformService.stopBroadcast(platform, deviceChannel.getChannelId(), stream);
  140 + } catch (InvalidArgumentException | ParseException | SsrcTransactionNotFoundException |
  141 + SipException e) {
  142 + logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}, channel: {}", platform.getServerGBId(), deviceChannel.getChannelId());
  143 + }
  144 + }else {
  145 + // 查看语音通道已经建立但是未占用
  146 + broadcastCatch.setApp(app);
  147 + broadcastCatch.setStream(stream);
  148 + broadcastCatch.setMediaServerItem(mediaServerItem);
  149 + audioBroadcastManager.update(broadcastCatch);
  150 + // 推流到设备
  151 + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, targetId, stream, null);
  152 + if (sendRtpItem == null) {
  153 + logger.warn("[国标级联] 语音喊话 异常,未找到发流信息, channelId: {}, stream: {}", targetId, stream);
  154 + logger.info("[国标级联] 语音喊话 重新开始,channelId: {}, stream: {}", targetId, stream);
  155 + try {
  156 + playService.audioBroadcastCmd(device, targetId, mediaServerItem, app, stream, 60, true, msg -> {
  157 + logger.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId);
  158 + });
  159 + } catch (SipException | InvalidArgumentException | ParseException e) {
  160 + logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
  161 + }
  162 + }else {
  163 + // 发流
  164 + JSONObject jsonObject = zlmrtpServerFactory.startSendRtp(mediaServerItem, sendRtpItem);
  165 + if (jsonObject != null && jsonObject.getInteger("code") == 0 ) {
  166 + logger.info("[语音喊话] 自动推流成功, device: {}, channel: {}", device.getDeviceId(), targetId);
  167 + }else {
  168 + logger.info("[语音喊话] 推流失败, 结果: {}", jsonObject);
  169 + }
  170 + }
  171 + }
  172 + }else {
  173 + try {
  174 + playService.audioBroadcastCmd(device, targetId, mediaServerItem, app, stream, 60, true, msg -> {
  175 + logger.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId);
  176 + });
  177 + } catch (SipException | InvalidArgumentException | ParseException e) {
  178 + logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
  179 + }
  180 + }
  181 +
  182 + }, eventResultForBroadcastInvite -> {
  183 + // 收到错误
  184 + logger.info("[国标级联-语音喊话] 与下级通道建立失败 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(),
  185 + targetId, eventResultForBroadcastInvite.statusCode, eventResultForBroadcastInvite.msg);
  186 + }, (code, msg)->{
  187 + // 超时
  188 + logger.info("[国标级联-语音喊话] 与下级通道建立超时 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(),
  189 + targetId, code, msg);
  190 + });
  191 + } catch (SipException | InvalidArgumentException | ParseException e) {
  192 + logger.info("[消息发送失败] 国标级联 语音喊话 invite消息 platform: {}", platform.getServerGBId());
  193 + }
  194 + });
  195 + } catch (SipException | InvalidArgumentException | ParseException e) {
  196 + logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
  197 + }
  198 +
  199 + }
  200 +}
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java
... ... @@ -7,6 +7,7 @@ import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus;
7 7 import com.genersoft.iot.vmp.gb28181.bean.Device;
8 8 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
9 9 import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
  10 +import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
10 11 import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
11 12 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
12 13 import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
... ... @@ -53,9 +54,9 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
53 54 @Override
54 55 public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
55 56  
56   - String channelId = getText(rootElement, "DeviceID");
57 57 SIPRequest request = (SIPRequest) evt.getRequest();
58 58 try {
  59 + String channelId = getText(rootElement, "DeviceID");
59 60 if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
60 61 // 回复410
61 62 responseAck((SIPRequest) evt.getRequest(), Response.GONE);
... ...
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
1 1 package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl;
2 2  
3   -import com.genersoft.iot.vmp.conf.SipConfig;
4 3 import com.genersoft.iot.vmp.gb28181.SipLayer;
5   -import com.genersoft.iot.vmp.gb28181.bean.Device;
6   -import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction;
7   -import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
8 4 import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
9 5 import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
10   -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
11 6 import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
12 7 import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract;
13   -import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
14   -import com.genersoft.iot.vmp.service.IDeviceService;
15   -import com.genersoft.iot.vmp.utils.GitUtil;
16 8 import gov.nist.javax.sip.ResponseEventExt;
17   -import gov.nist.javax.sip.SipProviderImpl;
18 9 import gov.nist.javax.sip.message.SIPResponse;
19   -import gov.nist.javax.sip.stack.SIPClientTransaction;
20   -import gov.nist.javax.sip.stack.SIPDialog;
21   -import gov.nist.javax.sip.stack.SIPTransaction;
22   -import gov.nist.javax.sip.stack.SIPTransactionImpl;
23 10 import org.slf4j.Logger;
24 11 import org.slf4j.LoggerFactory;
25 12 import org.springframework.beans.factory.annotation.Autowired;
26   -import org.springframework.beans.factory.annotation.Qualifier;
27 13 import org.springframework.stereotype.Component;
28 14  
29 15 import javax.sdp.SdpFactory;
30 16 import javax.sdp.SdpParseException;
31 17 import javax.sdp.SessionDescription;
32   -import javax.sip.*;
33   -import javax.sip.address.Address;
  18 +import javax.sip.InvalidArgumentException;
  19 +import javax.sip.ResponseEvent;
  20 +import javax.sip.SipException;
34 21 import javax.sip.address.SipURI;
35   -import javax.sip.header.CSeqHeader;
36   -import javax.sip.header.UserAgentHeader;
37 22 import javax.sip.message.Request;
38 23 import javax.sip.message.Response;
39 24 import java.text.ParseException;
... ... @@ -104,6 +89,7 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract {
104 89 } else {
105 90 sdp = SdpFactory.getInstance().createSessionDescription(contentString);
106 91 }
  92 + // 查看是否是来自设备的,此是回复
107 93  
108 94 SipURI requestUri = sipLayer.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort());
109 95 Request reqAck = headerProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response);
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
... ... @@ -280,6 +280,9 @@ public class ZLMHttpHookListener {
280 280 logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
281 281 }
282 282  
  283 + JSONObject ret = new JSONObject();
  284 + ret.put("code", 0);
  285 + ret.put("msg", "success");
283 286 MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId());
284 287 JSONObject json = (JSONObject) JSON.toJSON(param);
285 288 taskExecutor.execute(() -> {
... ... @@ -335,34 +338,34 @@ public class ZLMHttpHookListener {
335 338 }
336 339 }else if ("broadcast".equals(param.getApp())){
337 340 // 语音对讲推流 stream需要满足格式deviceId_channelId
338   - if (param.getStream().indexOf("_") > 0) {
339   - String[] streamArray = param.getStream().split("_");
340   - if (streamArray.length == 2) {
341   - String deviceId = streamArray[0];
342   - String channelId = streamArray[1];
343   - Device device = deviceService.getDevice(deviceId);
344   - if (device != null) {
345   - if (param.isRegist()) {
346   - if (audioBroadcastManager.exit(deviceId, channelId)) {
347   - playService.stopAudioBroadcast(deviceId, channelId);
348   - }
349   - // 开启语音对讲通道
350   - try {
351   - playService.audioBroadcastCmd(device, channelId, 60, mediaInfo, param.getApp(), param.getStream(), (msg)->{
352   - logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId);
353   - });
354   - } catch (InvalidArgumentException | ParseException | SipException e) {
355   - logger.error("[命令发送失败] 语音对讲: {}", e.getMessage());
356   - }
357   - }else {
358   - // 流注销
359   - playService.stopAudioBroadcast(deviceId, channelId);
360   - }
361   - } else{
362   - logger.info("[语音对讲] 未找到设备:{}", deviceId);
363   - }
364   - }
365   - }
  341 + if (param.getStream().indexOf("_") > 0) {
  342 + String[] streamArray = param.getStream().split("_");
  343 + if (streamArray.length == 2) {
  344 + String deviceId = streamArray[0];
  345 + String channelId = streamArray[1];
  346 + Device device = deviceService.getDevice(deviceId);
  347 + if (device != null) {
  348 + if (param.isRegist()) {
  349 + if (audioBroadcastManager.exit(deviceId, channelId)) {
  350 + playService.stopAudioBroadcast(deviceId, channelId);
  351 + }
  352 + // 开启语音对讲通道
  353 + try {
  354 + playService.audioBroadcastCmd(device, channelId, mediaInfo, param.getApp(), param.getStream(), 60, false, (msg)->{
  355 + logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId);
  356 + });
  357 + } catch (InvalidArgumentException | ParseException | SipException e) {
  358 + logger.error("[命令发送失败] 语音对讲: {}", e.getMessage());
  359 + }
  360 + }else {
  361 + // 流注销
  362 + playService.stopAudioBroadcast(deviceId, channelId);
  363 + }
  364 + } else{
  365 + logger.info("[语音对讲] 未找到设备:{}", deviceId);
  366 + }
  367 + }
  368 + }
366 369 }else if ("talk".equals(param.getApp())){
367 370 // 语音对讲推流 stream需要满足格式deviceId_channelId
368 371 if (param.getStream().indexOf("_") > 0) {
... ...
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java
... ... @@ -92,7 +92,7 @@ public class ZLMRTPServerFactory {
92 92 return result;
93 93 }
94 94  
95   - public int createRTPServer(MediaServerItem mediaServerItem, String streamId, int ssrc, Integer port) {
  95 + public int createRTPServer(MediaServerItem mediaServerItem, String streamId, int ssrc, Integer port, Boolean onlyAuto) {
96 96 int result = -1;
97 97 // 查询此rtp server 是否已经存在
98 98 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
... ... @@ -108,7 +108,7 @@ public class ZLMRTPServerFactory {
108 108 JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerItem, param);
109 109 if (jsonObject != null ) {
110 110 if (jsonObject.getInteger("code") == 0) {
111   - return createRTPServer(mediaServerItem, streamId, ssrc, port);
  111 + return createRTPServer(mediaServerItem, streamId, ssrc, port, onlyAuto);
112 112 }else {
113 113 logger.warn("[开启rtpServer], 重启RtpServer错误");
114 114 }
... ... @@ -131,6 +131,9 @@ public class ZLMRTPServerFactory {
131 131 param.put("port", port);
132 132 }
133 133 param.put("ssrc", ssrc);
  134 + if (onlyAuto != null) {
  135 + param.put("only_audio", onlyAuto?"1":"0");
  136 + }
134 137 JSONObject openRtpServerResultJson = zlmresTfulUtils.openRtpServer(mediaServerItem, param);
135 138 logger.info(JSONObject.toJSONString(openRtpServerResultJson));
136 139 if (openRtpServerResultJson != null) {
... ... @@ -352,4 +355,52 @@ public class ZLMRTPServerFactory {
352 355 return result;
353 356 }
354 357  
  358 + public JSONObject startSendRtp(MediaServerItem mediaInfo, SendRtpItem sendRtpItem) {
  359 + String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
  360 + logger.info("rtp/{}开始向上级推流, 目标={}:{},SSRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc());
  361 + Map<String, Object> param = new HashMap<>(12);
  362 + param.put("vhost","__defaultVhost__");
  363 + param.put("app",sendRtpItem.getApp());
  364 + param.put("stream",sendRtpItem.getStreamId());
  365 + param.put("ssrc", sendRtpItem.getSsrc());
  366 + param.put("src_port", sendRtpItem.getLocalPort());
  367 + param.put("pt", sendRtpItem.getPt());
  368 + param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
  369 + param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
  370 + if (!sendRtpItem.isTcp()) {
  371 + // udp模式下开启rtcp保活
  372 + param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
  373 + }
  374 +
  375 + if (mediaInfo == null) {
  376 + return null;
  377 + }
  378 + // 如果是非严格模式,需要关闭端口占用
  379 + JSONObject startSendRtpStreamResult = null;
  380 + if (sendRtpItem.getLocalPort() != 0) {
  381 + HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(sendRtpItem.getSsrc(), null, mediaInfo.getId());
  382 + hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout);
  383 + if (releasePort(mediaInfo, sendRtpItem.getSsrc())) {
  384 + if (sendRtpItem.isTcpActive()) {
  385 + startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param);
  386 + System.out.println(JSON.toJSON(param));
  387 + }else {
  388 + param.put("is_udp", is_Udp);
  389 + param.put("dst_url", sendRtpItem.getIp());
  390 + param.put("dst_port", sendRtpItem.getPort());
  391 + startSendRtpStreamResult = startSendRtpStream(mediaInfo, param);
  392 + }
  393 + }
  394 + }else {
  395 + if (sendRtpItem.isTcpActive()) {
  396 + startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param);
  397 + }else {
  398 + param.put("is_udp", is_Udp);
  399 + param.put("dst_url", sendRtpItem.getIp());
  400 + param.put("dst_port", sendRtpItem.getPort());
  401 + startSendRtpStreamResult = startSendRtpStream(mediaInfo, param);
  402 + }
  403 + }
  404 + return startSendRtpStreamResult;
  405 + }
355 406 }
... ...
src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java
... ... @@ -47,7 +47,7 @@ public interface IMediaServerService {
47 47  
48 48 SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback);
49 49  
50   - SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port);
  50 + SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto);
51 51  
52 52 void closeRTPServer(MediaServerItem mediaServerItem, String streamId);
53 53  
... ...
src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java
1 1 package com.genersoft.iot.vmp.service;
2 2  
  3 +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
3 4 import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
  5 +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
  6 +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
  7 +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  8 +import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
4 9 import com.github.pagehelper.PageInfo;
5 10  
  11 +import javax.sip.InvalidArgumentException;
  12 +import javax.sip.SipException;
  13 +import java.text.ParseException;
  14 +
6 15 /**
7 16 * 国标平台的业务类
8 17 * @author lin
... ... @@ -48,4 +57,23 @@ public interface IPlatformService {
48 57 * @param platformId 平台
49 58 */
50 59 void sendNotifyMobilePosition(String platformId);
  60 +
  61 + /**
  62 + * 向上级发送语音喊话的消息
  63 + * @param platform 平台
  64 + * @param channelId 通道
  65 + * @param hookEvent hook事件
  66 + * @param errorEvent 信令错误事件
  67 + * @param timeoutCallback 超时事件
  68 + */
  69 + void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent,
  70 + SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException;
  71 +
  72 + /**
  73 + * 语音喊话回复BYE
  74 + * @param platform 平台
  75 + * @param channelId 通道
  76 + * @param stream 流信息
  77 + */
  78 + void stopBroadcast(ParentPlatform platform, String channelId, String stream )throws InvalidArgumentException, ParseException, SsrcTransactionNotFoundException, SipException;
51 79 }
... ...
src/main/java/com/genersoft/iot/vmp/service/IPlayService.java
... ... @@ -12,7 +12,9 @@ import com.genersoft.iot.vmp.service.bean.PlayBackCallback;
12 12 import com.genersoft.iot.vmp.service.bean.SSRCInfo;
13 13 import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
14 14 import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
  15 +import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
15 16 import gov.nist.javax.sip.message.SIPResponse;
  17 +import org.springframework.web.context.request.async.DeferredResult;
16 18  
17 19 import javax.sip.InvalidArgumentException;
18 20 import javax.sip.SipException;
... ... @@ -54,6 +56,11 @@ public interface IPlayService {
54 56 void zlmServerOnline(String mediaServerId);
55 57  
56 58 AudioBroadcastResult audioBroadcast(Device device, String channelId, Boolean broadcastMode);
  59 +
  60 + boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException;
  61 +
  62 + boolean audioBroadcastInUse(Device device, String channelId);
  63 +
57 64 void stopAudioBroadcast(String deviceId, String channelId);
58 65  
59 66 void audioBroadcastCmd(Device device, String channelId, int timeout, MediaServerItem mediaServerItem, String sourceApp, String sourceStream, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException;
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
3   -import java.time.LocalDateTime;
4   -import java.util.ArrayList;
5   -import java.util.Collections;
6   -import java.util.HashMap;
7   -import java.util.List;
8   -import java.util.Map;
9   -import java.util.Set;
10   -
11   -import com.genersoft.iot.vmp.media.zlm.ZLMRunner;
12   -import com.genersoft.iot.vmp.service.IStreamProxyService;
13   -import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
14 3 import com.alibaba.fastjson2.JSON;
15 4 import com.alibaba.fastjson2.JSONArray;
16 5 import com.alibaba.fastjson2.JSONObject;
... ... @@ -39,8 +28,6 @@ import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
39 28 import okhttp3.OkHttpClient;
40 29 import okhttp3.Request;
41 30 import okhttp3.Response;
42   -import java.time.LocalDateTime;
43   -import java.util.*;
44 31 import org.slf4j.Logger;
45 32 import org.slf4j.LoggerFactory;
46 33 import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -134,7 +121,8 @@ public class MediaServerServiceImpl implements IMediaServerService {
134 121 }
135 122  
136 123 @Override
137   - public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, boolean isPlayback, Integer port) {
  124 + public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck,
  125 + boolean isPlayback, Integer port, Boolean onlyAuto) {
138 126 if (mediaServerItem == null || mediaServerItem.getId() == null) {
139 127 logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null");
140 128 return null;
... ... @@ -163,7 +151,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
163 151 }
164 152 int rtpServerPort;
165 153 if (mediaServerItem.isRtpEnable()) {
166   - rtpServerPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(ssrc):0, port);
  154 + rtpServerPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(ssrc):0, port, onlyAuto);
167 155 } else {
168 156 rtpServerPort = mediaServerItem.getRtpProxyPort();
169 157 }
... ... @@ -174,7 +162,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
174 162  
175 163 @Override
176 164 public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback) {
177   - return openRTPServer(mediaServerItem, streamId, ssrc, ssrcCheck, isPlayback, null);
  165 + return openRTPServer(mediaServerItem, streamId, ssrc, ssrcCheck, isPlayback, null, null);
178 166 }
179 167  
180 168 @Override
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
1 1 package com.genersoft.iot.vmp.service.impl;
2 2  
  3 +import com.alibaba.fastjson2.JSONObject;
  4 +import com.genersoft.iot.vmp.common.StreamInfo;
3 5 import com.genersoft.iot.vmp.conf.DynamicTask;
4 6 import com.genersoft.iot.vmp.conf.UserSetting;
  7 +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
5 8 import com.genersoft.iot.vmp.gb28181.bean.*;
6 9 import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
  10 +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
7 11 import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
8 12 import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
  13 +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
  14 +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
  15 +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
9 16 import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
10 17 import com.genersoft.iot.vmp.service.IMediaServerService;
11 18 import com.genersoft.iot.vmp.service.IPlatformService;
  19 +import com.genersoft.iot.vmp.service.IPlayService;
12 20 import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
  21 +import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
  22 +import com.genersoft.iot.vmp.service.bean.SSRCInfo;
13 23 import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
14 24 import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
15 25 import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper;
... ... @@ -21,11 +31,13 @@ import org.springframework.beans.factory.annotation.Autowired;
21 31 import org.springframework.stereotype.Service;
22 32  
23 33 import javax.sip.InvalidArgumentException;
  34 +import javax.sip.ResponseEvent;
24 35 import javax.sip.SipException;
25 36 import java.text.ParseException;
26 37 import java.util.HashMap;
27 38 import java.util.List;
28 39 import java.util.Map;
  40 +import java.util.UUID;
29 41  
30 42 /**
31 43 * @author lin
... ... @@ -65,6 +77,16 @@ public class PlatformServiceImpl implements IPlatformService {
65 77 @Autowired
66 78 private UserSetting userSetting;
67 79  
  80 + @Autowired
  81 + private ZlmHttpHookSubscribe subscribe;
  82 +
  83 + @Autowired
  84 + private VideoStreamSessionManager streamSession;
  85 +
  86 +
  87 + @Autowired
  88 + private IPlayService playService;
  89 +
68 90  
69 91  
70 92 @Override
... ... @@ -310,4 +332,138 @@ public class PlatformServiceImpl implements IPlatformService {
310 332 }
311 333 }
312 334 }
  335 +
  336 + @Override
  337 + public void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent,
  338 + SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException {
  339 +
  340 + if (mediaServerItem == null) {
  341 + logger.info("[国标级联] 语音喊话未找到可用的zlm. platform: {}", platform.getServerGBId());
  342 + return;
  343 + }
  344 + StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(platform.getServerGBId(), channelId);
  345 + if (streamInfo != null) {
  346 + // 如果zlm不存在这个流,则删除数据即可
  347 + MediaServerItem mediaServerItemForStreamInfo = mediaServerService.getOne(streamInfo.getMediaServerId());
  348 + if (mediaServerItemForStreamInfo != null) {
  349 + Boolean ready = zlmrtpServerFactory.isStreamReady(mediaServerItemForStreamInfo, streamInfo.getApp(), streamInfo.getStream());
  350 + if (!ready) {
  351 + // 错误存在于redis中的数据
  352 + redisCatchStorage.stopPlay(streamInfo);
  353 + }else {
  354 + // 流确实尚在推流,直接回调结果
  355 + JSONObject json = new JSONObject();
  356 + json.put("app", streamInfo.getApp());
  357 + json.put("stream", streamInfo.getStream());
  358 + hookEvent.response(mediaServerItemForStreamInfo, json);
  359 + return;
  360 + }
  361 + }
  362 + }
  363 +
  364 + String streamId = null;
  365 + if (mediaServerItem.isRtpEnable()) {
  366 + streamId = String.format("%s_%s", platform.getServerGBId(), channelId);
  367 + }
  368 + // 默认不进行SSRC校验, TODO 后续可改为配置
  369 + boolean ssrcCheck = false;
  370 + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, ssrcCheck, false, null, true);
  371 + if (ssrcInfo == null || ssrcInfo.getPort() < 0) {
  372 + logger.info("[国标级联] 发起语音喊话 开启端口监听失败, platform: {}, channel: {}", platform.getServerGBId(), channelId);
  373 + errorEvent.response(new SipSubscribe.EventResult(-1, "端口监听失败"));
  374 + return;
  375 + }
  376 + logger.info("[国标级联] 发起语音喊话 deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
  377 + platform.getServerGBId(), channelId, ssrcInfo.getPort(), userSetting.getBroadcastForPlatform(), ssrcInfo.getSsrc(), ssrcCheck);
  378 +
  379 + String timeOutTaskKey = UUID.randomUUID().toString();
  380 + dynamicTask.startDelay(timeOutTaskKey, () -> {
  381 + // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况
  382 + if (redisCatchStorage.queryPlayByDevice(platform.getServerGBId(), channelId) == null) {
  383 + logger.info("[国标级联] 发起语音喊话 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", platform.getServerGBId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc());
  384 + // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  385 + try {
  386 + commanderForPlatform.streamByeCmd(platform, channelId, ssrcInfo.getStream(), null, null);
  387 + } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
  388 + logger.error("[点播超时], 发送BYE失败 {}", e.getMessage());
  389 + } finally {
  390 + timeoutCallback.run(1, "收流超时");
  391 + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  392 + mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  393 + streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
  394 + mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  395 + }
  396 + }
  397 + }, userSetting.getPlayTimeout());
  398 + commanderForPlatform.broadcastInviteCmd(platform, channelId, mediaServerItem, ssrcInfo, (mediaServerItemForInvite, response)->{
  399 + dynamicTask.stop(timeOutTaskKey);
  400 + // hook响应
  401 + playService.onPublishHandlerForPlay(mediaServerItemForInvite, response, platform.getServerGBId(), channelId);
  402 + // 收到流
  403 + if (hookEvent != null) {
  404 + hookEvent.response(mediaServerItem, response);
  405 + }
  406 + }, event -> {
  407 + // 收到200OK 检测ssrc是否有变化,防止上级自定义了ssrc
  408 + ResponseEvent responseEvent = (ResponseEvent) event.event;
  409 + String contentString = new String(responseEvent.getResponse().getRawContent());
  410 + // 获取ssrc
  411 + int ssrcIndex = contentString.indexOf("y=");
  412 + // 检查是否有y字段
  413 + if (ssrcIndex >= 0) {
  414 + //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
  415 + String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  416 + // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
  417 + if (ssrcInfo.getSsrc().equals(ssrcInResponse) || ssrcCheck) {
  418 + return;
  419 + }
  420 + logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
  421 + if (!mediaServerItem.isRtpEnable()) {
  422 + logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
  423 +
  424 + if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
  425 + // ssrc 不可用
  426 + // 释放ssrc
  427 + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  428 + streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
  429 + event.msg = "下级自定义了ssrc,但是此ssrc不可用";
  430 + event.statusCode = 400;
  431 + errorEvent.response(event);
  432 + return;
  433 + }
  434 +
  435 + // 单端口模式streamId也有变化,需要重新设置监听
  436 + if (!mediaServerItem.isRtpEnable()) {
  437 + // 添加订阅
  438 + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
  439 + subscribe.removeSubscribe(hookSubscribe);
  440 + hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
  441 + subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
  442 + logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
  443 + dynamicTask.stop(timeOutTaskKey);
  444 + // hook响应
  445 + playService.onPublishHandlerForPlay(mediaServerItemInUse, response, platform.getServerGBId(), channelId);
  446 + hookEvent.response(mediaServerItemInUse, response);
  447 + });
  448 + }
  449 + // 关闭rtp server
  450 + mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  451 + // 重新开启ssrc server
  452 + mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, false, false, ssrcInfo.getPort(), true);
  453 +
  454 +
  455 + }
  456 + }
  457 + }, eventResult -> {
  458 + // 收到错误回复
  459 + if (errorEvent != null) {
  460 + errorEvent.response(eventResult);
  461 + }
  462 + });
  463 + }
  464 +
  465 + @Override
  466 + public void stopBroadcast(ParentPlatform platform, String channelId, String stream) throws InvalidArgumentException, ParseException, SsrcTransactionNotFoundException, SipException {
  467 + commanderForPlatform.streamByeCmd(platform, channelId, stream, null, null);
  468 + }
313 469 }
... ...
src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java
... ... @@ -490,7 +490,7 @@ public class PlayServiceImpl implements IPlayService {
490 490 // 关闭rtp server
491 491 mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
492 492 // 重新开启ssrc server
493   - mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false, ssrcInfo.getPort());
  493 + mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false, ssrcInfo.getPort(), false);
494 494  
495 495 }
496 496 }
... ... @@ -731,7 +731,7 @@ public class PlayServiceImpl implements IPlayService {
731 731 // 关闭rtp server
732 732 mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
733 733 // 重新开启ssrc server
734   - mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort());
  734 + mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort(), false);
735 735 }
736 736 }
737 737 }
... ... @@ -966,16 +966,16 @@ public class PlayServiceImpl implements IPlayService {
966 966 }
967 967  
968 968 @Override
969   - public void audioBroadcastCmd(Device device, String channelId, int timeout, MediaServerItem mediaServerItem, String sourceApp, String sourceStream, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException {
  969 + public boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException {
970 970 if (device == null || channelId == null) {
971   - return;
  971 + return false;
972 972 }
973 973 logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId);
974 974 DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
975 975 if (deviceChannel == null) {
976 976 logger.warn("开启语音广播的时候未找到通道: {}", channelId);
977 977 event.call("开启语音广播的时候未找到通道");
978   - return;
  978 + return false;
979 979 }
980 980 // 查询通道使用状态
981 981 if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
... ... @@ -986,7 +986,7 @@ public class PlayServiceImpl implements IPlayService {
986 986 if (streamReady) {
987 987 logger.warn("语音广播已经开启: {}", channelId);
988 988 event.call("语音广播已经开启");
989   - return;
  989 + return false;
990 990 } else {
991 991 stopAudioBroadcast(device.getDeviceId(), channelId);
992 992 }
... ... @@ -1008,8 +1008,7 @@ public class PlayServiceImpl implements IPlayService {
1008 1008 // 发送通知
1009 1009 cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> {
1010 1010 // 发送成功
1011   - AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId,
1012   - AudioBroadcastCatchStatus.Ready, mediaServerItem, sourceApp, sourceStream);
  1011 + AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, mediaServerItem, app, stream, event, AudioBroadcastCatchStatus.Ready, isFromPlatform);
1013 1012 audioBroadcastManager.update(audioBroadcastCatch);
1014 1013 }, eventResultForError -> {
1015 1014 // 发送失败
... ... @@ -1017,6 +1016,24 @@ public class PlayServiceImpl implements IPlayService {
1017 1016 event.call("语音广播发送失败");
1018 1017 stopAudioBroadcast(device.getDeviceId(), channelId);
1019 1018 });
  1019 + return true;
  1020 + }
  1021 +
  1022 + @Override
  1023 + public boolean audioBroadcastInUse(Device device, String channelId) {
  1024 + if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
  1025 + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
  1026 + if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
  1027 + // 查询流是否存在,不存在则认为是异常状态
  1028 + MediaServerItem mediaServerServiceOne = mediaServerService.getOne(sendRtpItem.getMediaServerId());
  1029 + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerServiceOne, sendRtpItem.getApp(), sendRtpItem.getStreamId());
  1030 + if (streamReady) {
  1031 + logger.warn("语音广播通道使用中: {}", channelId);
  1032 + return true;
  1033 + }
  1034 + }
  1035 + }
  1036 + return false;
1020 1037 }
1021 1038  
1022 1039  
... ...
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java
... ... @@ -250,10 +250,10 @@ public class PlayController {
250 250 @GetMapping("/broadcast/{deviceId}/{channelId}")
251 251 @PostMapping("/broadcast/{deviceId}/{channelId}")
252 252 public AudioBroadcastResult broadcastApi(@PathVariable String deviceId, @PathVariable String channelId, Integer timeout, Boolean broadcastMode) {
253   - if (logger.isDebugEnabled()) {
254   - logger.debug("语音广播API调用");
255   - }
256   - Device device = storager.queryVideoDevice(deviceId);
  253 + if (logger.isDebugEnabled()) {
  254 + logger.debug("语音广播API调用");
  255 + }
  256 + Device device = storager.queryVideoDevice(deviceId);
257 257 if (device == null) {
258 258 throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到设备: " + deviceId);
259 259 }
... ... @@ -265,7 +265,6 @@ public class PlayController {
265 265  
266 266 }
267 267  
268   -
269 268 @Operation(summary = "停止语音广播")
270 269 @Parameter(name = "deviceId", description = "设备Id", required = true)
271 270 @Parameter(name = "channelId", description = "通道Id", required = true)
... ...
src/main/resources/all-application.yml
... ... @@ -195,6 +195,8 @@ user-settings:
195 195 gb-send-stream-strict: false
196 196 # 设备上线时是否自动同步通道
197 197 sync-channel-on-device-online: false
  198 + # 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式
  199 + broadcast-for-platform: UDP
198 200 # 是否使用设备来源Ip作为回复IP, 不设置则为 false
199 201 sip-use-source-ip-as-remote-address: false
200 202 # 是否开启sip日志
... ...
web_src/src/components/dialog/devicePlayer.vue
... ... @@ -663,46 +663,46 @@ export default {
663 663 this.startBroadcast(streamInfo.rtc)
664 664 }
665 665  
666   - }else {
667   - this.$message({
668   - showClose: true,
669   - message: res.data.msg,
670   - type: "error",
671   - });
672   - }
673   - });
674   - }else if (this.broadcastStatus === 1) {
675   - this.broadcastStatus = -1;
676   - this.broadcastRtc.close()
677   - }
678   - },
679   - startBroadcast(url) {
680   - // 获取推流鉴权Key
681   - this.$axios({
682   - method: 'post',
683   - url: '/api/user/userInfo',
684   - }).then((res) => {
685   - if (res.data.code !== 0) {
686   - this.$message({
687   - showClose: true,
688   - message: "获取推流鉴权Key失败",
689   - type: "error",
690   - });
691   - this.broadcastStatus = -1;
692   - } else {
693   - let pushKey = res.data.data.pushKey;
694   - // 获取推流鉴权KEY
695   - url += "&sign=" + crypto.createHash('md5').update(pushKey, "utf8").digest('hex')
696   - console.log("开始语音对讲: " + url)
697   - this.broadcastRtc = new ZLMRTCClient.Endpoint({
698   - debug: true, // 是否打印日志
699   - zlmsdpUrl: url, //流地址
700   - simulecast: false,
701   - useCamera: false,
702   - audioEnable: true,
703   - videoEnable: false,
704   - recvOnly: false,
705   - })
  666 + }else {
  667 + this.$message({
  668 + showClose: true,
  669 + message: res.data.msg,
  670 + type: "error",
  671 + });
  672 + }
  673 + });
  674 + }else if (this.broadcastStatus === 1) {
  675 + this.broadcastStatus = -1;
  676 + this.broadcastRtc.close()
  677 + }
  678 + },
  679 + startBroadcast(url){
  680 + // 获取推流鉴权Key
  681 + this.$axios({
  682 + method: 'post',
  683 + url: '/api/user/userInfo',
  684 + }).then( (res)=> {
  685 + if (res.data.code !== 0) {
  686 + this.$message({
  687 + showClose: true,
  688 + message: "获取推流鉴权Key失败",
  689 + type: "error",
  690 + });
  691 + this.broadcastStatus = -1;
  692 + }else {
  693 + let pushKey = res.data.data.pushKey;
  694 + // 获取推流鉴权KEY
  695 + url += "&sign=" + crypto.createHash('md5').update(pushKey, "utf8").digest('hex')
  696 + console.log("开始语音喊话: " + url)
  697 + this.broadcastRtc = new ZLMRTCClient.Endpoint({
  698 + debug: true, // 是否打印日志
  699 + zlmsdpUrl: url, //流地址
  700 + simulecast: false,
  701 + useCamera: false,
  702 + audioEnable: true,
  703 + videoEnable: false,
  704 + recvOnly: false,
  705 + })
706 706  
707 707 // webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,(e)=>{//获取到了远端流,可以播放
708 708 // console.error('播放成功',e.streams)
... ... @@ -715,15 +715,15 @@ export default {
715 715 // // this.eventcallbacK("LOCAL STREAM", "获取到了本地流")
716 716 // });
717 717  
718   - this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_NOT_SUPPORT, (e) => {// 获取到了本地流
719   - console.error('不支持webrtc', e)
720   - this.$message({
721   - showClose: true,
722   - message: '不支持webrtc, 无法进行语音对讲',
723   - type: 'error'
724   - });
725   - this.broadcastStatus = -1;
726   - });
  718 + this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_NOT_SUPPORT,(e)=>{// 获取到了本地流
  719 + console.error('不支持webrtc',e)
  720 + this.$message({
  721 + showClose: true,
  722 + message: '不支持webrtc, 无法进行语音喊话',
  723 + type: 'error'
  724 + });
  725 + this.broadcastStatus = -1;
  726 + });
727 727  
728 728 this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, (e) => {// ICE 协商出错
729 729 console.error('ICE 协商出错')
... ...