Commit 26bdf2e7768ee5dfc400c3970a5aa129fed49453
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,7 +52,7 @@ | ||
| 52 | - [X] 报警订阅 | 52 | - [X] 报警订阅 |
| 53 | - [X] 目录订阅 | 53 | - [X] 目录订阅 |
| 54 | - [ ] 语音广播 | 54 | - [ ] 语音广播 |
| 55 | -- [ ] 语音对讲 | 55 | +- [ ] 语音喊话 |
| 56 | 56 | ||
| 57 | **作为下级平台** | 57 | **作为下级平台** |
| 58 | - [X] 注册 | 58 | - [X] 注册 |
| @@ -91,7 +91,7 @@ | @@ -91,7 +91,7 @@ | ||
| 91 | - [ ] 报警订阅 | 91 | - [ ] 报警订阅 |
| 92 | - [X] 目录订阅 | 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,6 +19,7 @@ | ||
| 19 | * [树形结构](_content/theory/channel_tree.md) | 19 | * [树形结构](_content/theory/channel_tree.md) |
| 20 | * [注册流程](_content/theory/register.md) | 20 | * [注册流程](_content/theory/register.md) |
| 21 | * [点播流程](_content/theory/play.md) | 21 | * [点播流程](_content/theory/play.md) |
| 22 | + * [级联语音喊话流程](_content/theory/broadcast_cascade.md) | ||
| 22 | * **必备技巧** | 23 | * **必备技巧** |
| 23 | * [抓包](_content/skill/tcpdump.md) | 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,6 +55,8 @@ public class UserSetting { | ||
| 55 | 55 | ||
| 56 | private String thirdPartyGBIdReg = "[\\s\\S]*"; | 56 | private String thirdPartyGBIdReg = "[\\s\\S]*"; |
| 57 | 57 | ||
| 58 | + private String broadcastForPlatform = "UDP"; | ||
| 59 | + | ||
| 58 | private List<String> interfaceAuthenticationExcludes = new ArrayList<>(); | 60 | private List<String> interfaceAuthenticationExcludes = new ArrayList<>(); |
| 59 | 61 | ||
| 60 | public Boolean getSavePositionHistory() { | 62 | public Boolean getSavePositionHistory() { |
| @@ -205,6 +207,14 @@ public class UserSetting { | @@ -205,6 +207,14 @@ public class UserSetting { | ||
| 205 | this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline; | 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 | public Boolean getPushStreamAfterAck() { | 218 | public Boolean getPushStreamAfterAck() { |
| 209 | return pushStreamAfterAck; | 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,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean; | ||
| 2 | 2 | ||
| 3 | 3 | ||
| 4 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | 4 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 5 | +import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent; | ||
| 5 | import gov.nist.javax.sip.message.SIPResponse; | 6 | import gov.nist.javax.sip.message.SIPResponse; |
| 6 | 7 | ||
| 7 | /** | 8 | /** |
| @@ -11,18 +12,24 @@ import gov.nist.javax.sip.message.SIPResponse; | @@ -11,18 +12,24 @@ import gov.nist.javax.sip.message.SIPResponse; | ||
| 11 | public class AudioBroadcastCatch { | 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 | this.deviceId = deviceId; | 25 | this.deviceId = deviceId; |
| 21 | this.channelId = channelId; | 26 | this.channelId = channelId; |
| 22 | this.status = status; | 27 | this.status = status; |
| 23 | - this.mediaServerItem = mediaServerItem; | 28 | + this.event = event; |
| 29 | + this.isFromPlatform = isFromPlatform; | ||
| 24 | this.app = app; | 30 | this.app = app; |
| 25 | this.stream = stream; | 31 | this.stream = stream; |
| 32 | + this.mediaServerItem = mediaServerItem; | ||
| 26 | } | 33 | } |
| 27 | 34 | ||
| 28 | public AudioBroadcastCatch() { | 35 | public AudioBroadcastCatch() { |
| @@ -39,21 +46,26 @@ public class AudioBroadcastCatch { | @@ -39,21 +46,26 @@ public class AudioBroadcastCatch { | ||
| 39 | private String channelId; | 46 | private String channelId; |
| 40 | 47 | ||
| 41 | /** | 48 | /** |
| 42 | - * 使用的流媒体 | 49 | + * 流媒体信息 |
| 43 | */ | 50 | */ |
| 44 | private MediaServerItem mediaServerItem; | 51 | private MediaServerItem mediaServerItem; |
| 45 | 52 | ||
| 46 | /** | 53 | /** |
| 47 | - * 待推送给设备的流应用名 | 54 | + * 关联的流APP |
| 48 | */ | 55 | */ |
| 49 | private String app; | 56 | private String app; |
| 50 | 57 | ||
| 51 | /** | 58 | /** |
| 52 | - * 待推送给设备的流ID | 59 | + * 关联的流STREAM |
| 53 | */ | 60 | */ |
| 54 | private String stream; | 61 | private String stream; |
| 55 | 62 | ||
| 56 | /** | 63 | /** |
| 64 | + * 是否是级联语音喊话 | ||
| 65 | + */ | ||
| 66 | + private boolean isFromPlatform; | ||
| 67 | + | ||
| 68 | + /** | ||
| 57 | * 语音广播状态 | 69 | * 语音广播状态 |
| 58 | */ | 70 | */ |
| 59 | private AudioBroadcastCatchStatus status; | 71 | private AudioBroadcastCatchStatus status; |
| @@ -63,6 +75,11 @@ public class AudioBroadcastCatch { | @@ -63,6 +75,11 @@ public class AudioBroadcastCatch { | ||
| 63 | */ | 75 | */ |
| 64 | private SipTransactionInfo sipTransactionInfo; | 76 | private SipTransactionInfo sipTransactionInfo; |
| 65 | 77 | ||
| 78 | + /** | ||
| 79 | + * 请求结果回调 | ||
| 80 | + */ | ||
| 81 | + private AudioBroadcastEvent event; | ||
| 82 | + | ||
| 66 | 83 | ||
| 67 | public String getDeviceId() { | 84 | public String getDeviceId() { |
| 68 | return deviceId; | 85 | return deviceId; |
| @@ -123,4 +140,44 @@ public class AudioBroadcastCatch { | @@ -123,4 +140,44 @@ public class AudioBroadcastCatch { | ||
| 123 | public void setMediaServerItem(MediaServerItem mediaServerItem) { | 140 | public void setMediaServerItem(MediaServerItem mediaServerItem) { |
| 124 | this.mediaServerItem = mediaServerItem; | 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,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean; | ||
| 2 | 2 | ||
| 3 | public enum InviteStreamType { | 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,7 +66,7 @@ public class ParentPlatform { | ||
| 66 | * 设备端口 | 66 | * 设备端口 |
| 67 | */ | 67 | */ |
| 68 | @Schema(description = "设备端口") | 68 | @Schema(description = "设备端口") |
| 69 | - private String devicePort; | 69 | + private int devicePort; |
| 70 | 70 | ||
| 71 | /** | 71 | /** |
| 72 | * SIP认证用户名(默认使用设备国标编号) | 72 | * SIP认证用户名(默认使用设备国标编号) |
| @@ -261,11 +261,11 @@ public class ParentPlatform { | @@ -261,11 +261,11 @@ public class ParentPlatform { | ||
| 261 | this.deviceIp = deviceIp; | 261 | this.deviceIp = deviceIp; |
| 262 | } | 262 | } |
| 263 | 263 | ||
| 264 | - public String getDevicePort() { | 264 | + public int getDevicePort() { |
| 265 | return devicePort; | 265 | return devicePort; |
| 266 | } | 266 | } |
| 267 | 267 | ||
| 268 | - public void setDevicePort(String devicePort) { | 268 | + public void setDevicePort(int devicePort) { |
| 269 | this.devicePort = devicePort; | 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,6 +86,11 @@ public class SipSubscribe { | ||
| 86 | public String callId; | 86 | public String callId; |
| 87 | public EventObject event; | 87 | public EventObject event; |
| 88 | 88 | ||
| 89 | + public EventResult(int statusCode, String msg) { | ||
| 90 | + this.statusCode = statusCode; | ||
| 91 | + this.msg = msg; | ||
| 92 | + } | ||
| 93 | + | ||
| 89 | public EventResult(EventObject event) { | 94 | public EventResult(EventObject event) { |
| 90 | this.event = event; | 95 | this.event = event; |
| 91 | if (event instanceof ResponseEvent) { | 96 | if (event instanceof ResponseEvent) { |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java
| 1 | package com.genersoft.iot.vmp.gb28181.transmit.cmd; | 1 | package com.genersoft.iot.vmp.gb28181.transmit.cmd; |
| 2 | 2 | ||
| 3 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | ||
| 3 | import com.genersoft.iot.vmp.gb28181.bean.*; | 4 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 4 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; | 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 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; | 8 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; |
| 9 | +import com.genersoft.iot.vmp.service.bean.SSRCInfo; | ||
| 6 | 10 | ||
| 7 | import javax.sip.InvalidArgumentException; | 11 | import javax.sip.InvalidArgumentException; |
| 8 | import javax.sip.SipException; | 12 | import javax.sip.SipException; |
| @@ -14,42 +18,56 @@ public interface ISIPCommanderForPlatform { | @@ -14,42 +18,56 @@ public interface ISIPCommanderForPlatform { | ||
| 14 | 18 | ||
| 15 | /** | 19 | /** |
| 16 | * 向上级平台注册 | 20 | * 向上级平台注册 |
| 21 | + * | ||
| 17 | * @param parentPlatform | 22 | * @param parentPlatform |
| 18 | * @return | 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 | * @param parentPlatform | 35 | * @param parentPlatform |
| 26 | * @return | 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 | * @param parentPlatform | 45 | * @param parentPlatform |
| 34 | * @return callId(作为接受回复的判定) | 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 | * @param parentPlatform 平台信息 | 56 | * @param parentPlatform 平台信息 |
| 43 | * @param sn | 57 | * @param sn |
| 44 | * @param fromTag | 58 | * @param fromTag |
| 45 | * @param size | 59 | * @param size |
| 46 | * @return | 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 | * 向上级回复DeviceInfo查询信息 | 69 | * 向上级回复DeviceInfo查询信息 |
| 70 | + * | ||
| 53 | * @param parentPlatform 平台信息 | 71 | * @param parentPlatform 平台信息 |
| 54 | * @param sn SN | 72 | * @param sn SN |
| 55 | * @param fromTag FROM头的tag信息 | 73 | * @param fromTag FROM头的tag信息 |
| @@ -59,32 +77,37 @@ public interface ISIPCommanderForPlatform { | @@ -59,32 +77,37 @@ public interface ISIPCommanderForPlatform { | ||
| 59 | 77 | ||
| 60 | /** | 78 | /** |
| 61 | * 向上级回复DeviceStatus查询信息 | 79 | * 向上级回复DeviceStatus查询信息 |
| 80 | + * | ||
| 62 | * @param parentPlatform 平台信息 | 81 | * @param parentPlatform 平台信息 |
| 63 | * @param sn | 82 | * @param sn |
| 64 | * @param fromTag | 83 | * @param fromTag |
| 65 | * @return | 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 | * @param parentPlatform 平台信息 | 91 | * @param parentPlatform 平台信息 |
| 72 | - * @param gpsMsgInfo GPS信息 | ||
| 73 | - * @param subscribeInfo 订阅相关的信息 | 92 | + * @param gpsMsgInfo GPS信息 |
| 93 | + * @param subscribeInfo 订阅相关的信息 | ||
| 74 | * @return | 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 | * @param parentPlatform 平台信息 | 102 | * @param parentPlatform 平台信息 |
| 81 | - * @param deviceAlarm 报警信息信息 | 103 | + * @param deviceAlarm 报警信息信息 |
| 82 | * @return | 104 | * @return |
| 83 | */ | 105 | */ |
| 84 | void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException; | 106 | void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException; |
| 85 | 107 | ||
| 86 | /** | 108 | /** |
| 87 | * 回复catalog事件-增加/更新 | 109 | * 回复catalog事件-增加/更新 |
| 110 | + * | ||
| 88 | * @param parentPlatform | 111 | * @param parentPlatform |
| 89 | * @param deviceChannels | 112 | * @param deviceChannels |
| 90 | */ | 113 | */ |
| @@ -92,22 +115,28 @@ public interface ISIPCommanderForPlatform { | @@ -92,22 +115,28 @@ public interface ISIPCommanderForPlatform { | ||
| 92 | 115 | ||
| 93 | /** | 116 | /** |
| 94 | * 回复catalog事件-删除 | 117 | * 回复catalog事件-删除 |
| 118 | + * | ||
| 95 | * @param parentPlatform | 119 | * @param parentPlatform |
| 96 | * @param deviceChannels | 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 | * 回复recordInfo | 127 | * 回复recordInfo |
| 102 | - * @param deviceChannel 通道信息 | 128 | + * |
| 129 | + * @param deviceChannel 通道信息 | ||
| 103 | * @param parentPlatform 平台信息 | 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 | * 录像播放推送完成时发送MediaStatus消息 | 138 | * 录像播放推送完成时发送MediaStatus消息 |
| 139 | + * | ||
| 111 | * @param platform | 140 | * @param platform |
| 112 | * @param sendRtpItem | 141 | * @param sendRtpItem |
| 113 | * @return | 142 | * @return |
| @@ -116,9 +145,19 @@ public interface ISIPCommanderForPlatform { | @@ -116,9 +145,19 @@ public interface ISIPCommanderForPlatform { | ||
| 116 | 145 | ||
| 117 | /** | 146 | /** |
| 118 | * 向发起点播的上级回复bye | 147 | * 向发起点播的上级回复bye |
| 148 | + * | ||
| 119 | * @param platform 平台信息 | 149 | * @param platform 平台信息 |
| 120 | - * @param callId callId | 150 | + * @param callId callId |
| 121 | */ | 151 | */ |
| 122 | void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException; | 152 | void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException; |
| 153 | + | ||
| 123 | void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException; | 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,6 +4,7 @@ import com.genersoft.iot.vmp.conf.SipConfig; | ||
| 4 | import com.genersoft.iot.vmp.gb28181.SipLayer; | 4 | import com.genersoft.iot.vmp.gb28181.SipLayer; |
| 5 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; | 5 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 6 | import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; | 6 | import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; |
| 7 | +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; | ||
| 7 | import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; | 8 | import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; |
| 8 | import com.genersoft.iot.vmp.gb28181.utils.SipUtils; | 9 | import com.genersoft.iot.vmp.gb28181.utils.SipUtils; |
| 9 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | 10 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| @@ -14,7 +15,8 @@ import org.springframework.beans.factory.annotation.Autowired; | @@ -14,7 +15,8 @@ import org.springframework.beans.factory.annotation.Autowired; | ||
| 14 | import org.springframework.stereotype.Component; | 15 | import org.springframework.stereotype.Component; |
| 15 | import org.springframework.util.DigestUtils; | 16 | import org.springframework.util.DigestUtils; |
| 16 | 17 | ||
| 17 | -import javax.sip.*; | 18 | +import javax.sip.InvalidArgumentException; |
| 19 | +import javax.sip.PeerUnavailableException; | ||
| 18 | import javax.sip.address.Address; | 20 | import javax.sip.address.Address; |
| 19 | import javax.sip.address.SipURI; | 21 | import javax.sip.address.SipURI; |
| 20 | import javax.sip.header.*; | 22 | import javax.sip.header.*; |
| @@ -22,7 +24,6 @@ import javax.sip.message.Request; | @@ -22,7 +24,6 @@ import javax.sip.message.Request; | ||
| 22 | import javax.validation.constraints.NotNull; | 24 | import javax.validation.constraints.NotNull; |
| 23 | import java.text.ParseException; | 25 | import java.text.ParseException; |
| 24 | import java.util.ArrayList; | 26 | import java.util.ArrayList; |
| 25 | -import java.util.List; | ||
| 26 | import java.util.UUID; | 27 | import java.util.UUID; |
| 27 | 28 | ||
| 28 | /** | 29 | /** |
| @@ -175,7 +176,7 @@ public class SIPRequestHeaderPlarformProvider { | @@ -175,7 +176,7 @@ public class SIPRequestHeaderPlarformProvider { | ||
| 175 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); | 176 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); |
| 176 | // via | 177 | // via |
| 177 | ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); | 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 | parentPlatform.getTransport(), viaTag); | 180 | parentPlatform.getTransport(), viaTag); |
| 180 | viaHeader.setRPort(); | 181 | viaHeader.setRPort(); |
| 181 | viaHeaders.add(viaHeader); | 182 | viaHeaders.add(viaHeader); |
| @@ -212,7 +213,7 @@ public class SIPRequestHeaderPlarformProvider { | @@ -212,7 +213,7 @@ public class SIPRequestHeaderPlarformProvider { | ||
| 212 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort()); | 213 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort()); |
| 213 | // via | 214 | // via |
| 214 | ArrayList<ViaHeader> viaHeaders = new ArrayList<>(); | 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 | parentPlatform.getTransport(), SipUtils.getNewViaTag()); | 217 | parentPlatform.getTransport(), SipUtils.getNewViaTag()); |
| 217 | viaHeader.setRPort(); | 218 | viaHeader.setRPort(); |
| 218 | viaHeaders.add(viaHeader); | 219 | viaHeaders.add(viaHeader); |
| @@ -272,7 +273,7 @@ public class SIPRequestHeaderPlarformProvider { | @@ -272,7 +273,7 @@ public class SIPRequestHeaderPlarformProvider { | ||
| 272 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort()); | 273 | SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort()); |
| 273 | // via | 274 | // via |
| 274 | ArrayList<ViaHeader> viaHeaders = new ArrayList<>(); | 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 | platform.getTransport(), SipUtils.getNewViaTag()); | 277 | platform.getTransport(), SipUtils.getNewViaTag()); |
| 277 | viaHeader.setRPort(); | 278 | viaHeader.setRPort(); |
| 278 | viaHeaders.add(viaHeader); | 279 | viaHeaders.add(viaHeader); |
| @@ -308,4 +309,80 @@ public class SIPRequestHeaderPlarformProvider { | @@ -308,4 +309,80 @@ public class SIPRequestHeaderPlarformProvider { | ||
| 308 | 309 | ||
| 309 | return request; | 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,12 +591,12 @@ public class SIPCommander implements ISIPCommander { | ||
| 591 | return; | 591 | return; |
| 592 | } | 592 | } |
| 593 | if (!mediaServerItem.isRtpEnable()) { | 593 | if (!mediaServerItem.isRtpEnable()) { |
| 594 | - // 单端口暂不支持语音对讲 | ||
| 595 | - logger.info("[语音对讲] 单端口暂不支持此操作"); | 594 | + // 单端口暂不支持语音喊话 |
| 595 | + logger.info("[语音喊话] 单端口暂不支持此操作"); | ||
| 596 | return; | 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 | HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId()); | 600 | HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId()); |
| 601 | subscribe.addSubscribe(hookSubscribeForStreamChange, (MediaServerItem mediaServerItemInUse, JSONObject json) -> { | 601 | subscribe.addSubscribe(hookSubscribeForStreamChange, (MediaServerItem mediaServerItemInUse, JSONObject json) -> { |
| 602 | if (event != null) { | 602 | if (event != null) { |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java
| 1 | package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; | 1 | package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; |
| 2 | 2 | ||
| 3 | import com.alibaba.fastjson2.JSON; | 3 | import com.alibaba.fastjson2.JSON; |
| 4 | +import com.alibaba.fastjson2.JSONObject; | ||
| 4 | import com.genersoft.iot.vmp.conf.DynamicTask; | 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 | import com.genersoft.iot.vmp.gb28181.SipLayer; | 8 | import com.genersoft.iot.vmp.gb28181.SipLayer; |
| 6 | import com.genersoft.iot.vmp.gb28181.bean.*; | 9 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 7 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; | 10 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; |
| 11 | +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | ||
| 8 | import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; | 12 | import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; |
| 9 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; | 13 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; |
| 10 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider; | 14 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider; |
| 11 | import com.genersoft.iot.vmp.gb28181.utils.SipUtils; | 15 | import com.genersoft.iot.vmp.gb28181.utils.SipUtils; |
| 12 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; | 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 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | 20 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 14 | import com.genersoft.iot.vmp.service.IMediaServerService; | 21 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 15 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; | 22 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; |
| 23 | +import com.genersoft.iot.vmp.service.bean.SSRCInfo; | ||
| 16 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | 24 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 17 | import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; | 25 | import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; |
| 18 | import com.genersoft.iot.vmp.utils.DateUtil; | 26 | import com.genersoft.iot.vmp.utils.DateUtil; |
| 19 | import gov.nist.javax.sip.message.MessageFactoryImpl; | 27 | import gov.nist.javax.sip.message.MessageFactoryImpl; |
| 20 | import gov.nist.javax.sip.message.SIPRequest; | 28 | import gov.nist.javax.sip.message.SIPRequest; |
| 29 | +import gov.nist.javax.sip.message.SIPResponse; | ||
| 21 | import org.slf4j.Logger; | 30 | import org.slf4j.Logger; |
| 22 | import org.slf4j.LoggerFactory; | 31 | import org.slf4j.LoggerFactory; |
| 23 | import org.springframework.beans.factory.annotation.Autowired; | 32 | import org.springframework.beans.factory.annotation.Autowired; |
| @@ -27,6 +36,7 @@ import org.springframework.stereotype.Component; | @@ -27,6 +36,7 @@ import org.springframework.stereotype.Component; | ||
| 27 | import org.springframework.util.ObjectUtils; | 36 | import org.springframework.util.ObjectUtils; |
| 28 | 37 | ||
| 29 | import javax.sip.InvalidArgumentException; | 38 | import javax.sip.InvalidArgumentException; |
| 39 | +import javax.sip.ResponseEvent; | ||
| 30 | import javax.sip.SipException; | 40 | import javax.sip.SipException; |
| 31 | import javax.sip.header.CallIdHeader; | 41 | import javax.sip.header.CallIdHeader; |
| 32 | import javax.sip.header.WWWAuthenticateHeader; | 42 | import javax.sip.header.WWWAuthenticateHeader; |
| @@ -63,6 +73,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { | @@ -63,6 +73,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { | ||
| 63 | private SIPSender sipSender; | 73 | private SIPSender sipSender; |
| 64 | 74 | ||
| 65 | @Autowired | 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 | private DynamicTask dynamicTask; | 86 | private DynamicTask dynamicTask; |
| 67 | 87 | ||
| 68 | @Override | 88 | @Override |
| @@ -336,6 +356,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { | @@ -336,6 +356,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { | ||
| 336 | sipSender.transmitRequest(parentPlatform.getDeviceIp(), request); | 356 | sipSender.transmitRequest(parentPlatform.getDeviceIp(), request); |
| 337 | } | 357 | } |
| 338 | 358 | ||
| 359 | + | ||
| 339 | /** | 360 | /** |
| 340 | * 向上级回复DeviceStatus查询信息 | 361 | * 向上级回复DeviceStatus查询信息 |
| 341 | * @param parentPlatform 平台信息 | 362 | * @param parentPlatform 平台信息 |
| @@ -702,4 +723,107 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { | @@ -702,4 +723,107 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { | ||
| 702 | } | 723 | } |
| 703 | sipSender.transmitRequest(platform.getDeviceIp(),byeRequest); | 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
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,7 +202,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | ||
| 202 | 202 | ||
| 203 | MediaServerItem mediaServerItem = null; | 203 | MediaServerItem mediaServerItem = null; |
| 204 | StreamPushItem streamPushItem = null; | 204 | StreamPushItem streamPushItem = null; |
| 205 | - StreamProxyItem proxyByAppAndStream =null; | 205 | + StreamProxyItem proxyByAppAndStream = null; |
| 206 | // 不是通道可能是直播流 | 206 | // 不是通道可能是直播流 |
| 207 | if (channel != null && gbStream == null) { | 207 | if (channel != null && gbStream == null) { |
| 208 | // 通道存在,发100,TRYING | 208 | // 通道存在,发100,TRYING |
| @@ -1018,7 +1018,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | @@ -1018,7 +1018,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | ||
| 1018 | 1018 | ||
| 1019 | 1019 | ||
| 1020 | CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); | 1020 | CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); |
| 1021 | - sendRtpItem.setPlayType(InviteStreamType.TALK); | 1021 | + sendRtpItem.setPlayType(InviteStreamType.BROADCAST); |
| 1022 | sendRtpItem.setCallId(callIdHeader.getCallId()); | 1022 | sendRtpItem.setCallId(callIdHeader.getCallId()); |
| 1023 | sendRtpItem.setPlatformId(requesterId); | 1023 | sendRtpItem.setPlatformId(requesterId); |
| 1024 | sendRtpItem.setStatus(1); | 1024 | sendRtpItem.setStatus(1); |
| @@ -1113,7 +1113,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | @@ -1113,7 +1113,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements | ||
| 1113 | } | 1113 | } |
| 1114 | 1114 | ||
| 1115 | } catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) { | 1115 | } catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) { |
| 1116 | - logger.error("[命令发送失败] 语音对讲 回复200OK(SDP): {}", e.getMessage()); | 1116 | + logger.error("[命令发送失败] 语音喊话 回复200OK(SDP): {}", e.getMessage()); |
| 1117 | } | 1117 | } |
| 1118 | return sipResponse; | 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,6 +7,7 @@ import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus; | ||
| 7 | import com.genersoft.iot.vmp.gb28181.bean.Device; | 7 | import com.genersoft.iot.vmp.gb28181.bean.Device; |
| 8 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; | 8 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; |
| 9 | import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; | 9 | import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; |
| 10 | +import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; | ||
| 10 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; | 11 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; |
| 11 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; | 12 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; |
| 12 | import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler; | 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,9 +54,9 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i | ||
| 53 | @Override | 54 | @Override |
| 54 | public void handForDevice(RequestEvent evt, Device device, Element rootElement) { | 55 | public void handForDevice(RequestEvent evt, Device device, Element rootElement) { |
| 55 | 56 | ||
| 56 | - String channelId = getText(rootElement, "DeviceID"); | ||
| 57 | SIPRequest request = (SIPRequest) evt.getRequest(); | 57 | SIPRequest request = (SIPRequest) evt.getRequest(); |
| 58 | try { | 58 | try { |
| 59 | + String channelId = getText(rootElement, "DeviceID"); | ||
| 59 | if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) { | 60 | if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) { |
| 60 | // 回复410 | 61 | // 回复410 |
| 61 | responseAck((SIPRequest) evt.getRequest(), Response.GONE); | 62 | responseAck((SIPRequest) evt.getRequest(), Response.GONE); |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java
| 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl; | 1 | package com.genersoft.iot.vmp.gb28181.transmit.event.response.impl; |
| 2 | 2 | ||
| 3 | -import com.genersoft.iot.vmp.conf.SipConfig; | ||
| 4 | import com.genersoft.iot.vmp.gb28181.SipLayer; | 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 | import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; | 4 | import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; |
| 9 | import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; | 5 | import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; |
| 10 | -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; | ||
| 11 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; | 6 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; |
| 12 | import com.genersoft.iot.vmp.gb28181.transmit.event.response.SIPResponseProcessorAbstract; | 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 | import gov.nist.javax.sip.ResponseEventExt; | 8 | import gov.nist.javax.sip.ResponseEventExt; |
| 17 | -import gov.nist.javax.sip.SipProviderImpl; | ||
| 18 | import gov.nist.javax.sip.message.SIPResponse; | 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 | import org.slf4j.Logger; | 10 | import org.slf4j.Logger; |
| 24 | import org.slf4j.LoggerFactory; | 11 | import org.slf4j.LoggerFactory; |
| 25 | import org.springframework.beans.factory.annotation.Autowired; | 12 | import org.springframework.beans.factory.annotation.Autowired; |
| 26 | -import org.springframework.beans.factory.annotation.Qualifier; | ||
| 27 | import org.springframework.stereotype.Component; | 13 | import org.springframework.stereotype.Component; |
| 28 | 14 | ||
| 29 | import javax.sdp.SdpFactory; | 15 | import javax.sdp.SdpFactory; |
| 30 | import javax.sdp.SdpParseException; | 16 | import javax.sdp.SdpParseException; |
| 31 | import javax.sdp.SessionDescription; | 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 | import javax.sip.address.SipURI; | 21 | import javax.sip.address.SipURI; |
| 35 | -import javax.sip.header.CSeqHeader; | ||
| 36 | -import javax.sip.header.UserAgentHeader; | ||
| 37 | import javax.sip.message.Request; | 22 | import javax.sip.message.Request; |
| 38 | import javax.sip.message.Response; | 23 | import javax.sip.message.Response; |
| 39 | import java.text.ParseException; | 24 | import java.text.ParseException; |
| @@ -104,6 +89,7 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract { | @@ -104,6 +89,7 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract { | ||
| 104 | } else { | 89 | } else { |
| 105 | sdp = SdpFactory.getInstance().createSessionDescription(contentString); | 90 | sdp = SdpFactory.getInstance().createSessionDescription(contentString); |
| 106 | } | 91 | } |
| 92 | + // 查看是否是来自设备的,此是回复 | ||
| 107 | 93 | ||
| 108 | SipURI requestUri = sipLayer.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort()); | 94 | SipURI requestUri = sipLayer.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort()); |
| 109 | Request reqAck = headerProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response); | 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,6 +280,9 @@ public class ZLMHttpHookListener { | ||
| 280 | logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); | 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 | MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); | 286 | MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); |
| 284 | JSONObject json = (JSONObject) JSON.toJSON(param); | 287 | JSONObject json = (JSONObject) JSON.toJSON(param); |
| 285 | taskExecutor.execute(() -> { | 288 | taskExecutor.execute(() -> { |
| @@ -335,34 +338,34 @@ public class ZLMHttpHookListener { | @@ -335,34 +338,34 @@ public class ZLMHttpHookListener { | ||
| 335 | } | 338 | } |
| 336 | }else if ("broadcast".equals(param.getApp())){ | 339 | }else if ("broadcast".equals(param.getApp())){ |
| 337 | // 语音对讲推流 stream需要满足格式deviceId_channelId | 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 | }else if ("talk".equals(param.getApp())){ | 369 | }else if ("talk".equals(param.getApp())){ |
| 367 | // 语音对讲推流 stream需要满足格式deviceId_channelId | 370 | // 语音对讲推流 stream需要满足格式deviceId_channelId |
| 368 | if (param.getStream().indexOf("_") > 0) { | 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,7 +92,7 @@ public class ZLMRTPServerFactory { | ||
| 92 | return result; | 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 | int result = -1; | 96 | int result = -1; |
| 97 | // 查询此rtp server 是否已经存在 | 97 | // 查询此rtp server 是否已经存在 |
| 98 | JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId); | 98 | JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId); |
| @@ -108,7 +108,7 @@ public class ZLMRTPServerFactory { | @@ -108,7 +108,7 @@ public class ZLMRTPServerFactory { | ||
| 108 | JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerItem, param); | 108 | JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerItem, param); |
| 109 | if (jsonObject != null ) { | 109 | if (jsonObject != null ) { |
| 110 | if (jsonObject.getInteger("code") == 0) { | 110 | if (jsonObject.getInteger("code") == 0) { |
| 111 | - return createRTPServer(mediaServerItem, streamId, ssrc, port); | 111 | + return createRTPServer(mediaServerItem, streamId, ssrc, port, onlyAuto); |
| 112 | }else { | 112 | }else { |
| 113 | logger.warn("[开启rtpServer], 重启RtpServer错误"); | 113 | logger.warn("[开启rtpServer], 重启RtpServer错误"); |
| 114 | } | 114 | } |
| @@ -131,6 +131,9 @@ public class ZLMRTPServerFactory { | @@ -131,6 +131,9 @@ public class ZLMRTPServerFactory { | ||
| 131 | param.put("port", port); | 131 | param.put("port", port); |
| 132 | } | 132 | } |
| 133 | param.put("ssrc", ssrc); | 133 | param.put("ssrc", ssrc); |
| 134 | + if (onlyAuto != null) { | ||
| 135 | + param.put("only_audio", onlyAuto?"1":"0"); | ||
| 136 | + } | ||
| 134 | JSONObject openRtpServerResultJson = zlmresTfulUtils.openRtpServer(mediaServerItem, param); | 137 | JSONObject openRtpServerResultJson = zlmresTfulUtils.openRtpServer(mediaServerItem, param); |
| 135 | logger.info(JSONObject.toJSONString(openRtpServerResultJson)); | 138 | logger.info(JSONObject.toJSONString(openRtpServerResultJson)); |
| 136 | if (openRtpServerResultJson != null) { | 139 | if (openRtpServerResultJson != null) { |
| @@ -352,4 +355,52 @@ public class ZLMRTPServerFactory { | @@ -352,4 +355,52 @@ public class ZLMRTPServerFactory { | ||
| 352 | return result; | 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,7 +47,7 @@ public interface IMediaServerService { | ||
| 47 | 47 | ||
| 48 | SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback); | 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 | void closeRTPServer(MediaServerItem mediaServerItem, String streamId); | 52 | void closeRTPServer(MediaServerItem mediaServerItem, String streamId); |
| 53 | 53 |
src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java
| 1 | package com.genersoft.iot.vmp.service; | 1 | package com.genersoft.iot.vmp.service; |
| 2 | 2 | ||
| 3 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | ||
| 3 | import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; | 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 | import com.github.pagehelper.PageInfo; | 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 | * @author lin | 17 | * @author lin |
| @@ -48,4 +57,23 @@ public interface IPlatformService { | @@ -48,4 +57,23 @@ public interface IPlatformService { | ||
| 48 | * @param platformId 平台 | 57 | * @param platformId 平台 |
| 49 | */ | 58 | */ |
| 50 | void sendNotifyMobilePosition(String platformId); | 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,7 +12,9 @@ import com.genersoft.iot.vmp.service.bean.PlayBackCallback; | ||
| 12 | import com.genersoft.iot.vmp.service.bean.SSRCInfo; | 12 | import com.genersoft.iot.vmp.service.bean.SSRCInfo; |
| 13 | import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; | 13 | import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; |
| 14 | import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent; | 14 | import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent; |
| 15 | +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; | ||
| 15 | import gov.nist.javax.sip.message.SIPResponse; | 16 | import gov.nist.javax.sip.message.SIPResponse; |
| 17 | +import org.springframework.web.context.request.async.DeferredResult; | ||
| 16 | 18 | ||
| 17 | import javax.sip.InvalidArgumentException; | 19 | import javax.sip.InvalidArgumentException; |
| 18 | import javax.sip.SipException; | 20 | import javax.sip.SipException; |
| @@ -54,6 +56,11 @@ public interface IPlayService { | @@ -54,6 +56,11 @@ public interface IPlayService { | ||
| 54 | void zlmServerOnline(String mediaServerId); | 56 | void zlmServerOnline(String mediaServerId); |
| 55 | 57 | ||
| 56 | AudioBroadcastResult audioBroadcast(Device device, String channelId, Boolean broadcastMode); | 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 | void stopAudioBroadcast(String deviceId, String channelId); | 64 | void stopAudioBroadcast(String deviceId, String channelId); |
| 58 | 65 | ||
| 59 | void audioBroadcastCmd(Device device, String channelId, int timeout, MediaServerItem mediaServerItem, String sourceApp, String sourceStream, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException; | 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 | package com.genersoft.iot.vmp.service.impl; | 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 | import com.alibaba.fastjson2.JSON; | 3 | import com.alibaba.fastjson2.JSON; |
| 15 | import com.alibaba.fastjson2.JSONArray; | 4 | import com.alibaba.fastjson2.JSONArray; |
| 16 | import com.alibaba.fastjson2.JSONObject; | 5 | import com.alibaba.fastjson2.JSONObject; |
| @@ -39,8 +28,6 @@ import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; | @@ -39,8 +28,6 @@ import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; | ||
| 39 | import okhttp3.OkHttpClient; | 28 | import okhttp3.OkHttpClient; |
| 40 | import okhttp3.Request; | 29 | import okhttp3.Request; |
| 41 | import okhttp3.Response; | 30 | import okhttp3.Response; |
| 42 | -import java.time.LocalDateTime; | ||
| 43 | -import java.util.*; | ||
| 44 | import org.slf4j.Logger; | 31 | import org.slf4j.Logger; |
| 45 | import org.slf4j.LoggerFactory; | 32 | import org.slf4j.LoggerFactory; |
| 46 | import org.springframework.beans.factory.annotation.Autowired; | 33 | import org.springframework.beans.factory.annotation.Autowired; |
| @@ -134,7 +121,8 @@ public class MediaServerServiceImpl implements IMediaServerService { | @@ -134,7 +121,8 @@ public class MediaServerServiceImpl implements IMediaServerService { | ||
| 134 | } | 121 | } |
| 135 | 122 | ||
| 136 | @Override | 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 | if (mediaServerItem == null || mediaServerItem.getId() == null) { | 126 | if (mediaServerItem == null || mediaServerItem.getId() == null) { |
| 139 | logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null"); | 127 | logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null"); |
| 140 | return null; | 128 | return null; |
| @@ -163,7 +151,7 @@ public class MediaServerServiceImpl implements IMediaServerService { | @@ -163,7 +151,7 @@ public class MediaServerServiceImpl implements IMediaServerService { | ||
| 163 | } | 151 | } |
| 164 | int rtpServerPort; | 152 | int rtpServerPort; |
| 165 | if (mediaServerItem.isRtpEnable()) { | 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 | } else { | 155 | } else { |
| 168 | rtpServerPort = mediaServerItem.getRtpProxyPort(); | 156 | rtpServerPort = mediaServerItem.getRtpProxyPort(); |
| 169 | } | 157 | } |
| @@ -174,7 +162,7 @@ public class MediaServerServiceImpl implements IMediaServerService { | @@ -174,7 +162,7 @@ public class MediaServerServiceImpl implements IMediaServerService { | ||
| 174 | 162 | ||
| 175 | @Override | 163 | @Override |
| 176 | public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback) { | 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 | @Override | 168 | @Override |
src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java
| 1 | package com.genersoft.iot.vmp.service.impl; | 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 | import com.genersoft.iot.vmp.conf.DynamicTask; | 5 | import com.genersoft.iot.vmp.conf.DynamicTask; |
| 4 | import com.genersoft.iot.vmp.conf.UserSetting; | 6 | import com.genersoft.iot.vmp.conf.UserSetting; |
| 7 | +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; | ||
| 5 | import com.genersoft.iot.vmp.gb28181.bean.*; | 8 | import com.genersoft.iot.vmp.gb28181.bean.*; |
| 6 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; | 9 | import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; |
| 10 | +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; | ||
| 7 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; | 11 | import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; |
| 8 | import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; | 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 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; | 16 | import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; |
| 10 | import com.genersoft.iot.vmp.service.IMediaServerService; | 17 | import com.genersoft.iot.vmp.service.IMediaServerService; |
| 11 | import com.genersoft.iot.vmp.service.IPlatformService; | 18 | import com.genersoft.iot.vmp.service.IPlatformService; |
| 19 | +import com.genersoft.iot.vmp.service.IPlayService; | ||
| 12 | import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; | 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 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; | 23 | import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
| 14 | import com.genersoft.iot.vmp.storager.dao.GbStreamMapper; | 24 | import com.genersoft.iot.vmp.storager.dao.GbStreamMapper; |
| 15 | import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper; | 25 | import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper; |
| @@ -21,11 +31,13 @@ import org.springframework.beans.factory.annotation.Autowired; | @@ -21,11 +31,13 @@ import org.springframework.beans.factory.annotation.Autowired; | ||
| 21 | import org.springframework.stereotype.Service; | 31 | import org.springframework.stereotype.Service; |
| 22 | 32 | ||
| 23 | import javax.sip.InvalidArgumentException; | 33 | import javax.sip.InvalidArgumentException; |
| 34 | +import javax.sip.ResponseEvent; | ||
| 24 | import javax.sip.SipException; | 35 | import javax.sip.SipException; |
| 25 | import java.text.ParseException; | 36 | import java.text.ParseException; |
| 26 | import java.util.HashMap; | 37 | import java.util.HashMap; |
| 27 | import java.util.List; | 38 | import java.util.List; |
| 28 | import java.util.Map; | 39 | import java.util.Map; |
| 40 | +import java.util.UUID; | ||
| 29 | 41 | ||
| 30 | /** | 42 | /** |
| 31 | * @author lin | 43 | * @author lin |
| @@ -65,6 +77,16 @@ public class PlatformServiceImpl implements IPlatformService { | @@ -65,6 +77,16 @@ public class PlatformServiceImpl implements IPlatformService { | ||
| 65 | @Autowired | 77 | @Autowired |
| 66 | private UserSetting userSetting; | 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 | @Override | 92 | @Override |
| @@ -310,4 +332,138 @@ public class PlatformServiceImpl implements IPlatformService { | @@ -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,7 +490,7 @@ public class PlayServiceImpl implements IPlayService { | ||
| 490 | // 关闭rtp server | 490 | // 关闭rtp server |
| 491 | mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); | 491 | mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); |
| 492 | // 重新开启ssrc server | 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,7 +731,7 @@ public class PlayServiceImpl implements IPlayService { | ||
| 731 | // 关闭rtp server | 731 | // 关闭rtp server |
| 732 | mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); | 732 | mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); |
| 733 | // 重新开启ssrc server | 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,16 +966,16 @@ public class PlayServiceImpl implements IPlayService { | ||
| 966 | } | 966 | } |
| 967 | 967 | ||
| 968 | @Override | 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 | if (device == null || channelId == null) { | 970 | if (device == null || channelId == null) { |
| 971 | - return; | 971 | + return false; |
| 972 | } | 972 | } |
| 973 | logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId); | 973 | logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId); |
| 974 | DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId); | 974 | DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId); |
| 975 | if (deviceChannel == null) { | 975 | if (deviceChannel == null) { |
| 976 | logger.warn("开启语音广播的时候未找到通道: {}", channelId); | 976 | logger.warn("开启语音广播的时候未找到通道: {}", channelId); |
| 977 | event.call("开启语音广播的时候未找到通道"); | 977 | event.call("开启语音广播的时候未找到通道"); |
| 978 | - return; | 978 | + return false; |
| 979 | } | 979 | } |
| 980 | // 查询通道使用状态 | 980 | // 查询通道使用状态 |
| 981 | if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) { | 981 | if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) { |
| @@ -986,7 +986,7 @@ public class PlayServiceImpl implements IPlayService { | @@ -986,7 +986,7 @@ public class PlayServiceImpl implements IPlayService { | ||
| 986 | if (streamReady) { | 986 | if (streamReady) { |
| 987 | logger.warn("语音广播已经开启: {}", channelId); | 987 | logger.warn("语音广播已经开启: {}", channelId); |
| 988 | event.call("语音广播已经开启"); | 988 | event.call("语音广播已经开启"); |
| 989 | - return; | 989 | + return false; |
| 990 | } else { | 990 | } else { |
| 991 | stopAudioBroadcast(device.getDeviceId(), channelId); | 991 | stopAudioBroadcast(device.getDeviceId(), channelId); |
| 992 | } | 992 | } |
| @@ -1008,8 +1008,7 @@ public class PlayServiceImpl implements IPlayService { | @@ -1008,8 +1008,7 @@ public class PlayServiceImpl implements IPlayService { | ||
| 1008 | // 发送通知 | 1008 | // 发送通知 |
| 1009 | cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> { | 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 | audioBroadcastManager.update(audioBroadcastCatch); | 1012 | audioBroadcastManager.update(audioBroadcastCatch); |
| 1014 | }, eventResultForError -> { | 1013 | }, eventResultForError -> { |
| 1015 | // 发送失败 | 1014 | // 发送失败 |
| @@ -1017,6 +1016,24 @@ public class PlayServiceImpl implements IPlayService { | @@ -1017,6 +1016,24 @@ public class PlayServiceImpl implements IPlayService { | ||
| 1017 | event.call("语音广播发送失败"); | 1016 | event.call("语音广播发送失败"); |
| 1018 | stopAudioBroadcast(device.getDeviceId(), channelId); | 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,10 +250,10 @@ public class PlayController { | ||
| 250 | @GetMapping("/broadcast/{deviceId}/{channelId}") | 250 | @GetMapping("/broadcast/{deviceId}/{channelId}") |
| 251 | @PostMapping("/broadcast/{deviceId}/{channelId}") | 251 | @PostMapping("/broadcast/{deviceId}/{channelId}") |
| 252 | public AudioBroadcastResult broadcastApi(@PathVariable String deviceId, @PathVariable String channelId, Integer timeout, Boolean broadcastMode) { | 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 | if (device == null) { | 257 | if (device == null) { |
| 258 | throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到设备: " + deviceId); | 258 | throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到设备: " + deviceId); |
| 259 | } | 259 | } |
| @@ -265,7 +265,6 @@ public class PlayController { | @@ -265,7 +265,6 @@ public class PlayController { | ||
| 265 | 265 | ||
| 266 | } | 266 | } |
| 267 | 267 | ||
| 268 | - | ||
| 269 | @Operation(summary = "停止语音广播") | 268 | @Operation(summary = "停止语音广播") |
| 270 | @Parameter(name = "deviceId", description = "设备Id", required = true) | 269 | @Parameter(name = "deviceId", description = "设备Id", required = true) |
| 271 | @Parameter(name = "channelId", description = "通道Id", required = true) | 270 | @Parameter(name = "channelId", description = "通道Id", required = true) |
src/main/resources/all-application.yml
| @@ -195,6 +195,8 @@ user-settings: | @@ -195,6 +195,8 @@ user-settings: | ||
| 195 | gb-send-stream-strict: false | 195 | gb-send-stream-strict: false |
| 196 | # 设备上线时是否自动同步通道 | 196 | # 设备上线时是否自动同步通道 |
| 197 | sync-channel-on-device-online: false | 197 | sync-channel-on-device-online: false |
| 198 | + # 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式 | ||
| 199 | + broadcast-for-platform: UDP | ||
| 198 | # 是否使用设备来源Ip作为回复IP, 不设置则为 false | 200 | # 是否使用设备来源Ip作为回复IP, 不设置则为 false |
| 199 | sip-use-source-ip-as-remote-address: false | 201 | sip-use-source-ip-as-remote-address: false |
| 200 | # 是否开启sip日志 | 202 | # 是否开启sip日志 |
web_src/src/components/dialog/devicePlayer.vue
| @@ -663,46 +663,46 @@ export default { | @@ -663,46 +663,46 @@ export default { | ||
| 663 | this.startBroadcast(streamInfo.rtc) | 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 | // webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,(e)=>{//获取到了远端流,可以播放 | 707 | // webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,(e)=>{//获取到了远端流,可以播放 |
| 708 | // console.error('播放成功',e.streams) | 708 | // console.error('播放成功',e.streams) |
| @@ -715,15 +715,15 @@ export default { | @@ -715,15 +715,15 @@ export default { | ||
| 715 | // // this.eventcallbacK("LOCAL STREAM", "获取到了本地流") | 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 | this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, (e) => {// ICE 协商出错 | 728 | this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, (e) => {// ICE 协商出错 |
| 729 | console.error('ICE 协商出错') | 729 | console.error('ICE 协商出错') |