mirror of
https://gitee.com/pan648540858/wvp-GB28181-pro.git
synced 2026-05-27 23:47:49 +08:00
Pre Merge pull request !28 from panll/wvp-28181-2.0
This commit is contained in:
commit
baf1b55aec
@ -134,3 +134,11 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
|
||||
[mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001)
|
||||
|
||||
|
||||
ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/broadcast/34020000001320000101_34020000001310000001
|
||||
|
||||
ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/talk/34020000001320000011_34020000001370000001
|
||||
|
||||
|
||||
|
||||
ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/talk/34020000001320000101_34020000001310000001
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@
|
||||
- [X] 报警订阅
|
||||
- [X] 目录订阅
|
||||
- [ ] 语音广播
|
||||
- [ ] 语音对讲
|
||||
- [ ] 语音喊话
|
||||
|
||||
**作为下级平台**
|
||||
- [X] 注册
|
||||
@ -95,7 +95,7 @@
|
||||
- [ ] 报警订阅
|
||||
- [X] 目录订阅
|
||||
- [ ] 语音广播
|
||||
- [ ] 语音对讲
|
||||
- [ ] 语音喊话
|
||||
|
||||
|
||||
|
||||
|
||||
27
doc/_content/broadcast.md
Normal file
27
doc/_content/broadcast.md
Normal file
@ -0,0 +1,27 @@
|
||||
# 原理图
|
||||
|
||||
## 使用ffmpeg测试语音对讲原理
|
||||
```plantuml
|
||||
@startuml
|
||||
"FFMPEG" -> "ZLMediaKit": 推流到zlm
|
||||
"WVP-PRO" <- "ZLMediaKit": 通知收到语音对讲推流,携带设备和通道信息
|
||||
"WVP-PRO" -> "设备": 开始语音对讲
|
||||
"WVP-PRO" <-- "设备": 语音对讲建立成功,携带收流端口
|
||||
"WVP-PRO" -> "ZLMediaKit": 通知zlm将流推送到设备收流端口
|
||||
"ZLMediaKit" -> "设备": 向设备推流
|
||||
@enduml
|
||||
```
|
||||
|
||||
## 使用网页测试语音对讲原理
|
||||
```plantuml
|
||||
@startuml
|
||||
"前端页面" -> "WVP-PRO": 请求推流地址
|
||||
"前端页面" <-- "WVP-PRO": 返回推流地址
|
||||
"前端页面" -> "ZLMediaKit": 使用webrtc推流到zlm,以下过程相同
|
||||
"WVP-PRO" <- "ZLMediaKit": 通知收到语音对讲推流,携带设备和通道信息
|
||||
"WVP-PRO" -> "设备": 开始语音对讲
|
||||
"WVP-PRO" <-- "设备": 语音对讲建立成功,携带收流端口
|
||||
"WVP-PRO" -> "ZLMediaKit": 通知zlm将流推送到设备收流端口
|
||||
"ZLMediaKit" -> "设备": 向设备推流
|
||||
@enduml
|
||||
```
|
||||
@ -21,9 +21,9 @@
|
||||
4. WVP-PRO与ZLM支持分开部署,但是wvp-pro-assist必须与zlm部署在同一台主机;
|
||||
5. 生产环境按需开放端口,但是建议修改默认端口,尤其是5060端口,易受到攻击;
|
||||
6. zlm使用docker部署的情况,要求端口映射一致,比如映射5060,应将外部端口也映射为5060端口;
|
||||
7. 启动服务,以linux为例
|
||||
### 启动WVP-PRO
|
||||
**jar包:**
|
||||
7. zlm与wvp会保持高频率的通信,所以不要去将wvp与zlm分属在两个网络,比如wvp在内网,zlm却在公网的情况。
|
||||
8. 启动服务,以linux为例
|
||||
**启动WVP-PRO**
|
||||
```shell
|
||||
nohup java -jar wvp-pro-*.jar &
|
||||
```
|
||||
|
||||
46
doc/_content/theory/broadcast_cascade.md
Normal file
46
doc/_content/theory/broadcast_cascade.md
Normal file
@ -0,0 +1,46 @@
|
||||
<!-- 点播流程 -->
|
||||
|
||||
# 点播流程
|
||||
> 以下为WVP-PRO级联语音喊话流程。
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
"上级平台" -> "下级平台": 1. 发起语音喊话请求
|
||||
"上级平台" <-- "下级平台": 2. 200OK
|
||||
"上级平台" <- "下级平台": 3. 回复Result OK
|
||||
"上级平台" --> "下级平台": 4. 200OK
|
||||
|
||||
"下级平台" -> "设备": 5. 发起语音喊话请求
|
||||
"下级平台" <-- "设备": 6. 200OK
|
||||
"下级平台" <- "设备": 7. 回复Result OK
|
||||
"下级平台" --> "设备": 8. 200OK
|
||||
|
||||
"下级平台" <- "设备": 9. invite(broadcast)
|
||||
"下级平台" --> "设备": 10. 100 trying
|
||||
"下级平台" --> "设备": 11. 200OK SDP
|
||||
"下级平台" <-- "设备": 12. ack
|
||||
|
||||
"上级平台" <- "下级平台": 13. invite(broadcast)
|
||||
"上级平台" --> "下级平台": 14. 100 trying
|
||||
"上级平台" --> "下级平台": 15. 200OK SDP
|
||||
"上级平台" <-- "下级平台": 16. ack
|
||||
|
||||
"上级平台" -> "下级平台": 17. 推送RTP
|
||||
"下级平台" -> "设备": 18. 推送RTP
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
|
||||
## 注册流程描述如下:
|
||||
1. 用户从网页或调用接口发起点播请求;
|
||||
2. WVP-PRO向摄像机发送Invite消息,消息头域中携带 Subject字段,表明点播的视频源ID、发送方媒体流序列号、ZLMediaKit接收流使用的IP、端口号、
|
||||
接收端媒体流序列号等参数,SDP消息体中 s字段为“Play”代表实时点播,y字段描述SSRC值,f字段描述媒体参数。
|
||||
3. 摄像机向WVP-PRO回复200OK,消息体中描述了媒体流发送者发送媒体流的IP、端口、媒体格式、SSRC字段等内容。
|
||||
4. WVP-PRO向设备回复Ack, 会话建立成功。
|
||||
5. 设备向ZLMediaKit发送实时流。
|
||||
6. ZLMediaKit向WVP-PRO发送流改变事件。
|
||||
7. WVP-PRO向WEB用户回复播放地址。
|
||||
8. ZLMediaKit向WVP发送流无人观看事件。
|
||||
9. WVP-PRO向设备回复Bye, 结束会话。
|
||||
10. 设备回复200OK,会话结束成功。
|
||||
@ -20,6 +20,7 @@
|
||||
* [树形结构](_content/theory/channel_tree.md)
|
||||
* [注册流程](_content/theory/register.md)
|
||||
* [点播流程](_content/theory/play.md)
|
||||
* [级联语音喊话流程](_content/theory/broadcast_cascade.md)
|
||||
* **必备技巧**
|
||||
* [抓包](_content/skill/tcpdump.md)
|
||||
|
||||
|
||||
@ -3,5 +3,7 @@ package com.genersoft.iot.vmp.common;
|
||||
public enum InviteSessionType {
|
||||
PLAY,
|
||||
PLAYBACK,
|
||||
DOWNLOAD
|
||||
DOWNLOAD,
|
||||
BROADCAST,
|
||||
TALK
|
||||
}
|
||||
|
||||
@ -240,11 +240,11 @@ public class StreamInfo implements Serializable, Cloneable{
|
||||
}
|
||||
}
|
||||
|
||||
public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam) {
|
||||
public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam, boolean isPlay) {
|
||||
if (callIdParam != null) {
|
||||
callIdParam = Objects.equals(callIdParam, "") ? callIdParam : callIdParam.replace("?", "&");
|
||||
}
|
||||
String file = String.format("index/api/webrtc?app=%s&stream=%s&type=play%s", app, stream, callIdParam);
|
||||
String file = String.format("index/api/webrtc?app=%s&stream=%s&type=%s%s", app, stream, isPlay?"play":"push", callIdParam);
|
||||
if (port > 0) {
|
||||
this.rtc = new StreamURL("http", host, port, file);
|
||||
}
|
||||
|
||||
@ -68,6 +68,7 @@ public class VideoManagerConstants {
|
||||
public static final String SYSTEM_INFO_NET_PREFIX = "VMP_SYSTEM_INFO_NET_";
|
||||
|
||||
public static final String SYSTEM_INFO_DISK_PREFIX = "VMP_SYSTEM_INFO_DISK_";
|
||||
public static final String BROADCAST_WAITE_INVITE = "task_broadcast_waite_invite_";
|
||||
|
||||
public static final String REGISTER_EXPIRE_TASK_KEY_PREFIX = "VMP_device_register_expire_";
|
||||
public static final String PUSH_STREAM_LIST = "VMP_PUSH_STREAM_LIST_";
|
||||
|
||||
@ -32,7 +32,7 @@ public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType, @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) {
|
||||
// 排除api文档的接口,这个接口不需要统一
|
||||
String[] excludePath = {"/v3/api-docs","/api/v1","/index/hook"};
|
||||
String[] excludePath = {"/v3/api-docs","/api/v1","/index/hook","/api/video-"};
|
||||
for (String path : excludePath) {
|
||||
if (request.getURI().getPath().startsWith(path)) {
|
||||
return body;
|
||||
@ -59,8 +59,8 @@ public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||
* 防止返回string时出错
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public HttpMessageConverters fast() {
|
||||
/*@Bean
|
||||
public HttpMessageConverters custHttpMessageConverter() {
|
||||
return new HttpMessageConverters(new FastJsonHttpMessageConverter());
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@ -97,7 +97,7 @@ public class MediaConfig{
|
||||
|
||||
public String getHookIp() {
|
||||
if (ObjectUtils.isEmpty(hookIp)){
|
||||
return sipIp.split(",")[0];
|
||||
return sipIp;
|
||||
}else {
|
||||
return hookIp;
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ public class UserSetting {
|
||||
|
||||
private Boolean logInDatabase = Boolean.TRUE;
|
||||
|
||||
private Boolean usePushingAsStatus = Boolean.TRUE;
|
||||
private Boolean usePushingAsStatus = Boolean.FALSE;
|
||||
|
||||
private Boolean useSourceIpAsStreamIp = Boolean.FALSE;
|
||||
|
||||
@ -58,6 +58,8 @@ public class UserSetting {
|
||||
|
||||
private String thirdPartyGBIdReg = "[\\s\\S]*";
|
||||
|
||||
private String broadcastForPlatform = "UDP";
|
||||
|
||||
private String civilCodeFile = "classpath:civilCode.csv";
|
||||
|
||||
private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
|
||||
@ -210,6 +212,14 @@ public class UserSetting {
|
||||
this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline;
|
||||
}
|
||||
|
||||
public String getBroadcastForPlatform() {
|
||||
return broadcastForPlatform;
|
||||
}
|
||||
|
||||
public void setBroadcastForPlatform(String broadcastForPlatform) {
|
||||
this.broadcastForPlatform = broadcastForPlatform;
|
||||
}
|
||||
|
||||
public Boolean getSipUseSourceIpAsRemoteAddress() {
|
||||
return sipUseSourceIpAsRemoteAddress;
|
||||
}
|
||||
|
||||
@ -100,6 +100,9 @@ public class LoginUser implements UserDetails, CredentialsContainer {
|
||||
return user.getRole();
|
||||
}
|
||||
|
||||
public String getPushKey() {
|
||||
return user.getPushKey();
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
|
||||
@ -0,0 +1,159 @@
|
||||
package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
|
||||
/**
|
||||
* 缓存语音广播的状态
|
||||
* @author lin
|
||||
*/
|
||||
public class AudioBroadcastCatch {
|
||||
|
||||
|
||||
public AudioBroadcastCatch(
|
||||
String deviceId,
|
||||
String channelId,
|
||||
MediaServerItem mediaServerItem,
|
||||
String app,
|
||||
String stream,
|
||||
AudioBroadcastEvent event,
|
||||
AudioBroadcastCatchStatus status,
|
||||
boolean isFromPlatform
|
||||
) {
|
||||
this.deviceId = deviceId;
|
||||
this.channelId = channelId;
|
||||
this.status = status;
|
||||
this.event = event;
|
||||
this.isFromPlatform = isFromPlatform;
|
||||
this.app = app;
|
||||
this.stream = stream;
|
||||
this.mediaServerItem = mediaServerItem;
|
||||
}
|
||||
|
||||
public AudioBroadcastCatch() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备编号
|
||||
*/
|
||||
private String deviceId;
|
||||
|
||||
/**
|
||||
* 通道编号
|
||||
*/
|
||||
private String channelId;
|
||||
|
||||
/**
|
||||
* 流媒体信息
|
||||
*/
|
||||
private MediaServerItem mediaServerItem;
|
||||
|
||||
/**
|
||||
* 关联的流APP
|
||||
*/
|
||||
private String app;
|
||||
|
||||
/**
|
||||
* 关联的流STREAM
|
||||
*/
|
||||
private String stream;
|
||||
|
||||
/**
|
||||
* 是否是级联语音喊话
|
||||
*/
|
||||
private boolean isFromPlatform;
|
||||
|
||||
/**
|
||||
* 语音广播状态
|
||||
*/
|
||||
private AudioBroadcastCatchStatus status;
|
||||
|
||||
/**
|
||||
* 请求信息
|
||||
*/
|
||||
private SipTransactionInfo sipTransactionInfo;
|
||||
|
||||
/**
|
||||
* 请求结果回调
|
||||
*/
|
||||
private AudioBroadcastEvent event;
|
||||
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setDeviceId(String deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public String getChannelId() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
public void setChannelId(String channelId) {
|
||||
this.channelId = channelId;
|
||||
}
|
||||
|
||||
public AudioBroadcastCatchStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(AudioBroadcastCatchStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public SipTransactionInfo getSipTransactionInfo() {
|
||||
return sipTransactionInfo;
|
||||
}
|
||||
|
||||
public MediaServerItem getMediaServerItem() {
|
||||
return mediaServerItem;
|
||||
}
|
||||
|
||||
public void setMediaServerItem(MediaServerItem mediaServerItem) {
|
||||
this.mediaServerItem = mediaServerItem;
|
||||
}
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public String getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void setStream(String stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public boolean isFromPlatform() {
|
||||
return isFromPlatform;
|
||||
}
|
||||
|
||||
public void setFromPlatform(boolean fromPlatform) {
|
||||
isFromPlatform = fromPlatform;
|
||||
}
|
||||
|
||||
public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
|
||||
this.sipTransactionInfo = sipTransactionInfo;
|
||||
}
|
||||
|
||||
public AudioBroadcastEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
public void setEvent(AudioBroadcastEvent event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public void setSipTransactionInfoByRequset(SIPResponse sipResponse) {
|
||||
this.sipTransactionInfo = new SipTransactionInfo(sipResponse);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
/**
|
||||
* 语音广播状态
|
||||
* @author lin
|
||||
*/
|
||||
public enum AudioBroadcastCatchStatus {
|
||||
|
||||
// 发送语音广播消息等待对方回复语音广播
|
||||
Ready,
|
||||
// 收到回复等待invite消息
|
||||
WaiteInvite,
|
||||
// 收到invite消息
|
||||
Ok,
|
||||
}
|
||||
@ -188,8 +188,8 @@ public class Device {
|
||||
@Schema(description = "设备注册的事务信息")
|
||||
private SipTransactionInfo sipTransactionInfo;
|
||||
|
||||
|
||||
|
||||
@Schema(description = "控制语音对讲流程,释放收到ACK后发流")
|
||||
private boolean broadcastPushAfterAck;
|
||||
|
||||
public String getDeviceId() {
|
||||
return deviceId;
|
||||
@ -452,5 +452,11 @@ public class Device {
|
||||
public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
|
||||
this.sipTransactionInfo = sipTransactionInfo;
|
||||
}
|
||||
|
||||
public boolean isBroadcastPushAfterAck() {
|
||||
return broadcastPushAfterAck;
|
||||
}
|
||||
|
||||
public void setBroadcastPushAfterAck(boolean broadcastPushAfterAck) {
|
||||
this.broadcastPushAfterAck = broadcastPushAfterAck;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
public enum InviteStreamType {
|
||||
|
||||
PLAY,PLAYBACK,DOWNLOAD,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY
|
||||
PLAY,PLAYBACK,DOWNLOAD,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,BROADCAST,TALK
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ public class ParentPlatform {
|
||||
* 设备端口
|
||||
*/
|
||||
@Schema(description = "设备端口")
|
||||
private String devicePort;
|
||||
private int devicePort;
|
||||
|
||||
/**
|
||||
* SIP认证用户名(默认使用设备国标编号)
|
||||
@ -261,11 +261,11 @@ public class ParentPlatform {
|
||||
this.deviceIp = deviceIp;
|
||||
}
|
||||
|
||||
public String getDevicePort() {
|
||||
public int getDevicePort() {
|
||||
return devicePort;
|
||||
}
|
||||
|
||||
public void setDevicePort(String devicePort) {
|
||||
public void setDevicePort(int devicePort) {
|
||||
this.devicePort = devicePort;
|
||||
}
|
||||
|
||||
|
||||
@ -49,7 +49,7 @@ public class SendRtpItem {
|
||||
/**
|
||||
* 设备推流的streamId
|
||||
*/
|
||||
private String streamId;
|
||||
private String stream;
|
||||
|
||||
/**
|
||||
* 是否为tcp
|
||||
@ -117,6 +117,11 @@ public class SendRtpItem {
|
||||
*/
|
||||
private InviteStreamType playType;
|
||||
|
||||
/**
|
||||
* 发流的同时收流
|
||||
*/
|
||||
private String receiveStream;
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
@ -181,12 +186,12 @@ public class SendRtpItem {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public String getStreamId() {
|
||||
return streamId;
|
||||
public String getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void setStreamId(String streamId) {
|
||||
this.streamId = streamId;
|
||||
public void setStream(String stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public boolean isTcp() {
|
||||
@ -292,4 +297,12 @@ public class SendRtpItem {
|
||||
public void setRtcp(boolean rtcp) {
|
||||
this.rtcp = rtcp;
|
||||
}
|
||||
|
||||
public String getReceiveStream() {
|
||||
return receiveStream;
|
||||
}
|
||||
|
||||
public void setReceiveStream(String receiveStream) {
|
||||
this.receiveStream = receiveStream;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
|
||||
public class SipTransactionInfo {
|
||||
@ -10,11 +9,23 @@ public class SipTransactionInfo {
|
||||
private String toTag;
|
||||
private String viaBranch;
|
||||
|
||||
// 自己是否媒体流发送者
|
||||
private boolean asSender;
|
||||
|
||||
public SipTransactionInfo(SIPResponse response, boolean asSender) {
|
||||
this.callId = response.getCallIdHeader().getCallId();
|
||||
this.fromTag = response.getFromTag();
|
||||
this.toTag = response.getToTag();
|
||||
this.viaBranch = response.getTopmostViaHeader().getBranch();
|
||||
this.asSender = asSender;
|
||||
}
|
||||
|
||||
public SipTransactionInfo(SIPResponse response) {
|
||||
this.callId = response.getCallIdHeader().getCallId();
|
||||
this.fromTag = response.getFromTag();
|
||||
this.toTag = response.getToTag();
|
||||
this.viaBranch = response.getTopmostViaHeader().getBranch();
|
||||
this.asSender = false;
|
||||
}
|
||||
|
||||
public SipTransactionInfo() {
|
||||
@ -51,4 +62,12 @@ public class SipTransactionInfo {
|
||||
public void setViaBranch(String viaBranch) {
|
||||
this.viaBranch = viaBranch;
|
||||
}
|
||||
|
||||
public boolean isAsSender() {
|
||||
return asSender;
|
||||
}
|
||||
|
||||
public void setAsSender(boolean asSender) {
|
||||
this.asSender = asSender;
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,8 +78,10 @@ public class SipSubscribe {
|
||||
dialogTerminated,
|
||||
// 设备未找到
|
||||
deviceNotFoundEvent,
|
||||
// 设备未找到
|
||||
cmdSendFailEvent
|
||||
// 消息发送失败
|
||||
cmdSendFailEvent,
|
||||
// 消息发送失败
|
||||
failedToGetPort
|
||||
}
|
||||
|
||||
public static class EventResult<EventObject>{
|
||||
|
||||
@ -0,0 +1,103 @@
|
||||
package com.genersoft.iot.vmp.gb28181.session;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 语音广播消息管理类
|
||||
* @author lin
|
||||
*/
|
||||
@Component
|
||||
public class AudioBroadcastManager {
|
||||
|
||||
@Autowired
|
||||
private SipConfig config;
|
||||
|
||||
public static Map<String, AudioBroadcastCatch> data = new ConcurrentHashMap<>();
|
||||
|
||||
public void update(AudioBroadcastCatch audioBroadcastCatch) {
|
||||
if (SipUtils.isFrontEnd(audioBroadcastCatch.getDeviceId())) {
|
||||
data.put(audioBroadcastCatch.getDeviceId(), audioBroadcastCatch);
|
||||
}else {
|
||||
data.put(audioBroadcastCatch.getDeviceId() + audioBroadcastCatch.getChannelId(), audioBroadcastCatch);
|
||||
}
|
||||
}
|
||||
|
||||
public void del(String deviceId, String channelId) {
|
||||
if (SipUtils.isFrontEnd(deviceId)) {
|
||||
data.remove(deviceId);
|
||||
}else {
|
||||
data.remove(deviceId + channelId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void delByDeviceId(String deviceId) {
|
||||
for (String key : data.keySet()) {
|
||||
if (key.startsWith(deviceId)) {
|
||||
data.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<AudioBroadcastCatch> getAll(){
|
||||
Collection<AudioBroadcastCatch> values = data.values();
|
||||
return new ArrayList<>(values);
|
||||
}
|
||||
|
||||
|
||||
public boolean exit(String deviceId, String channelId) {
|
||||
for (String key : data.keySet()) {
|
||||
if (SipUtils.isFrontEnd(deviceId)) {
|
||||
return key.equals(deviceId);
|
||||
}else {
|
||||
return key.equals(deviceId + channelId);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public AudioBroadcastCatch get(String deviceId, String channelId) {
|
||||
AudioBroadcastCatch audioBroadcastCatch;
|
||||
if (SipUtils.isFrontEnd(deviceId)) {
|
||||
audioBroadcastCatch = data.get(deviceId);
|
||||
}else {
|
||||
audioBroadcastCatch = data.get(deviceId + channelId);
|
||||
}
|
||||
if (audioBroadcastCatch == null) {
|
||||
Stream<AudioBroadcastCatch> allAudioBroadcastCatchStreamForDevice = data.values().stream().filter(
|
||||
audioBroadcastCatchItem -> Objects.equals(audioBroadcastCatchItem.getDeviceId(), deviceId));
|
||||
List<AudioBroadcastCatch> audioBroadcastCatchList = allAudioBroadcastCatchStreamForDevice.collect(Collectors.toList());
|
||||
if (audioBroadcastCatchList.size() == 1 && Objects.equals(config.getId(), channelId)) {
|
||||
audioBroadcastCatch = audioBroadcastCatchList.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
return audioBroadcastCatch;
|
||||
}
|
||||
|
||||
public List<AudioBroadcastCatch> get(String deviceId) {
|
||||
List<AudioBroadcastCatch> audioBroadcastCatchList= new ArrayList<>();
|
||||
if (SipUtils.isFrontEnd(deviceId)) {
|
||||
if (data.get(deviceId) != null) {
|
||||
audioBroadcastCatchList.add(data.get(deviceId));
|
||||
}
|
||||
}else {
|
||||
for (String key : data.keySet()) {
|
||||
if (key.startsWith(deviceId)) {
|
||||
audioBroadcastCatchList.add(data.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return audioBroadcastCatchList;
|
||||
}
|
||||
}
|
||||
@ -106,13 +106,13 @@ public class SipRunner implements CommandLineRunner {
|
||||
if (sendRtpItems.size() > 0) {
|
||||
for (SendRtpItem sendRtpItem : sendRtpItems) {
|
||||
MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(),sendRtpItem.getChannelId(), sendRtpItem.getCallId(),sendRtpItem.getStreamId());
|
||||
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(),sendRtpItem.getChannelId(), sendRtpItem.getCallId(),sendRtpItem.getStream());
|
||||
if (mediaServerItem != null) {
|
||||
ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("vhost","__defaultVhost__");
|
||||
param.put("app",sendRtpItem.getApp());
|
||||
param.put("stream",sendRtpItem.getStreamId());
|
||||
param.put("stream",sendRtpItem.getStream());
|
||||
param.put("ssrc",sendRtpItem.getSsrc());
|
||||
JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaServerItem, param);
|
||||
if (jsonObject != null && jsonObject.getInteger("code") == 0) {
|
||||
|
||||
@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
|
||||
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||
@ -124,13 +125,19 @@ public interface ISIPCommander {
|
||||
String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent,
|
||||
SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||
|
||||
|
||||
/**
|
||||
* 视频流停止
|
||||
*/
|
||||
void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
|
||||
|
||||
void talkStreamCmd(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, Device device, String channelId, String callId, ZlmHttpHookSubscribe.Event event, ZlmHttpHookSubscribe.Event eventForPush, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||
|
||||
|
||||
void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException;
|
||||
|
||||
void streamByeCmd(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
|
||||
|
||||
/**
|
||||
* 回放暂停
|
||||
*/
|
||||
@ -160,21 +167,15 @@ public interface ISIPCommander {
|
||||
void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
|
||||
/**
|
||||
* 语音广播
|
||||
*
|
||||
* @param device 视频设备
|
||||
* @param channelId 预览通道
|
||||
*/
|
||||
void audioBroadcastCmd(Device device,String channelId);
|
||||
void streamByeCmdForDeviceInvite(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
|
||||
|
||||
/**
|
||||
* /**
|
||||
* 语音广播
|
||||
*
|
||||
* @param device 视频设备
|
||||
*/
|
||||
void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||
void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
|
||||
void audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||
|
||||
/**
|
||||
* 音视频录像控制
|
||||
@ -362,4 +363,5 @@ public interface ISIPCommander {
|
||||
*/
|
||||
void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.cmd;
|
||||
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.SipException;
|
||||
@ -14,6 +18,7 @@ public interface ISIPCommanderForPlatform {
|
||||
|
||||
/**
|
||||
* 向上级平台注册
|
||||
*
|
||||
* @param parentPlatform
|
||||
* @return
|
||||
*/
|
||||
@ -26,6 +31,7 @@ public interface ISIPCommanderForPlatform {
|
||||
|
||||
/**
|
||||
* 向上级平台注销
|
||||
*
|
||||
* @param parentPlatform
|
||||
* @return
|
||||
*/
|
||||
@ -34,14 +40,17 @@ public interface ISIPCommanderForPlatform {
|
||||
|
||||
/**
|
||||
* 向上级平发送心跳信息
|
||||
*
|
||||
* @param parentPlatform
|
||||
* @return callId(作为接受回复的判定)
|
||||
*/
|
||||
String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
|
||||
String keepalive(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
|
||||
throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
|
||||
/**
|
||||
* 向上级回复通道信息
|
||||
*
|
||||
* @param channel 通道信息
|
||||
* @param parentPlatform 平台信息
|
||||
* @param sn
|
||||
@ -49,11 +58,15 @@ public interface ISIPCommanderForPlatform {
|
||||
* @param size
|
||||
* @return
|
||||
*/
|
||||
void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException;
|
||||
void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag) throws InvalidArgumentException, ParseException, SipException;
|
||||
void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size)
|
||||
throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
void catalogQuery(List<DeviceChannel> channels, ParentPlatform parentPlatform, String sn, String fromTag)
|
||||
throws InvalidArgumentException, ParseException, SipException;
|
||||
|
||||
/**
|
||||
* 向上级回复DeviceInfo查询信息
|
||||
*
|
||||
* @param parentPlatform 平台信息
|
||||
* @param sn SN
|
||||
* @param fromTag FROM头的tag信息
|
||||
@ -63,6 +76,7 @@ public interface ISIPCommanderForPlatform {
|
||||
|
||||
/**
|
||||
* 向上级回复DeviceStatus查询信息
|
||||
*
|
||||
* @param parentPlatform 平台信息
|
||||
* @param sn
|
||||
* @param fromTag
|
||||
@ -72,15 +86,18 @@ public interface ISIPCommanderForPlatform {
|
||||
|
||||
/**
|
||||
* 向上级回复移动位置订阅消息
|
||||
*
|
||||
* @param parentPlatform 平台信息
|
||||
* @param gpsMsgInfo GPS信息
|
||||
* @param subscribeInfo 订阅相关的信息
|
||||
* @return
|
||||
*/
|
||||
void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
|
||||
void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo)
|
||||
throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
|
||||
|
||||
/**
|
||||
* 向上级回复报警消息
|
||||
*
|
||||
* @param parentPlatform 平台信息
|
||||
* @param deviceAlarm 报警信息信息
|
||||
* @return
|
||||
@ -89,6 +106,7 @@ public interface ISIPCommanderForPlatform {
|
||||
|
||||
/**
|
||||
* 回复catalog事件-增加/更新
|
||||
*
|
||||
* @param parentPlatform
|
||||
* @param deviceChannels
|
||||
*/
|
||||
@ -96,22 +114,28 @@ public interface ISIPCommanderForPlatform {
|
||||
|
||||
/**
|
||||
* 回复catalog事件-删除
|
||||
*
|
||||
* @param parentPlatform
|
||||
* @param deviceChannels
|
||||
*/
|
||||
void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException;
|
||||
void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List<DeviceChannel> deviceChannels,
|
||||
SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException,
|
||||
ParseException, NoSuchFieldException, SipException, IllegalAccessException;
|
||||
|
||||
/**
|
||||
* 回复recordInfo
|
||||
*
|
||||
* @param deviceChannel 通道信息
|
||||
* @param parentPlatform 平台信息
|
||||
* @param fromTag fromTag
|
||||
* @param recordInfo 录像信息
|
||||
*/
|
||||
void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) throws SipException, InvalidArgumentException, ParseException;
|
||||
void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo)
|
||||
throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
/**
|
||||
* 录像播放推送完成时发送MediaStatus消息
|
||||
*
|
||||
* @param platform
|
||||
* @param sendRtpItem
|
||||
* @return
|
||||
@ -120,9 +144,19 @@ public interface ISIPCommanderForPlatform {
|
||||
|
||||
/**
|
||||
* 向发起点播的上级回复bye
|
||||
*
|
||||
* @param platform 平台信息
|
||||
* @param callId callId
|
||||
*/
|
||||
void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException;
|
||||
|
||||
void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
|
||||
|
||||
void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,
|
||||
SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent,
|
||||
SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException;
|
||||
|
||||
void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.gb28181.SipLayer;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
@ -55,7 +56,7 @@ public class SIPRequestHeaderPlarformProvider {
|
||||
//via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(),
|
||||
Integer.parseInt(parentPlatform.getDevicePort()), parentPlatform.getTransport(), SipUtils.getNewViaTag());
|
||||
parentPlatform.getDevicePort(), parentPlatform.getTransport(), SipUtils.getNewViaTag());
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
//from
|
||||
@ -182,7 +183,7 @@ public class SIPRequestHeaderPlarformProvider {
|
||||
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
|
||||
parentPlatform.getTransport(), viaTag);
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
@ -219,7 +220,7 @@ public class SIPRequestHeaderPlarformProvider {
|
||||
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()),
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(),
|
||||
parentPlatform.getTransport(), SipUtils.getNewViaTag());
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
@ -279,7 +280,7 @@ public class SIPRequestHeaderPlarformProvider {
|
||||
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort());
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(platform.getDeviceIp(), Integer.parseInt(platform.getDevicePort()),
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(),
|
||||
platform.getTransport(), SipUtils.getNewViaTag());
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
@ -311,6 +312,82 @@ public class SIPRequestHeaderPlarformProvider {
|
||||
|
||||
request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
public Request createInviteRequest(ParentPlatform platform, String channelId, String content, String viaTag, String fromTag, String ssrc, CallIdHeader callIdHeader) throws PeerUnavailableException, ParseException, InvalidArgumentException {
|
||||
Request request = null;
|
||||
//请求行
|
||||
String platformHostAddress = platform.getServerIP() + ":" + platform.getServerPort();
|
||||
String localHostAddress = sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort();
|
||||
SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, platformHostAddress);
|
||||
//via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), viaTag);
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
|
||||
//from
|
||||
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getDeviceGBId(), sipConfig.getDomain());
|
||||
Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
|
||||
FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack
|
||||
//to
|
||||
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, platformHostAddress);
|
||||
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
|
||||
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null);
|
||||
|
||||
//Forwards
|
||||
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
|
||||
|
||||
//ceq
|
||||
CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE);
|
||||
request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
|
||||
|
||||
request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
|
||||
|
||||
Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),localHostAddress));
|
||||
request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
|
||||
// Subject
|
||||
SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0));
|
||||
request.addHeader(subjectHeader);
|
||||
ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
|
||||
request.setContent(content, contentTypeHeader);
|
||||
return request;
|
||||
}
|
||||
|
||||
public Request createByteRequest(ParentPlatform platform, String channelId, SipTransactionInfo transactionInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException {
|
||||
String deviceHostAddress = platform.getDeviceIp() + ":" + platform.getDevicePort();
|
||||
Request request = null;
|
||||
SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, deviceHostAddress);
|
||||
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag());
|
||||
viaHeaders.add(viaHeader);
|
||||
//from
|
||||
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
|
||||
Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
|
||||
FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.isAsSender()?transactionInfo.getFromTag():transactionInfo.getToTag());
|
||||
//to
|
||||
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, deviceHostAddress);
|
||||
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
|
||||
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,transactionInfo.isAsSender()?transactionInfo.getToTag():transactionInfo.getFromTag());
|
||||
|
||||
//Forwards
|
||||
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
|
||||
|
||||
//ceq
|
||||
CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
|
||||
CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
|
||||
request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
|
||||
|
||||
request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
|
||||
|
||||
Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort()));
|
||||
request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
|
||||
|
||||
request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,6 +197,41 @@ public class SIPRequestHeaderProvider {
|
||||
return request;
|
||||
}
|
||||
|
||||
public Request createByteRequestForDeviceInvite(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException {
|
||||
Request request = null;
|
||||
//请求行
|
||||
SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag());
|
||||
viaHeaders.add(viaHeader);
|
||||
//from
|
||||
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain());
|
||||
Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
|
||||
FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getToTag());
|
||||
//to
|
||||
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress());
|
||||
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
|
||||
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, transactionInfo.getFromTag());
|
||||
|
||||
//Forwards
|
||||
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
|
||||
|
||||
//ceq
|
||||
CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE);
|
||||
CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId());
|
||||
request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards);
|
||||
|
||||
request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
|
||||
|
||||
Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort()));
|
||||
request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
|
||||
|
||||
request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
|
||||
Request request = null;
|
||||
// sipuri
|
||||
@ -315,6 +350,38 @@ public class SIPRequestHeaderProvider {
|
||||
|
||||
request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
|
||||
|
||||
return request;
|
||||
}
|
||||
public Request createBroadcastMessageRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
|
||||
Request request = null;
|
||||
// sipuri
|
||||
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
|
||||
// via
|
||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag);
|
||||
viaHeader.setRPort();
|
||||
viaHeaders.add(viaHeader);
|
||||
// from
|
||||
SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain());
|
||||
Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI);
|
||||
FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag);
|
||||
// to
|
||||
SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress());
|
||||
Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI);
|
||||
ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag);
|
||||
|
||||
// Forwards
|
||||
MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70);
|
||||
// ceq
|
||||
CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE);
|
||||
|
||||
ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml");
|
||||
|
||||
request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader,
|
||||
toHeader, viaHeaders, maxForwards, contentTypeHeader, content);
|
||||
|
||||
request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.SipLayer;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||
@ -17,9 +18,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamPush;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
|
||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||
@ -41,6 +44,7 @@ import javax.sip.SipFactory;
|
||||
import javax.sip.header.CallIdHeader;
|
||||
import javax.sip.message.Request;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -75,11 +79,12 @@ public class SIPCommander implements ISIPCommander {
|
||||
@Autowired
|
||||
private ZlmHttpHookSubscribe subscribe;
|
||||
|
||||
|
||||
|
||||
@Autowired
|
||||
private IMediaServerService mediaServerService;
|
||||
|
||||
@Autowired
|
||||
private ZLMServerFactory zlmServerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* 云台方向放控制,使用配置文件中的默认镜头移动速度
|
||||
@ -589,6 +594,70 @@ public class SIPCommander implements ISIPCommander {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void talkStreamCmd(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, Device device, String channelId, String callId, ZlmHttpHookSubscribe.Event event, ZlmHttpHookSubscribe.Event eventForPush, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
|
||||
|
||||
String stream = sendRtpItem.getStream();
|
||||
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
if (!mediaServerItem.isRtpEnable()) {
|
||||
// 单端口暂不支持语音喊话
|
||||
logger.info("[语音喊话] 单端口暂不支持此操作");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("[语音喊话] {} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), sendRtpItem.getPort());
|
||||
HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
|
||||
subscribe.addSubscribe(hookSubscribeForStreamChange, (mediaServerItemInUse, hookParam) -> {
|
||||
if (event != null) {
|
||||
event.response(mediaServerItemInUse, hookParam);
|
||||
subscribe.removeSubscribe(hookSubscribeForStreamChange);
|
||||
}
|
||||
});
|
||||
|
||||
CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport());
|
||||
callIdHeader.setCallId(callId);
|
||||
HookSubscribeForStreamPush hookSubscribeForStreamPush = HookSubscribeFactory.on_publish("rtp", stream, null, mediaServerItem.getId());
|
||||
subscribe.addSubscribe(hookSubscribeForStreamPush, (mediaServerItemInUse, hookParam) -> {
|
||||
if (eventForPush != null) {
|
||||
eventForPush.response(mediaServerItemInUse, hookParam);
|
||||
}
|
||||
});
|
||||
//
|
||||
StringBuffer content = new StringBuffer(200);
|
||||
content.append("v=0\r\n");
|
||||
content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
|
||||
content.append("s=Talk\r\n");
|
||||
content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
|
||||
content.append("t=0 0\r\n");
|
||||
|
||||
content.append("m=audio " + sendRtpItem.getPort() + " TCP/RTP/AVP 8\r\n");
|
||||
content.append("a=setup:passive\r\n");
|
||||
content.append("a=connection:new\r\n");
|
||||
content.append("a=sendrecv\r\n");
|
||||
content.append("a=rtpmap:8 PCMA/8000\r\n");
|
||||
|
||||
content.append("y=" + sendRtpItem.getSsrc() + "\r\n");//ssrc
|
||||
// f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
|
||||
content.append("f=v/////a/1/8/1" + "\r\n");
|
||||
|
||||
Request request = headerProvider.createInviteRequest(device, channelId, content.toString(),
|
||||
SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sendRtpItem.getSsrc(), callIdHeader);
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> {
|
||||
streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream());
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
|
||||
errorEvent.response(e);
|
||||
}), e -> {
|
||||
// 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值
|
||||
ResponseEvent responseEvent = (ResponseEvent) e.event;
|
||||
SIPResponse response = (SIPResponse) responseEvent.getResponse();
|
||||
streamSession.put(device.getDeviceId(), channelId, "talk", stream, sendRtpItem.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.TALK);
|
||||
okEvent.response(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频流停止, 不使用回调
|
||||
*/
|
||||
@ -620,18 +689,19 @@ public class SIPCommander implements ISIPCommander {
|
||||
streamSession.removeByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getCallId());
|
||||
Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 语音广播
|
||||
*
|
||||
* @param device 视频设备
|
||||
* @param channelId 预览通道
|
||||
*/
|
||||
@Override
|
||||
public void audioBroadcastCmd(Device device, String channelId) {
|
||||
public void streamByeCmd(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
|
||||
Request byteRequest = headerProvider.createByteRequest(device, channelId, sipTransactionInfo);
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamByeCmdForDeviceInvite(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
|
||||
Request byteRequest = headerProvider.createByteRequestForDeviceInvite(device, channelId, sipTransactionInfo);
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -640,8 +710,7 @@ public class SIPCommander implements ISIPCommander {
|
||||
* @param device 视频设备
|
||||
*/
|
||||
@Override
|
||||
public void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException {
|
||||
|
||||
public void audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
|
||||
StringBuffer broadcastXml = new StringBuffer(200);
|
||||
String charset = device.getCharset();
|
||||
broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
|
||||
@ -649,33 +718,11 @@ public class SIPCommander implements ISIPCommander {
|
||||
broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
|
||||
broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
|
||||
broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
|
||||
broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
|
||||
broadcastXml.append("<TargetID>" + channelId + "</TargetID>\r\n");
|
||||
broadcastXml.append("</Notify>\r\n");
|
||||
|
||||
|
||||
|
||||
Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException {
|
||||
|
||||
StringBuffer broadcastXml = new StringBuffer(200);
|
||||
String charset = device.getCharset();
|
||||
broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
|
||||
broadcastXml.append("<Notify>\r\n");
|
||||
broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
|
||||
broadcastXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
|
||||
broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\r\n");
|
||||
broadcastXml.append("<TargetID>" + device.getDeviceId() + "</TargetID>\r\n");
|
||||
broadcastXml.append("</Notify>\r\n");
|
||||
|
||||
|
||||
|
||||
Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent);
|
||||
|
||||
}
|
||||
|
||||
@ -752,6 +799,8 @@ public class SIPCommander implements ISIPCommander {
|
||||
cmdXml.append("<GuardCmd>" + guardCmdStr + "</GuardCmd>\r\n");
|
||||
cmdXml.append("</Control>\r\n");
|
||||
|
||||
|
||||
|
||||
Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
|
||||
}
|
||||
@ -949,6 +998,8 @@ public class SIPCommander implements ISIPCommander {
|
||||
catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
|
||||
catalogXml.append("</Query>\r\n");
|
||||
|
||||
|
||||
|
||||
Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
|
||||
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
|
||||
@ -1312,8 +1363,6 @@ public class SIPCommander implements ISIPCommander {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 回放暂停
|
||||
*/
|
||||
|
||||
@ -1,24 +1,34 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.genersoft.iot.vmp.common.InviteSessionType;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.SipLayer;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
|
||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.utils.GitUtil;
|
||||
import gov.nist.javax.sip.message.MessageFactoryImpl;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -28,6 +38,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.ResponseEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.SipFactory;
|
||||
import javax.sip.header.CallIdHeader;
|
||||
@ -64,6 +75,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
|
||||
@Autowired
|
||||
private SIPSender sipSender;
|
||||
|
||||
@Autowired
|
||||
private ZlmHttpHookSubscribe subscribe;
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
|
||||
@Autowired
|
||||
private VideoStreamSessionManager streamSession;
|
||||
|
||||
@Autowired
|
||||
private DynamicTask dynamicTask;
|
||||
|
||||
@ -462,6 +483,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
|
||||
sipSender.transmitRequest(parentPlatform.getDeviceIp(), request);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 向上级回复DeviceStatus查询信息
|
||||
* @param parentPlatform 平台信息
|
||||
@ -811,26 +833,129 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamByeCmd(ParentPlatform parentPlatform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
|
||||
public synchronized void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException {
|
||||
if (sendRtpItem == null ) {
|
||||
logger.info("[向上级发送BYE], sendRtpItem 为NULL");
|
||||
return;
|
||||
}
|
||||
if (parentPlatform == null) {
|
||||
if (platform == null) {
|
||||
logger.info("[向上级发送BYE], platform 为NULL");
|
||||
return;
|
||||
}
|
||||
logger.info("[向上级发送BYE], {}/{}", parentPlatform.getServerGBId(), sendRtpItem.getChannelId());
|
||||
logger.info("[向上级发送BYE], {}/{}", platform.getServerGBId(), sendRtpItem.getChannelId());
|
||||
String mediaServerId = sendRtpItem.getMediaServerId();
|
||||
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
|
||||
if (mediaServerItem != null) {
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
|
||||
zlmServerFactory.closeRtpServer(mediaServerItem, sendRtpItem.getStreamId());
|
||||
zlmServerFactory.closeRtpServer(mediaServerItem, sendRtpItem.getStream());
|
||||
}
|
||||
SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(parentPlatform, sendRtpItem);
|
||||
SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(platform, sendRtpItem);
|
||||
if (byeRequest == null) {
|
||||
logger.warn("[向上级发送bye]:无法创建 byeRequest");
|
||||
}
|
||||
sipSender.transmitRequest(parentPlatform.getDeviceIp(),byeRequest);
|
||||
sipSender.transmitRequest(platform.getDeviceIp(),byeRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException {
|
||||
SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(platform.getServerGBId(), channelId, callId, stream);
|
||||
if (ssrcTransaction == null) {
|
||||
throw new SsrcTransactionNotFoundException(platform.getServerGBId(), channelId, callId, stream);
|
||||
}
|
||||
|
||||
mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc());
|
||||
mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream());
|
||||
streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream());
|
||||
|
||||
Request byteRequest = headerProviderPlatformProvider.createByteRequest(platform, channelId, ssrcTransaction.getSipTransactionInfo());
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), byteRequest, null, okEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException {
|
||||
if (platform == null || deviceChannel == null) {
|
||||
return;
|
||||
}
|
||||
String characterSet = platform.getCharacterSet();
|
||||
StringBuffer mediaStatusXml = new StringBuffer(200);
|
||||
mediaStatusXml.append("<?xml version=\"1.0\" encoding=\"" + characterSet + "\"?>\r\n");
|
||||
mediaStatusXml.append("<Response>\r\n");
|
||||
mediaStatusXml.append("<CmdType>Broadcast</CmdType>\r\n");
|
||||
mediaStatusXml.append("<SN>" + sn + "</SN>\r\n");
|
||||
mediaStatusXml.append("<DeviceID>" + deviceChannel.getChannelId() + "</DeviceID>\r\n");
|
||||
mediaStatusXml.append("<Result>" + (result?"OK":"ERROR") + "</Result>\r\n");
|
||||
mediaStatusXml.append("</Response>\r\n");
|
||||
|
||||
CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(platform.getDeviceIp(), platform.getTransport());
|
||||
|
||||
SIPRequest messageRequest = (SIPRequest)headerProviderPlatformProvider.createMessageRequest(platform, mediaStatusXml.toString(),
|
||||
SipUtils.getNewFromTag(), SipUtils.getNewViaTag(), callIdHeader);
|
||||
|
||||
sipSender.transmitRequest(platform.getDeviceIp(),messageRequest, errorEvent, okEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem,
|
||||
SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent,
|
||||
SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException {
|
||||
String stream = ssrcInfo.getStream();
|
||||
|
||||
if (platform == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
|
||||
HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
|
||||
subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, HookParam hookParam) -> {
|
||||
if (event != null) {
|
||||
event.response(mediaServerItemInUse, hookParam);
|
||||
subscribe.removeSubscribe(hookSubscribe);
|
||||
}
|
||||
});
|
||||
String sdpIp = mediaServerItem.getSdpIp();
|
||||
|
||||
StringBuffer content = new StringBuffer(200);
|
||||
content.append("v=0\r\n");
|
||||
content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n");
|
||||
content.append("s=Play\r\n");
|
||||
content.append("c=IN IP4 " + sdpIp + "\r\n");
|
||||
content.append("t=0 0\r\n");
|
||||
|
||||
if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
|
||||
content.append("m=audio " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n");
|
||||
} else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
|
||||
content.append("m=audio " + ssrcInfo.getPort() + " TCP/RTP/AVP 8 96\r\n");
|
||||
} else if ("UDP".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
|
||||
content.append("m=audio " + ssrcInfo.getPort() + " RTP/AVP 8 96\r\n");
|
||||
}
|
||||
|
||||
content.append("a=recvonly\r\n");
|
||||
content.append("a=rtpmap:8 PCMA/8000\r\n");
|
||||
content.append("a=rtpmap:96 PS/90000\r\n");
|
||||
if ("TCP-PASSIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
|
||||
content.append("a=setup:passive\r\n");
|
||||
content.append("a=connection:new\r\n");
|
||||
}else if ("TCP-ACTIVE".equalsIgnoreCase(userSetting.getBroadcastForPlatform())) {
|
||||
content.append("a=setup:active\r\n");
|
||||
content.append("a=connection:new\r\n");
|
||||
}
|
||||
|
||||
content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc
|
||||
CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getTransport());
|
||||
|
||||
Request request = headerProviderPlatformProvider.createInviteRequest(platform, channelId,
|
||||
content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), ssrcInfo.getSsrc(),
|
||||
callIdHeader);
|
||||
sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), request, (e -> {
|
||||
streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
|
||||
subscribe.removeSubscribe(hookSubscribe);
|
||||
errorEvent.response(e);
|
||||
}), e -> {
|
||||
ResponseEvent responseEvent = (ResponseEvent) e.event;
|
||||
SIPResponse response = (SIPResponse) responseEvent.getResponse();
|
||||
streamSession.put(platform.getServerGBId(), channelId, callIdHeader.getCallId(), stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.BROADCAST);
|
||||
okEvent.response(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +82,7 @@ public abstract class SIPRequestProcessorParent {
|
||||
return responseAck(sipRequest, statusCode, msg, null);
|
||||
}
|
||||
|
||||
|
||||
public SIPResponse responseAck(SIPRequest sipRequest, int statusCode, String msg, ResponseAckExtraParam responseAckExtraParam) throws SipException, InvalidArgumentException, ParseException {
|
||||
if (sipRequest.getToHeader().getTag() == null) {
|
||||
sipRequest.getToHeader().setTag(SipUtils.getNewTag());
|
||||
@ -124,6 +125,8 @@ public abstract class SIPRequestProcessorParent {
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 回复带sdp的200
|
||||
*/
|
||||
@ -141,7 +144,10 @@ public abstract class SIPRequestProcessorParent {
|
||||
responseAckExtraParam.content = sdp;
|
||||
responseAckExtraParam.sipURI = sipURI;
|
||||
|
||||
return responseAck(request, Response.OK, null, responseAckExtraParam);
|
||||
SIPResponse sipResponse = responseAck(request, Response.OK, null, responseAckExtraParam);
|
||||
|
||||
|
||||
return sipResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -174,7 +180,8 @@ public abstract class SIPRequestProcessorParent {
|
||||
reader.setEncoding(charset);
|
||||
// 对海康出现的未转义字符做处理。
|
||||
String[] destStrArray = new String[]{"<",">","&","'","""};
|
||||
char despChar = '&'; // 或许可扩展兼容其他字符
|
||||
// 或许可扩展兼容其他字符
|
||||
char despChar = '&';
|
||||
byte destBye = (byte) despChar;
|
||||
List<Byte> result = new ArrayList<>();
|
||||
byte[] rawContent = request.getRawContent();
|
||||
@ -220,4 +227,5 @@ public abstract class SIPRequestProcessorParent {
|
||||
return xml.getRootElement();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,22 +1,20 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.service.IDeviceService;
|
||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
|
||||
import com.genersoft.iot.vmp.service.IPlayService;
|
||||
import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
|
||||
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
@ -27,25 +25,23 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.RequestEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.address.SipURI;
|
||||
import javax.sip.header.CallIdHeader;
|
||||
import javax.sip.header.FromHeader;
|
||||
import javax.sip.header.HeaderAddress;
|
||||
import javax.sip.header.ToHeader;
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* SIP命令类型: ACK请求
|
||||
* @author lin
|
||||
*/
|
||||
@Component
|
||||
public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class);
|
||||
private final Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class);
|
||||
private final String method = "ACK";
|
||||
|
||||
@Autowired
|
||||
@ -66,6 +62,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
@Autowired
|
||||
private IVideoManagerStorage storager;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Autowired
|
||||
private ZLMServerFactory zlmServerFactory;
|
||||
|
||||
@ -75,113 +74,126 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
@Autowired
|
||||
private IMediaServerService mediaServerService;
|
||||
|
||||
@Autowired
|
||||
private ZlmHttpHookSubscribe subscribe;
|
||||
|
||||
@Autowired
|
||||
private DynamicTask dynamicTask;
|
||||
|
||||
@Autowired
|
||||
private ISIPCommander cmder;
|
||||
|
||||
@Autowired
|
||||
private ISIPCommanderForPlatform commanderForPlatform;
|
||||
|
||||
@Autowired
|
||||
private RedisGbPlayMsgListener redisGbPlayMsgListener;
|
||||
|
||||
@Autowired
|
||||
private IPlayService playService;
|
||||
|
||||
|
||||
/**
|
||||
* 处理 ACK请求
|
||||
*
|
||||
* @param evt
|
||||
*/
|
||||
@Override
|
||||
public void process(RequestEvent evt) {
|
||||
CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
|
||||
|
||||
String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
|
||||
logger.info("[收到ACK]: platformGbId->{}", platformGbId);
|
||||
ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformGbId);
|
||||
// 取消设置的超时任务
|
||||
dynamicTask.stop(callIdHeader.getCallId());
|
||||
String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId());
|
||||
String fromUserId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser();
|
||||
String toUserId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
|
||||
logger.info("[收到ACK]: 来自->{}", fromUserId);
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
|
||||
if (sendRtpItem == null) {
|
||||
logger.warn("[收到ACK]:未找到通道({})的推流信息", channelId);
|
||||
logger.warn("[收到ACK]:未找到来自{},目标为({})的推流信息",fromUserId, toUserId);
|
||||
return;
|
||||
}
|
||||
// tcp主动时,此时是级联下级平台,在回复200ok时,本地已经请求zlm开启监听,跳过下面步骤
|
||||
if (sendRtpItem.isTcpActive()) {
|
||||
logger.info("收到ACK,rtp/{} TCP主动方式后续处理", sendRtpItem.getStreamId());
|
||||
logger.info("收到ACK,rtp/{} TCP主动方式后续处理", sendRtpItem.getStream());
|
||||
return;
|
||||
}
|
||||
String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, 协议:{}",
|
||||
sendRtpItem.getStreamId(),
|
||||
sendRtpItem.getStream(),
|
||||
sendRtpItem.getIp(),
|
||||
sendRtpItem.getPort(),
|
||||
sendRtpItem.getSsrc(),
|
||||
sendRtpItem.isTcp()?(sendRtpItem.isTcpActive()?"TCP主动":"TCP被动"):"UDP"
|
||||
);
|
||||
ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(fromUserId);
|
||||
|
||||
if (parentPlatform != null) {
|
||||
Map<String, Object> param = getSendRtpParam(sendRtpItem);
|
||||
if (mediaInfo == null) {
|
||||
RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
|
||||
sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStream(),
|
||||
sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
|
||||
sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
|
||||
redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> {
|
||||
playService.startSendRtpStreamHand(sendRtpItem, parentPlatform, json, param, callIdHeader);
|
||||
});
|
||||
} else {
|
||||
JSONObject startSendRtpStreamResult = sendRtp(sendRtpItem, mediaInfo, param);
|
||||
if (startSendRtpStreamResult != null) {
|
||||
playService.startSendRtpStreamHand(sendRtpItem, parentPlatform, startSendRtpStreamResult, param, callIdHeader);
|
||||
}
|
||||
}
|
||||
}else {
|
||||
Device device = deviceService.getDevice(fromUserId);
|
||||
if (device == null) {
|
||||
logger.warn("[收到ACK]:来自{},目标为({})的推流信息为找到流体服务[{}]信息",fromUserId, toUserId, sendRtpItem.getMediaServerId());
|
||||
return;
|
||||
}
|
||||
// 设置为收到ACK后发送语音的设备已经在发送200OK开始发流了
|
||||
if (!device.isBroadcastPushAfterAck()) {
|
||||
return;
|
||||
}
|
||||
if (mediaInfo == null) {
|
||||
logger.warn("[收到ACK]:来自{},目标为({})的推流信息为找到流体服务[{}]信息",fromUserId, toUserId, sendRtpItem.getMediaServerId());
|
||||
return;
|
||||
}
|
||||
Map<String, Object> param = getSendRtpParam(sendRtpItem);
|
||||
JSONObject startSendRtpStreamResult = sendRtp(sendRtpItem, mediaInfo, param);
|
||||
if (startSendRtpStreamResult != null) {
|
||||
playService.startSendRtpStreamHand(sendRtpItem, device, startSendRtpStreamResult, param, callIdHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> getSendRtpParam(SendRtpItem sendRtpItem) {
|
||||
String isUdp = sendRtpItem.isTcp() ? "0" : "1";
|
||||
Map<String, Object> param = new HashMap<>(12);
|
||||
param.put("vhost","__defaultVhost__");
|
||||
param.put("app",sendRtpItem.getApp());
|
||||
param.put("stream",sendRtpItem.getStreamId());
|
||||
param.put("stream",sendRtpItem.getStream());
|
||||
param.put("ssrc", sendRtpItem.getSsrc());
|
||||
param.put("dst_url",sendRtpItem.getIp());
|
||||
param.put("dst_port", sendRtpItem.getPort());
|
||||
param.put("is_udp", is_Udp);
|
||||
param.put("src_port", sendRtpItem.getLocalPort());
|
||||
param.put("pt", sendRtpItem.getPt());
|
||||
param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
|
||||
param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
|
||||
param.put("is_udp", isUdp);
|
||||
if (!sendRtpItem.isTcp()) {
|
||||
// 开启rtcp保活
|
||||
// udp模式下开启rtcp保活
|
||||
param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
|
||||
}
|
||||
if (mediaInfo == null) {
|
||||
RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
|
||||
sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(),
|
||||
sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
|
||||
sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
|
||||
redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, jsonObject->{
|
||||
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
|
||||
});
|
||||
return param;
|
||||
}
|
||||
|
||||
private JSONObject sendRtp(SendRtpItem sendRtpItem, MediaServerItem mediaInfo, Map<String, Object> param){
|
||||
JSONObject startSendRtpStreamResult = null;
|
||||
if (sendRtpItem.getLocalPort() != 0) {
|
||||
if (sendRtpItem.isTcpActive()) {
|
||||
startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
|
||||
}else {
|
||||
JSONObject startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
|
||||
if (startSendRtpStreamResult != null) {
|
||||
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, param, callIdHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform,
|
||||
JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) {
|
||||
if (jsonObject == null) {
|
||||
logger.error("RTP推流失败: 请检查ZLM服务");
|
||||
} else if (jsonObject.getInteger("code") == 0) {
|
||||
logger.info("调用ZLM推流接口, 结果: {}", jsonObject);
|
||||
logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
|
||||
if (sendRtpItem.getPlayType() == InviteStreamType.PUSH) {
|
||||
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, sendRtpItem.getApp(), sendRtpItem.getStreamId(),
|
||||
sendRtpItem.getChannelId(), parentPlatform.getServerGBId(), parentPlatform.getName(), userSetting.getServerId(),
|
||||
sendRtpItem.getMediaServerId());
|
||||
messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
|
||||
redisCatchStorage.sendPlatformStartPlayMsg(messageForPushChannel);
|
||||
param.put("dst_url", sendRtpItem.getIp());
|
||||
param.put("dst_port", sendRtpItem.getPort());
|
||||
startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
|
||||
}
|
||||
}else {
|
||||
logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param));
|
||||
if (sendRtpItem.isOnlyAudio()) {
|
||||
// TODO 可能是语音对讲
|
||||
if (sendRtpItem.isTcpActive()) {
|
||||
startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
|
||||
}else {
|
||||
// 向上级平台
|
||||
try {
|
||||
commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
|
||||
}
|
||||
param.put("dst_url", sendRtpItem.getIp());
|
||||
param.put("dst_port", sendRtpItem.getPort());
|
||||
startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
|
||||
}
|
||||
}
|
||||
return startSendRtpStreamResult;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,10 +5,12 @@ import com.genersoft.iot.vmp.common.InviteSessionType;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
|
||||
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
|
||||
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
|
||||
@ -45,6 +47,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
@Autowired
|
||||
private ISIPCommander cmder;
|
||||
|
||||
@Autowired
|
||||
private ISIPCommanderForPlatform commanderForPlatform;
|
||||
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@ -57,6 +62,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Autowired
|
||||
private AudioBroadcastManager audioBroadcastManager;
|
||||
|
||||
@Autowired
|
||||
private IDeviceChannelService channelService;
|
||||
|
||||
@ -78,6 +86,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
@Autowired
|
||||
private VideoStreamSessionManager streamSession;
|
||||
|
||||
@Autowired
|
||||
private IPlayService playService;
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
@ -100,18 +111,19 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
logger.error("[回复BYE信息失败],{}", e.getMessage());
|
||||
}
|
||||
CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
|
||||
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
|
||||
|
||||
// 收流端发送的停止
|
||||
if (sendRtpItem != null){
|
||||
logger.info("[收到bye] 来自平台{}, 停止通道:{}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId());
|
||||
String streamId = sendRtpItem.getStreamId();
|
||||
logger.info("[收到bye] 来自{},停止通道:{}, 类型: {}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getPlayType());
|
||||
|
||||
String streamId = sendRtpItem.getStream();
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("vhost","__defaultVhost__");
|
||||
param.put("app",sendRtpItem.getApp());
|
||||
param.put("stream",streamId);
|
||||
param.put("ssrc",sendRtpItem.getSsrc());
|
||||
logger.info("[收到bye] 停止向上级推流:{}", streamId);
|
||||
logger.info("[收到bye] 停止推流:{}", streamId);
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(),
|
||||
callIdHeader.getCallId(), null);
|
||||
@ -123,7 +135,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
|
||||
if (platform != null) {
|
||||
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
|
||||
sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
|
||||
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
|
||||
sendRtpItem.getPlatformId(), platform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
|
||||
messageForPushChannel.setPlatFormIndex(platform.getId());
|
||||
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
|
||||
@ -132,6 +144,13 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
}
|
||||
}
|
||||
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
if (audioBroadcastCatch != null && audioBroadcastCatch.getSipTransactionInfo().getCallId().equals(callIdHeader.getCallId())) {
|
||||
// 来自上级平台的停止对讲
|
||||
logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
audioBroadcastManager.del(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
}
|
||||
|
||||
int totalReaderCount = zlmServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId);
|
||||
if (totalReaderCount <= 0) {
|
||||
logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId);
|
||||
@ -149,7 +168,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
}
|
||||
}
|
||||
}
|
||||
}else {
|
||||
}
|
||||
|
||||
// 可能是设备发送的停止
|
||||
SsrcTransaction ssrcTransaction = streamSession.getSsrcTransactionByCallId(callIdHeader.getCallId());
|
||||
@ -158,6 +177,23 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
}
|
||||
logger.info("[收到bye] 来自设备:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
|
||||
|
||||
ParentPlatform platform = platformService.queryPlatformByServerGBId(ssrcTransaction.getDeviceId());
|
||||
if (platform != null ) {
|
||||
if (ssrcTransaction.getType().equals(InviteSessionType.BROADCAST)) {
|
||||
logger.info("[收到bye] 上级停止语音对讲,来自:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
|
||||
DeviceChannel channel = storager.queryChannelInParentPlatform(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
|
||||
if (channel == null) {
|
||||
logger.info("[收到bye] 未找到通道,设备:{}, 通道:{}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
|
||||
return;
|
||||
}
|
||||
String mediaServerId = ssrcTransaction.getMediaServerId();
|
||||
platformService.stopBroadcast(platform, channel, ssrcTransaction.getStream(), false,
|
||||
mediaServerService.getOne(mediaServerId));
|
||||
|
||||
playService.stopAudioBroadcast(channel.getDeviceId(), channel.getChannelId());
|
||||
}
|
||||
|
||||
}else {
|
||||
Device device = deviceService.getDevice(ssrcTransaction.getDeviceId());
|
||||
if (device == null) {
|
||||
logger.info("[收到bye] 未找到设备:{} ", ssrcTransaction.getDeviceId());
|
||||
@ -182,6 +218,19 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
|
||||
}
|
||||
streamSession.removeByCallId(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getCallId());
|
||||
if (ssrcTransaction.getType() == InviteSessionType.BROADCAST) {
|
||||
// 查找来源的对讲设备,发送停止
|
||||
Device sourceDevice = storager.queryVideoDeviceByPlatformIdAndChannelId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
|
||||
if (sourceDevice != null) {
|
||||
playService.stopAudioBroadcast(sourceDevice.getDeviceId(), channel.getChannelId());
|
||||
}
|
||||
}
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(ssrcTransaction.getDeviceId(), channel.getChannelId());
|
||||
if (audioBroadcastCatch != null) {
|
||||
// 来自上级平台的停止对讲
|
||||
logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", ssrcTransaction.getDeviceId(), channel.getChannelId());
|
||||
audioBroadcastManager.del(ssrcTransaction.getDeviceId(), channel.getChannelId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,14 +2,19 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.InviteSessionType;
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.common.VideoManagerConstants;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
|
||||
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
|
||||
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
@ -65,13 +70,14 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
private final String method = "INVITE";
|
||||
|
||||
@Autowired
|
||||
private SIPCommanderFroPlatform cmderFroPlatform;
|
||||
private ISIPCommanderForPlatform cmderFroPlatform;
|
||||
|
||||
@Autowired
|
||||
private IVideoManagerStorage storager;
|
||||
|
||||
@Autowired
|
||||
private IStreamPushService streamPushService;
|
||||
|
||||
@Autowired
|
||||
private IStreamProxyService streamProxyService;
|
||||
|
||||
@ -96,6 +102,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
@Autowired
|
||||
private SIPSender sipSender;
|
||||
|
||||
@Autowired
|
||||
private AudioBroadcastManager audioBroadcastManager;
|
||||
|
||||
@Autowired
|
||||
private ZLMServerFactory zlmServerFactory;
|
||||
|
||||
@ -114,10 +123,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
@Autowired
|
||||
private ZLMMediaListManager mediaListManager;
|
||||
|
||||
@Autowired
|
||||
private SipConfig config;
|
||||
|
||||
|
||||
@Autowired
|
||||
private RedisGbPlayMsgListener redisGbPlayMsgListener;
|
||||
|
||||
@Autowired
|
||||
private VideoStreamSessionManager streamSession;
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
@ -168,7 +183,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
// 查询请求是否来自上级平台\设备
|
||||
ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
|
||||
if (platform == null) {
|
||||
inviteFromDeviceHandle(request, requesterId);
|
||||
inviteFromDeviceHandle(request, requesterId, channelId);
|
||||
|
||||
} else {
|
||||
// 查询平台下是否有该通道
|
||||
@ -440,7 +455,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
Map<String, Object> param = new HashMap<>(12);
|
||||
param.put("vhost","__defaultVhost__");
|
||||
param.put("app",sendRtpItem.getApp());
|
||||
param.put("stream",sendRtpItem.getStreamId());
|
||||
param.put("stream",sendRtpItem.getStream());
|
||||
param.put("ssrc", sendRtpItem.getSsrc());
|
||||
if (!sendRtpItem.isTcpActive()) {
|
||||
param.put("dst_url",sendRtpItem.getIp());
|
||||
@ -456,7 +471,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
// 开启rtcp保活
|
||||
param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
|
||||
}
|
||||
JSONObject startSendRtpStreamResult = zlmServerFactory.startSendRtpStreamForPassive(mediaInfo, param);
|
||||
JSONObject startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
|
||||
if (startSendRtpStreamResult != null) {
|
||||
startSendRtpStreamHand(evt, sendRtpItem, null, startSendRtpStreamResult, param, callIdHeader);
|
||||
}
|
||||
@ -482,8 +497,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
String startTimeStr = DateUtil.urlFormatter.format(start);
|
||||
String endTimeStr = DateUtil.urlFormatter.format(end);
|
||||
String stream = device.getDeviceId() + "_" + channelId + "_" + startTimeStr + "_" + endTimeStr;
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam());
|
||||
sendRtpItem.setStreamId(ssrcInfo.getStream());
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, stream, null, device.isSsrcCheck(), true, 0,false, false, device.getStreamModeForParam());
|
||||
// 写入redis, 超时时回复
|
||||
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
||||
playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start),
|
||||
@ -512,8 +526,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
}
|
||||
|
||||
sendRtpItem.setPlayType(InviteStreamType.DOWNLOAD);
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam());
|
||||
sendRtpItem.setStreamId(ssrcInfo.getStream());
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam());
|
||||
sendRtpItem.setStream(ssrcInfo.getStream());
|
||||
// 写入redis, 超时时回复
|
||||
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
||||
playService.download(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start),
|
||||
@ -532,7 +546,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
} else {
|
||||
sendRtpItem.setPlayType(InviteStreamType.PLAY);
|
||||
String streamId = String.format("%s_%s", device.getDeviceId(), channelId);
|
||||
sendRtpItem.setStreamId(streamId);
|
||||
sendRtpItem.setStream(streamId);
|
||||
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
||||
SSRCInfo ssrcInfo = playService.play(mediaServerItem, device.getDeviceId(), channelId, ssrc, ((code, msg, data) -> {
|
||||
if (code == InviteErrorCode.SUCCESS.getCode()) {
|
||||
@ -644,6 +658,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void pushStream(RequestEvent evt, SIPRequest request, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform,
|
||||
CallIdHeader callIdHeader, MediaServerItem mediaServerItem,
|
||||
int port, Boolean tcpActive, boolean mediaTransmissionTCP,
|
||||
@ -678,7 +693,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
if (response != null) {
|
||||
sendRtpItem.setToTag(response.getToTag());
|
||||
}
|
||||
|
||||
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
||||
|
||||
} else {
|
||||
@ -693,6 +707,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知流上线
|
||||
*/
|
||||
@ -893,8 +908,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
content.append("t=0 0\r\n");
|
||||
// 非严格模式端口不统一, 增加兼容性,修改为一个不为0的端口
|
||||
int localPort = sendRtpItem.getLocalPort();
|
||||
if(localPort == 0)
|
||||
{
|
||||
if (localPort == 0) {
|
||||
localPort = new Random().nextInt(65535) + 1;
|
||||
}
|
||||
content.append("m=video " + localPort + " RTP/AVP 96\r\n");
|
||||
@ -913,39 +927,75 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
|
||||
try {
|
||||
return responseSdpAck(request, content.toString(), platform);
|
||||
} catch (SipException e) {
|
||||
logger.error("未处理的异常 ", e);
|
||||
} catch (InvalidArgumentException e) {
|
||||
logger.error("未处理的异常 ", e);
|
||||
} catch (ParseException e) {
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("未处理的异常 ", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void inviteFromDeviceHandle(SIPRequest request, String requesterId) {
|
||||
public void inviteFromDeviceHandle(SIPRequest request, String requesterId, String channelId) {
|
||||
|
||||
String realChannelId = null;
|
||||
|
||||
// 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备)
|
||||
Device device = redisCatchStorage.getDevice(requesterId);
|
||||
// 判断requesterId是设备还是通道
|
||||
if (device == null) {
|
||||
device = storager.queryVideoDeviceByChannelId(requesterId);
|
||||
realChannelId = requesterId;
|
||||
}else {
|
||||
realChannelId = channelId;
|
||||
}
|
||||
if (device == null) {
|
||||
// 检查channelID是否可用
|
||||
device = redisCatchStorage.getDevice(channelId);
|
||||
if (device == null) {
|
||||
device = storager.queryVideoDeviceByChannelId(channelId);
|
||||
realChannelId = channelId;
|
||||
}
|
||||
}
|
||||
|
||||
if (device == null) {
|
||||
logger.warn("来自设备的Invite请求,无法从请求信息中确定所属设备,已忽略,requesterId: {}/{}", requesterId, channelId);
|
||||
try {
|
||||
responseAck(request, Response.FORBIDDEN);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] 来自设备的Invite请求,无法从请求信息中确定所属设备 FORBIDDEN: {}", e.getMessage());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(device.getDeviceId(), realChannelId);
|
||||
if (broadcastCatch == null) {
|
||||
logger.warn("来自设备的Invite请求非语音广播,已忽略,requesterId: {}/{}", requesterId, channelId);
|
||||
try {
|
||||
responseAck(request, Response.FORBIDDEN);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] 来自设备的Invite请求非语音广播 FORBIDDEN: {}", e.getMessage());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (device != null) {
|
||||
logger.info("收到设备" + requesterId + "的语音广播Invite请求");
|
||||
String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + broadcastCatch.getChannelId();
|
||||
dynamicTask.stop(key);
|
||||
try {
|
||||
responseAck(request, Response.TRYING);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] invite BAD_REQUEST: {}", e.getMessage());
|
||||
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
|
||||
return;
|
||||
}
|
||||
String contentString = new String(request.getRawContent());
|
||||
// jainSip不支持y=字段, 移除移除以解析。
|
||||
String ssrc = "0000000404";
|
||||
|
||||
try {
|
||||
Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
|
||||
SessionDescription sdp = gb28181Sdp.getBaseSdb();
|
||||
// 获取支持的格式
|
||||
Vector mediaDescriptions = sdp.getMediaDescriptions(true);
|
||||
|
||||
// 查看是否支持PS 负载96
|
||||
int port = -1;
|
||||
//boolean recvonly = false;
|
||||
boolean mediaTransmissionTCP = false;
|
||||
Boolean tcpActive = null;
|
||||
for (int i = 0; i < mediaDescriptions.size(); i++) {
|
||||
@ -977,26 +1027,147 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
||||
try {
|
||||
responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] invite 不支持的媒体格式,返回415, {}", e.getMessage());
|
||||
logger.error("[命令发送失败] invite 不支持的媒体格式: {}", e.getMessage());
|
||||
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
String username = sdp.getOrigin().getUsername();
|
||||
String addressStr = sdp.getConnection().getAddress();
|
||||
logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc);
|
||||
} catch (SdpException e) {
|
||||
logger.error("[SDP解析异常]", e);
|
||||
String addressStr = sdp.getOrigin().getAddress();
|
||||
logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}, {}", requesterId, addressStr, port, gb28181Sdp.getSsrc(),
|
||||
mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP");
|
||||
|
||||
MediaServerItem mediaServerItem = broadcastCatch.getMediaServerItem();
|
||||
if (mediaServerItem == null) {
|
||||
logger.warn("未找到语音喊话使用的zlm");
|
||||
try {
|
||||
responseAck(request, Response.BUSY_HERE);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] invite 未找到可用的zlm: {}", e.getMessage());
|
||||
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
logger.info("设备{}请求语音流, 收流地址:{}:{},ssrc:{}, {}, 对讲方式:{}", requesterId, addressStr, port, gb28181Sdp.getSsrc(),
|
||||
mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP", sdp.getSessionName().getValue());
|
||||
CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
|
||||
|
||||
SendRtpItem sendRtpItem = zlmServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, gb28181Sdp.getSsrc(), requesterId,
|
||||
device.getDeviceId(), broadcastCatch.getChannelId(),
|
||||
mediaTransmissionTCP, false);
|
||||
|
||||
if (sendRtpItem == null) {
|
||||
logger.warn("服务器端口资源不足");
|
||||
try {
|
||||
responseAck(request, Response.BUSY_HERE);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage());
|
||||
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
sendRtpItem.setPlayType(InviteStreamType.BROADCAST);
|
||||
sendRtpItem.setCallId(callIdHeader.getCallId());
|
||||
sendRtpItem.setPlatformId(requesterId);
|
||||
sendRtpItem.setStatus(1);
|
||||
sendRtpItem.setApp(broadcastCatch.getApp());
|
||||
sendRtpItem.setStream(broadcastCatch.getStream());
|
||||
sendRtpItem.setPt(8);
|
||||
sendRtpItem.setUsePs(false);
|
||||
sendRtpItem.setRtcp(false);
|
||||
sendRtpItem.setOnlyAudio(true);
|
||||
sendRtpItem.setTcp(mediaTransmissionTCP);
|
||||
if (tcpActive != null) {
|
||||
sendRtpItem.setTcpActive(tcpActive);
|
||||
}
|
||||
|
||||
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
||||
|
||||
Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, broadcastCatch.getApp(), broadcastCatch.getStream());
|
||||
if (streamReady) {
|
||||
sendOk(device, sendRtpItem, sdp, request, mediaServerItem, mediaTransmissionTCP, gb28181Sdp.getSsrc());
|
||||
} else {
|
||||
logger.warn("[语音通话], 未发现待推送的流,app={},stream={}", broadcastCatch.getApp(), broadcastCatch.getStream());
|
||||
try {
|
||||
responseAck(request, Response.GONE);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] 语音通话 回复410失败, {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
|
||||
}
|
||||
} catch (SdpException e) {
|
||||
logger.error("[SDP解析异常]", e);
|
||||
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
|
||||
}
|
||||
} else {
|
||||
logger.warn("来自无效设备/平台的请求");
|
||||
try {
|
||||
responseAck(request, Response.BAD_REQUEST);; // 不支持的格式,发415
|
||||
responseAck(request, Response.BAD_REQUEST);
|
||||
; // 不支持的格式,发415
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] invite 来自无效设备/平台的请求, {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SIPResponse sendOk(Device device, SendRtpItem sendRtpItem, SessionDescription sdp, SIPRequest request, MediaServerItem mediaServerItem, boolean mediaTransmissionTCP, String ssrc) {
|
||||
SIPResponse sipResponse = null;
|
||||
try {
|
||||
sendRtpItem.setStatus(2);
|
||||
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
||||
StringBuffer content = new StringBuffer(200);
|
||||
content.append("v=0\r\n");
|
||||
content.append("o=" + config.getId() + " " + sdp.getOrigin().getSessionId() + " " + sdp.getOrigin().getSessionVersion() + " IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
|
||||
content.append("s=Play\r\n");
|
||||
content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
|
||||
content.append("t=0 0\r\n");
|
||||
|
||||
if (mediaTransmissionTCP) {
|
||||
content.append("m=audio " + sendRtpItem.getLocalPort() + " TCP/RTP/AVP 8\r\n");
|
||||
} else {
|
||||
content.append("m=audio " + sendRtpItem.getLocalPort() + " RTP/AVP 8\r\n");
|
||||
}
|
||||
|
||||
content.append("a=rtpmap:8 PCMA/8000/1\r\n");
|
||||
|
||||
content.append("a=sendonly\r\n");
|
||||
if (sendRtpItem.isTcp()) {
|
||||
content.append("a=connection:new\r\n");
|
||||
if (!sendRtpItem.isTcpActive()) {
|
||||
content.append("a=setup:active\r\n");
|
||||
} else {
|
||||
content.append("a=setup:passive\r\n");
|
||||
}
|
||||
}
|
||||
content.append("y=" + ssrc + "\r\n");
|
||||
content.append("f=v/////a/1/8/1\r\n");
|
||||
|
||||
ParentPlatform parentPlatform = new ParentPlatform();
|
||||
parentPlatform.setServerIP(device.getIp());
|
||||
parentPlatform.setServerPort(device.getPort());
|
||||
parentPlatform.setServerGBId(device.getDeviceId());
|
||||
|
||||
sipResponse = responseSdpAck(request, content.toString(), parentPlatform);
|
||||
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), sendRtpItem.getChannelId());
|
||||
|
||||
audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.Ok);
|
||||
audioBroadcastCatch.setSipTransactionInfoByRequset(sipResponse);
|
||||
audioBroadcastManager.update(audioBroadcastCatch);
|
||||
streamSession.put(device.getDeviceId(), sendRtpItem.getChannelId(), request.getCallIdHeader().getCallId(), sendRtpItem.getStream(), sendRtpItem.getSsrc(), sendRtpItem.getMediaServerId(), sipResponse, InviteSessionType.BROADCAST);
|
||||
// 开启发流,大华在收到200OK后就会开始建立连接
|
||||
if (!device.isBroadcastPushAfterAck()) {
|
||||
logger.info("[语音喊话] 回复200OK后发现 BroadcastPushAfterAck为False,现在开始推流");
|
||||
playService.startPushStream(sendRtpItem, sipResponse, parentPlatform, request.getCallIdHeader());
|
||||
}
|
||||
|
||||
} catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) {
|
||||
logger.error("[命令发送失败] 语音喊话 回复200OK(SDP): {}", e.getMessage());
|
||||
}
|
||||
return sipResponse;
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +27,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.sip.*;
|
||||
import javax.sip.header.*;
|
||||
import javax.sip.message.Request;
|
||||
import javax.sip.RequestEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.header.AuthorizationHeader;
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||
|
||||
import com.genersoft.iot.vmp.common.VideoManagerConstants;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.CmdType;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
|
||||
|
||||
@ -109,7 +109,7 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I
|
||||
String contentSubType = header.getContentSubType();
|
||||
if ("Application".equalsIgnoreCase(contentType) && "MANSRTSP".equalsIgnoreCase(contentSubType)) {
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId());
|
||||
String streamId = sendRtpItem.getStreamId();
|
||||
String streamId = sendRtpItem.getStream();
|
||||
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId);
|
||||
if (null == inviteInfo) {
|
||||
responseAck(request, Response.NOT_FOUND, "stream " + streamId + " not found");
|
||||
|
||||
@ -5,10 +5,17 @@ import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd.CatalogQueryMessageHandler;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import org.dom4j.Element;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.RequestEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.message.Response;
|
||||
import java.text.ParseException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@ -16,6 +23,8 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
|
||||
|
||||
public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent implements IMessageHandler{
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MessageHandlerAbstract.class);
|
||||
|
||||
public Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
@ -28,6 +37,14 @@ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent i
|
||||
@Override
|
||||
public void handForDevice(RequestEvent evt, Device device, Element element) {
|
||||
String cmd = getText(element, "CmdType");
|
||||
if (cmd == null) {
|
||||
try {
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.OK);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] 回复200 OK: {}", e.getMessage());
|
||||
}
|
||||
return;
|
||||
}
|
||||
IMessageHandler messageHandler = messageHandlerMap.get(cmd);
|
||||
|
||||
if (messageHandler != null) {
|
||||
|
||||
@ -0,0 +1,197 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.NotifyMessageHandler;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
|
||||
import com.genersoft.iot.vmp.service.IDeviceService;
|
||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.service.IPlatformService;
|
||||
import com.genersoft.iot.vmp.service.IPlayService;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import org.dom4j.Element;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.RequestEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.message.Response;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* 状态信息(心跳)报送
|
||||
*/
|
||||
@Component
|
||||
public class BroadcastNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(BroadcastNotifyMessageHandler.class);
|
||||
private final static String cmdType = "Broadcast";
|
||||
|
||||
@Autowired
|
||||
private NotifyMessageHandler notifyMessageHandler;
|
||||
|
||||
@Autowired
|
||||
private IVideoManagerStorage storage;
|
||||
|
||||
@Autowired
|
||||
private ISIPCommanderForPlatform commanderForPlatform;
|
||||
|
||||
@Autowired
|
||||
private IMediaServerService mediaServerService;
|
||||
|
||||
@Autowired
|
||||
private IPlayService playService;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Autowired
|
||||
private IPlatformService platformService;
|
||||
|
||||
@Autowired
|
||||
private AudioBroadcastManager audioBroadcastManager;
|
||||
|
||||
@Autowired
|
||||
private ZLMServerFactory zlmServerFactory;
|
||||
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
notifyMessageHandler.addHandler(cmdType, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handForDevice(RequestEvent evt, Device device, Element element) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handForPlatform(RequestEvent evt, ParentPlatform platform, Element rootElement) {
|
||||
// 来自上级平台的语音喊话请求
|
||||
SIPRequest request = (SIPRequest) evt.getRequest();
|
||||
try {
|
||||
Element snElement = rootElement.element("SN");
|
||||
if (snElement == null) {
|
||||
responseAck(request, Response.BAD_REQUEST, "sn must not null");
|
||||
return;
|
||||
}
|
||||
String sn = snElement.getText();
|
||||
Element targetIDElement = rootElement.element("TargetID");
|
||||
if (targetIDElement == null) {
|
||||
responseAck(request, Response.BAD_REQUEST, "TargetID must not null");
|
||||
return;
|
||||
}
|
||||
String targetId = targetIDElement.getText();
|
||||
|
||||
|
||||
logger.info("[国标级联 语音喊话] platform: {}, channel: {}", platform.getServerGBId(), targetId);
|
||||
|
||||
DeviceChannel deviceChannel = storage.queryChannelInParentPlatform(platform.getServerGBId(), targetId);
|
||||
if (deviceChannel == null) {
|
||||
logger.warn("[国标级联 语音喊话] 未找到通道 platform: {}, channel: {}", platform.getServerGBId(), targetId);
|
||||
responseAck(request, Response.NOT_FOUND, "TargetID not found");
|
||||
return;
|
||||
}
|
||||
// 向下级发送语音的喊话请求
|
||||
Device device = deviceService.getDevice(deviceChannel.getDeviceId());
|
||||
if (device == null) {
|
||||
responseAck(request, Response.NOT_FOUND, "device not found");
|
||||
return;
|
||||
}
|
||||
responseAck(request, Response.OK);
|
||||
|
||||
// 查看语音通道是否已经建立并且已经在使用
|
||||
if (playService.audioBroadcastInUse(device, targetId)) {
|
||||
commanderForPlatform.broadcastResultCmd(platform, deviceChannel, sn, false,null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
MediaServerItem mediaServerForMinimumLoad = mediaServerService.getMediaServerForMinimumLoad(null);
|
||||
commanderForPlatform.broadcastResultCmd(platform, deviceChannel, sn, true, eventResult->{
|
||||
logger.info("[国标级联] 语音喊话 回复失败 platform: {}, 错误:{}/{}", platform.getServerGBId(), eventResult.statusCode, eventResult.msg);
|
||||
}, eventResult->{
|
||||
|
||||
// 消息发送成功, 向上级发送invite,获取推流
|
||||
try {
|
||||
platformService.broadcastInvite(platform, deviceChannel.getChannelId(), mediaServerForMinimumLoad, (mediaServerItem, hookParam)->{
|
||||
OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam)hookParam;
|
||||
// 上级平台推流成功
|
||||
AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(device.getDeviceId(), targetId);
|
||||
if (broadcastCatch != null ) {
|
||||
if (playService.audioBroadcastInUse(device, targetId)) {
|
||||
logger.info("[国标级联] 语音喊话 设备正在使用中 platform: {}, channel: {}",
|
||||
platform.getServerGBId(), deviceChannel.getChannelId());
|
||||
// 查看语音通道已经建立且已经占用 回复BYE
|
||||
platformService.stopBroadcast(platform, deviceChannel, streamChangedHookParam.getStream(), true, mediaServerItem);
|
||||
}else {
|
||||
// 查看语音通道已经建立但是未占用
|
||||
broadcastCatch.setApp(streamChangedHookParam.getApp());
|
||||
broadcastCatch.setStream(streamChangedHookParam.getStream());
|
||||
broadcastCatch.setMediaServerItem(mediaServerItem);
|
||||
audioBroadcastManager.update(broadcastCatch);
|
||||
// 推流到设备
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, targetId, streamChangedHookParam.getStream(), null);
|
||||
if (sendRtpItem == null) {
|
||||
logger.warn("[国标级联] 语音喊话 异常,未找到发流信息, channelId: {}, stream: {}", targetId, streamChangedHookParam.getStream());
|
||||
logger.info("[国标级联] 语音喊话 重新开始,channelId: {}, stream: {}", targetId, streamChangedHookParam.getStream());
|
||||
try {
|
||||
playService.audioBroadcastCmd(device, targetId, mediaServerItem, streamChangedHookParam.getApp(), streamChangedHookParam.getStream(), 60, true, msg -> {
|
||||
logger.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId);
|
||||
});
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
|
||||
}
|
||||
}else {
|
||||
// 发流
|
||||
JSONObject jsonObject = zlmServerFactory.startSendRtp(mediaServerItem, sendRtpItem);
|
||||
if (jsonObject != null && jsonObject.getInteger("code") == 0 ) {
|
||||
logger.info("[语音喊话] 自动推流成功, device: {}, channel: {}", device.getDeviceId(), targetId);
|
||||
}else {
|
||||
logger.info("[语音喊话] 推流失败, 结果: {}", jsonObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else {
|
||||
try {
|
||||
playService.audioBroadcastCmd(device, targetId, mediaServerItem, streamChangedHookParam.getApp(), streamChangedHookParam.getStream(), 60, true, msg -> {
|
||||
logger.info("[语音喊话] 通道建立成功, device: {}, channel: {}", device.getDeviceId(), targetId);
|
||||
});
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
|
||||
}
|
||||
}
|
||||
|
||||
}, eventResultForBroadcastInvite -> {
|
||||
// 收到错误
|
||||
logger.info("[国标级联-语音喊话] 与下级通道建立失败 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(),
|
||||
targetId, eventResultForBroadcastInvite.statusCode, eventResultForBroadcastInvite.msg);
|
||||
}, (code, msg)->{
|
||||
// 超时
|
||||
logger.info("[国标级联-语音喊话] 与下级通道建立超时 device: {}, channel: {}, 错误:{}/{}", device.getDeviceId(),
|
||||
targetId, code, msg);
|
||||
});
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.info("[消息发送失败] 国标级联 语音喊话 invite消息 platform: {}", platform.getServerGBId());
|
||||
}
|
||||
});
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.info("[消息发送失败] 国标级联 语音喊话 platform: {}", platform.getServerGBId());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -102,7 +102,7 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
|
||||
|
||||
try {
|
||||
cmder.streamByeCmd(device, ssrcTransaction.getChannelId(), null, callIdHeader.getCallId());
|
||||
} catch (InvalidArgumentException | ParseException | SsrcTransactionNotFoundException | SipException e) {
|
||||
} catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
|
||||
logger.error("[录像流]推送完毕,收到关流通知, 发送BYE失败 {}", e.getMessage());
|
||||
}
|
||||
// 去除监听流注销自动停止下载的监听
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.VideoManagerConstants;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.XmlUtil;
|
||||
import com.genersoft.iot.vmp.service.IPlayService;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import org.dom4j.Element;
|
||||
import org.slf4j.Logger;
|
||||
@ -35,7 +38,13 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
|
||||
private ResponseMessageHandler responseMessageHandler;
|
||||
|
||||
@Autowired
|
||||
private DeferredResultHolder deferredResultHolder;
|
||||
private DynamicTask dynamicTask;
|
||||
|
||||
@Autowired
|
||||
private AudioBroadcastManager audioBroadcastManager;
|
||||
|
||||
@Autowired
|
||||
private IPlayService playService;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
@ -44,23 +53,38 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
|
||||
|
||||
@Override
|
||||
public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
|
||||
|
||||
SIPRequest request = (SIPRequest) evt.getRequest();
|
||||
try {
|
||||
String channelId = getText(rootElement, "DeviceID");
|
||||
String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId() + channelId;
|
||||
// 回复200 OK
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.OK);
|
||||
// 此处是对本平台发出Broadcast指令的应答
|
||||
JSONObject json = new JSONObject();
|
||||
XmlUtil.node2Json(rootElement, json);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(json.toJSONString());
|
||||
if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
|
||||
// 回复410
|
||||
responseAck((SIPRequest) evt.getRequest(), Response.GONE);
|
||||
return;
|
||||
}
|
||||
RequestMessage msg = new RequestMessage();
|
||||
msg.setKey(key);
|
||||
msg.setData(json);
|
||||
deferredResultHolder.invokeAllResult(msg);
|
||||
|
||||
String result = getText(rootElement, "Result");
|
||||
Element infoElement = rootElement.element("Info");
|
||||
String reason = null;
|
||||
if (infoElement != null) {
|
||||
reason = getText(infoElement, "Reason");
|
||||
}
|
||||
logger.info("[语音广播]回复:{}, {}/{}", reason == null? result : result + ": " + reason, device.getDeviceId(), channelId );
|
||||
|
||||
// 回复200 OK
|
||||
responseAck(request, Response.OK);
|
||||
if (result.equalsIgnoreCase("OK")) {
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId);
|
||||
audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite);
|
||||
audioBroadcastManager.update(audioBroadcastCatch);
|
||||
// 等待invite消息, 超时则结束
|
||||
String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + channelId;
|
||||
dynamicTask.startDelay(key, ()->{
|
||||
logger.info("[语音广播]等待invite消息超时:{}/{}", device.getDeviceId(), channelId);
|
||||
playService.stopAudioBroadcast(device.getDeviceId(), channelId);
|
||||
}, 2000);
|
||||
}else {
|
||||
playService.stopAudioBroadcast(device.getDeviceId(), channelId);
|
||||
}
|
||||
} catch (ParseException | SipException | InvalidArgumentException e) {
|
||||
logger.error("[命令发送失败] 国标级联 语音喊话: {}", e.getMessage());
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class);
|
||||
|
||||
private final String cmdType = "Catalog";
|
||||
|
||||
@Autowired
|
||||
|
||||
@ -19,6 +19,9 @@ import javax.sdp.SessionDescription;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.ResponseEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.ResponseEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.SipFactory;
|
||||
import javax.sip.address.SipURI;
|
||||
import javax.sip.message.Request;
|
||||
|
||||
@ -140,6 +140,26 @@ public class SipUtils {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String getNewCallId() {
|
||||
return (int) Math.floor(Math.random() * 1000000000) + "";
|
||||
}
|
||||
|
||||
public static int getTypeCodeFromGbCode(String deviceId) {
|
||||
if (ObjectUtils.isEmpty(deviceId)) {
|
||||
return 0;
|
||||
}
|
||||
return Integer.parseInt(deviceId.substring(10, 13));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是前端外围设备
|
||||
* @param deviceId
|
||||
* @return
|
||||
*/
|
||||
public static boolean isFrontEnd(String deviceId) {
|
||||
int typeCodeFromGbCode = getTypeCodeFromGbCode(deviceId);
|
||||
return typeCodeFromGbCode > 130 && typeCodeFromGbCode < 199;
|
||||
}
|
||||
/**
|
||||
* 从请求中获取设备ip地址和端口号
|
||||
* @param request 请求
|
||||
|
||||
@ -10,13 +10,14 @@ import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
|
||||
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
|
||||
import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
|
||||
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
|
||||
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookType;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
|
||||
@ -66,7 +67,13 @@ public class ZLMHttpHookListener {
|
||||
private SIPCommander cmder;
|
||||
|
||||
@Autowired
|
||||
private SIPCommanderFroPlatform commanderFroPlatform;
|
||||
private ISIPCommanderForPlatform commanderFroPlatform;
|
||||
|
||||
@Autowired
|
||||
private AudioBroadcastManager audioBroadcastManager;
|
||||
|
||||
@Autowired
|
||||
private ZLMServerFactory zlmServerFactory;
|
||||
|
||||
@Autowired
|
||||
private IPlayService playService;
|
||||
@ -306,8 +313,16 @@ public class ZLMHttpHookListener {
|
||||
result.setModify_stamp(2);
|
||||
}
|
||||
}
|
||||
// 如果是talk对讲,则默认获取声音
|
||||
if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.TALK) {
|
||||
result.setEnable_audio(true);
|
||||
}
|
||||
}
|
||||
} else if (param.getApp().equals("broadcast")) {
|
||||
result.setEnable_audio(true);
|
||||
} else if (param.getApp().equals("talk")) {
|
||||
result.setEnable_audio(true);
|
||||
}
|
||||
if (param.getApp().equalsIgnoreCase("rtp")) {
|
||||
String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + param.getStream();
|
||||
OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo) redisTemplate.opsForValue().get(receiveKey);
|
||||
@ -336,7 +351,6 @@ public class ZLMHttpHookListener {
|
||||
logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream());
|
||||
}
|
||||
|
||||
|
||||
JSONObject json = (JSONObject) JSON.toJSON(param);
|
||||
taskExecutor.execute(() -> {
|
||||
ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json);
|
||||
@ -366,15 +380,14 @@ public class ZLMHttpHookListener {
|
||||
redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if ("rtsp".equals(param.getSchema())) {
|
||||
// 更新流媒体负载信息
|
||||
logger.info("流变化:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream());
|
||||
if (param.isRegist()) {
|
||||
mediaServerService.addCount(param.getMediaServerId());
|
||||
} else {
|
||||
mediaServerService.removeCount(param.getMediaServerId());
|
||||
}
|
||||
// 设置拉流代理上线/离线
|
||||
|
||||
int updateStatusResult = streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream());
|
||||
if (updateStatusResult > 0) {
|
||||
|
||||
@ -386,6 +399,63 @@ public class ZLMHttpHookListener {
|
||||
inviteStreamService.removeInviteInfo(inviteInfo);
|
||||
storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
|
||||
}
|
||||
} else if ("broadcast".equals(param.getApp())) {
|
||||
// 语音对讲推流 stream需要满足格式deviceId_channelId
|
||||
if (param.getStream().indexOf("_") > 0) {
|
||||
String[] streamArray = param.getStream().split("_");
|
||||
if (streamArray.length == 2) {
|
||||
String deviceId = streamArray[0];
|
||||
String channelId = streamArray[1];
|
||||
Device device = deviceService.getDevice(deviceId);
|
||||
if (device != null) {
|
||||
if (param.isRegist()) {
|
||||
if (audioBroadcastManager.exit(deviceId, channelId)) {
|
||||
playService.stopAudioBroadcast(deviceId, channelId);
|
||||
}
|
||||
// 开启语音对讲通道
|
||||
try {
|
||||
playService.audioBroadcastCmd(device, channelId, mediaInfo, param.getApp(), param.getStream(), 60, false, (msg) -> {
|
||||
logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId);
|
||||
});
|
||||
} catch (InvalidArgumentException | ParseException | SipException e) {
|
||||
logger.error("[命令发送失败] 语音对讲: {}", e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// 流注销
|
||||
playService.stopAudioBroadcast(deviceId, channelId);
|
||||
}
|
||||
} else {
|
||||
logger.info("[语音对讲] 未找到设备:{}", deviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("talk".equals(param.getApp())) {
|
||||
// 语音对讲推流 stream需要满足格式deviceId_channelId
|
||||
if (param.getStream().indexOf("_") > 0) {
|
||||
String[] streamArray = param.getStream().split("_");
|
||||
if (streamArray.length == 2) {
|
||||
String deviceId = streamArray[0];
|
||||
String channelId = streamArray[1];
|
||||
Device device = deviceService.getDevice(deviceId);
|
||||
if (device != null) {
|
||||
if (param.isRegist()) {
|
||||
if (audioBroadcastManager.exit(deviceId, channelId)) {
|
||||
playService.stopAudioBroadcast(deviceId, channelId);
|
||||
}
|
||||
// 开启语音对讲通道
|
||||
playService.talkCmd(device, channelId, mediaInfo, param.getStream(), (msg) -> {
|
||||
logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId);
|
||||
});
|
||||
} else {
|
||||
// 流注销
|
||||
playService.stopTalk(device, channelId, param.isRegist());
|
||||
}
|
||||
} else {
|
||||
logger.info("[语音对讲] 未找到设备:{}", deviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!"rtp".equals(param.getApp())) {
|
||||
String type = OriginType.values()[param.getOriginType()].getType();
|
||||
@ -458,13 +528,22 @@ public class ZLMHttpHookListener {
|
||||
if (platform != null) {
|
||||
commanderFroPlatform.streamByeCmd(platform, sendRtpItem);
|
||||
redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(),
|
||||
sendRtpItem.getCallId(), sendRtpItem.getStreamId());
|
||||
sendRtpItem.getCallId(), sendRtpItem.getStream());
|
||||
} else {
|
||||
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId());
|
||||
if (sendRtpItem.getPlayType().equals(InviteStreamType.BROADCAST)
|
||||
|| sendRtpItem.getPlayType().equals(InviteStreamType.TALK)) {
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
if (audioBroadcastCatch != null) {
|
||||
// 来自上级平台的停止对讲
|
||||
logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
audioBroadcastManager.del(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SipException | InvalidArgumentException | ParseException |
|
||||
SsrcTransactionNotFoundException e) {
|
||||
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
|
||||
logger.error("[命令发送失败] 发送BYE: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -472,7 +551,6 @@ public class ZLMHttpHookListener {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return HookResult.SUCCESS();
|
||||
}
|
||||
|
||||
@ -512,10 +590,10 @@ public class ZLMHttpHookListener {
|
||||
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
|
||||
}
|
||||
redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
|
||||
sendRtpItem.getCallId(), sendRtpItem.getStreamId());
|
||||
sendRtpItem.getCallId(), sendRtpItem.getStream());
|
||||
if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
|
||||
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
|
||||
sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
|
||||
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
|
||||
sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
|
||||
messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
|
||||
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
|
||||
@ -548,6 +626,13 @@ public class ZLMHttpHookListener {
|
||||
storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
|
||||
return ret;
|
||||
}
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, param.getStream(), null);
|
||||
if (sendRtpItem != null && "talk".equals(sendRtpItem.getApp())) {
|
||||
ret.put("close", false);
|
||||
return ret;
|
||||
}
|
||||
} else if ("talk".equals(param.getApp()) || "broadcast".equals(param.getApp())) {
|
||||
ret.put("close", false);
|
||||
} else {
|
||||
// 非国标流 推流/拉流代理
|
||||
// 拉流代理
|
||||
@ -676,7 +761,7 @@ public class ZLMHttpHookListener {
|
||||
|
||||
if (!exist) {
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaInfo, param.getStream(), null,
|
||||
device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam());
|
||||
device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam());
|
||||
playService.playBack(mediaInfo, ssrcInfo, deviceId, channelId, startTime, endTime, (code, message, data) -> {
|
||||
msg.setData(new HookResult(code, message));
|
||||
resultHolder.invokeResult(msg);
|
||||
@ -749,7 +834,7 @@ public class ZLMHttpHookListener {
|
||||
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
|
||||
}
|
||||
redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
|
||||
sendRtpItem.getCallId(), sendRtpItem.getStreamId());
|
||||
sendRtpItem.getCallId(), sendRtpItem.getStream());
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -762,7 +847,8 @@ public class ZLMHttpHookListener {
|
||||
*/
|
||||
@ResponseBody
|
||||
@PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8")
|
||||
public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param) {
|
||||
public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam
|
||||
param) {
|
||||
logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc());
|
||||
|
||||
taskExecutor.execute(() -> {
|
||||
|
||||
@ -96,6 +96,7 @@ public class ZLMRESTfulUtils {
|
||||
if (callback == null) {
|
||||
try {
|
||||
Response response = client.newCall(request).execute();
|
||||
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody != null) {
|
||||
@ -103,6 +104,8 @@ public class ZLMRESTfulUtils {
|
||||
responseJSON = JSON.parseObject(responseStr);
|
||||
}
|
||||
}else {
|
||||
System.out.println( 2222);
|
||||
System.out.println( response.code());
|
||||
response.close();
|
||||
Objects.requireNonNull(response.body()).close();
|
||||
}
|
||||
@ -111,11 +114,11 @@ public class ZLMRESTfulUtils {
|
||||
|
||||
if(e instanceof SocketTimeoutException){
|
||||
//读取超时超时异常
|
||||
logger.error(String.format("读取ZLM数据失败: %s, %s", url, e.getMessage()));
|
||||
logger.error(String.format("读取ZLM数据超时失败: %s, %s", url, e.getMessage()));
|
||||
}
|
||||
if(e instanceof ConnectException){
|
||||
//判断连接异常,我这里是报Failed to connect to 10.7.5.144
|
||||
logger.error(String.format("连接ZLM失败: %s, %s", url, e.getMessage()));
|
||||
logger.error(String.format("连接ZLM连接失败: %s, %s", url, e.getMessage()));
|
||||
}
|
||||
|
||||
}catch (Exception e){
|
||||
@ -323,6 +326,10 @@ public class ZLMRESTfulUtils {
|
||||
return sendPost(mediaServerItem, "startSendRtpPassive",param, null);
|
||||
}
|
||||
|
||||
public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Map<String, Object> param, RequestCallback callback) {
|
||||
return sendPost(mediaServerItem, "startSendRtpPassive",param, callback);
|
||||
}
|
||||
|
||||
public JSONObject stopSendRtp(MediaServerItem mediaServerItem, Map<String, Object> param) {
|
||||
return sendPost(mediaServerItem, "stopSendRtp",param, null);
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ public class ZLMServerFactory {
|
||||
* @param tcpMode 0/null udp 模式,1 tcp 被动模式, 2 tcp 主动模式。
|
||||
* @return
|
||||
*/
|
||||
public int createRTPServer(MediaServerItem mediaServerItem, String streamId, long ssrc, Integer port, Boolean reUsePort, Integer tcpMode) {
|
||||
public int createRTPServer(MediaServerItem mediaServerItem, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) {
|
||||
int result = -1;
|
||||
// 查询此rtp server 是否已经存在
|
||||
JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
|
||||
@ -58,7 +58,7 @@ public class ZLMServerFactory {
|
||||
JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerItem, param);
|
||||
if (jsonObject != null ) {
|
||||
if (jsonObject.getInteger("code") == 0) {
|
||||
return createRTPServer(mediaServerItem, streamId, ssrc, port, reUsePort, tcpMode);
|
||||
return createRTPServer(mediaServerItem, streamId, ssrc, port,onlyAuto, reUsePort, tcpMode);
|
||||
}else {
|
||||
logger.warn("[开启rtpServer], 重启RtpServer错误");
|
||||
}
|
||||
@ -86,6 +86,9 @@ public class ZLMServerFactory {
|
||||
}else {
|
||||
param.put("port", port);
|
||||
}
|
||||
if (onlyAuto != null) {
|
||||
param.put("only_audio", onlyAuto?"1":"0");
|
||||
}
|
||||
if (ssrc != 0) {
|
||||
param.put("ssrc", ssrc);
|
||||
}
|
||||
@ -111,9 +114,10 @@ public class ZLMServerFactory {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("stream_id", streamId);
|
||||
JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(serverItem, param);
|
||||
logger.info("关闭RTP Server " + jsonObject);
|
||||
if (jsonObject != null ) {
|
||||
if (jsonObject.getInteger("code") == 0) {
|
||||
result = jsonObject.getInteger("hit") == 1;
|
||||
result = jsonObject.getInteger("hit") >= 1;
|
||||
}else {
|
||||
logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
|
||||
}
|
||||
@ -206,7 +210,7 @@ public class ZLMServerFactory {
|
||||
sendRtpItem.setPort(port);
|
||||
sendRtpItem.setSsrc(ssrc);
|
||||
sendRtpItem.setApp(app);
|
||||
sendRtpItem.setStreamId(stream);
|
||||
sendRtpItem.setStream(stream);
|
||||
sendRtpItem.setPlatformId(platformId);
|
||||
sendRtpItem.setChannelId(channelId);
|
||||
sendRtpItem.setTcp(tcp);
|
||||
@ -227,10 +231,14 @@ public class ZLMServerFactory {
|
||||
/**
|
||||
* 调用zlm RESTFUL API —— startSendRtpPassive
|
||||
*/
|
||||
public JSONObject startSendRtpStreamForPassive(MediaServerItem mediaServerItem, Map<String, Object>param) {
|
||||
public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Map<String, Object>param) {
|
||||
return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param);
|
||||
}
|
||||
|
||||
public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Map<String, Object>param, ZLMRESTfulUtils.RequestCallback callback) {
|
||||
return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询待转推的流是否就绪
|
||||
*/
|
||||
@ -289,13 +297,54 @@ public class ZLMServerFactory {
|
||||
result= true;
|
||||
logger.info("[停止RTP推流] 成功");
|
||||
} else {
|
||||
logger.error("[停止RTP推流] 失败: {}, 参数:{}->\r\n{}",jsonObject.getString("msg"), JSON.toJSON(param), jsonObject);
|
||||
logger.warn("[停止RTP推流] 失败: {}, 参数:{}->\r\n{}",jsonObject.getString("msg"), JSON.toJSON(param), jsonObject);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void closeAllSendRtpStream() {
|
||||
public JSONObject startSendRtp(MediaServerItem mediaInfo, SendRtpItem sendRtpItem) {
|
||||
String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
|
||||
logger.info("rtp/{}开始推流, 目标={}:{},SSRC={}", sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc());
|
||||
Map<String, Object> param = new HashMap<>(12);
|
||||
param.put("vhost","__defaultVhost__");
|
||||
param.put("app",sendRtpItem.getApp());
|
||||
param.put("stream",sendRtpItem.getStream());
|
||||
param.put("ssrc", sendRtpItem.getSsrc());
|
||||
param.put("src_port", sendRtpItem.getLocalPort());
|
||||
param.put("pt", sendRtpItem.getPt());
|
||||
param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
|
||||
param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
|
||||
if (!sendRtpItem.isTcp()) {
|
||||
// udp模式下开启rtcp保活
|
||||
param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
|
||||
}
|
||||
|
||||
if (mediaInfo == null) {
|
||||
return null;
|
||||
}
|
||||
// 如果是非严格模式,需要关闭端口占用
|
||||
JSONObject startSendRtpStreamResult = null;
|
||||
if (sendRtpItem.getLocalPort() != 0) {
|
||||
if (sendRtpItem.isTcpActive()) {
|
||||
startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param);
|
||||
System.out.println(JSON.toJSON(param));
|
||||
}else {
|
||||
param.put("is_udp", is_Udp);
|
||||
param.put("dst_url", sendRtpItem.getIp());
|
||||
param.put("dst_port", sendRtpItem.getPort());
|
||||
startSendRtpStreamResult = startSendRtpStream(mediaInfo, param);
|
||||
}
|
||||
}else {
|
||||
if (sendRtpItem.isTcpActive()) {
|
||||
startSendRtpStreamResult = startSendRtpPassive(mediaInfo, param);
|
||||
}else {
|
||||
param.put("is_udp", is_Udp);
|
||||
param.put("dst_url", sendRtpItem.getIp());
|
||||
param.put("dst_port", sendRtpItem.getPort());
|
||||
startSendRtpStreamResult = startSendRtpStream(mediaInfo, param);
|
||||
}
|
||||
}
|
||||
return startSendRtpStreamResult;
|
||||
}
|
||||
|
||||
public Boolean updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc) {
|
||||
|
||||
@ -39,6 +39,7 @@ public class ZlmHttpHookSubscribe {
|
||||
hookSubscribe.setExpires(expiresInstant);
|
||||
}
|
||||
allSubscribes.computeIfAbsent(hookSubscribe.getHookType(), k -> new ConcurrentHashMap<>()).put(hookSubscribe, event);
|
||||
System.out.println(allSubscribes);
|
||||
}
|
||||
|
||||
public ZlmHttpHookSubscribe.Event sendNotify(HookType type, JSONObject hookResponse) {
|
||||
@ -49,6 +50,7 @@ public class ZlmHttpHookSubscribe {
|
||||
}
|
||||
for (IHookSubscribe key : eventMap.keySet()) {
|
||||
Boolean result = null;
|
||||
|
||||
for (String s : key.getContent().keySet()) {
|
||||
if (result == null) {
|
||||
result = key.getContent().getString(s).equals(hookResponse.getString(s));
|
||||
|
||||
@ -35,6 +35,21 @@ public class HookSubscribeFactory {
|
||||
return hookSubscribe;
|
||||
}
|
||||
|
||||
public static HookSubscribeForStreamPush on_publish(String app, String stream, String scheam, String mediaServerId) {
|
||||
HookSubscribeForStreamPush hookSubscribe = new HookSubscribeForStreamPush();
|
||||
JSONObject subscribeKey = new JSONObject();
|
||||
subscribeKey.put("app", app);
|
||||
subscribeKey.put("stream", stream);
|
||||
if (scheam != null) {
|
||||
subscribeKey.put("schema", scheam);
|
||||
}
|
||||
subscribeKey.put("mediaServerId", mediaServerId);
|
||||
hookSubscribe.setContent(subscribeKey);
|
||||
|
||||
return hookSubscribe;
|
||||
}
|
||||
|
||||
|
||||
public static HookSubscribeForServerStarted on_server_started() {
|
||||
HookSubscribeForServerStarted hookSubscribe = new HookSubscribeForServerStarted();
|
||||
hookSubscribe.setContent(new JSONObject());
|
||||
@ -52,4 +67,5 @@ public class HookSubscribeFactory {
|
||||
|
||||
return hookSubscribe;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
package com.genersoft.iot.vmp.media.zlm.dto;
|
||||
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* hook订阅-开始推流
|
||||
* @author lin
|
||||
*/
|
||||
public class HookSubscribeForStreamPush implements IHookSubscribe{
|
||||
|
||||
private HookType hookType = HookType.on_publish;
|
||||
|
||||
private JSONObject content;
|
||||
|
||||
private Instant expires;
|
||||
|
||||
@Override
|
||||
public HookType getHookType() {
|
||||
return hookType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(JSONObject content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getExpires() {
|
||||
return expires;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpires(Instant expires) {
|
||||
this.expires = expires;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,168 @@
|
||||
package com.genersoft.iot.vmp.media.zlm.dto;
|
||||
|
||||
|
||||
/**
|
||||
* 精简的MediaServerItem信息,方便给前端返回数据
|
||||
*/
|
||||
public class MediaServerItemLite {
|
||||
|
||||
private String id;
|
||||
|
||||
private String ip;
|
||||
|
||||
private String hookIp;
|
||||
|
||||
private String sdpIp;
|
||||
|
||||
private String streamIp;
|
||||
|
||||
private int httpPort;
|
||||
|
||||
private int httpSSlPort;
|
||||
|
||||
private int rtmpPort;
|
||||
|
||||
private int rtmpSSlPort;
|
||||
|
||||
private int rtpProxyPort;
|
||||
|
||||
private int rtspPort;
|
||||
|
||||
private int rtspSSLPort;
|
||||
|
||||
private String secret;
|
||||
|
||||
private int recordAssistPort;
|
||||
|
||||
|
||||
|
||||
public MediaServerItemLite(MediaServerItem mediaServerItem) {
|
||||
this.id = mediaServerItem.getId();
|
||||
this.ip = mediaServerItem.getIp();
|
||||
this.hookIp = mediaServerItem.getHookIp();
|
||||
this.sdpIp = mediaServerItem.getSdpIp();
|
||||
this.streamIp = mediaServerItem.getStreamIp();
|
||||
this.httpPort = mediaServerItem.getHttpPort();
|
||||
this.httpSSlPort = mediaServerItem.getHttpSSlPort();
|
||||
this.rtmpPort = mediaServerItem.getRtmpPort();
|
||||
this.rtmpSSlPort = mediaServerItem.getRtmpSSlPort();
|
||||
this.rtpProxyPort = mediaServerItem.getRtpProxyPort();
|
||||
this.rtspPort = mediaServerItem.getRtspPort();
|
||||
this.rtspSSLPort = mediaServerItem.getRtspSSLPort();
|
||||
this.secret = mediaServerItem.getSecret();
|
||||
this.recordAssistPort = mediaServerItem.getRecordAssistPort();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public String getHookIp() {
|
||||
return hookIp;
|
||||
}
|
||||
|
||||
public void setHookIp(String hookIp) {
|
||||
this.hookIp = hookIp;
|
||||
}
|
||||
|
||||
public String getSdpIp() {
|
||||
return sdpIp;
|
||||
}
|
||||
|
||||
public void setSdpIp(String sdpIp) {
|
||||
this.sdpIp = sdpIp;
|
||||
}
|
||||
|
||||
public String getStreamIp() {
|
||||
return streamIp;
|
||||
}
|
||||
|
||||
public void setStreamIp(String streamIp) {
|
||||
this.streamIp = streamIp;
|
||||
}
|
||||
|
||||
public int getHttpPort() {
|
||||
return httpPort;
|
||||
}
|
||||
|
||||
public void setHttpPort(int httpPort) {
|
||||
this.httpPort = httpPort;
|
||||
}
|
||||
|
||||
public int getHttpSSlPort() {
|
||||
return httpSSlPort;
|
||||
}
|
||||
|
||||
public void setHttpSSlPort(int httpSSlPort) {
|
||||
this.httpSSlPort = httpSSlPort;
|
||||
}
|
||||
|
||||
public int getRtmpPort() {
|
||||
return rtmpPort;
|
||||
}
|
||||
|
||||
public void setRtmpPort(int rtmpPort) {
|
||||
this.rtmpPort = rtmpPort;
|
||||
}
|
||||
|
||||
public int getRtmpSSlPort() {
|
||||
return rtmpSSlPort;
|
||||
}
|
||||
|
||||
public void setRtmpSSlPort(int rtmpSSlPort) {
|
||||
this.rtmpSSlPort = rtmpSSlPort;
|
||||
}
|
||||
|
||||
public int getRtpProxyPort() {
|
||||
return rtpProxyPort;
|
||||
}
|
||||
|
||||
public void setRtpProxyPort(int rtpProxyPort) {
|
||||
this.rtpProxyPort = rtpProxyPort;
|
||||
}
|
||||
|
||||
public int getRtspPort() {
|
||||
return rtspPort;
|
||||
}
|
||||
|
||||
public void setRtspPort(int rtspPort) {
|
||||
this.rtspPort = rtspPort;
|
||||
}
|
||||
|
||||
public int getRtspSSLPort() {
|
||||
return rtspSSLPort;
|
||||
}
|
||||
|
||||
public void setRtspSSLPort(int rtspSSLPort) {
|
||||
this.rtspSSLPort = rtspSSLPort;
|
||||
}
|
||||
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void setSecret(String secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public int getRecordAssistPort() {
|
||||
return recordAssistPort;
|
||||
}
|
||||
|
||||
public void setRecordAssistPort(int recordAssistPort) {
|
||||
this.recordAssistPort = recordAssistPort;
|
||||
}
|
||||
}
|
||||
@ -45,8 +45,10 @@ public interface IMediaServerService {
|
||||
|
||||
void updateVmServer(List<MediaServerItem> mediaServerItemList);
|
||||
|
||||
SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck,
|
||||
boolean isPlayback, Integer port, Boolean reUsePort, Integer tcpMode);
|
||||
SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck,
|
||||
boolean isPlayback, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode);
|
||||
|
||||
SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto);
|
||||
|
||||
void closeRTPServer(MediaServerItem mediaServerItem, String streamId);
|
||||
|
||||
|
||||
@ -40,5 +40,5 @@ public interface IMediaService {
|
||||
* @param stream
|
||||
* @return
|
||||
*/
|
||||
StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId);
|
||||
StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId, boolean isPlay);
|
||||
}
|
||||
|
||||
@ -1,9 +1,18 @@
|
||||
package com.genersoft.iot.vmp.service;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.SipException;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* 国标平台的业务类
|
||||
* @author lin
|
||||
@ -56,5 +65,21 @@ public interface IPlatformService {
|
||||
*/
|
||||
void sendNotifyMobilePosition(String platformId);
|
||||
|
||||
/**
|
||||
* 向上级发送语音喊话的消息
|
||||
* @param platform 平台
|
||||
* @param channelId 通道
|
||||
* @param hookEvent hook事件
|
||||
* @param errorEvent 信令错误事件
|
||||
* @param timeoutCallback 超时事件
|
||||
*/
|
||||
void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent,
|
||||
SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException;
|
||||
|
||||
/**
|
||||
* 语音喊话回复BYE
|
||||
*/
|
||||
void stopBroadcast(ParentPlatform platform, DeviceChannel channel, String stream,boolean sendBye, MediaServerItem mediaServerItem);
|
||||
|
||||
void addSimulatedSubscribeInfo(ParentPlatform parentPlatform);
|
||||
}
|
||||
|
||||
@ -1,16 +1,25 @@
|
||||
package com.genersoft.iot.vmp.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.conf.exception.ServiceException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
|
||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
|
||||
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.header.CallIdHeader;
|
||||
import java.text.ParseException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 点播处理
|
||||
@ -21,6 +30,8 @@ public interface IPlayService {
|
||||
ErrorCallback<Object> callback);
|
||||
SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback);
|
||||
|
||||
StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId);
|
||||
|
||||
MediaServerItem getNewMediaServerItem(Device device);
|
||||
|
||||
void playBack(String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback);
|
||||
@ -34,10 +45,27 @@ public interface IPlayService {
|
||||
|
||||
void zlmServerOnline(String mediaServerId);
|
||||
|
||||
AudioBroadcastResult audioBroadcast(Device device, String channelId, Boolean broadcastMode);
|
||||
|
||||
boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException;
|
||||
|
||||
boolean audioBroadcastInUse(Device device, String channelId);
|
||||
|
||||
void stopAudioBroadcast(String deviceId, String channelId);
|
||||
|
||||
void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException;
|
||||
|
||||
void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException;
|
||||
|
||||
void startPushStream(SendRtpItem sendRtpItem, SIPResponse sipResponse, ParentPlatform platform, CallIdHeader callIdHeader);
|
||||
|
||||
void startSendRtpStreamHand(SendRtpItem sendRtpItem, Object correlationInfo,
|
||||
JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader);
|
||||
|
||||
void talkCmd(Device device, String channelId, MediaServerItem mediaServerItem, String stream, AudioBroadcastEvent event);
|
||||
|
||||
void stopTalk(Device device, String channelId, Boolean streamIsReady);
|
||||
|
||||
void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback);
|
||||
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
|
||||
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
|
||||
import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
|
||||
@ -13,6 +14,8 @@ import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.service.IDeviceChannelService;
|
||||
import com.genersoft.iot.vmp.service.IDeviceService;
|
||||
import com.genersoft.iot.vmp.service.IInviteStreamService;
|
||||
@ -37,9 +40,7 @@ import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.SipException;
|
||||
import java.text.ParseException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@ -98,6 +99,12 @@ public class DeviceServiceImpl implements IDeviceService {
|
||||
@Autowired
|
||||
private IMediaServerService mediaServerService;
|
||||
|
||||
@Autowired
|
||||
private AudioBroadcastManager audioBroadcastManager;
|
||||
|
||||
@Autowired
|
||||
private ZLMRESTfulUtils zlmresTfulUtils;
|
||||
|
||||
@Override
|
||||
public void online(Device device, SipTransactionInfo sipTransactionInfo) {
|
||||
logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
|
||||
@ -229,6 +236,25 @@ public class DeviceServiceImpl implements IDeviceService {
|
||||
// 移除订阅
|
||||
removeCatalogSubscribe(device, null);
|
||||
removeMobilePositionSubscribe(device, null);
|
||||
|
||||
List<AudioBroadcastCatch> audioBroadcastCatches = audioBroadcastManager.get(deviceId);
|
||||
if (audioBroadcastCatches.size() > 0) {
|
||||
for (AudioBroadcastCatch audioBroadcastCatch : audioBroadcastCatches) {
|
||||
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, audioBroadcastCatch.getChannelId(), null, null);
|
||||
if (sendRtpItem != null) {
|
||||
redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null);
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("vhost", "__defaultVhost__");
|
||||
param.put("app", sendRtpItem.getApp());
|
||||
param.put("stream", sendRtpItem.getStream());
|
||||
zlmresTfulUtils.stopSendRtp(mediaInfo, param);
|
||||
}
|
||||
|
||||
audioBroadcastManager.del(deviceId, audioBroadcastCatch.getChannelId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -119,6 +119,8 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
@ -145,7 +147,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
|
||||
@Override
|
||||
public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck,
|
||||
boolean isPlayback, Integer port, Boolean reUsePort, Integer tcpMode) {
|
||||
boolean isPlayback, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) {
|
||||
if (mediaServerItem == null || mediaServerItem.getId() == null) {
|
||||
logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null");
|
||||
return null;
|
||||
@ -171,13 +173,19 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
}
|
||||
int rtpServerPort;
|
||||
if (mediaServerItem.isRtpEnable()) {
|
||||
rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck ? Long.parseLong(ssrc) : 0, port, reUsePort, tcpMode);
|
||||
rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck ? Long.parseLong(ssrc) : 0, port, onlyAuto, reUsePort, tcpMode);
|
||||
} else {
|
||||
rtpServerPort = mediaServerItem.getRtpProxyPort();
|
||||
}
|
||||
return new SSRCInfo(rtpServerPort, ssrc, streamId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto) {
|
||||
return openRTPServer(mediaServerItem, streamId, ssrc, ssrcCheck, isPlayback, port, onlyAuto, null, 0);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void closeRTPServer(MediaServerItem mediaServerItem, String streamId) {
|
||||
if (mediaServerItem == null) {
|
||||
@ -198,7 +206,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
@Override
|
||||
public void closeRTPServer(String mediaServerId, String streamId) {
|
||||
MediaServerItem mediaServerItem = this.getOne(mediaServerId);
|
||||
if (mediaServerItem.isRtpEnable()) {
|
||||
if (mediaServerItem != null && mediaServerItem.isRtpEnable()) {
|
||||
closeRTPServer(mediaServerItem, streamId);
|
||||
}
|
||||
zlmresTfulUtils.closeStreams(mediaServerItem, "rtp", streamId);
|
||||
@ -306,6 +314,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
||||
return JsonUtil.redisJsonToObject(redisTemplate, key, MediaServerItem.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public MediaServerItem getDefaultMediaServer() {
|
||||
return mediaServerMapper.queryDefault();
|
||||
|
||||
@ -4,21 +4,18 @@ import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.common.StreamURL;
|
||||
import com.genersoft.iot.vmp.conf.MediaConfig;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
|
||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.service.IMediaService;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||
import com.genersoft.iot.vmp.service.IMediaService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
@Service
|
||||
public class MediaServiceImpl implements IMediaService {
|
||||
|
||||
@ -42,7 +39,7 @@ public class MediaServiceImpl implements IMediaService {
|
||||
|
||||
@Override
|
||||
public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String callId) {
|
||||
return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null, callId);
|
||||
return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null, callId, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -70,9 +67,9 @@ public class MediaServiceImpl implements IMediaService {
|
||||
JSONObject mediaJSON = data.getJSONObject(0);
|
||||
JSONArray tracks = mediaJSON.getJSONArray("tracks");
|
||||
if (authority) {
|
||||
streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld);
|
||||
streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld, true);
|
||||
}else {
|
||||
streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null);
|
||||
streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,7 +84,7 @@ public class MediaServiceImpl implements IMediaService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId) {
|
||||
public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId, boolean isPlay) {
|
||||
StreamInfo streamInfoResult = new StreamInfo();
|
||||
streamInfoResult.setStream(stream);
|
||||
streamInfoResult.setApp(app);
|
||||
@ -104,10 +101,9 @@ public class MediaServiceImpl implements IMediaService {
|
||||
streamInfoResult.setFmp4(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setHls(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setTs(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setRtc(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam);
|
||||
streamInfoResult.setRtc(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam, isPlay);
|
||||
|
||||
streamInfoResult.setTracks(tracks);
|
||||
return streamInfoResult;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,34 +1,58 @@
|
||||
package com.genersoft.iot.vmp.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.genersoft.iot.vmp.common.InviteInfo;
|
||||
import com.genersoft.iot.vmp.common.InviteSessionStatus;
|
||||
import com.genersoft.iot.vmp.common.InviteSessionType;
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
|
||||
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
|
||||
import com.genersoft.iot.vmp.service.IInviteStreamService;
|
||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||
import com.genersoft.iot.vmp.service.IPlatformService;
|
||||
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
||||
import com.genersoft.iot.vmp.service.IPlayService;
|
||||
import com.genersoft.iot.vmp.service.bean.*;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.dao.*;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import gov.nist.javax.sip.message.SIPRequest;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.sdp.*;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.ResponseEvent;
|
||||
import javax.sip.PeerUnavailableException;
|
||||
import javax.sip.SipException;
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author lin
|
||||
@ -47,15 +71,6 @@ public class PlatformServiceImpl implements IPlatformService {
|
||||
@Autowired
|
||||
private ParentPlatformMapper platformMapper;
|
||||
|
||||
@Autowired
|
||||
private PlatformCatalogMapper catalogMapper;
|
||||
|
||||
@Autowired
|
||||
private PlatformChannelMapper platformChannelMapper;
|
||||
|
||||
@Autowired
|
||||
private PlatformGbStreamMapper platformGbStreamMapper;
|
||||
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@ -83,6 +98,21 @@ public class PlatformServiceImpl implements IPlatformService {
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
|
||||
@Autowired
|
||||
private ZlmHttpHookSubscribe subscribe;
|
||||
|
||||
@Autowired
|
||||
private VideoStreamSessionManager streamSession;
|
||||
|
||||
|
||||
@Autowired
|
||||
private IPlayService playService;
|
||||
|
||||
@Autowired
|
||||
private IInviteStreamService inviteStreamService;
|
||||
|
||||
@Autowired
|
||||
private ZLMRESTfulUtils zlmresTfulUtils;
|
||||
|
||||
|
||||
@Override
|
||||
@ -374,7 +404,7 @@ public class PlatformServiceImpl implements IPlatformService {
|
||||
Map<String, Object> param = new HashMap<>(3);
|
||||
param.put("vhost", "__defaultVhost__");
|
||||
param.put("app", sendRtpItem.getApp());
|
||||
param.put("stream", sendRtpItem.getStreamId());
|
||||
param.put("stream", sendRtpItem.getStream());
|
||||
zlmServerFactory.stopSendRtpStream(mediaInfo, param);
|
||||
}
|
||||
}
|
||||
@ -431,4 +461,319 @@ public class PlatformServiceImpl implements IPlatformService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent,
|
||||
SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException {
|
||||
|
||||
if (mediaServerItem == null) {
|
||||
logger.info("[国标级联] 语音喊话未找到可用的zlm. platform: {}", platform.getServerGBId());
|
||||
return;
|
||||
}
|
||||
InviteInfo inviteInfoForOld = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, platform.getServerGBId(), channelId);
|
||||
|
||||
if (inviteInfoForOld != null && inviteInfoForOld.getStreamInfo() != null) {
|
||||
// 如果zlm不存在这个流,则删除数据即可
|
||||
MediaServerItem mediaServerItemForStreamInfo = mediaServerService.getOne(inviteInfoForOld.getStreamInfo().getMediaServerId());
|
||||
if (mediaServerItemForStreamInfo != null) {
|
||||
Boolean ready = zlmServerFactory.isStreamReady(mediaServerItemForStreamInfo, inviteInfoForOld.getStreamInfo().getApp(), inviteInfoForOld.getStreamInfo().getStream());
|
||||
if (!ready) {
|
||||
// 错误存在于redis中的数据
|
||||
inviteStreamService.removeInviteInfo(inviteInfoForOld);
|
||||
}else {
|
||||
// 流确实尚在推流,直接回调结果
|
||||
OnStreamChangedHookParam hookParam = new OnStreamChangedHookParam();
|
||||
hookParam.setApp(inviteInfoForOld.getStreamInfo().getApp());
|
||||
hookParam.setStream(inviteInfoForOld.getStreamInfo().getStream());
|
||||
|
||||
hookEvent.response(mediaServerItemForStreamInfo, hookParam);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String streamId = null;
|
||||
if (mediaServerItem.isRtpEnable()) {
|
||||
streamId = String.format("%s_%s", platform.getServerGBId(), channelId);
|
||||
}
|
||||
// 默认不进行SSRC校验, TODO 后续可改为配置
|
||||
boolean ssrcCheck = false;
|
||||
int tcpMode;
|
||||
if (userSetting.getBroadcastForPlatform().equalsIgnoreCase("TCP-PASSIVE")) {
|
||||
tcpMode = 1;
|
||||
}else if (userSetting.getBroadcastForPlatform().equalsIgnoreCase("TCP-ACTIVE")) {
|
||||
tcpMode = 2;
|
||||
} else {
|
||||
tcpMode = 0;
|
||||
}
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, ssrcCheck, false, null, true, false, tcpMode);
|
||||
if (ssrcInfo == null || ssrcInfo.getPort() < 0) {
|
||||
logger.info("[国标级联] 发起语音喊话 开启端口监听失败, platform: {}, channel: {}", platform.getServerGBId(), channelId);
|
||||
SipSubscribe.EventResult<Object> eventResult = new SipSubscribe.EventResult<>();
|
||||
eventResult.statusCode = -1;
|
||||
eventResult.msg = "端口监听失败";
|
||||
eventResult.type = SipSubscribe.EventResultType.failedToGetPort;
|
||||
errorEvent.response(eventResult);
|
||||
return;
|
||||
}
|
||||
logger.info("[国标级联] 语音喊话,发起Invite消息 deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
|
||||
platform.getServerGBId(), channelId, ssrcInfo.getPort(), userSetting.getBroadcastForPlatform(), ssrcInfo.getSsrc(), ssrcCheck);
|
||||
|
||||
// 初始化redis中的invite消息状态
|
||||
InviteInfo inviteInfo = InviteInfo.getInviteInfo(platform.getServerGBId(), channelId, ssrcInfo.getStream(), ssrcInfo,
|
||||
mediaServerItem.getSdpIp(), ssrcInfo.getPort(), userSetting.getBroadcastForPlatform(), InviteSessionType.BROADCAST,
|
||||
InviteSessionStatus.ready);
|
||||
inviteStreamService.updateInviteInfo(inviteInfo);
|
||||
String timeOutTaskKey = UUID.randomUUID().toString();
|
||||
dynamicTask.startDelay(timeOutTaskKey, () -> {
|
||||
// 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况
|
||||
InviteInfo inviteInfoForBroadcast = inviteStreamService.getInviteInfo(InviteSessionType.BROADCAST, platform.getServerGBId(), channelId, null);
|
||||
if (inviteInfoForBroadcast == null) {
|
||||
logger.info("[国标级联] 发起语音喊话 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", platform.getServerGBId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc());
|
||||
// 点播超时回复BYE 同时释放ssrc以及此次点播的资源
|
||||
try {
|
||||
commanderForPlatform.streamByeCmd(platform, channelId, ssrcInfo.getStream(), null, null);
|
||||
} catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
|
||||
logger.error("[点播超时], 发送BYE失败 {}", e.getMessage());
|
||||
} finally {
|
||||
timeoutCallback.run(1, "收流超时");
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
|
||||
mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
|
||||
streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
|
||||
mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
|
||||
}
|
||||
}
|
||||
}, userSetting.getPlayTimeout());
|
||||
commanderForPlatform.broadcastInviteCmd(platform, channelId, mediaServerItem, ssrcInfo, (mediaServerItemForInvite, hookParam)->{
|
||||
logger.info("[国标级联] 发起语音喊话 收到上级推流 deviceId: {}, channelId: {}", platform.getServerGBId(), channelId);
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
// hook响应
|
||||
playService.onPublishHandlerForPlay(mediaServerItemForInvite, hookParam, platform.getServerGBId(), channelId);
|
||||
// 收到流
|
||||
if (hookEvent != null) {
|
||||
hookEvent.response(mediaServerItem, hookParam);
|
||||
}
|
||||
}, event -> {
|
||||
|
||||
inviteOKHandler(event, ssrcInfo, tcpMode, ssrcCheck, mediaServerItem, platform, channelId, timeOutTaskKey,
|
||||
null, inviteInfo, InviteSessionType.BROADCAST);
|
||||
// // 收到200OK 检测ssrc是否有变化,防止上级自定义了ssrc
|
||||
// ResponseEvent responseEvent = (ResponseEvent) event.event;
|
||||
// String contentString = new String(responseEvent.getResponse().getRawContent());
|
||||
// // 获取ssrc
|
||||
// int ssrcIndex = contentString.indexOf("y=");
|
||||
// // 检查是否有y字段
|
||||
// if (ssrcIndex >= 0) {
|
||||
// //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
|
||||
// String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
|
||||
// // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
|
||||
// if (ssrcInfo.getSsrc().equals(ssrcInResponse) || ssrcCheck) {
|
||||
// tcpActiveHandler(platform, )
|
||||
// return;
|
||||
// }
|
||||
// logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
|
||||
// if (!mediaServerItem.isRtpEnable()) {
|
||||
// logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
|
||||
// // 释放ssrc
|
||||
// mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
|
||||
// // 单端口模式streamId也有变化,需要重新设置监听
|
||||
// if (!mediaServerItem.isRtpEnable()) {
|
||||
// // 添加订阅
|
||||
// HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
|
||||
// subscribe.removeSubscribe(hookSubscribe);
|
||||
// hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
|
||||
// subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> {
|
||||
// logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + hookParam);
|
||||
// dynamicTask.stop(timeOutTaskKey);
|
||||
// // hook响应
|
||||
// playService.onPublishHandlerForPlay(mediaServerItemInUse, hookParam, platform.getServerGBId(), channelId);
|
||||
// hookEvent.response(mediaServerItemInUse, hookParam);
|
||||
// });
|
||||
// }
|
||||
// // 关闭rtp server
|
||||
// mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
|
||||
// // 重新开启ssrc server
|
||||
// mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, false, false, ssrcInfo.getPort(), true, false, tcpMode);
|
||||
// }
|
||||
// }
|
||||
}, eventResult -> {
|
||||
// 收到错误回复
|
||||
if (errorEvent != null) {
|
||||
errorEvent.response(eventResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void inviteOKHandler(SipSubscribe.EventResult eventResult, SSRCInfo ssrcInfo, int tcpMode, boolean ssrcCheck, MediaServerItem mediaServerItem,
|
||||
ParentPlatform platform, String channelId, String timeOutTaskKey, ErrorCallback<Object> callback,
|
||||
InviteInfo inviteInfo, InviteSessionType inviteSessionType){
|
||||
inviteInfo.setStatus(InviteSessionStatus.ok);
|
||||
ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
|
||||
String contentString = new String(responseEvent.getResponse().getRawContent());
|
||||
System.out.println(1111);
|
||||
System.out.println(contentString);
|
||||
String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString);
|
||||
// 兼容回复的消息中缺少ssrc(y字段)的情况
|
||||
if (ssrcInResponse == null) {
|
||||
ssrcInResponse = ssrcInfo.getSsrc();
|
||||
}
|
||||
if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
|
||||
// ssrc 一致
|
||||
if (mediaServerItem.isRtpEnable()) {
|
||||
// 多端口
|
||||
if (tcpMode == 2) {
|
||||
tcpActiveHandler(platform, channelId, contentString, mediaServerItem, tcpMode, ssrcCheck,
|
||||
timeOutTaskKey, ssrcInfo, callback);
|
||||
}
|
||||
}else {
|
||||
// 单端口
|
||||
if (tcpMode == 2) {
|
||||
logger.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流");
|
||||
}
|
||||
}
|
||||
}else {
|
||||
logger.info("[Invite 200OK] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
|
||||
// ssrc 不一致
|
||||
if (mediaServerItem.isRtpEnable()) {
|
||||
// 多端口
|
||||
if (ssrcCheck) {
|
||||
// ssrc检验
|
||||
// 更新ssrc
|
||||
logger.info("[Invite 200OK] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
|
||||
// 释放ssrc
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
|
||||
Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse);
|
||||
if (!result) {
|
||||
try {
|
||||
logger.warn("[Invite 200OK] 更新ssrc失败,停止喊话 {}/{}", platform.getServerGBId(), channelId);
|
||||
commanderForPlatform.streamByeCmd(platform, channelId, ssrcInfo.getStream(), null, null);
|
||||
} catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
|
||||
logger.error("[命令发送失败] 停止播放, 发送BYE: {}", e.getMessage());
|
||||
}
|
||||
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
// 释放ssrc
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
|
||||
|
||||
streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
|
||||
|
||||
callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
|
||||
"下级自定义了ssrc,重新设置收流信息失败", null);
|
||||
inviteStreamService.call(inviteSessionType, platform.getServerGBId(), channelId, null,
|
||||
InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
|
||||
"下级自定义了ssrc,重新设置收流信息失败", null);
|
||||
|
||||
}else {
|
||||
ssrcInfo.setSsrc(ssrcInResponse);
|
||||
inviteInfo.setSsrcInfo(ssrcInfo);
|
||||
inviteInfo.setStream(ssrcInfo.getStream());
|
||||
if (tcpMode == 2) {
|
||||
if (mediaServerItem.isRtpEnable()) {
|
||||
tcpActiveHandler(platform, channelId, contentString, mediaServerItem, tcpMode, ssrcCheck,
|
||||
timeOutTaskKey, ssrcInfo, callback);
|
||||
}else {
|
||||
logger.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流");
|
||||
}
|
||||
}
|
||||
inviteStreamService.updateInviteInfo(inviteInfo);
|
||||
}
|
||||
}else {
|
||||
ssrcInfo.setSsrc(ssrcInResponse);
|
||||
inviteInfo.setSsrcInfo(ssrcInfo);
|
||||
inviteInfo.setStream(ssrcInfo.getStream());
|
||||
if (tcpMode == 2) {
|
||||
if (mediaServerItem.isRtpEnable()) {
|
||||
tcpActiveHandler(platform, channelId, contentString, mediaServerItem, tcpMode, ssrcCheck,
|
||||
timeOutTaskKey, ssrcInfo, callback);
|
||||
}else {
|
||||
logger.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流");
|
||||
}
|
||||
}
|
||||
inviteStreamService.updateInviteInfo(inviteInfo);
|
||||
}
|
||||
}else {
|
||||
if (ssrcInResponse != null) {
|
||||
// 单端口
|
||||
// 重新订阅流上线
|
||||
SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(inviteInfo.getDeviceId(),
|
||||
inviteInfo.getChannelId(), null, inviteInfo.getStream());
|
||||
streamSession.remove(inviteInfo.getDeviceId(),
|
||||
inviteInfo.getChannelId(), inviteInfo.getStream());
|
||||
inviteStreamService.updateInviteInfoForSSRC(inviteInfo, ssrcInResponse);
|
||||
streamSession.put(platform.getServerGBId(), channelId, ssrcTransaction.getCallId(),
|
||||
inviteInfo.getStream(), ssrcInResponse, mediaServerItem.getId(), (SIPResponse) responseEvent.getResponse(), inviteSessionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void tcpActiveHandler(ParentPlatform platform, String channelId, String contentString,
|
||||
MediaServerItem mediaServerItem, int tcpMode, boolean ssrcCheck,
|
||||
String timeOutTaskKey, SSRCInfo ssrcInfo, ErrorCallback<Object> callback){
|
||||
if (tcpMode != 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
String substring;
|
||||
if (contentString.indexOf("y=") > 0) {
|
||||
substring = contentString.substring(0, contentString.indexOf("y="));
|
||||
}else {
|
||||
substring = contentString;
|
||||
}
|
||||
try {
|
||||
SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
|
||||
int port = -1;
|
||||
Vector mediaDescriptions = sdp.getMediaDescriptions(true);
|
||||
for (Object description : mediaDescriptions) {
|
||||
MediaDescription mediaDescription = (MediaDescription) description;
|
||||
Media media = mediaDescription.getMedia();
|
||||
|
||||
Vector mediaFormats = media.getMediaFormats(false);
|
||||
if (mediaFormats.contains("8") || mediaFormats.contains("0")) {
|
||||
port = media.getMediaPort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
logger.info("[TCP主动连接对方] serverGbId: {}, channelId: {}, 连接对方的地址:{}:{}, SSRC: {}, SSRC校验:{}",
|
||||
platform.getServerGBId(), channelId, sdp.getConnection().getAddress(), port, ssrcInfo.getSsrc(), ssrcCheck);
|
||||
JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
|
||||
logger.info("[TCP主动连接对方] 结果: {}", jsonObject);
|
||||
} catch (SdpException e) {
|
||||
logger.error("[TCP主动连接对方] serverGbId: {}, channelId: {}, 解析200OK的SDP信息失败", platform.getServerGBId(), channelId, e);
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
|
||||
// 释放ssrc
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
|
||||
|
||||
streamSession.remove(platform.getServerGBId(), channelId, ssrcInfo.getStream());
|
||||
|
||||
callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
|
||||
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
|
||||
inviteStreamService.call(InviteSessionType.PLAY, platform.getServerGBId(), channelId, null,
|
||||
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
|
||||
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopBroadcast(ParentPlatform platform, DeviceChannel channel, String stream, boolean sendBye, MediaServerItem mediaServerItem) {
|
||||
|
||||
try {
|
||||
if (sendBye) {
|
||||
commanderForPlatform.streamByeCmd(platform, channel.getChannelId(), stream, null, null);
|
||||
}
|
||||
} catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) {
|
||||
logger.warn("[消息发送失败] 停止语音对讲, 平台:{},通道:{}", platform.getId(), channel.getChannelId() );
|
||||
} finally {
|
||||
mediaServerService.closeRTPServer(mediaServerItem, stream);
|
||||
InviteInfo inviteInfo = inviteStreamService.getInviteInfo(null, platform.getServerGBId(), channel.getChannelId(), stream);
|
||||
if (inviteInfo != null) {
|
||||
// 释放ssrc
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), inviteInfo.getSsrcInfo().getSsrc());
|
||||
inviteStreamService.removeInviteInfo(inviteInfo);
|
||||
}
|
||||
streamSession.remove(platform.getServerGBId(), channel.getChannelId(), stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,19 +8,29 @@ import com.genersoft.iot.vmp.common.InviteSessionStatus;
|
||||
import com.genersoft.iot.vmp.common.InviteSessionType;
|
||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||
import com.genersoft.iot.vmp.conf.exception.ServiceException;
|
||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||
import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
|
||||
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
|
||||
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
|
||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.*;
|
||||
import com.genersoft.iot.vmp.media.zlm.*;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRecordMp4;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
|
||||
@ -29,19 +39,32 @@ import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
|
||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
|
||||
import com.genersoft.iot.vmp.service.*;
|
||||
import com.genersoft.iot.vmp.service.bean.*;
|
||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
||||
import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
|
||||
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
|
||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||
import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
|
||||
import com.genersoft.iot.vmp.utils.CloudRecordUtils;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
|
||||
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
|
||||
import gov.nist.javax.sip.message.SIPResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@ -49,13 +72,12 @@ import javax.sdp.*;
|
||||
import javax.sip.InvalidArgumentException;
|
||||
import javax.sip.ResponseEvent;
|
||||
import javax.sip.SipException;
|
||||
import javax.sip.header.CallIdHeader;
|
||||
import java.io.File;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.ParseException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.Vector;
|
||||
import java.util.*;
|
||||
|
||||
@SuppressWarnings(value = {"rawtypes", "unchecked"})
|
||||
@Service
|
||||
@ -71,11 +93,20 @@ public class PlayServiceImpl implements IPlayService {
|
||||
private ISIPCommander cmder;
|
||||
|
||||
@Autowired
|
||||
private SIPCommanderFroPlatform sipCommanderFroPlatform;
|
||||
private AudioBroadcastManager audioBroadcastManager;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
|
||||
@Autowired
|
||||
private ISIPCommanderForPlatform sipCommanderFroPlatform;
|
||||
|
||||
@Autowired
|
||||
private IRedisCatchStorage redisCatchStorage;
|
||||
|
||||
@Autowired
|
||||
private ZLMServerFactory zlmServerFactory;
|
||||
|
||||
@Autowired
|
||||
private IInviteStreamService inviteStreamService;
|
||||
|
||||
@ -83,10 +114,10 @@ public class PlayServiceImpl implements IPlayService {
|
||||
private ZlmHttpHookSubscribe subscribe;
|
||||
|
||||
@Autowired
|
||||
private ZLMRESTfulUtils zlmresTfulUtils;
|
||||
private SendRtpPortManager sendRtpPortManager;
|
||||
|
||||
@Autowired
|
||||
private ZLMServerFactory zlmServerFactory;
|
||||
private ZLMRESTfulUtils zlmresTfulUtils;
|
||||
|
||||
@Autowired
|
||||
private IMediaService mediaService;
|
||||
@ -98,17 +129,40 @@ public class PlayServiceImpl implements IPlayService {
|
||||
private VideoStreamSessionManager streamSession;
|
||||
|
||||
@Autowired
|
||||
private IDeviceService deviceService;
|
||||
private UserSetting userSetting;
|
||||
|
||||
@Autowired
|
||||
private IDeviceChannelService channelService;
|
||||
|
||||
@Autowired
|
||||
private UserSetting userSetting;
|
||||
private SipConfig sipConfig;
|
||||
|
||||
@Autowired
|
||||
private DynamicTask dynamicTask;
|
||||
|
||||
@Autowired
|
||||
private CloudRecordServiceMapper cloudRecordServiceMapper;
|
||||
|
||||
@Autowired
|
||||
private ISIPCommanderForPlatform commanderForPlatform;
|
||||
|
||||
|
||||
@Qualifier("taskExecutor")
|
||||
@Autowired
|
||||
private ThreadPoolTaskExecutor taskExecutor;
|
||||
|
||||
@Autowired
|
||||
private RedisGbPlayMsgListener redisGbPlayMsgListener;
|
||||
|
||||
@Autowired
|
||||
private ZlmHttpHookSubscribe hookSubscribe;
|
||||
|
||||
@Autowired
|
||||
private SSRCFactory ssrcFactory;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<Object, Object> redisTemplate;
|
||||
|
||||
|
||||
@Override
|
||||
public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback) {
|
||||
@ -166,7 +220,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
}
|
||||
}
|
||||
String streamId = String.format("%s_%s", device.getDeviceId(), channelId);;
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam());
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(), false, 0, false, false, device.getStreamModeForParam());
|
||||
if (ssrcInfo == null) {
|
||||
callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null);
|
||||
inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
|
||||
@ -179,6 +233,147 @@ public class PlayServiceImpl implements IPlayService {
|
||||
return ssrcInfo;
|
||||
}
|
||||
|
||||
private void talk(MediaServerItem mediaServerItem, Device device, String channelId, String stream,
|
||||
ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
|
||||
Runnable timeoutCallback, AudioBroadcastEvent audioEvent) {
|
||||
|
||||
String playSsrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId());
|
||||
|
||||
if (playSsrc == null) {
|
||||
audioEvent.call("ssrc已经用尽");
|
||||
return;
|
||||
}
|
||||
SendRtpItem sendRtpItem = new SendRtpItem();
|
||||
sendRtpItem.setApp("talk");
|
||||
sendRtpItem.setStream(stream);
|
||||
sendRtpItem.setSsrc(playSsrc);
|
||||
sendRtpItem.setDeviceId(device.getDeviceId());
|
||||
sendRtpItem.setPlatformId(device.getDeviceId());
|
||||
sendRtpItem.setChannelId(channelId);
|
||||
sendRtpItem.setRtcp(false);
|
||||
sendRtpItem.setMediaServerId(mediaServerItem.getId());
|
||||
sendRtpItem.setOnlyAudio(true);
|
||||
sendRtpItem.setPlayType(InviteStreamType.TALK);
|
||||
sendRtpItem.setPt(8);
|
||||
sendRtpItem.setStatus(1);
|
||||
sendRtpItem.setTcpActive(false);
|
||||
sendRtpItem.setTcp(true);
|
||||
sendRtpItem.setUsePs(false);
|
||||
sendRtpItem.setReceiveStream(stream + "_talk");
|
||||
|
||||
String callId = SipUtils.getNewCallId();
|
||||
int port = sendRtpPortManager.getNextPort(mediaServerItem);
|
||||
//端口获取失败的ssrcInfo 没有必要发送点播指令
|
||||
if (port <= 0) {
|
||||
logger.info("[语音对讲] 端口分配异常,deviceId={},channelId={}", device.getDeviceId(), channelId);
|
||||
audioEvent.call("端口分配异常");
|
||||
return;
|
||||
}
|
||||
sendRtpItem.setLocalPort(port);
|
||||
sendRtpItem.setPort(port);
|
||||
logger.info("[语音对讲]开始 deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sendRtpItem.getLocalPort(), device.getStreamMode(), sendRtpItem.getSsrc(), false);
|
||||
// 超时处理
|
||||
String timeOutTaskKey = UUID.randomUUID().toString();
|
||||
dynamicTask.startDelay(timeOutTaskKey, () -> {
|
||||
|
||||
logger.info("[语音对讲] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, sendRtpItem.getPort(), sendRtpItem.getSsrc());
|
||||
timeoutCallback.run();
|
||||
// 点播超时回复BYE 同时释放ssrc以及此次点播的资源
|
||||
try {
|
||||
cmder.streamByeCmd(device, channelId, sendRtpItem.getStream(), null);
|
||||
} catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
|
||||
logger.error("[语音对讲]超时, 发送BYE失败 {}", e.getMessage());
|
||||
} finally {
|
||||
timeoutCallback.run();
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
|
||||
streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream());
|
||||
}
|
||||
}, userSetting.getPlayTimeout());
|
||||
|
||||
Map<String, Object> param = new HashMap<>(12);
|
||||
param.put("vhost","__defaultVhost__");
|
||||
param.put("app", sendRtpItem.getApp());
|
||||
param.put("stream", sendRtpItem.getStream());
|
||||
param.put("ssrc", sendRtpItem.getSsrc());
|
||||
param.put("src_port", sendRtpItem.getLocalPort());
|
||||
param.put("pt", sendRtpItem.getPt());
|
||||
param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
|
||||
param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
|
||||
param.put("is_udp", sendRtpItem.isTcp() ? "0" : "1");
|
||||
param.put("recv_stream_id", sendRtpItem.getReceiveStream());
|
||||
param.put("close_delay_ms", userSetting.getPlayTimeout() * 1000);
|
||||
|
||||
zlmServerFactory.startSendRtpPassive(mediaServerItem, param, jsonObject -> {
|
||||
if (jsonObject == null || jsonObject.getInteger("code") != 0 ) {
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
|
||||
logger.info("[语音对讲]失败 deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
|
||||
audioEvent.call("失败, " + jsonObject.getString("msg"));
|
||||
// 查看是否已经建立了通道,存在则发送bye
|
||||
stopTalk(device, channelId);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 查看设备是否已经在推流
|
||||
try {
|
||||
cmder.talkStreamCmd(mediaServerItem, sendRtpItem, device, channelId, callId, (mediaServerItemInuse, hookParam) -> {
|
||||
logger.info("[语音对讲] 流已生成, 开始推流: " + hookParam);
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
// TODO 暂不做处理
|
||||
}, (mediaServerItemInuse, hookParam) -> {
|
||||
logger.info("[语音对讲] 设备开始推流: " + hookParam);
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
|
||||
}, (event) -> {
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
|
||||
if (event.event instanceof ResponseEvent) {
|
||||
ResponseEvent responseEvent = (ResponseEvent) event.event;
|
||||
if (responseEvent.getResponse() instanceof SIPResponse) {
|
||||
SIPResponse response = (SIPResponse) responseEvent.getResponse();
|
||||
sendRtpItem.setFromTag(response.getFromTag());
|
||||
sendRtpItem.setToTag(response.getToTag());
|
||||
sendRtpItem.setCallId(response.getCallIdHeader().getCallId());
|
||||
redisCatchStorage.updateSendRTPSever(sendRtpItem);
|
||||
|
||||
streamSession.put(device.getDeviceId(), channelId, "talk",
|
||||
sendRtpItem.getStream(), sendRtpItem.getSsrc(), sendRtpItem.getMediaServerId(),
|
||||
response, InviteSessionType.TALK);
|
||||
} else {
|
||||
logger.error("[语音对讲]收到的消息错误,response不是SIPResponse");
|
||||
}
|
||||
} else {
|
||||
logger.error("[语音对讲]收到的消息错误,event不是ResponseEvent");
|
||||
}
|
||||
|
||||
}, (event) -> {
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
mediaServerService.closeRTPServer(mediaServerItem, sendRtpItem.getStream());
|
||||
// 释放ssrc
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
|
||||
streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream());
|
||||
errorEvent.response(event);
|
||||
});
|
||||
} catch (InvalidArgumentException | SipException | ParseException e) {
|
||||
|
||||
logger.error("[命令发送失败] 对讲消息: {}", e.getMessage());
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
mediaServerService.closeRTPServer(mediaServerItem, sendRtpItem.getStream());
|
||||
// 释放ssrc
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
|
||||
|
||||
streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream());
|
||||
SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult();
|
||||
eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent;
|
||||
eventResult.statusCode = -1;
|
||||
eventResult.msg = "命令发送失败";
|
||||
errorEvent.response(eventResult);
|
||||
}
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel,
|
||||
@ -344,6 +539,21 @@ public class PlayServiceImpl implements IPlayService {
|
||||
logger.info("[TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
|
||||
JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
|
||||
logger.info("[TCP主动连接对方] 结果: {}" , jsonObject);
|
||||
if (jsonObject.getInteger("code") != 0) {
|
||||
// 主动连接失败,结束流程, 清理数据
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
|
||||
// 释放ssrc
|
||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
|
||||
|
||||
streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
|
||||
|
||||
callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
|
||||
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
|
||||
inviteStreamService.call(InviteSessionType.BROADCAST, device.getDeviceId(), channelId, null,
|
||||
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
|
||||
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
|
||||
}
|
||||
} catch (SdpException e) {
|
||||
logger.error("[TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
|
||||
dynamicTask.stop(timeOutTaskKey);
|
||||
@ -355,7 +565,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
|
||||
callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
|
||||
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
|
||||
inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
|
||||
inviteStreamService.call(InviteSessionType.BROADCAST, device.getDeviceId(), channelId, null,
|
||||
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
|
||||
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
|
||||
}
|
||||
@ -383,7 +593,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
|
||||
}
|
||||
|
||||
private StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId) {
|
||||
public StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId) {
|
||||
StreamInfo streamInfo = null;
|
||||
Device device = redisCatchStorage.getDevice(deviceId);
|
||||
OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam)hookParam;
|
||||
@ -466,7 +676,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
.replace(":", "")
|
||||
.replace(" ", "");
|
||||
String stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr;
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam());
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam());
|
||||
playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, callback);
|
||||
}
|
||||
|
||||
@ -659,7 +869,7 @@ public class PlayServiceImpl implements IPlayService {
|
||||
return;
|
||||
}
|
||||
// 录像下载不使用固定流地址,固定流地址会导致如果开始时间与结束时间一致时文件错误的叠加在一起
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam());
|
||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(), true, 0, false,false, device.getStreamModeForParam());
|
||||
download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed, callback);
|
||||
}
|
||||
|
||||
@ -898,6 +1108,142 @@ public class PlayServiceImpl implements IPlayService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AudioBroadcastResult audioBroadcast(Device device, String channelId, Boolean broadcastMode) {
|
||||
// TODO 必须多端口模式才支持语音喊话鹤语音对讲
|
||||
if (device == null || channelId == null) {
|
||||
return null;
|
||||
}
|
||||
logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId);
|
||||
DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
|
||||
if (deviceChannel == null) {
|
||||
logger.warn("开启语音广播的时候未找到通道: {}", channelId);
|
||||
return null;
|
||||
}
|
||||
MediaServerItem mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null);
|
||||
if (broadcastMode == null) {
|
||||
broadcastMode = true;
|
||||
}
|
||||
String app = broadcastMode?"broadcast":"talk";
|
||||
String stream = device.getDeviceId() + "_" + channelId;
|
||||
AudioBroadcastResult audioBroadcastResult = new AudioBroadcastResult();
|
||||
audioBroadcastResult.setApp(app);
|
||||
audioBroadcastResult.setStream(stream);
|
||||
audioBroadcastResult.setStreamInfo(new StreamContent(mediaService.getStreamInfoByAppAndStream(mediaServerItem, app, stream, null, null, null, false)));
|
||||
audioBroadcastResult.setCodec("G.711");
|
||||
return audioBroadcastResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException {
|
||||
if (device == null || channelId == null) {
|
||||
return false;
|
||||
}
|
||||
logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId);
|
||||
DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
|
||||
if (deviceChannel == null) {
|
||||
logger.warn("开启语音广播的时候未找到通道: {}", channelId);
|
||||
event.call("开启语音广播的时候未找到通道");
|
||||
return false;
|
||||
}
|
||||
// 查询通道使用状态
|
||||
if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
|
||||
if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
|
||||
// 查询流是否存在,不存在则认为是异常状态
|
||||
Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStream());
|
||||
if (streamReady) {
|
||||
logger.warn("语音广播已经开启: {}", channelId);
|
||||
event.call("语音广播已经开启");
|
||||
return false;
|
||||
} else {
|
||||
stopAudioBroadcast(device.getDeviceId(), channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
// SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
|
||||
// if (sendRtpItem != null) {
|
||||
// MediaServerItem mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
// Boolean streamReady = zlmServerFactory.isStreamReady(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream());
|
||||
// if (streamReady) {
|
||||
// logger.warn("[语音对讲] 进行中: {}", channelId);
|
||||
// event.call("语音对讲进行中");
|
||||
// return false;
|
||||
// } else {
|
||||
// stopTalk(device, channelId);
|
||||
// }
|
||||
// }
|
||||
|
||||
// 发送通知
|
||||
cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> {
|
||||
// 发送成功
|
||||
AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, mediaServerItem, app, stream, event, AudioBroadcastCatchStatus.Ready, isFromPlatform);
|
||||
audioBroadcastManager.update(audioBroadcastCatch);
|
||||
}, eventResultForError -> {
|
||||
// 发送失败
|
||||
logger.error("语音广播发送失败: {}:{}", channelId, eventResultForError.msg);
|
||||
event.call("语音广播发送失败");
|
||||
stopAudioBroadcast(device.getDeviceId(), channelId);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean audioBroadcastInUse(Device device, String channelId) {
|
||||
if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
|
||||
if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
|
||||
// 查询流是否存在,不存在则认为是异常状态
|
||||
MediaServerItem mediaServerServiceOne = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerServiceOne, sendRtpItem.getApp(), sendRtpItem.getStream());
|
||||
if (streamReady) {
|
||||
logger.warn("语音广播通道使用中: {}", channelId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void stopAudioBroadcast(String deviceId, String channelId) {
|
||||
logger.info("[停止对讲] 设备:{}, 通道:{}", deviceId, channelId);
|
||||
List<AudioBroadcastCatch> audioBroadcastCatchList = new ArrayList<>();
|
||||
if (channelId == null) {
|
||||
audioBroadcastCatchList.addAll(audioBroadcastManager.get(deviceId));
|
||||
} else {
|
||||
audioBroadcastCatchList.add(audioBroadcastManager.get(deviceId, channelId));
|
||||
}
|
||||
if (audioBroadcastCatchList.size() > 0) {
|
||||
for (AudioBroadcastCatch audioBroadcastCatch : audioBroadcastCatchList) {
|
||||
Device device = deviceService.getDevice(deviceId);
|
||||
if (device == null || audioBroadcastCatch == null) {
|
||||
return;
|
||||
}
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, audioBroadcastCatch.getChannelId(), null, null);
|
||||
if (sendRtpItem != null) {
|
||||
redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null);
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("vhost", "__defaultVhost__");
|
||||
param.put("app", sendRtpItem.getApp());
|
||||
param.put("stream", sendRtpItem.getStream());
|
||||
zlmresTfulUtils.stopSendRtp(mediaInfo, param);
|
||||
try {
|
||||
cmder.streamByeCmdForDeviceInvite(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
|
||||
} catch (InvalidArgumentException | ParseException | SipException |
|
||||
SsrcTransactionNotFoundException e) {
|
||||
logger.error("[消息发送失败] 发送语音喊话BYE失败");
|
||||
}
|
||||
}
|
||||
|
||||
audioBroadcastManager.del(deviceId, channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void zlmServerOnline(String mediaServerId) {
|
||||
// TODO 查找之前的点播,流如果不存在则给下级发送bye
|
||||
@ -1006,6 +1352,199 @@ public class PlayServiceImpl implements IPlayService {
|
||||
cmder.playResumeCmd(device, inviteInfo.getStreamInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPushStream(SendRtpItem sendRtpItem, SIPResponse sipResponse, ParentPlatform platform, CallIdHeader callIdHeader) {
|
||||
// 开始发流
|
||||
String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
logger.info("[开始推流] rtp/{}, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStream(),
|
||||
sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp());
|
||||
Map<String, Object> param = new HashMap<>(12);
|
||||
param.put("vhost", "__defaultVhost__");
|
||||
param.put("app", sendRtpItem.getApp());
|
||||
param.put("stream", sendRtpItem.getStream());
|
||||
param.put("ssrc", sendRtpItem.getSsrc());
|
||||
param.put("src_port", sendRtpItem.getLocalPort());
|
||||
param.put("pt", sendRtpItem.getPt());
|
||||
param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
|
||||
param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
|
||||
param.put("is_udp", is_Udp);
|
||||
if (!sendRtpItem.isTcp()) {
|
||||
// udp模式下开启rtcp保活
|
||||
param.put("udp_rtcp_timeout", sendRtpItem.isRtcp() ? "1" : "0");
|
||||
}
|
||||
|
||||
if (mediaInfo == null) {
|
||||
RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
|
||||
sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStream(),
|
||||
sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
|
||||
sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
|
||||
redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> {
|
||||
startSendRtpStreamHand(sendRtpItem, platform, json, param, callIdHeader);
|
||||
});
|
||||
} else {
|
||||
// 如果是严格模式,需要关闭端口占用
|
||||
JSONObject startSendRtpStreamResult = null;
|
||||
if (sendRtpItem.getLocalPort() != 0) {
|
||||
if (sendRtpItem.isTcpActive()) {
|
||||
startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
|
||||
} else {
|
||||
param.put("dst_url", sendRtpItem.getIp());
|
||||
param.put("dst_port", sendRtpItem.getPort());
|
||||
startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
|
||||
}
|
||||
} else {
|
||||
if (sendRtpItem.isTcpActive()) {
|
||||
startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
|
||||
} else {
|
||||
param.put("dst_url", sendRtpItem.getIp());
|
||||
param.put("dst_port", sendRtpItem.getPort());
|
||||
startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
|
||||
}
|
||||
}
|
||||
if (startSendRtpStreamResult != null) {
|
||||
startSendRtpStreamHand(sendRtpItem, platform, startSendRtpStreamResult, param, callIdHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSendRtpStreamHand(SendRtpItem sendRtpItem, Object correlationInfo,
|
||||
JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) {
|
||||
if (jsonObject == null) {
|
||||
logger.error("RTP推流失败: 请检查ZLM服务");
|
||||
} else if (jsonObject.getInteger("code") == 0) {
|
||||
logger.info("调用ZLM推流接口, 结果: {}", jsonObject);
|
||||
logger.info("RTP推流成功[ {}/{} ],{}->{}, ", param.get("app"), param.get("stream"), jsonObject.getString("local_port"),
|
||||
sendRtpItem.isTcpActive()?"被动发流": param.get("dst_url") + ":" + param.get("dst_port"));
|
||||
} else {
|
||||
logger.error("RTP推流失败: {}, 参数:{}", jsonObject.getString("msg"), JSONObject.toJSONString(param));
|
||||
if (sendRtpItem.isOnlyAudio()) {
|
||||
Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
|
||||
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||
if (audioBroadcastCatch != null) {
|
||||
try {
|
||||
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
|
||||
} catch (SipException | ParseException | InvalidArgumentException |
|
||||
SsrcTransactionNotFoundException e) {
|
||||
logger.error("[命令发送失败] 停止语音对讲: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 向上级平台
|
||||
if (correlationInfo instanceof ParentPlatform) {
|
||||
try {
|
||||
ParentPlatform parentPlatform = (ParentPlatform)correlationInfo;
|
||||
commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void talkCmd(Device device, String channelId, MediaServerItem mediaServerItem, String stream, AudioBroadcastEvent event) {
|
||||
if (device == null || channelId == null) {
|
||||
return;
|
||||
}
|
||||
// TODO 必须多端口模式才支持语音喊话鹤语音对讲
|
||||
logger.info("[语音对讲] device: {}, channel: {}", device.getDeviceId(), channelId);
|
||||
DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
|
||||
if (deviceChannel == null) {
|
||||
logger.warn("开启语音对讲的时候未找到通道: {}", channelId);
|
||||
event.call("开启语音对讲的时候未找到通道");
|
||||
return;
|
||||
}
|
||||
// 查询通道使用状态
|
||||
if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
|
||||
if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
|
||||
// 查询流是否存在,不存在则认为是异常状态
|
||||
MediaServerItem mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
Boolean streamReady = zlmServerFactory.isStreamReady(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream());
|
||||
if (streamReady) {
|
||||
logger.warn("[语音对讲] 正在语音广播,无法开启语音通话: {}", channelId);
|
||||
event.call("正在语音广播");
|
||||
return;
|
||||
} else {
|
||||
stopAudioBroadcast(device.getDeviceId(), channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, stream, null);
|
||||
if (sendRtpItem != null) {
|
||||
MediaServerItem mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
Boolean streamReady = zlmServerFactory.isStreamReady(mediaServer, "rtp", sendRtpItem.getReceiveStream());
|
||||
if (streamReady) {
|
||||
logger.warn("[语音对讲] 进行中: {}", channelId);
|
||||
event.call("语音对讲进行中");
|
||||
return;
|
||||
} else {
|
||||
stopTalk(device, channelId);
|
||||
}
|
||||
}
|
||||
|
||||
talk(mediaServerItem, device, channelId, stream, (mediaServerItem1, hookParam) -> {
|
||||
logger.info("[语音对讲] 收到设备发来的流");
|
||||
}, eventResult -> {
|
||||
logger.warn("[语音对讲] 失败,{}/{}, 错误码 {} {}", device.getDeviceId(), channelId, eventResult.statusCode, eventResult.msg);
|
||||
event.call("失败,错误码 " + eventResult.statusCode + ", " + eventResult.msg);
|
||||
}, () -> {
|
||||
logger.warn("[语音对讲] 失败,{}/{} 超时", device.getDeviceId(), channelId);
|
||||
event.call("失败,超时 ");
|
||||
stopTalk(device, channelId);
|
||||
}, errorMsg -> {
|
||||
logger.warn("[语音对讲] 失败,{}/{} {}", device.getDeviceId(), channelId, errorMsg);
|
||||
event.call(errorMsg);
|
||||
stopTalk(device, channelId);
|
||||
});
|
||||
}
|
||||
|
||||
private void stopTalk(Device device, String channelId) {
|
||||
stopTalk(device, channelId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopTalk(Device device, String channelId, Boolean streamIsReady) {
|
||||
logger.info("[语音对讲] 停止, {}/{}", device.getDeviceId(), channelId);
|
||||
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
|
||||
if (sendRtpItem == null) {
|
||||
logger.info("[语音对讲] 停止失败, 未找到发送信息,可能已经停止");
|
||||
return;
|
||||
}
|
||||
// 停止向设备推流
|
||||
String mediaServerId = sendRtpItem.getMediaServerId();
|
||||
if (mediaServerId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MediaServerItem mediaServer = mediaServerService.getOne(mediaServerId);
|
||||
|
||||
if (streamIsReady == null || streamIsReady) {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("vhost", "__defaultVhost__");
|
||||
param.put("app", sendRtpItem.getApp());
|
||||
param.put("stream", sendRtpItem.getStream());
|
||||
param.put("ssrc", sendRtpItem.getSsrc());
|
||||
zlmServerFactory.stopSendRtpStream(mediaServer, param);
|
||||
}
|
||||
|
||||
ssrcFactory.releaseSsrc(mediaServerId, sendRtpItem.getSsrc());
|
||||
|
||||
SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, null, sendRtpItem.getStream());
|
||||
if (ssrcTransaction != null) {
|
||||
try {
|
||||
cmder.streamByeCmd(device, channelId, sendRtpItem.getStream(), null);
|
||||
} catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
|
||||
logger.info("[语音对讲] 停止消息发送失败,可能已经停止");
|
||||
}
|
||||
}
|
||||
redisCatchStorage.deleteSendRTPServer(device.getDeviceId(), channelId,null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback) {
|
||||
Device device = deviceService.getDevice(deviceId);
|
||||
|
||||
@ -89,12 +89,6 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
|
||||
@Autowired
|
||||
private PlatformGbStreamMapper platformGbStreamMapper;
|
||||
|
||||
@Autowired
|
||||
private EventPublisher eventPublisher;
|
||||
|
||||
@Autowired
|
||||
private ParentPlatformMapper parentPlatformMapper;
|
||||
|
||||
@Autowired
|
||||
private IGbStreamService gbStreamService;
|
||||
|
||||
|
||||
@ -79,7 +79,7 @@ public class RedisPushStreamCloseResponseListener implements MessageListener {
|
||||
for (SendRtpItem sendRtpItem : sendRtpItems) {
|
||||
ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
|
||||
if (parentPlatform != null) {
|
||||
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStreamId());
|
||||
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStream());
|
||||
try {
|
||||
commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem);
|
||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||
@ -88,7 +88,7 @@ public class RedisPushStreamCloseResponseListener implements MessageListener {
|
||||
}
|
||||
if (push.isSelf()) {
|
||||
// 停止向上级推流
|
||||
String streamId = sendRtpItem.getStreamId();
|
||||
String streamId = sendRtpItem.getStream();
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
param.put("vhost","__defaultVhost__");
|
||||
param.put("app",sendRtpItem.getApp());
|
||||
@ -96,11 +96,11 @@ public class RedisPushStreamCloseResponseListener implements MessageListener {
|
||||
param.put("ssrc",sendRtpItem.getSsrc());
|
||||
logger.info("[REDIS消息-推流结束] 停止向上级推流:{}", streamId);
|
||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStreamId());
|
||||
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStream());
|
||||
zlmServerFactory.stopSendRtpStream(mediaInfo, param);
|
||||
if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
|
||||
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
|
||||
sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(),
|
||||
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
|
||||
sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
|
||||
messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
|
||||
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
|
||||
|
||||
@ -43,6 +43,7 @@ public interface DeviceMapper {
|
||||
"geo_coord_sys," +
|
||||
"on_line," +
|
||||
"media_server_id," +
|
||||
"broadcast_push_after_ack," +
|
||||
"(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+
|
||||
" FROM wvp_device WHERE device_id = #{deviceId}")
|
||||
Device getDeviceByDeviceId(String deviceId);
|
||||
@ -73,6 +74,7 @@ public interface DeviceMapper {
|
||||
"subscribe_cycle_for_alarm,"+
|
||||
"ssrc_check,"+
|
||||
"as_message_channel,"+
|
||||
"broadcast_push_after_ack,"+
|
||||
"geo_coord_sys,"+
|
||||
"on_line"+
|
||||
") VALUES (" +
|
||||
@ -101,6 +103,7 @@ public interface DeviceMapper {
|
||||
"#{subscribeCycleForAlarm}," +
|
||||
"#{ssrcCheck}," +
|
||||
"#{asMessageChannel}," +
|
||||
"#{broadcastPushAfterAck}," +
|
||||
"#{geoCoordSys}," +
|
||||
"#{onLine}" +
|
||||
")")
|
||||
@ -155,6 +158,7 @@ public interface DeviceMapper {
|
||||
"subscribe_cycle_for_alarm,"+
|
||||
"ssrc_check,"+
|
||||
"as_message_channel,"+
|
||||
"broadcast_push_after_ack,"+
|
||||
"geo_coord_sys,"+
|
||||
"on_line,"+
|
||||
"media_server_id,"+
|
||||
@ -195,6 +199,7 @@ public interface DeviceMapper {
|
||||
"subscribe_cycle_for_alarm,"+
|
||||
"ssrc_check,"+
|
||||
"as_message_channel,"+
|
||||
"broadcast_push_after_ack,"+
|
||||
"geo_coord_sys,"+
|
||||
"on_line"+
|
||||
" FROM wvp_device WHERE on_line = true")
|
||||
@ -225,6 +230,7 @@ public interface DeviceMapper {
|
||||
"subscribe_cycle_for_alarm,"+
|
||||
"ssrc_check,"+
|
||||
"as_message_channel,"+
|
||||
"broadcast_push_after_ack,"+
|
||||
"geo_coord_sys,"+
|
||||
"on_line"+
|
||||
" FROM wvp_device WHERE ip = #{host} AND port=#{port}")
|
||||
@ -246,6 +252,7 @@ public interface DeviceMapper {
|
||||
"<if test=\"subscribeCycleForAlarm != null\">, subscribe_cycle_for_alarm=#{subscribeCycleForAlarm}</if>" +
|
||||
"<if test=\"ssrcCheck != null\">, ssrc_check=#{ssrcCheck}</if>" +
|
||||
"<if test=\"asMessageChannel != null\">, as_message_channel=#{asMessageChannel}</if>" +
|
||||
"<if test=\"broadcastPushAfterAck != null\">, broadcast_push_after_ack=#{broadcastPushAfterAck}</if>" +
|
||||
"<if test=\"geoCoordSys != null\">, geo_coord_sys=#{geoCoordSys}</if>" +
|
||||
"<if test=\"mediaServerId != null\">, media_server_id=#{mediaServerId}</if>" +
|
||||
"WHERE device_id=#{deviceId}"+
|
||||
@ -262,6 +269,7 @@ public interface DeviceMapper {
|
||||
"charset,"+
|
||||
"ssrc_check,"+
|
||||
"as_message_channel,"+
|
||||
"broadcastPushAfterAck,"+
|
||||
"geo_coord_sys,"+
|
||||
"on_line,"+
|
||||
"media_server_id"+
|
||||
@ -275,6 +283,7 @@ public interface DeviceMapper {
|
||||
"#{charset}," +
|
||||
"#{ssrcCheck}," +
|
||||
"#{asMessageChannel}," +
|
||||
"#{broadcastPushAfterAck}," +
|
||||
"#{geoCoordSys}," +
|
||||
"#{onLine}," +
|
||||
"#{mediaServerId}" +
|
||||
|
||||
@ -150,7 +150,7 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
|
||||
+ sendRtpItem.getMediaServerId() + "_"
|
||||
+ sendRtpItem.getPlatformId() + "_"
|
||||
+ sendRtpItem.getChannelId() + "_"
|
||||
+ sendRtpItem.getStreamId() + "_"
|
||||
+ sendRtpItem.getStream() + "_"
|
||||
+ sendRtpItem.getCallId();
|
||||
redisTemplate.opsForValue().set(key, sendRtpItem);
|
||||
}
|
||||
|
||||
@ -96,12 +96,6 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage {
|
||||
@Autowired
|
||||
private PlatformGbStreamMapper platformGbStreamMapper;
|
||||
|
||||
@Autowired
|
||||
private IGbStreamService gbStreamService;
|
||||
|
||||
@Autowired
|
||||
private ParentPlatformMapper parentPlatformMapper;
|
||||
|
||||
/**
|
||||
* 根据设备ID判断设备是否存在
|
||||
*
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
package com.genersoft.iot.vmp.vmanager.bean;
|
||||
|
||||
/**
|
||||
* @author lin
|
||||
*/
|
||||
public class AudioBroadcastResult {
|
||||
/**
|
||||
* 推流的各个方式流地址
|
||||
*/
|
||||
private StreamContent streamInfo;
|
||||
|
||||
/**
|
||||
* 编码格式
|
||||
*/
|
||||
private String codec;
|
||||
|
||||
/**
|
||||
* 向zlm推流的应用名
|
||||
*/
|
||||
private String app;
|
||||
|
||||
/**
|
||||
* 向zlm推流的流ID
|
||||
*/
|
||||
private String stream;
|
||||
|
||||
|
||||
public StreamContent getStreamInfo() {
|
||||
return streamInfo;
|
||||
}
|
||||
|
||||
public void setStreamInfo(StreamContent streamInfo) {
|
||||
this.streamInfo = streamInfo;
|
||||
}
|
||||
|
||||
public String getCodec() {
|
||||
return codec;
|
||||
}
|
||||
|
||||
public void setCodec(String codec) {
|
||||
this.codec = codec;
|
||||
}
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public String getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void setStream(String stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
}
|
||||
@ -26,6 +26,7 @@ import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.*;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||
@ -48,6 +49,10 @@ import java.text.ParseException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
/**
|
||||
* @author lin
|
||||
*/
|
||||
@Tag(name = "国标设备点播")
|
||||
|
||||
@RestController
|
||||
@ -271,68 +276,42 @@ public class PlayController {
|
||||
|
||||
@Operation(summary = "语音广播命令", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
|
||||
@GetMapping("/broadcast/{deviceId}")
|
||||
@PostMapping("/broadcast/{deviceId}")
|
||||
public DeferredResult<String> broadcastApi(@PathVariable String deviceId) {
|
||||
@Parameter(name = "deviceId", description = "通道国标编号", required = true)
|
||||
@Parameter(name = "timeout", description = "推流超时时间(秒)", required = true)
|
||||
@GetMapping("/broadcast/{deviceId}/{channelId}")
|
||||
@PostMapping("/broadcast/{deviceId}/{channelId}")
|
||||
public AudioBroadcastResult broadcastApi(@PathVariable String deviceId, @PathVariable String channelId, Integer timeout, Boolean broadcastMode) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("语音广播API调用");
|
||||
}
|
||||
Device device = storager.queryVideoDevice(deviceId);
|
||||
DeferredResult<String> result = new DeferredResult<>(3 * 1000L);
|
||||
String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + deviceId;
|
||||
if (resultHolder.exist(key, null)) {
|
||||
result.setResult("设备使用中");
|
||||
return result;
|
||||
}
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
if (device == null) {
|
||||
|
||||
resultHolder.put(key, key, result);
|
||||
RequestMessage msg = new RequestMessage();
|
||||
msg.setKey(key);
|
||||
msg.setId(uuid);
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("DeviceID", deviceId);
|
||||
json.put("CmdType", "Broadcast");
|
||||
json.put("Result", "Failed");
|
||||
json.put("Description", "Device 不存在");
|
||||
msg.setData(json);
|
||||
resultHolder.invokeResult(msg);
|
||||
return result;
|
||||
throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到设备: " + deviceId);
|
||||
}
|
||||
try {
|
||||
cmder.audioBroadcastCmd(device, (event) -> {
|
||||
RequestMessage msg = new RequestMessage();
|
||||
msg.setKey(key);
|
||||
msg.setId(uuid);
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("DeviceID", deviceId);
|
||||
json.put("CmdType", "Broadcast");
|
||||
json.put("Result", "Failed");
|
||||
json.put("Description", String.format("语音广播操作失败,错误码: %s, %s", event.statusCode, event.msg));
|
||||
msg.setData(json);
|
||||
resultHolder.invokeResult(msg);
|
||||
});
|
||||
} catch (InvalidArgumentException | SipException | ParseException e) {
|
||||
logger.error("[命令发送失败] 语音广播: {}", e.getMessage());
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
|
||||
if (channelId == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到通道: " + channelId);
|
||||
}
|
||||
|
||||
result.onTimeout(() -> {
|
||||
logger.warn("语音广播操作超时, 设备未返回应答指令");
|
||||
RequestMessage msg = new RequestMessage();
|
||||
msg.setKey(key);
|
||||
msg.setId(uuid);
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("DeviceID", deviceId);
|
||||
json.put("CmdType", "Broadcast");
|
||||
json.put("Result", "Failed");
|
||||
json.put("Error", "Timeout. Device did not response to broadcast command.");
|
||||
msg.setData(json);
|
||||
resultHolder.invokeResult(msg);
|
||||
});
|
||||
resultHolder.put(key, uuid, result);
|
||||
return result;
|
||||
return playService.audioBroadcast(device, channelId, broadcastMode);
|
||||
|
||||
}
|
||||
|
||||
@Operation(summary = "停止语音广播")
|
||||
@Parameter(name = "deviceId", description = "设备Id", required = true)
|
||||
@Parameter(name = "channelId", description = "通道Id", required = true)
|
||||
@GetMapping("/broadcast/stop/{deviceId}/{channelId}")
|
||||
@PostMapping("/broadcast/stop/{deviceId}/{channelId}")
|
||||
public void stopBroadcast(@PathVariable String deviceId, @PathVariable String channelId) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("停止语音广播API调用");
|
||||
}
|
||||
// try {
|
||||
// playService.stopAudioBroadcast(deviceId, channelId);
|
||||
// } catch (InvalidArgumentException | ParseException | SipException e) {
|
||||
// logger.error("[命令发送失败] 停止语音: {}", e.getMessage());
|
||||
// throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage());
|
||||
// }
|
||||
playService.stopAudioBroadcast(deviceId, channelId);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有的ssrc", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
package com.genersoft.iot.vmp.vmanager.gb28181.play.bean;
|
||||
|
||||
|
||||
/**
|
||||
* @author lin
|
||||
*/
|
||||
public interface AudioBroadcastEvent {
|
||||
void call(String msg);
|
||||
}
|
||||
@ -102,7 +102,7 @@ public class PsController {
|
||||
}
|
||||
}
|
||||
String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_PS_INFO + userSetting.getServerId() + "_" + callId + "_" + stream;
|
||||
int localPort = zlmServerFactory.createRTPServer(mediaServerItem, stream, ssrcInt, null, false, tcpMode);
|
||||
int localPort = zlmServerFactory.createRTPServer(mediaServerItem, stream, ssrcInt, null, false, false, tcpMode);
|
||||
if (localPort == 0) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取端口失败");
|
||||
}
|
||||
|
||||
@ -102,8 +102,8 @@ public class RtpController {
|
||||
}
|
||||
}
|
||||
String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + callId + "_" + stream;
|
||||
int localPortForVideo = zlmServerFactory.createRTPServer(mediaServerItem, stream, ssrcInt, null, false, tcpMode);
|
||||
int localPortForAudio = zlmServerFactory.createRTPServer(mediaServerItem, stream + "_a" , ssrcInt, null, false, tcpMode);
|
||||
int localPortForVideo = zlmServerFactory.createRTPServer(mediaServerItem, stream, ssrcInt, null, false, false, tcpMode);
|
||||
int localPortForAudio = zlmServerFactory.createRTPServer(mediaServerItem, stream + "_a" , ssrcInt, null, false, false, tcpMode);
|
||||
if (localPortForVideo == 0 || localPortForAudio == 0) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取端口失败");
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import javax.security.sasl.AuthenticationException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "用户管理")
|
||||
@ -206,4 +207,17 @@ public class UserController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/userInfo")
|
||||
@Operation(summary = "管理员修改普通用户密码")
|
||||
public LoginUser getUserInfo() {
|
||||
// 获取当前登录用户id
|
||||
LoginUser userInfo = SecurityUtils.getUserInfo();
|
||||
|
||||
if (userInfo == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100);
|
||||
}
|
||||
User user = userService.getUser(userInfo.getUsername(), userInfo.getPassword());
|
||||
return new LoginUser(user, LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,12 +217,16 @@ user-settings:
|
||||
push-authority: true
|
||||
# 设备上线时是否自动同步通道
|
||||
sync-channel-on-device-online: false
|
||||
# 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式
|
||||
broadcast-for-platform: UDP
|
||||
# 是否使用设备来源Ip作为回复IP, 不设置则为 false
|
||||
sip-use-source-ip-as-remote-address: false
|
||||
# 是否开启sip日志
|
||||
sip-log: true
|
||||
# 是否开启sql日志
|
||||
sql-log: true
|
||||
# 收到ack消息后开始发流,默认false, 回复200ok后直接开始发流
|
||||
push-stream-after-ack: false
|
||||
# 消息通道功能-缺少国标ID是否给所有上级发送消息
|
||||
send-to-platforms-when-id-lost: true
|
||||
# 保持通道状态,不接受notify通道状态变化, 兼容海康平台发送错误消息
|
||||
|
||||
BIN
src/main/resources/local.jks
Normal file
BIN
src/main/resources/local.jks
Normal file
Binary file not shown.
@ -12,14 +12,14 @@ module.exports = {
|
||||
assetsPublicPath: '/',
|
||||
proxyTable: {
|
||||
'/debug': {
|
||||
target: 'http://localhost:18080',
|
||||
target: 'http://127.0.0.1:18082',
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/debug': '/'
|
||||
}
|
||||
},
|
||||
'/static/snap': {
|
||||
target: 'http://localhost:18080',
|
||||
target: 'http://127.0.0.1:18082',
|
||||
changeOrigin: true,
|
||||
// pathRewrite: {
|
||||
// '^/static/snap': '/static/snap'
|
||||
|
||||
@ -43,6 +43,9 @@
|
||||
<el-option key="UTF-8" label="UTF-8" value="utf-8"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="语音发送通道" prop="name">
|
||||
<el-input v-model="form.audioChannelForReceive" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="地理坐标系" prop="geoCoordSys" >
|
||||
<el-select v-model="form.geoCoordSys" style="float: left; width: 100%" >
|
||||
<el-option key="WGS84" label="WGS84" value="WGS84"></el-option>
|
||||
@ -61,6 +64,7 @@
|
||||
<el-form-item label="其他选项">
|
||||
<el-checkbox label="SSRC校验" v-model="form.ssrcCheck" style="float: left"></el-checkbox>
|
||||
<el-checkbox label="作为消息通道" v-model="form.asMessageChannel" style="float: left"></el-checkbox>
|
||||
<el-checkbox label="收到ACK后发流" v-model="form.broadcastPushAfterAck" style="float: left"></el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div style="float: right;">
|
||||
|
||||
@ -3,20 +3,26 @@
|
||||
|
||||
<el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" @close="close()" v-if="showVideoDialog">
|
||||
<div style="width: 100%; height: 100%">
|
||||
<el-tabs type="card" :stretch="true" v-model="activePlayer" @tab-click="changePlayer" v-if="Object.keys(this.player).length > 1">
|
||||
<!-- <el-tab-pane label="LivePlayer" name="livePlayer">-->
|
||||
<!-- <LivePlayer v-if="showVideoDialog" ref="livePlayer" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasAudio" fluent autoplay live></LivePlayer>-->
|
||||
<!-- </el-tab-pane>-->
|
||||
<el-tabs type="card" :stretch="true" v-model="activePlayer" @tab-click="changePlayer"
|
||||
v-if="Object.keys(this.player).length > 1">
|
||||
<el-tab-pane label="Jessibuca" name="jessibuca">
|
||||
<jessibucaPlayer v-if="activePlayer === 'jessibuca'" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></jessibucaPlayer>
|
||||
<jessibucaPlayer v-if="activePlayer === 'jessibuca'" ref="jessibuca" :visible.sync="showVideoDialog"
|
||||
:videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px"
|
||||
:hasAudio="hasAudio" fluent autoplay live></jessibucaPlayer>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="WebRTC" name="webRTC">
|
||||
<rtc-player v-if="activePlayer === 'webRTC'" ref="webRTC" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player>
|
||||
<rtc-player v-if="activePlayer === 'webRTC'" ref="webRTC" :visible.sync="showVideoDialog"
|
||||
:videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px"
|
||||
:hasAudio="hasAudio" fluent autoplay live></rtc-player>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="h265web">h265web敬请期待</el-tab-pane>
|
||||
</el-tabs>
|
||||
<jessibucaPlayer v-if="Object.keys(this.player).length == 1 && this.player.jessibuca" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></jessibucaPlayer>
|
||||
<rtc-player v-if="Object.keys(this.player).length == 1 && this.player.webRTC" ref="jessibuca" :visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px" :hasAudio="hasAudio" fluent autoplay live ></rtc-player>
|
||||
<jessibucaPlayer v-if="Object.keys(this.player).length == 1 && this.player.jessibuca" ref="jessibuca"
|
||||
:visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError"
|
||||
height="100px" :hasAudio="hasAudio" fluent autoplay live></jessibucaPlayer>
|
||||
<rtc-player v-if="Object.keys(this.player).length == 1 && this.player.webRTC" ref="jessibuca"
|
||||
:visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError"
|
||||
height="100px" :hasAudio="hasAudio" fluent autoplay live></rtc-player>
|
||||
|
||||
</div>
|
||||
<div id="shared" style="text-align: right; margin-top: 1rem;">
|
||||
@ -26,7 +32,8 @@
|
||||
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">播放地址:</span>
|
||||
<el-input v-model="getPlayerShared.sharedUrl" :disabled="true">
|
||||
<template slot="append">
|
||||
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedUrl" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
|
||||
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedUrl"
|
||||
@success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
@ -34,14 +41,17 @@
|
||||
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe:</span>
|
||||
<el-input v-model="getPlayerShared.sharedIframe" :disabled="true">
|
||||
<template slot="append">
|
||||
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedIframe" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
|
||||
<i class="cpoy-btn el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedIframe"
|
||||
@success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
|
||||
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址:</span>
|
||||
<el-input v-model="getPlayerShared.sharedRtmp" :disabled="true">
|
||||
<el-button slot="append" icon="el-icon-document-copy" title="点击拷贝" v-clipboard="getPlayerShared.sharedRtmp" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></el-button>
|
||||
<el-button slot="append" icon="el-icon-document-copy" title="点击拷贝"
|
||||
v-clipboard="getPlayerShared.sharedRtmp"
|
||||
@success="$message({type:'success', message:'成功拷贝到粘贴板'})"></el-button>
|
||||
<el-dropdown slot="prepend" v-if="streamInfo" trigger="click" @command="copyUrl">
|
||||
<el-button>
|
||||
更多地址<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
@ -165,8 +175,12 @@
|
||||
<div class="control-round">
|
||||
<div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
|
||||
</div>
|
||||
<div style="position: absolute; left: 7.25rem; top: 1.25rem" @mousedown="ptzCamera('zoomin')" @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-in control-zoom-btn" style="font-size: 1.875rem;"></i></div>
|
||||
<div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;" @mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')"><i class="el-icon-zoom-out control-zoom-btn"></i></div>
|
||||
<div style="position: absolute; left: 7.25rem; top: 1.25rem" @mousedown="ptzCamera('zoomin')"
|
||||
@mouseup="ptzCamera('stop')"><i class="el-icon-zoom-in control-zoom-btn"
|
||||
style="font-size: 1.875rem;"></i></div>
|
||||
<div style="position: absolute; left: 7.25rem; top: 3.25rem; font-size: 1.875rem;"
|
||||
@mousedown="ptzCamera('zoomout')" @mouseup="ptzCamera('stop')"><i
|
||||
class="el-icon-zoom-out control-zoom-btn"></i></div>
|
||||
<div class="contro-speed" style="position: absolute; left: 4px; top: 7rem; width: 9rem;">
|
||||
<el-slider v-model="controSpeed" :max="255"></el-slider>
|
||||
</div>
|
||||
@ -174,32 +188,84 @@
|
||||
|
||||
<div class="control-panel">
|
||||
<el-button-group>
|
||||
<el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium">预置位编号</el-tag>
|
||||
<el-input-number style="position: absolute; left: 5rem; top: 0rem; width: 6rem" size="mini" v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1" :max="255"></el-input-number>
|
||||
<el-button style="position: absolute; left: 11rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="presetPosition(129, presetPos)">设置</el-button>
|
||||
<el-button style="position: absolute; left: 27rem; top: 0rem; width: 5rem" size="mini" type="primary" icon="el-icon-place" @click="presetPosition(130, presetPos)">调用</el-button>
|
||||
<el-button style="position: absolute; left: 16rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除</el-button>
|
||||
<el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">巡航速度</el-tag>
|
||||
<el-input-number style="position: absolute; left: 5rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
|
||||
<el-button style="position: absolute; left: 11rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置</el-button>
|
||||
<el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">停留时间</el-tag>
|
||||
<el-input-number style="position: absolute; left: 21rem; top: 2.5rem; width: 6rem" size="mini" v-model="cruisingTime" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
|
||||
<el-button style="position: absolute; left: 27rem; top: 2.5rem; width: 5rem" size="mini" icon="el-icon-timer" @click="setSpeedOrTime(135, cruisingGroup, cruisingTime)">设置</el-button>
|
||||
<el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center" size="medium">巡航组编号</el-tag>
|
||||
<el-input-number style="position: absolute; left: 5rem; top: 4.5rem; width: 6rem" size="mini" v-model="cruisingGroup" controls-position="right" :precision="0" :min="0" :max="255"></el-input-number>
|
||||
<el-button style="position: absolute; left: 11rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-add-location" @click="setCommand(132, cruisingGroup, presetPos)">添加点</el-button>
|
||||
<el-button style="position: absolute; left: 16rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="setCommand(133, cruisingGroup, presetPos)">删除点</el-button>
|
||||
<el-button style="position: absolute; left: 21rem; top: 4.5rem; width: 5rem" size="mini" icon="el-icon-delete" @click="setCommand(133, cruisingGroup, 0)">删除组</el-button>
|
||||
<el-button style="position: absolute; left: 27rem; top: 5rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(136, cruisingGroup, 0)">巡航</el-button>
|
||||
<el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center" size="medium">扫描速度</el-tag>
|
||||
<el-input-number style="position: absolute; left: 5rem; top: 7rem; width: 6rem" size="mini" v-model="scanSpeed" controls-position="right" :precision="0" :min="1" :max="4095"></el-input-number>
|
||||
<el-button style="position: absolute; left: 11rem; top: 7rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置</el-button>
|
||||
<el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center" size="medium">扫描组编号</el-tag>
|
||||
<el-input-number style="position: absolute; left: 5rem; top: 9rem; width: 6rem" size="mini" v-model="scanGroup" controls-position="right" :precision="0" :step="1" :min="0" :max="255"></el-input-number>
|
||||
<el-button style="position: absolute; left: 11rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-left" @click="setCommand(137, scanGroup, 1)">左边界</el-button>
|
||||
<el-button style="position: absolute; left: 16rem; top: 9rem; width: 5rem" size="mini" icon="el-icon-d-arrow-right" @click="setCommand(137, scanGroup, 2)">右边界</el-button>
|
||||
<el-button style="position: absolute; left: 27rem; top: 7rem; width: 5rem" size="mini" type="primary" icon="el-icon-video-camera-solid" @click="setCommand(137, scanGroup, 0)">扫描</el-button>
|
||||
<el-button style="position: absolute; left: 27rem; top: 9rem; width: 5rem" size="mini" type="danger" icon="el-icon-switch-button" @click="ptzCamera('stop')">停止</el-button>
|
||||
<el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center"
|
||||
size="medium">预置位编号
|
||||
</el-tag>
|
||||
<el-input-number style="position: absolute; left: 5rem; top: 0rem; width: 6rem" size="mini"
|
||||
v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1"
|
||||
:max="255"></el-input-number>
|
||||
<el-button style="position: absolute; left: 11rem; top: 0rem; width: 5rem" size="mini"
|
||||
icon="el-icon-add-location" @click="presetPosition(129, presetPos)">设置
|
||||
</el-button>
|
||||
<el-button style="position: absolute; left: 27rem; top: 0rem; width: 5rem" size="mini" type="primary"
|
||||
icon="el-icon-place" @click="presetPosition(130, presetPos)">调用
|
||||
</el-button>
|
||||
<el-button style="position: absolute; left: 16rem; top: 0rem; width: 5rem" size="mini"
|
||||
icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除
|
||||
</el-button>
|
||||
<el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center"
|
||||
size="medium">巡航速度
|
||||
</el-tag>
|
||||
<el-input-number style="position: absolute; left: 5rem; top: 2.5rem; width: 6rem" size="mini"
|
||||
v-model="cruisingSpeed" controls-position="right" :precision="0" :min="1"
|
||||
:max="4095"></el-input-number>
|
||||
<el-button style="position: absolute; left: 11rem; top: 2.5rem; width: 5rem" size="mini"
|
||||
icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置
|
||||
</el-button>
|
||||
<el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center"
|
||||
size="medium">停留时间
|
||||
</el-tag>
|
||||
<el-input-number style="position: absolute; left: 21rem; top: 2.5rem; width: 6rem" size="mini"
|
||||
v-model="cruisingTime" controls-position="right" :precision="0" :min="1"
|
||||
:max="4095"></el-input-number>
|
||||
<el-button style="position: absolute; left: 27rem; top: 2.5rem; width: 5rem" size="mini"
|
||||
icon="el-icon-timer" @click="setSpeedOrTime(135, cruisingGroup, cruisingTime)">设置
|
||||
</el-button>
|
||||
<el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center"
|
||||
size="medium">巡航组编号
|
||||
</el-tag>
|
||||
<el-input-number style="position: absolute; left: 5rem; top: 4.5rem; width: 6rem" size="mini"
|
||||
v-model="cruisingGroup" controls-position="right" :precision="0" :min="0"
|
||||
:max="255"></el-input-number>
|
||||
<el-button style="position: absolute; left: 11rem; top: 4.5rem; width: 5rem" size="mini"
|
||||
icon="el-icon-add-location" @click="setCommand(132, cruisingGroup, presetPos)">添加点
|
||||
</el-button>
|
||||
<el-button style="position: absolute; left: 16rem; top: 4.5rem; width: 5rem" size="mini"
|
||||
icon="el-icon-delete-location" @click="setCommand(133, cruisingGroup, presetPos)">删除点
|
||||
</el-button>
|
||||
<el-button style="position: absolute; left: 21rem; top: 4.5rem; width: 5rem" size="mini"
|
||||
icon="el-icon-delete" @click="setCommand(133, cruisingGroup, 0)">删除组
|
||||
</el-button>
|
||||
<el-button style="position: absolute; left: 27rem; top: 5rem; width: 5rem" size="mini" type="primary"
|
||||
icon="el-icon-video-camera-solid" @click="setCommand(136, cruisingGroup, 0)">巡航
|
||||
</el-button>
|
||||
<el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center"
|
||||
size="medium">扫描速度
|
||||
</el-tag>
|
||||
<el-input-number style="position: absolute; left: 5rem; top: 7rem; width: 6rem" size="mini"
|
||||
v-model="scanSpeed" controls-position="right" :precision="0" :min="1"
|
||||
:max="4095"></el-input-number>
|
||||
<el-button style="position: absolute; left: 11rem; top: 7rem; width: 5rem" size="mini"
|
||||
icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置
|
||||
</el-button>
|
||||
<el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center"
|
||||
size="medium">扫描组编号
|
||||
</el-tag>
|
||||
<el-input-number style="position: absolute; left: 5rem; top: 9rem; width: 6rem" size="mini"
|
||||
v-model="scanGroup" controls-position="right" :precision="0" :step="1" :min="0"
|
||||
:max="255"></el-input-number>
|
||||
<el-button style="position: absolute; left: 11rem; top: 9rem; width: 5rem" size="mini"
|
||||
icon="el-icon-d-arrow-left" @click="setCommand(137, scanGroup, 1)">左边界
|
||||
</el-button>
|
||||
<el-button style="position: absolute; left: 16rem; top: 9rem; width: 5rem" size="mini"
|
||||
icon="el-icon-d-arrow-right" @click="setCommand(137, scanGroup, 2)">右边界
|
||||
</el-button>
|
||||
<el-button style="position: absolute; left: 27rem; top: 7rem; width: 5rem" size="mini" type="primary"
|
||||
icon="el-icon-video-camera-solid" @click="setCommand(137, scanGroup, 0)">扫描
|
||||
</el-button>
|
||||
<el-button style="position: absolute; left: 27rem; top: 9rem; width: 5rem" size="mini" type="danger"
|
||||
icon="el-icon-switch-button" @click="ptzCamera('stop')">停止
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
@ -231,6 +297,24 @@
|
||||
</div>
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="语音对讲" name="broadcast">
|
||||
<div style="padding: 0 10px">
|
||||
<el-switch v-model="broadcastMode" :disabled="broadcastStatus !== -1" active-color="#409EFF"
|
||||
active-text="喊话(Broadcast)"
|
||||
inactive-text="对讲(Talk)"></el-switch>
|
||||
</div>
|
||||
<div class="trank" style="text-align: center;">
|
||||
<el-button @click="broadcastStatusClick()" :type="getBroadcastStatus()" :disabled="broadcastStatus === -2"
|
||||
circle icon="el-icon-microphone" style="font-size: 32px; padding: 24px;margin-top: 24px;"/>
|
||||
<p>
|
||||
<span v-if="broadcastStatus === -2">正在释放资源</span>
|
||||
<span v-if="broadcastStatus === -1">点击开始对讲</span>
|
||||
<span v-if="broadcastStatus === 0">等待接通中...</span>
|
||||
<span v-if="broadcastStatus === 1">请说话</span>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</div>
|
||||
@ -241,7 +325,9 @@
|
||||
<script>
|
||||
import rtcPlayer from '../dialog/rtcPlayer.vue'
|
||||
import LivePlayer from '@liveqing/liveplayer'
|
||||
import crypto from 'crypto'
|
||||
import jessibucaPlayer from '../common/jessibuca.vue'
|
||||
|
||||
export default {
|
||||
name: 'devicePlayer',
|
||||
props: {},
|
||||
@ -258,7 +344,9 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
console.log("created")
|
||||
console.log(this.player)
|
||||
this.broadcastStatus = -1;
|
||||
if (Object.keys(this.player).length === 1) {
|
||||
this.activePlayer = Object.keys(this.player)[0]
|
||||
}
|
||||
@ -271,7 +359,6 @@ export default {
|
||||
// 如何你只是用一种播放器,直接注释掉不用的部分即可
|
||||
player: {
|
||||
jessibuca: ["ws_flv", "wss_flv"],
|
||||
livePlayer : ["ws_flv", "wss_flv"],
|
||||
webRTC: ["rtc", "rtcs"],
|
||||
},
|
||||
showVideoDialog: false,
|
||||
@ -307,6 +394,9 @@ export default {
|
||||
recordStartTime: 0,
|
||||
showTimeText: "00:00:00",
|
||||
streamInfo: null,
|
||||
broadcastMode: true,
|
||||
broadcastRtc: null,
|
||||
broadcastStatus: -1, // -2 正在释放资源 -1 默认状态 0 等待接通 1 接通成功
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@ -332,7 +422,8 @@ export default {
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
}).catch(function (e) {});
|
||||
}).catch(function (e) {
|
||||
});
|
||||
}
|
||||
},
|
||||
changePlayer: function (tab) {
|
||||
@ -441,7 +532,8 @@ export default {
|
||||
console.error(res.data.msg)
|
||||
}
|
||||
if (callback) callback();
|
||||
}).catch(function (e) {});
|
||||
}).catch(function (e) {
|
||||
});
|
||||
that.coverPlaying = false;
|
||||
that.convertKey = "";
|
||||
// if (callback )callback();
|
||||
@ -457,8 +549,6 @@ export default {
|
||||
this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
close: function () {
|
||||
console.log('关闭视频');
|
||||
@ -472,6 +562,7 @@ export default {
|
||||
this.convertStop();
|
||||
}
|
||||
this.convertKey = ''
|
||||
this.stopBroadcast()
|
||||
},
|
||||
|
||||
copySharedInfo: function (data) {
|
||||
@ -502,7 +593,8 @@ export default {
|
||||
this.$axios({
|
||||
method: 'post',
|
||||
url: '/api/ptz/control/' + this.deviceId + '/' + this.channelId + '?command=' + command + '&horizonSpeed=' + this.controSpeed + '&verticalSpeed=' + this.controSpeed + '&zoomSpeed=' + this.controSpeed
|
||||
}).then(function (res) {});
|
||||
}).then(function (res) {
|
||||
});
|
||||
},
|
||||
//////////////////////播放器事件处理//////////////////////////
|
||||
videoError: function (e) {
|
||||
@ -514,7 +606,8 @@ export default {
|
||||
this.$axios({
|
||||
method: 'post',
|
||||
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=0¶meter2=' + presetPos + '&combindCode2=0'
|
||||
}).then(function (res) {});
|
||||
}).then(function (res) {
|
||||
});
|
||||
},
|
||||
setSpeedOrTime: function (cmdCode, groupNum, parameter) {
|
||||
let that = this;
|
||||
@ -524,7 +617,8 @@ export default {
|
||||
this.$axios({
|
||||
method: 'post',
|
||||
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=' + groupNum + '¶meter2=' + parameter2 + '&combindCode2=' + combindCode2
|
||||
}).then(function (res) {});
|
||||
}).then(function (res) {
|
||||
});
|
||||
},
|
||||
setCommand: function (cmdCode, groupNum, parameter) {
|
||||
let that = this;
|
||||
@ -532,7 +626,8 @@ export default {
|
||||
this.$axios({
|
||||
method: 'post',
|
||||
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=' + groupNum + '¶meter2=' + parameter + '&combindCode2=0'
|
||||
}).then(function (res) {});
|
||||
}).then(function (res) {
|
||||
});
|
||||
},
|
||||
copyUrl: function (dropdownItem) {
|
||||
console.log(dropdownItem)
|
||||
@ -542,9 +637,168 @@ export default {
|
||||
|
||||
})
|
||||
},
|
||||
getBroadcastStatus() {
|
||||
if (this.broadcastStatus == -2) {
|
||||
return "primary"
|
||||
}
|
||||
if (this.broadcastStatus == -1) {
|
||||
return "primary"
|
||||
}
|
||||
if (this.broadcastStatus == 0) {
|
||||
return "warning"
|
||||
}
|
||||
if (this.broadcastStatus == 1) {
|
||||
return "danger"
|
||||
}
|
||||
|
||||
},
|
||||
broadcastStatusClick() {
|
||||
if (this.broadcastStatus == -1) {
|
||||
// 默认状态, 开始
|
||||
this.broadcastStatus = 0
|
||||
// 发起语音对讲
|
||||
this.$axios({
|
||||
method: 'get',
|
||||
url: '/api/play/broadcast/' + this.deviceId + '/' + this.channelId + "?timeout=30&broadcastMode=" + this.broadcastMode
|
||||
}).then((res) => {
|
||||
if (res.data.code === 0) {
|
||||
let streamInfo = res.data.data.streamInfo;
|
||||
if (document.location.protocol.includes("https")) {
|
||||
this.startBroadcast(streamInfo.rtcs)
|
||||
} else {
|
||||
this.startBroadcast(streamInfo.rtc)
|
||||
}
|
||||
} else {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: res.data.msg,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (this.broadcastStatus === 1) {
|
||||
this.broadcastStatus = -1;
|
||||
this.broadcastRtc.close()
|
||||
}
|
||||
},
|
||||
startBroadcast(url) {
|
||||
// 获取推流鉴权Key
|
||||
this.$axios({
|
||||
method: 'post',
|
||||
url: '/api/user/userInfo',
|
||||
}).then((res) => {
|
||||
if (res.data.code !== 0) {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: "获取推流鉴权Key失败",
|
||||
type: "error",
|
||||
});
|
||||
this.broadcastStatus = -1;
|
||||
} else {
|
||||
let pushKey = res.data.data.pushKey;
|
||||
// 获取推流鉴权KEY
|
||||
url += "&sign=" + crypto.createHash('md5').update(pushKey, "utf8").digest('hex')
|
||||
console.log("开始语音喊话: " + url)
|
||||
this.broadcastRtc = new ZLMRTCClient.Endpoint({
|
||||
debug: true, // 是否打印日志
|
||||
zlmsdpUrl: url, //流地址
|
||||
simulecast: false,
|
||||
useCamera: false,
|
||||
audioEnable: true,
|
||||
videoEnable: false,
|
||||
recvOnly: false,
|
||||
})
|
||||
|
||||
// webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,(e)=>{//获取到了远端流,可以播放
|
||||
// console.error('播放成功',e.streams)
|
||||
// this.broadcastStatus = 1;
|
||||
// });
|
||||
//
|
||||
// webrtcPlayer.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,(s)=>{// 获取到了本地流
|
||||
// this.broadcastStatus = 1;
|
||||
// // document.getElementById('selfVideo').srcObject=s;
|
||||
// // this.eventcallbacK("LOCAL STREAM", "获取到了本地流")
|
||||
// });
|
||||
|
||||
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_NOT_SUPPORT, (e) => {// 获取到了本地流
|
||||
console.error('不支持webrtc', e)
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '不支持webrtc, 无法进行语音喊话',
|
||||
type: 'error'
|
||||
});
|
||||
this.broadcastStatus = -1;
|
||||
});
|
||||
|
||||
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, (e) => {// ICE 协商出错
|
||||
console.error('ICE 协商出错')
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: 'ICE 协商出错',
|
||||
type: 'error'
|
||||
});
|
||||
this.broadcastStatus = -1;
|
||||
});
|
||||
|
||||
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, (e) => {// offer anwser 交换失败
|
||||
console.error('offer anwser 交换失败', e)
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: 'offer anwser 交换失败' + e,
|
||||
type: 'error'
|
||||
});
|
||||
this.broadcastStatus = -1;
|
||||
});
|
||||
this.broadcastRtc.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE, (e) => {// offer anwser 交换失败
|
||||
console.log('状态改变', e)
|
||||
if (e === "connecting") {
|
||||
this.broadcastStatus = 0;
|
||||
} else if (e === "connected") {
|
||||
this.broadcastStatus = 1;
|
||||
} else if (e === "disconnected") {
|
||||
this.broadcastStatus = -1;
|
||||
}
|
||||
});
|
||||
this.broadcastRtc.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED, (e) => {// offer anwser 交换失败
|
||||
console.log('捕获流失败', e)
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: '捕获流失败' + e,
|
||||
type: 'error'
|
||||
});
|
||||
this.broadcastStatus = -1;
|
||||
});
|
||||
}
|
||||
}).catch((e) => {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: e,
|
||||
type: 'error'
|
||||
});
|
||||
this.broadcastStatus = -1;
|
||||
});
|
||||
|
||||
|
||||
|
||||
},
|
||||
stopBroadcast() {
|
||||
this.broadcastRtc.close();
|
||||
this.broadcastStatus = -1;
|
||||
this.$axios({
|
||||
method: 'get',
|
||||
url: '/api/play/broadcast/stop/' + this.deviceId + '/' + this.channelId
|
||||
}).then((res) => {
|
||||
if (res.data.code == 0) {
|
||||
// this.broadcastStatus = -1;
|
||||
// this.broadcastRtc.close()
|
||||
} else {
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: res.data.msg,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -581,6 +835,7 @@ export default {
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
cursor: pointer
|
||||
}
|
||||
@ -592,9 +847,11 @@ export default {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.control-btn i:hover {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.control-zoom-btn:hover {
|
||||
cursor: pointer
|
||||
}
|
||||
@ -724,6 +981,7 @@ export default {
|
||||
.control-bottom .fa {
|
||||
transform: rotate(-45deg) translateY(7px);
|
||||
}
|
||||
|
||||
.trank {
|
||||
width: 80%;
|
||||
height: 180px;
|
||||
@ -731,6 +989,7 @@ export default {
|
||||
padding: 0 10%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.trankInfo {
|
||||
width: 80%;
|
||||
padding: 0 10%;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user