mirror of
https://gitee.com/pan648540858/wvp-GB28181-pro.git
synced 2026-05-26 06:57:50 +08:00
Merge branch 'master' into 结构优化
# Conflicts: # src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java # src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java # src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java # src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java # src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java # src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java # src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java # src/main/java/com/genersoft/iot/vmp/service/IMediaService.java # src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java # src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java # src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java # src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java # src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java # src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java # src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java # src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamCloseResponseListener.java # src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java # src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java # 数据库/初始化-postgresql-kingbase.sql
This commit is contained in:
commit
3c092f4195
130
DOCKERFILE
130
DOCKERFILE
@ -1,130 +0,0 @@
|
|||||||
#很久没维护了,已经与定前版本不匹配
|
|
||||||
|
|
||||||
|
|
||||||
FROM ubuntu:20.04 AS build
|
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
|
||||||
ENV TZ=Asia/Shanghai
|
|
||||||
|
|
||||||
EXPOSE 18080/tcp
|
|
||||||
|
|
||||||
EXPOSE 5060/tcp
|
|
||||||
EXPOSE 5060/udp
|
|
||||||
|
|
||||||
EXPOSE 6379/tcp
|
|
||||||
|
|
||||||
EXPOSE 18081/tcp
|
|
||||||
|
|
||||||
EXPOSE 80/tcp
|
|
||||||
EXPOSE 1935/tcp
|
|
||||||
EXPOSE 554/tcp
|
|
||||||
EXPOSE 554/udp
|
|
||||||
EXPOSE 30000-30500/tcp
|
|
||||||
EXPOSE 30000-30500/udp
|
|
||||||
|
|
||||||
ENV LC_ALL zh_CN.UTF-8
|
|
||||||
|
|
||||||
# 使用了自己的settings.xml作为maven的源,加快打包速度
|
|
||||||
RUN apt-get update && \
|
|
||||||
DEBIAN_FRONTEND="noninteractive" && \
|
|
||||||
apt-get install -y --no-install-recommends openjdk-11-jre git maven nodejs npm build-essential tcl language-pack-zh-hans \
|
|
||||||
cmake curl vim ca-certificates tzdata libmysqlclient-dev redis-server libssl-dev libx264-dev libfaac-dev ffmpeg
|
|
||||||
WORKDIR /home
|
|
||||||
|
|
||||||
RUN git clone https://gitee.com/pan648540858/maven.git && \
|
|
||||||
cp maven/settings.xml /usr/share/maven/conf/ && \
|
|
||||||
git clone https://gitee.com/pan648540858/wvp-GB28181.git && \
|
|
||||||
git clone https://gitee.com/pan648540858/wvp-pro-assist.git
|
|
||||||
# 编译前端界面
|
|
||||||
WORKDIR /home/wvp-GB28181/web_src
|
|
||||||
|
|
||||||
RUN npm install && \
|
|
||||||
npm run build && \
|
|
||||||
mkdir -p /opt/wvp/config && \
|
|
||||||
mkdir -p /opt/assist/config && \
|
|
||||||
cp /home/wvp-GB28181/src/main/resources/application-dev.yml /opt/wvp/config/application.yml && \
|
|
||||||
cp /home/wvp-pro-assist/src/main/resources/application-dev.yml /opt/assist/config/application.yml
|
|
||||||
|
|
||||||
# wvp打包
|
|
||||||
WORKDIR /home/wvp-GB28181
|
|
||||||
RUN mvn compile && \
|
|
||||||
mvn package && \
|
|
||||||
cp /home/wvp-GB28181/target/wvp*.jar /opt/wvp/
|
|
||||||
|
|
||||||
# wvp 录像管理打包
|
|
||||||
WORKDIR /home/wvp-pro-assist
|
|
||||||
RUN mvn compile && \
|
|
||||||
mvn package && \
|
|
||||||
cp /home/wvp-pro-assist/target/*.jar /opt/assist/
|
|
||||||
|
|
||||||
# zlm打包
|
|
||||||
WORKDIR /home
|
|
||||||
RUN mkdir -p /opt/media && \
|
|
||||||
git clone --depth=1 https://gitee.com/xia-chu/ZLMediaKit && \
|
|
||||||
cd ZLMediaKit && git submodule update --init --recursive && \
|
|
||||||
mkdir -p build release/linux/Release/ &&\
|
|
||||||
cd build && \
|
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release .. && \
|
|
||||||
make -j4 && \
|
|
||||||
rm -rf ../release/linux/Release/config.ini && \
|
|
||||||
cp -r ../release/linux/Release/* /opt/media && \
|
|
||||||
mkdir -p /opt/media/www/record
|
|
||||||
|
|
||||||
|
|
||||||
# 清理
|
|
||||||
RUN rm -rf /home/wiki && \
|
|
||||||
rm -rf /home/wvp-GB28181 && \
|
|
||||||
apt-get autoremove -y git maven nodejs npm && \
|
|
||||||
apt-get clean -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/*dic
|
|
||||||
|
|
||||||
WORKDIR /opt/wvp
|
|
||||||
RUN echo '#!/bin/bash' > run.sh && \
|
|
||||||
echo 'echo ${WVP_IP}' >> run.sh && \
|
|
||||||
echo 'echo ${WVP_CONFIG}' >> run.sh && \
|
|
||||||
echo 'redis-server --daemonize yes --bind 0.0.0.0' >> run.sh && \
|
|
||||||
echo 'cd /opt/assist' >> run.sh && \
|
|
||||||
echo 'nohup java -jar *.jar --userSettings.record=/opt/media/www/record/ &' >> run.sh && \
|
|
||||||
echo 'nohup /opt/media/MediaServer -d -m 3 &' >> run.sh && \
|
|
||||||
echo 'cd /opt/wvp' >> run.sh && \
|
|
||||||
echo 'if [-n "${WVP_CONFIG}"]; then' >> run.sh && \
|
|
||||||
echo ' java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 ${WVP_CONFIG}' >> run.sh && \
|
|
||||||
echo 'else' >> run.sh && \
|
|
||||||
echo ' java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 --media.ip=127.0.0.1 --media.sdp-ip=${WVP_IP} --sip.ip=${WVP_IP} --media.stream-ip=${WVP_IP}' >> run.sh && \
|
|
||||||
echo 'fi' >> run.sh
|
|
||||||
RUN chmod +x run.sh
|
|
||||||
|
|
||||||
FROM ubuntu:20.04
|
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
|
||||||
ENV TZ=Asia/Shanghai
|
|
||||||
|
|
||||||
EXPOSE 18080/tcp
|
|
||||||
|
|
||||||
EXPOSE 5060/tcp
|
|
||||||
EXPOSE 5060/udp
|
|
||||||
|
|
||||||
EXPOSE 6379/tcp
|
|
||||||
|
|
||||||
EXPOSE 18081/tcp
|
|
||||||
|
|
||||||
EXPOSE 80/tcp
|
|
||||||
EXPOSE 1935/tcp
|
|
||||||
EXPOSE 554/tcp
|
|
||||||
EXPOSE 554/udp
|
|
||||||
EXPOSE 30000-30500/tcp
|
|
||||||
EXPOSE 30000-30500/udp
|
|
||||||
|
|
||||||
ENV LC_ALL zh_CN.UTF-8
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
DEBIAN_FRONTEND="noninteractive" && \
|
|
||||||
apt-get install -y --no-install-recommends openjdk-11-jre tcl language-pack-zh-hans \
|
|
||||||
ca-certificates tzdata libmysqlclient21 redis-server libssl1.1 libx264-155 libfaac0 ffmpeg && \
|
|
||||||
apt-get autoremove -y && \
|
|
||||||
apt-get clean -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/*dic
|
|
||||||
|
|
||||||
WORKDIR /opt/wvp
|
|
||||||
COPY --from=build /opt /opt
|
|
||||||
CMD ["sh", "run.sh"]
|
|
||||||
10
README.md
10
README.md
@ -68,6 +68,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
|
|||||||
- [X] 支持国标网络校时
|
- [X] 支持国标网络校时
|
||||||
- [X] 支持播放H264和H265
|
- [X] 支持播放H264和H265
|
||||||
- [X] 报警信息处理,支持向前端推送报警信息
|
- [X] 报警信息处理,支持向前端推送报警信息
|
||||||
|
- [X] 语音对讲
|
||||||
- [X] 支持订阅与通知方法
|
- [X] 支持订阅与通知方法
|
||||||
- [X] 移动位置订阅
|
- [X] 移动位置订阅
|
||||||
- [X] 移动位置通知处理
|
- [X] 移动位置通知处理
|
||||||
@ -94,6 +95,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
|
|||||||
- [X] 目录订阅与通知
|
- [X] 目录订阅与通知
|
||||||
- [X] 录像查看与播放
|
- [X] 录像查看与播放
|
||||||
- [X] GPS订阅与通知(直播推流)
|
- [X] GPS订阅与通知(直播推流)
|
||||||
|
- [X] 语音对讲
|
||||||
- [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题;
|
- [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题;
|
||||||
- [X] 多流媒体节点,自动选择负载最低的节点使用。
|
- [X] 多流媒体节点,自动选择负载最低的节点使用。
|
||||||
- [X] 支持启用udp多端口模式, 提高udp模式下媒体传输性能;
|
- [X] 支持启用udp多端口模式, 提高udp模式下媒体传输性能;
|
||||||
@ -135,3 +137,11 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git
|
|||||||
[mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001)
|
[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
|
||||||
|
|
||||||
|
|||||||
@ -55,8 +55,8 @@
|
|||||||
- [X] 移动设备位置订阅
|
- [X] 移动设备位置订阅
|
||||||
- [X] 报警订阅
|
- [X] 报警订阅
|
||||||
- [X] 目录订阅
|
- [X] 目录订阅
|
||||||
- [ ] 语音广播
|
- [X] 语音广播
|
||||||
- [ ] 语音对讲
|
- [X] 语音喊话
|
||||||
|
|
||||||
**作为下级平台**
|
**作为下级平台**
|
||||||
- [X] 注册
|
- [X] 注册
|
||||||
@ -94,8 +94,8 @@
|
|||||||
- [X] 移动设备位置订阅
|
- [X] 移动设备位置订阅
|
||||||
- [ ] 报警订阅
|
- [ ] 报警订阅
|
||||||
- [X] 目录订阅
|
- [X] 目录订阅
|
||||||
- [ ] 语音广播
|
- [X] 语音广播
|
||||||
- [ ] 语音对讲
|
- [X] 语音喊话
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
76
doc/_content/ability/continuous_broadcast.md
Normal file
76
doc/_content/ability/continuous_broadcast.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# 语音对讲
|
||||||
|
## 流程和原理
|
||||||
|
语音对讲在国标28181-2016中分为broadcast(广播)和talk(对讲)两种模式,broadcast模式是从服务端把音频传送到设备端,是单向的,
|
||||||
|
需要结合点播视频来实现双向对讲,talk模式支持双向,不过wvp只处理了和broadcast一样的把音频传递设备,这样两种模式可以使用一样的逻辑处理即可。
|
||||||
|
不同的设备对于两种模式的支持不同且通常差异很大,不同的设备对同一个设备的支持也有一些不同,所以语音对讲中的兼容和适配也是问题最多的。talk模式因为在国标28181-2022中已经移除,所以这里不再讨论它了。
|
||||||
|
### 1. broadcast模式流程
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
"WVP-PRO" -> "设备": 语音广播通知
|
||||||
|
"WVP-PRO" <-- "设备": 200OK
|
||||||
|
"WVP-PRO" <- "设备": 语音广播应答
|
||||||
|
"WVP-PRO" --> "设备": 200OK
|
||||||
|
"WVP-PRO" <- "设备": Invite
|
||||||
|
"WVP-PRO" --> "设备": 200OK(携带SDP消息体)
|
||||||
|
"WVP-PRO" <-- "设备": ACK
|
||||||
|
"ZLMediaKit" -> "设备": 向设备发送语音流
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
与点播的流程不同的是,这里的invite消息是由设备发送给wvp的,wvp按照invite协商的方式给设备推送语音流,所有对讲的使用那种方式(UDP/TCP被动/TCP主动)传输语音流由设备决定
|
||||||
|
## 使用条件与限制
|
||||||
|
因为invite消息是由设备发送给wvp的,这决定了发送语音流的方式,这也就决定了有的设备不能用于公网对讲,比如大部分的海康设备只支持udp方式收流(目前新版的海康设备已经在着手解决这个问题),那么wvp发流时只能按照sdp中指定的ip端口发流,所以如果wvp在公网,设备在内网中,那么wvp无法连接设备提供的IP,发流也就失败了。
|
||||||
|
与海康不同的,大华以及很多执法记录仪厂商是支持tcp主动方式取流的,这样是可以实现公网对讲的。
|
||||||
|
|
||||||
|
|
||||||
|
## 使用ffmpeg快速测试
|
||||||
|
由于浏览器对于音频的采集需要网页支持https才可以,所以如果想要实现网页音频对讲,那么你必须给wvp和zlm配置证书以使用https。
|
||||||
|
测试阶段如果只是想测试功能可以用ffmpeg来模拟语音流,推送到wvp后可以实现把音频文件推送到摄像头。
|
||||||
|
测试命令格式如下:
|
||||||
|
```shell
|
||||||
|
ffmpeg -re -i {音频文件} -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp 'rtsp://{zlm的IP}:{zlm的RTSP端口}/broadcast/{设备国标编号}_{通道国标编号}?sign={md5(pushKey)}'
|
||||||
|
```
|
||||||
|
例如
|
||||||
|
```shell
|
||||||
|
ffmpeg -re -i test.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp 'rtsp://192.168.1.3:22554/broadcast/34020000001320000001_34020000001320000001?sign=41db35390ddad33f83944f44b8b75ded'
|
||||||
|
```
|
||||||
|
测试流程如下:
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
"FFMPEG" -> "ZLMediaKit": 推流到zlm
|
||||||
|
"WVP-PRO" <- "ZLMediaKit": 通知收到语音对讲推流,携带设备和通道信息
|
||||||
|
"WVP-PRO" -> "设备": 开始语音对讲
|
||||||
|
"WVP-PRO" <-- "设备": 语音对讲建立成功,携带收流端口
|
||||||
|
"WVP-PRO" -> "ZLMediaKit": 通知zlm将流推送到设备收流端口
|
||||||
|
"ZLMediaKit" -> "设备": 向设备推流
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
如果听到设备播放你推送的音频,那么意味着调用成功,此过程推流即可需要调用任何接口
|
||||||
|
## 生产环境网页发起语音对讲
|
||||||
|
生产环境下使用语音对讲,如果是自己的客户端设备那么直接上面的ffmpeg测试方式,按照固定格式推流到zlm即可。
|
||||||
|
对于WEB程序,主要是局域网和公网的区别,两个原因:
|
||||||
|
1. 很多设备不支持公网对讲
|
||||||
|
2. 公网和局域网获取证书实现https支持的方式不同
|
||||||
|
### 公网使用
|
||||||
|
公网你可以直接使用证书厂商或者云服务器厂商提供的证书,这是很方便的。
|
||||||
|
### 局域网使用
|
||||||
|
局域网你需要为wvp和zlm生成自签名证书,这里我推荐一种生成自签名证书相对方便的方式,
|
||||||
|
此方式为linux下的一种方式。
|
||||||
|
下载证书生成工具:
|
||||||
|
[https://github.com/FiloSottile/mkcert/releases/tag/v1.4.4](https://github.com/FiloSottile/mkcert/releases/tag/v1.4.4)
|
||||||
|
安装此工具, 进入解压的工具目录,执行
|
||||||
|
```shell
|
||||||
|
./mkcert-v1.4.4-linux-amd64 -install
|
||||||
|
```
|
||||||
|
生成pem证书
|
||||||
|
```shell
|
||||||
|
./mkcert-v1.4.4-linux-amd64 局域网IP 局域网IP2 局域网IP3
|
||||||
|
```
|
||||||
|
你会得到两文件*-key.pem和*.pem, 此文件配置到wvp后既可实现证书的加载
|
||||||
|
生成zlm使用的证书
|
||||||
|
```shell
|
||||||
|
cat *.pem *-key.pem> ./zlm.pem
|
||||||
|
```
|
||||||
|
得到的文件就是可以给zlm使用的证书
|
||||||
|
zlm下使用证书有两种方式:
|
||||||
|
1. 替换zlm下的default.pem, 即删除此文件并把zlm.pem重命名为default.pem
|
||||||
|
2. 在启动zlm的使用添加 `-s zlm.pem`
|
||||||
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部署在同一台主机;
|
4. WVP-PRO与ZLM支持分开部署,但是wvp-pro-assist必须与zlm部署在同一台主机;
|
||||||
5. 生产环境按需开放端口,但是建议修改默认端口,尤其是5060端口,易受到攻击;
|
5. 生产环境按需开放端口,但是建议修改默认端口,尤其是5060端口,易受到攻击;
|
||||||
6. zlm使用docker部署的情况,要求端口映射一致,比如映射5060,应将外部端口也映射为5060端口;
|
6. zlm使用docker部署的情况,要求端口映射一致,比如映射5060,应将外部端口也映射为5060端口;
|
||||||
7. 启动服务,以linux为例
|
7. zlm与wvp会保持高频率的通信,所以不要去将wvp与zlm分属在两个网络,比如wvp在内网,zlm却在公网的情况。
|
||||||
### 启动WVP-PRO
|
8. 启动服务,以linux为例
|
||||||
**jar包:**
|
**启动WVP-PRO**
|
||||||
```shell
|
```shell
|
||||||
nohup java -jar wvp-pro-*.jar &
|
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,会话结束成功。
|
||||||
@ -15,11 +15,13 @@
|
|||||||
* [节点管理](_content/ability/node_manger.md)
|
* [节点管理](_content/ability/node_manger.md)
|
||||||
* [云端录像](_content/ability/cloud_record.md)
|
* [云端录像](_content/ability/cloud_record.md)
|
||||||
* [不间断录像](_content/ability/continuous_recording.md)
|
* [不间断录像](_content/ability/continuous_recording.md)
|
||||||
|
* [语音对讲](_content/ability/continuous_broadcast.md)
|
||||||
* **流程与原理**
|
* **流程与原理**
|
||||||
* [统一编码规则](_content/theory/code.md)
|
* [统一编码规则](_content/theory/code.md)
|
||||||
* [树形结构](_content/theory/channel_tree.md)
|
* [树形结构](_content/theory/channel_tree.md)
|
||||||
* [注册流程](_content/theory/register.md)
|
* [注册流程](_content/theory/register.md)
|
||||||
* [点播流程](_content/theory/play.md)
|
* [点播流程](_content/theory/play.md)
|
||||||
|
* [级联语音喊话流程](_content/theory/broadcast_cascade.md)
|
||||||
* **必备技巧**
|
* **必备技巧**
|
||||||
* [抓包](_content/skill/tcpdump.md)
|
* [抓包](_content/skill/tcpdump.md)
|
||||||
|
|
||||||
|
|||||||
@ -3,5 +3,7 @@ package com.genersoft.iot.vmp.common;
|
|||||||
public enum InviteSessionType {
|
public enum InviteSessionType {
|
||||||
PLAY,
|
PLAY,
|
||||||
PLAYBACK,
|
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) {
|
if (callIdParam != null) {
|
||||||
callIdParam = Objects.equals(callIdParam, "") ? callIdParam : callIdParam.replace("?", "&");
|
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) {
|
if (port > 0) {
|
||||||
this.rtc = new StreamURL("http", host, port, file);
|
this.rtc = new StreamURL("http", host, port, file);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,6 +49,7 @@ public class VideoManagerConstants {
|
|||||||
public static final String SYSTEM_INFO_NET_PREFIX = "VMP_SYSTEM_INFO_NET_";
|
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 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 REGISTER_EXPIRE_TASK_KEY_PREFIX = "VMP_device_register_expire_";
|
||||||
public static final String PUSH_STREAM_LIST = "VMP_PUSH_STREAM_LIST_";
|
public static final String PUSH_STREAM_LIST = "VMP_PUSH_STREAM_LIST_";
|
||||||
|
|||||||
@ -32,7 +32,7 @@ public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
|
|||||||
@Override
|
@Override
|
||||||
public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType, @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) {
|
public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType, @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) {
|
||||||
// 排除api文档的接口,这个接口不需要统一
|
// 排除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) {
|
for (String path : excludePath) {
|
||||||
if (request.getURI().getPath().startsWith(path)) {
|
if (request.getURI().getPath().startsWith(path)) {
|
||||||
return body;
|
return body;
|
||||||
@ -59,8 +59,8 @@ public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
|
|||||||
* 防止返回string时出错
|
* 防止返回string时出错
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Bean
|
/*@Bean
|
||||||
public HttpMessageConverters fast() {
|
public HttpMessageConverters custHttpMessageConverter() {
|
||||||
return new HttpMessageConverters(new FastJsonHttpMessageConverter());
|
return new HttpMessageConverters(new FastJsonHttpMessageConverter());
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,7 +97,7 @@ public class MediaConfig{
|
|||||||
|
|
||||||
public String getHookIp() {
|
public String getHookIp() {
|
||||||
if (ObjectUtils.isEmpty(hookIp)){
|
if (ObjectUtils.isEmpty(hookIp)){
|
||||||
return sipIp.split(",")[0];
|
return sipIp;
|
||||||
}else {
|
}else {
|
||||||
return hookIp;
|
return hookIp;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ public class UserSetting {
|
|||||||
|
|
||||||
private Boolean logInDatabase = Boolean.TRUE;
|
private Boolean logInDatabase = Boolean.TRUE;
|
||||||
|
|
||||||
private Boolean usePushingAsStatus = Boolean.TRUE;
|
private Boolean usePushingAsStatus = Boolean.FALSE;
|
||||||
|
|
||||||
private Boolean useSourceIpAsStreamIp = Boolean.FALSE;
|
private Boolean useSourceIpAsStreamIp = Boolean.FALSE;
|
||||||
|
|
||||||
@ -58,6 +58,8 @@ public class UserSetting {
|
|||||||
|
|
||||||
private String thirdPartyGBIdReg = "[\\s\\S]*";
|
private String thirdPartyGBIdReg = "[\\s\\S]*";
|
||||||
|
|
||||||
|
private String broadcastForPlatform = "UDP";
|
||||||
|
|
||||||
private String civilCodeFile = "classpath:civilCode.csv";
|
private String civilCodeFile = "classpath:civilCode.csv";
|
||||||
|
|
||||||
private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
|
private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
|
||||||
@ -210,6 +212,14 @@ public class UserSetting {
|
|||||||
this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline;
|
this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBroadcastForPlatform() {
|
||||||
|
return broadcastForPlatform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBroadcastForPlatform(String broadcastForPlatform) {
|
||||||
|
this.broadcastForPlatform = broadcastForPlatform;
|
||||||
|
}
|
||||||
|
|
||||||
public Boolean getSipUseSourceIpAsRemoteAddress() {
|
public Boolean getSipUseSourceIpAsRemoteAddress() {
|
||||||
return sipUseSourceIpAsRemoteAddress;
|
return sipUseSourceIpAsRemoteAddress;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -100,6 +100,9 @@ public class LoginUser implements UserDetails, CredentialsContainer {
|
|||||||
return user.getRole();
|
return user.getRole();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPushKey() {
|
||||||
|
return user.getPushKey();
|
||||||
|
}
|
||||||
|
|
||||||
public String getAccessToken() {
|
public String getAccessToken() {
|
||||||
return accessToken;
|
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,
|
||||||
|
}
|
||||||
@ -206,6 +206,8 @@ public class Device {
|
|||||||
public void setId(int id) {
|
public void setId(int id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
@Schema(description = "控制语音对讲流程,释放收到ACK后发流")
|
||||||
|
private boolean broadcastPushAfterAck;
|
||||||
|
|
||||||
public String getDeviceId() {
|
public String getDeviceId() {
|
||||||
return deviceId;
|
return deviceId;
|
||||||
@ -468,6 +470,9 @@ public class Device {
|
|||||||
public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
|
public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) {
|
||||||
this.sipTransactionInfo = sipTransactionInfo;
|
this.sipTransactionInfo = sipTransactionInfo;
|
||||||
}
|
}
|
||||||
|
public boolean isBroadcastPushAfterAck() {
|
||||||
|
return broadcastPushAfterAck;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isAutoSyncChannel() {
|
public boolean isAutoSyncChannel() {
|
||||||
return autoSyncChannel;
|
return autoSyncChannel;
|
||||||
@ -476,4 +481,7 @@ public class Device {
|
|||||||
public void setAutoSyncChannel(boolean autoSyncChannel) {
|
public void setAutoSyncChannel(boolean autoSyncChannel) {
|
||||||
this.autoSyncChannel = autoSyncChannel;
|
this.autoSyncChannel = autoSyncChannel;
|
||||||
}
|
}
|
||||||
|
public void setBroadcastPushAfterAck(boolean broadcastPushAfterAck) {
|
||||||
|
this.broadcastPushAfterAck = broadcastPushAfterAck;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean;
|
|||||||
|
|
||||||
public enum InviteStreamType {
|
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 = "设备端口")
|
@Schema(description = "设备端口")
|
||||||
private String devicePort;
|
private int devicePort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SIP认证用户名(默认使用设备国标编号)
|
* SIP认证用户名(默认使用设备国标编号)
|
||||||
@ -261,11 +261,11 @@ public class ParentPlatform {
|
|||||||
this.deviceIp = deviceIp;
|
this.deviceIp = deviceIp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDevicePort() {
|
public int getDevicePort() {
|
||||||
return devicePort;
|
return devicePort;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDevicePort(String devicePort) {
|
public void setDevicePort(int devicePort) {
|
||||||
this.devicePort = devicePort;
|
this.devicePort = devicePort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -51,7 +51,7 @@ public class SendRtpItem {
|
|||||||
/**
|
/**
|
||||||
* 设备推流的streamId
|
* 设备推流的streamId
|
||||||
*/
|
*/
|
||||||
private String streamId;
|
private String stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否为tcp
|
* 是否为tcp
|
||||||
@ -119,6 +119,11 @@ public class SendRtpItem {
|
|||||||
*/
|
*/
|
||||||
private InviteStreamType playType;
|
private InviteStreamType playType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发流的同时收流
|
||||||
|
*/
|
||||||
|
private String receiveStream;
|
||||||
|
|
||||||
public String getIp() {
|
public String getIp() {
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
@ -175,12 +180,12 @@ public class SendRtpItem {
|
|||||||
this.app = app;
|
this.app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStreamId() {
|
public String getStream() {
|
||||||
return streamId;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStreamId(String streamId) {
|
public void setStream(String stream) {
|
||||||
this.streamId = streamId;
|
this.stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTcp() {
|
public boolean isTcp() {
|
||||||
@ -302,4 +307,12 @@ public class SendRtpItem {
|
|||||||
public void setId(String id) {
|
public void setId(String id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getReceiveStream() {
|
||||||
|
return receiveStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReceiveStream(String receiveStream) {
|
||||||
|
this.receiveStream = receiveStream;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package com.genersoft.iot.vmp.gb28181.bean;
|
package com.genersoft.iot.vmp.gb28181.bean;
|
||||||
|
|
||||||
import gov.nist.javax.sip.message.SIPRequest;
|
|
||||||
import gov.nist.javax.sip.message.SIPResponse;
|
import gov.nist.javax.sip.message.SIPResponse;
|
||||||
|
|
||||||
public class SipTransactionInfo {
|
public class SipTransactionInfo {
|
||||||
@ -10,11 +9,23 @@ public class SipTransactionInfo {
|
|||||||
private String toTag;
|
private String toTag;
|
||||||
private String viaBranch;
|
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) {
|
public SipTransactionInfo(SIPResponse response) {
|
||||||
this.callId = response.getCallIdHeader().getCallId();
|
this.callId = response.getCallIdHeader().getCallId();
|
||||||
this.fromTag = response.getFromTag();
|
this.fromTag = response.getFromTag();
|
||||||
this.toTag = response.getToTag();
|
this.toTag = response.getToTag();
|
||||||
this.viaBranch = response.getTopmostViaHeader().getBranch();
|
this.viaBranch = response.getTopmostViaHeader().getBranch();
|
||||||
|
this.asSender = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SipTransactionInfo() {
|
public SipTransactionInfo() {
|
||||||
@ -51,4 +62,12 @@ public class SipTransactionInfo {
|
|||||||
public void setViaBranch(String viaBranch) {
|
public void setViaBranch(String viaBranch) {
|
||||||
this.viaBranch = 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,
|
dialogTerminated,
|
||||||
// 设备未找到
|
// 设备未找到
|
||||||
deviceNotFoundEvent,
|
deviceNotFoundEvent,
|
||||||
// 设备未找到
|
// 消息发送失败
|
||||||
cmdSendFailEvent
|
cmdSendFailEvent,
|
||||||
|
// 消息发送失败
|
||||||
|
failedToGetPort
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EventResult<EventObject>{
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -113,7 +113,7 @@ public class SipRunner implements CommandLineRunner {
|
|||||||
Map<String, Object> param = new HashMap<>();
|
Map<String, Object> param = new HashMap<>();
|
||||||
param.put("vhost","__defaultVhost__");
|
param.put("vhost","__defaultVhost__");
|
||||||
param.put("app",sendRtpItem.getApp());
|
param.put("app",sendRtpItem.getApp());
|
||||||
param.put("stream",sendRtpItem.getStreamId());
|
param.put("stream",sendRtpItem.getStream());
|
||||||
param.put("ssrc",sendRtpItem.getSsrc());
|
param.put("ssrc",sendRtpItem.getSsrc());
|
||||||
JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaServerItem, param);
|
JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaServerItem, param);
|
||||||
if (jsonObject != null && jsonObject.getInteger("code") == 0) {
|
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.common.StreamInfo;
|
||||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
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.Device;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.command.PTZCommand;
|
import com.genersoft.iot.vmp.gb28181.bean.command.PTZCommand;
|
||||||
@ -67,13 +68,19 @@ public interface ISIPCommander {
|
|||||||
String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent,
|
String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent,
|
||||||
SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
|
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 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, 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回放暂停
|
* 回放暂停
|
||||||
*/
|
*/
|
||||||
@ -103,21 +110,15 @@ public interface ISIPCommander {
|
|||||||
void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
|
void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
void streamByeCmdForDeviceInvite(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException;
|
||||||
* 语音广播
|
|
||||||
*
|
|
||||||
* @param device 视频设备
|
|
||||||
* @param channelId 预览通道
|
|
||||||
*/
|
|
||||||
void audioBroadcastCmd(Device device,String channelId);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* /**
|
||||||
* 语音广播
|
* 语音广播
|
||||||
*
|
*
|
||||||
* @param device 视频设备
|
* @param device 视频设备
|
||||||
*/
|
*/
|
||||||
void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException;
|
void audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException;
|
||||||
void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 音视频录像控制
|
* 音视频录像控制
|
||||||
@ -305,4 +306,5 @@ public interface ISIPCommander {
|
|||||||
*/
|
*/
|
||||||
void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException;
|
void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
package com.genersoft.iot.vmp.gb28181.transmit.cmd;
|
package com.genersoft.iot.vmp.gb28181.transmit.cmd;
|
||||||
|
|
||||||
import com.genersoft.iot.vmp.common.CommonGbChannel;
|
import com.genersoft.iot.vmp.common.CommonGbChannel;
|
||||||
|
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
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.GPSMsgInfo;
|
||||||
|
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||||
|
|
||||||
import javax.sip.InvalidArgumentException;
|
import javax.sip.InvalidArgumentException;
|
||||||
import javax.sip.SipException;
|
import javax.sip.SipException;
|
||||||
@ -15,6 +19,7 @@ public interface ISIPCommanderForPlatform {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 向上级平台注册
|
* 向上级平台注册
|
||||||
|
*
|
||||||
* @param parentPlatform
|
* @param parentPlatform
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@ -27,6 +32,7 @@ public interface ISIPCommanderForPlatform {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 向上级平台注销
|
* 向上级平台注销
|
||||||
|
*
|
||||||
* @param parentPlatform
|
* @param parentPlatform
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@ -35,14 +41,17 @@ public interface ISIPCommanderForPlatform {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 向上级平发送心跳信息
|
* 向上级平发送心跳信息
|
||||||
|
*
|
||||||
* @param parentPlatform
|
* @param parentPlatform
|
||||||
* @return callId(作为接受回复的判定)
|
* @return callId(作为接受回复的判定)
|
||||||
*/
|
*/
|
||||||
String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException;
|
String keepalive(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent)
|
||||||
|
throws SipException, InvalidArgumentException, ParseException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向上级回复通道信息
|
* 向上级回复通道信息
|
||||||
|
*
|
||||||
* @param channel 通道信息
|
* @param channel 通道信息
|
||||||
* @param parentPlatform 平台信息
|
* @param parentPlatform 平台信息
|
||||||
* @param sn
|
* @param sn
|
||||||
@ -55,6 +64,7 @@ public interface ISIPCommanderForPlatform {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 向上级回复DeviceInfo查询信息
|
* 向上级回复DeviceInfo查询信息
|
||||||
|
*
|
||||||
* @param parentPlatform 平台信息
|
* @param parentPlatform 平台信息
|
||||||
* @param sn SN
|
* @param sn SN
|
||||||
* @param fromTag FROM头的tag信息
|
* @param fromTag FROM头的tag信息
|
||||||
@ -64,6 +74,7 @@ public interface ISIPCommanderForPlatform {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 向上级回复DeviceStatus查询信息
|
* 向上级回复DeviceStatus查询信息
|
||||||
|
*
|
||||||
* @param parentPlatform 平台信息
|
* @param parentPlatform 平台信息
|
||||||
* @param sn
|
* @param sn
|
||||||
* @param fromTag
|
* @param fromTag
|
||||||
@ -73,15 +84,18 @@ public interface ISIPCommanderForPlatform {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 向上级回复移动位置订阅消息
|
* 向上级回复移动位置订阅消息
|
||||||
|
*
|
||||||
* @param parentPlatform 平台信息
|
* @param parentPlatform 平台信息
|
||||||
* @param gpsMsgInfo GPS信息
|
* @param gpsMsgInfo GPS信息
|
||||||
* @param subscribeInfo 订阅相关的信息
|
* @param subscribeInfo 订阅相关的信息
|
||||||
* @return
|
* @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 parentPlatform 平台信息
|
||||||
* @param deviceAlarm 报警信息信息
|
* @param deviceAlarm 报警信息信息
|
||||||
* @return
|
* @return
|
||||||
@ -109,6 +123,7 @@ public interface ISIPCommanderForPlatform {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 录像播放推送完成时发送MediaStatus消息
|
* 录像播放推送完成时发送MediaStatus消息
|
||||||
|
*
|
||||||
* @param platform
|
* @param platform
|
||||||
* @param sendRtpItem
|
* @param sendRtpItem
|
||||||
* @return
|
* @return
|
||||||
@ -117,9 +132,19 @@ public interface ISIPCommanderForPlatform {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 向发起点播的上级回复bye
|
* 向发起点播的上级回复bye
|
||||||
|
*
|
||||||
* @param platform 平台信息
|
* @param platform 平台信息
|
||||||
* @param callId callId
|
* @param callId callId
|
||||||
*/
|
*/
|
||||||
void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException;
|
void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException;
|
||||||
|
|
||||||
void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) 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.SipLayer;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
|
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.bean.SubscribeInfo;
|
||||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||||
@ -55,7 +56,7 @@ public class SIPRequestHeaderPlarformProvider {
|
|||||||
//via
|
//via
|
||||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||||
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(),
|
ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(),
|
||||||
Integer.parseInt(parentPlatform.getDevicePort()), parentPlatform.getTransport(), SipUtils.getNewViaTag());
|
parentPlatform.getDevicePort(), parentPlatform.getTransport(), SipUtils.getNewViaTag());
|
||||||
viaHeader.setRPort();
|
viaHeader.setRPort();
|
||||||
viaHeaders.add(viaHeader);
|
viaHeaders.add(viaHeader);
|
||||||
//from
|
//from
|
||||||
@ -182,7 +183,7 @@ public class SIPRequestHeaderPlarformProvider {
|
|||||||
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
|
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress);
|
||||||
// via
|
// via
|
||||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
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);
|
parentPlatform.getTransport(), viaTag);
|
||||||
viaHeader.setRPort();
|
viaHeader.setRPort();
|
||||||
viaHeaders.add(viaHeader);
|
viaHeaders.add(viaHeader);
|
||||||
@ -219,7 +220,7 @@ public class SIPRequestHeaderPlarformProvider {
|
|||||||
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
|
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort());
|
||||||
// via
|
// via
|
||||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
|
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());
|
parentPlatform.getTransport(), SipUtils.getNewViaTag());
|
||||||
viaHeader.setRPort();
|
viaHeader.setRPort();
|
||||||
viaHeaders.add(viaHeader);
|
viaHeaders.add(viaHeader);
|
||||||
@ -279,7 +280,7 @@ public class SIPRequestHeaderPlarformProvider {
|
|||||||
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort());
|
SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort());
|
||||||
// via
|
// via
|
||||||
ArrayList<ViaHeader> viaHeaders = new ArrayList<>();
|
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());
|
platform.getTransport(), SipUtils.getNewViaTag());
|
||||||
viaHeader.setRPort();
|
viaHeader.setRPort();
|
||||||
viaHeaders.add(viaHeader);
|
viaHeaders.add(viaHeader);
|
||||||
@ -311,6 +312,82 @@ public class SIPRequestHeaderPlarformProvider {
|
|||||||
|
|
||||||
request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
|
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;
|
return request;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -197,6 +197,41 @@ public class SIPRequestHeaderProvider {
|
|||||||
return request;
|
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 {
|
public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
|
||||||
Request request = null;
|
Request request = null;
|
||||||
// sipuri
|
// sipuri
|
||||||
@ -315,6 +350,38 @@ public class SIPRequestHeaderProvider {
|
|||||||
|
|
||||||
request.addHeader(SipUtils.createUserAgentHeader(gitUtil));
|
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;
|
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.UserSetting;
|
||||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||||
import com.genersoft.iot.vmp.gb28181.SipLayer;
|
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.Device;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||||
@ -18,9 +19,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.transmit.cmd.SIPRequestHeaderProvider;
|
||||||
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
|
import com.genersoft.iot.vmp.gb28181.utils.NumericUtil;
|
||||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
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.ZlmHttpHookSubscribe;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
|
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.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.MediaServerItem;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
|
import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
|
||||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||||
@ -39,6 +42,7 @@ import javax.sip.*;
|
|||||||
import javax.sip.header.CallIdHeader;
|
import javax.sip.header.CallIdHeader;
|
||||||
import javax.sip.message.Request;
|
import javax.sip.message.Request;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,16 +77,80 @@ public class SIPCommander implements ISIPCommander {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ZlmHttpHookSubscribe subscribe;
|
private ZlmHttpHookSubscribe subscribe;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IMediaServerService mediaServerService;
|
private IMediaServerService mediaServerService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ZLMServerFactory zlmServerFactory;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void ptzCmd(Device device, String channelId, PTZCommand ptzCommand) throws InvalidArgumentException, SipException, ParseException {
|
public void ptzZoomCmd(Device device, String channelId, int inOut, int zoomSpeed) throws InvalidArgumentException, ParseException, SipException {
|
||||||
String cmdStr = SipUtils.cmdString(ptzCommand);
|
ptzCmd(device, channelId, 0, 0, inOut, 0, zoomSpeed);
|
||||||
frontEndCmd(device, channelId, cmdStr);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 云台指令码计算
|
||||||
|
*
|
||||||
|
* @param cmdCode 指令码
|
||||||
|
* @param parameter1 数据1
|
||||||
|
* @param parameter2 数据2
|
||||||
|
* @param combineCode2 组合码2
|
||||||
|
*/
|
||||||
|
public static String frontEndCmdString(int cmdCode, int parameter1, int parameter2, int combineCode2) {
|
||||||
|
StringBuilder builder = new StringBuilder("A50F01");
|
||||||
|
String strTmp;
|
||||||
|
strTmp = String.format("%02X", cmdCode);
|
||||||
|
builder.append(strTmp, 0, 2);
|
||||||
|
strTmp = String.format("%02X", parameter1);
|
||||||
|
builder.append(strTmp, 0, 2);
|
||||||
|
strTmp = String.format("%02X", parameter2);
|
||||||
|
builder.append(strTmp, 0, 2);
|
||||||
|
//优化zoom变倍速率
|
||||||
|
if ((combineCode2 > 0) && (combineCode2 <16))
|
||||||
|
{
|
||||||
|
combineCode2 = 16;
|
||||||
|
}
|
||||||
|
strTmp = String.format("%X", combineCode2);
|
||||||
|
builder.append(strTmp, 0, 1).append("0");
|
||||||
|
//计算校验码
|
||||||
|
int checkCode = (0XA5 + 0X0F + 0X01 + cmdCode + parameter1 + parameter2 + (combineCode2 & 0XF0)) % 0X100;
|
||||||
|
strTmp = String.format("%02X", checkCode);
|
||||||
|
builder.append(strTmp, 0, 2);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 云台控制,支持方向与缩放控制
|
||||||
|
*
|
||||||
|
* @param device 控制设备
|
||||||
|
* @param channelId 预览通道
|
||||||
|
* @param leftRight 镜头左移右移 0:停止 1:左移 2:右移
|
||||||
|
* @param upDown 镜头上移下移 0:停止 1:上移 2:下移
|
||||||
|
* @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大
|
||||||
|
* @param moveSpeed 镜头移动速度
|
||||||
|
* @param zoomSpeed 镜头缩放速度
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void ptzCmd(Device device, String channelId, int leftRight, int upDown, int inOut, int moveSpeed,
|
||||||
|
int zoomSpeed) throws InvalidArgumentException, SipException, ParseException {
|
||||||
|
String cmdStr = SipUtils.cmdString(leftRight, upDown, inOut, moveSpeed, zoomSpeed);
|
||||||
|
StringBuilder ptzXml = new StringBuilder(200);
|
||||||
|
String charset = device.getCharset();
|
||||||
|
ptzXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
|
||||||
|
ptzXml.append("<Control>\r\n");
|
||||||
|
ptzXml.append("<CmdType>DeviceControl</CmdType>\r\n");
|
||||||
|
ptzXml.append("<SN>" + (int) ((Math.random() * 9 + 1) * 100000) + "</SN>\r\n");
|
||||||
|
ptzXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
|
||||||
|
ptzXml.append("<PTZCmd>" + cmdStr + "</PTZCmd>\r\n");
|
||||||
|
ptzXml.append("<Info>\r\n");
|
||||||
|
ptzXml.append("<ControlPriority>5</ControlPriority>\r\n");
|
||||||
|
ptzXml.append("</Info>\r\n");
|
||||||
|
ptzXml.append("</Control>\r\n");
|
||||||
|
|
||||||
|
Request request = headerProvider.createMessageRequest(device, ptzXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()));
|
||||||
|
|
||||||
|
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -450,6 +518,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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 视频流停止, 不使用回调
|
* 视频流停止, 不使用回调
|
||||||
*/
|
*/
|
||||||
@ -481,18 +613,19 @@ public class SIPCommander implements ISIPCommander {
|
|||||||
streamSession.removeByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getCallId());
|
streamSession.removeByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getCallId());
|
||||||
Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
|
Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo());
|
||||||
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
|
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 语音广播
|
|
||||||
*
|
|
||||||
* @param device 视频设备
|
|
||||||
* @param channelId 预览通道
|
|
||||||
*/
|
|
||||||
@Override
|
@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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -528,15 +661,13 @@ public class SIPCommander implements ISIPCommander {
|
|||||||
broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
|
broadcastXml.append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>\r\n");
|
||||||
broadcastXml.append("<Notify>\r\n");
|
broadcastXml.append("<Notify>\r\n");
|
||||||
broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
|
broadcastXml.append("<CmdType>Broadcast</CmdType>\r\n");
|
||||||
broadcastXml.append("<SN>" + SipUtils.getNewSn() + "</SN>\r\n");
|
broadcastXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
|
||||||
broadcastXml.append("<SourceID>" + sipConfig.getId() + "</SourceID>\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");
|
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()));
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -594,6 +725,7 @@ public class SIPCommander implements ISIPCommander {
|
|||||||
cmdXml.append("</Control>\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()));
|
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);
|
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request);
|
||||||
}
|
}
|
||||||
@ -621,6 +753,8 @@ public class SIPCommander implements ISIPCommander {
|
|||||||
}
|
}
|
||||||
cmdXml.append("</Control>\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()));
|
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);
|
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
|
||||||
}
|
}
|
||||||
@ -732,6 +866,8 @@ public class SIPCommander implements ISIPCommander {
|
|||||||
cmdXml.append("</HomePosition>\r\n");
|
cmdXml.append("</HomePosition>\r\n");
|
||||||
cmdXml.append("</Control>\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()));
|
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);
|
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent);
|
||||||
}
|
}
|
||||||
@ -816,6 +952,8 @@ public class SIPCommander implements ISIPCommander {
|
|||||||
catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
|
catalogXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
|
||||||
catalogXml.append("</Query>\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()));
|
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);
|
sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent);
|
||||||
@ -1178,8 +1316,6 @@ public class SIPCommander implements ISIPCommander {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回放暂停
|
* 回放暂停
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -2,25 +2,35 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
|
|||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.genersoft.iot.vmp.common.CommonGbChannel;
|
import com.genersoft.iot.vmp.common.CommonGbChannel;
|
||||||
|
import com.genersoft.iot.vmp.common.InviteSessionType;
|
||||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
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.SipLayer;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
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.SIPSender;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider;
|
import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider;
|
||||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||||
import com.genersoft.iot.vmp.media.zlm.IStreamSendManager;
|
import com.genersoft.iot.vmp.media.zlm.IStreamSendManager;
|
||||||
|
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.ZLMServerFactory;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
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.IMediaServerService;
|
||||||
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
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.IRedisCatchStorage;
|
||||||
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
|
import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo;
|
||||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||||
import com.genersoft.iot.vmp.utils.GitUtil;
|
import com.genersoft.iot.vmp.utils.GitUtil;
|
||||||
import gov.nist.javax.sip.message.MessageFactoryImpl;
|
import gov.nist.javax.sip.message.MessageFactoryImpl;
|
||||||
import gov.nist.javax.sip.message.SIPRequest;
|
import gov.nist.javax.sip.message.SIPRequest;
|
||||||
|
import gov.nist.javax.sip.message.SIPResponse;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -30,6 +40,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
import javax.sip.InvalidArgumentException;
|
import javax.sip.InvalidArgumentException;
|
||||||
|
import javax.sip.ResponseEvent;
|
||||||
import javax.sip.SipException;
|
import javax.sip.SipException;
|
||||||
import javax.sip.SipFactory;
|
import javax.sip.SipFactory;
|
||||||
import javax.sip.header.CallIdHeader;
|
import javax.sip.header.CallIdHeader;
|
||||||
@ -66,6 +77,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SIPSender sipSender;
|
private SIPSender sipSender;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ZlmHttpHookSubscribe subscribe;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserSetting userSetting;
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VideoStreamSessionManager streamSession;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private DynamicTask dynamicTask;
|
private DynamicTask dynamicTask;
|
||||||
|
|
||||||
@ -411,6 +432,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
|
|||||||
sipSender.transmitRequest(parentPlatform.getDeviceIp(), request);
|
sipSender.transmitRequest(parentPlatform.getDeviceIp(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向上级回复DeviceStatus查询信息
|
* 向上级回复DeviceStatus查询信息
|
||||||
* @param parentPlatform 平台信息
|
* @param parentPlatform 平台信息
|
||||||
@ -824,26 +846,129 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 ) {
|
if (sendRtpItem == null ) {
|
||||||
logger.info("[向上级发送BYE], sendRtpItem 为NULL");
|
logger.info("[向上级发送BYE], sendRtpItem 为NULL");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (parentPlatform == null) {
|
if (platform == null) {
|
||||||
logger.info("[向上级发送BYE], platform 为NULL");
|
logger.info("[向上级发送BYE], platform 为NULL");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.info("[向上级发送BYE], {}/{}", parentPlatform.getServerGBId(), sendRtpItem.getChannelId());
|
logger.info("[向上级发送BYE], {}/{}", platform.getServerGBId(), sendRtpItem.getChannelId());
|
||||||
String mediaServerId = sendRtpItem.getMediaServerId();
|
String mediaServerId = sendRtpItem.getMediaServerId();
|
||||||
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
|
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
|
||||||
if (mediaServerItem != null) {
|
if (mediaServerItem != null) {
|
||||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
|
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) {
|
if (byeRequest == null) {
|
||||||
logger.warn("[向上级发送bye]:无法创建 byeRequest");
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,6 +81,7 @@ public abstract class SIPRequestProcessorParent {
|
|||||||
return responseAck(sipRequest, statusCode, msg, null);
|
return responseAck(sipRequest, statusCode, msg, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public SIPResponse responseAck(SIPRequest sipRequest, int statusCode, String msg, ResponseAckExtraParam responseAckExtraParam) throws SipException, InvalidArgumentException, ParseException {
|
public SIPResponse responseAck(SIPRequest sipRequest, int statusCode, String msg, ResponseAckExtraParam responseAckExtraParam) throws SipException, InvalidArgumentException, ParseException {
|
||||||
if (sipRequest.getToHeader().getTag() == null) {
|
if (sipRequest.getToHeader().getTag() == null) {
|
||||||
sipRequest.getToHeader().setTag(SipUtils.getNewTag());
|
sipRequest.getToHeader().setTag(SipUtils.getNewTag());
|
||||||
@ -123,6 +124,8 @@ public abstract class SIPRequestProcessorParent {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回复带sdp的200
|
* 回复带sdp的200
|
||||||
*/
|
*/
|
||||||
@ -140,7 +143,10 @@ public abstract class SIPRequestProcessorParent {
|
|||||||
responseAckExtraParam.content = sdp;
|
responseAckExtraParam.content = sdp;
|
||||||
responseAckExtraParam.sipURI = sipURI;
|
responseAckExtraParam.sipURI = sipURI;
|
||||||
|
|
||||||
return responseAck(request, Response.OK, null, responseAckExtraParam);
|
SIPResponse sipResponse = responseAck(request, Response.OK, null, responseAckExtraParam);
|
||||||
|
|
||||||
|
|
||||||
|
return sipResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,7 +179,8 @@ public abstract class SIPRequestProcessorParent {
|
|||||||
reader.setEncoding(charset);
|
reader.setEncoding(charset);
|
||||||
// 对海康出现的未转义字符做处理。
|
// 对海康出现的未转义字符做处理。
|
||||||
String[] destStrArray = new String[]{"<",">","&","'","""};
|
String[] destStrArray = new String[]{"<",">","&","'","""};
|
||||||
char despChar = '&'; // 或许可扩展兼容其他字符
|
// 或许可扩展兼容其他字符
|
||||||
|
char despChar = '&';
|
||||||
byte destBye = (byte) despChar;
|
byte destBye = (byte) despChar;
|
||||||
List<Byte> result = new ArrayList<>();
|
List<Byte> result = new ArrayList<>();
|
||||||
byte[] rawContent = request.getRawContent();
|
byte[] rawContent = request.getRawContent();
|
||||||
@ -219,4 +226,5 @@ public abstract class SIPRequestProcessorParent {
|
|||||||
return xml.getRootElement();
|
return xml.getRootElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,21 @@
|
|||||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
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.ParentPlatform;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
|
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
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.ISIPRequestProcessor;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||||
import com.genersoft.iot.vmp.media.zlm.IStreamSendManager;
|
import com.genersoft.iot.vmp.media.zlm.IStreamSendManager;
|
||||||
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
|
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
|
||||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
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.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.bean.RequestPushStreamMsg;
|
||||||
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
|
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
|
||||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||||
@ -28,25 +26,23 @@ import org.springframework.beans.factory.InitializingBean;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.sip.InvalidArgumentException;
|
|
||||||
import javax.sip.RequestEvent;
|
import javax.sip.RequestEvent;
|
||||||
import javax.sip.SipException;
|
|
||||||
import javax.sip.address.SipURI;
|
import javax.sip.address.SipURI;
|
||||||
import javax.sip.header.CallIdHeader;
|
import javax.sip.header.CallIdHeader;
|
||||||
import javax.sip.header.FromHeader;
|
import javax.sip.header.FromHeader;
|
||||||
import javax.sip.header.HeaderAddress;
|
import javax.sip.header.HeaderAddress;
|
||||||
import javax.sip.header.ToHeader;
|
import javax.sip.header.ToHeader;
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SIP命令类型: ACK请求
|
* SIP命令类型: ACK请求
|
||||||
|
* @author lin
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
|
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";
|
private final String method = "ACK";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -67,6 +63,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IVideoManagerStorage storager;
|
private IVideoManagerStorage storager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IDeviceService deviceService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ZLMServerFactory zlmServerFactory;
|
private ZLMServerFactory zlmServerFactory;
|
||||||
|
|
||||||
@ -76,113 +75,124 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IMediaServerService mediaServerService;
|
private IMediaServerService mediaServerService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ZlmHttpHookSubscribe subscribe;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private DynamicTask dynamicTask;
|
private DynamicTask dynamicTask;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ISIPCommander cmder;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ISIPCommanderForPlatform commanderForPlatform;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisGbPlayMsgListener redisGbPlayMsgListener;
|
private RedisGbPlayMsgListener redisGbPlayMsgListener;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IPlayService playService;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理 ACK请求
|
* 处理 ACK请求
|
||||||
*
|
|
||||||
* @param evt
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void process(RequestEvent evt) {
|
public void process(RequestEvent evt) {
|
||||||
CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME);
|
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());
|
dynamicTask.stop(callIdHeader.getCallId());
|
||||||
String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
|
String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser();
|
||||||
SendRtpItem sendRtpItem = streamSendManager.getByCallId(callIdHeader.getCallId());
|
SendRtpItem sendRtpItem = streamSendManager.getByCallId(callIdHeader.getCallId());
|
||||||
if (sendRtpItem == null) {
|
if (sendRtpItem == null) {
|
||||||
logger.warn("[收到ACK]:未找到通道({})的推流信息", channelId);
|
logger.warn("[收到ACK]:未找到来自{},目标为({})的推流信息",fromUserId, toUserId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// tcp主动时,此时是级联下级平台,在回复200ok时,本地已经请求zlm开启监听,跳过下面步骤
|
// tcp主动时,此时是级联下级平台,在回复200ok时,本地已经请求zlm开启监听,跳过下面步骤
|
||||||
if (sendRtpItem.isTcpActive()) {
|
if (sendRtpItem.isTcpActive()) {
|
||||||
logger.info("收到ACK,rtp/{} TCP主动方式后续处理", sendRtpItem.getStreamId());
|
logger.info("收到ACK,rtp/{} TCP主动方式后续处理", sendRtpItem.getStream());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
|
|
||||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||||
logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, 协议:{}",
|
logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, 协议:{}",
|
||||||
sendRtpItem.getStreamId(),
|
sendRtpItem.getStream(),
|
||||||
sendRtpItem.getIp(),
|
sendRtpItem.getIp(),
|
||||||
sendRtpItem.getPort(),
|
sendRtpItem.getPort(),
|
||||||
sendRtpItem.getSsrc(),
|
sendRtpItem.getSsrc(),
|
||||||
sendRtpItem.isTcp()?(sendRtpItem.isTcpActive()?"TCP主动":"TCP被动"):"UDP"
|
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);
|
Map<String, Object> param = new HashMap<>(12);
|
||||||
param.put("vhost","__defaultVhost__");
|
param.put("vhost","__defaultVhost__");
|
||||||
param.put("app",sendRtpItem.getApp());
|
param.put("app",sendRtpItem.getApp());
|
||||||
param.put("stream",sendRtpItem.getStreamId());
|
param.put("stream",sendRtpItem.getStream());
|
||||||
param.put("ssrc", sendRtpItem.getSsrc());
|
param.put("ssrc", sendRtpItem.getSsrc());
|
||||||
param.put("dst_url",sendRtpItem.getIp());
|
param.put("dst_url",sendRtpItem.getIp());
|
||||||
param.put("dst_port", sendRtpItem.getPort());
|
param.put("dst_port", sendRtpItem.getPort());
|
||||||
param.put("is_udp", is_Udp);
|
|
||||||
param.put("src_port", sendRtpItem.getLocalPort());
|
param.put("src_port", sendRtpItem.getLocalPort());
|
||||||
param.put("pt", sendRtpItem.getPt());
|
param.put("pt", sendRtpItem.getPt());
|
||||||
param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
|
param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
|
||||||
param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
|
param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
|
||||||
|
param.put("is_udp", isUdp);
|
||||||
if (!sendRtpItem.isTcp()) {
|
if (!sendRtpItem.isTcp()) {
|
||||||
// 开启rtcp保活
|
// udp模式下开启rtcp保活
|
||||||
param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
|
param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
|
||||||
}
|
}
|
||||||
if (mediaInfo == null) {
|
return param;
|
||||||
RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
|
}
|
||||||
sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(),
|
|
||||||
sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
|
private JSONObject sendRtp(SendRtpItem sendRtpItem, MediaServerItem mediaInfo, Map<String, Object> param){
|
||||||
sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
|
JSONObject startSendRtpStreamResult = null;
|
||||||
redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, jsonObject->{
|
if (sendRtpItem.getLocalPort() != 0) {
|
||||||
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader);
|
if (sendRtpItem.isTcpActive()) {
|
||||||
});
|
startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
|
||||||
}else {
|
}else {
|
||||||
JSONObject startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
|
param.put("dst_url", sendRtpItem.getIp());
|
||||||
if (startSendRtpStreamResult != null) {
|
param.put("dst_port", sendRtpItem.getPort());
|
||||||
startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, param, callIdHeader);
|
startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param));
|
if (sendRtpItem.isTcpActive()) {
|
||||||
if (sendRtpItem.isOnlyAudio()) {
|
startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
|
||||||
// TODO 可能是语音对讲
|
|
||||||
}else {
|
}else {
|
||||||
// 向上级平台
|
param.put("dst_url", sendRtpItem.getIp());
|
||||||
try {
|
param.put("dst_port", sendRtpItem.getPort());
|
||||||
commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
|
startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
|
||||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
|
||||||
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return startSendRtpStreamResult;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,9 +6,12 @@ import com.genersoft.iot.vmp.common.InviteSessionType;
|
|||||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
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.session.VideoStreamSessionManager;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
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.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.ISIPRequestProcessor;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||||
import com.genersoft.iot.vmp.media.zlm.IStreamSendManager;
|
import com.genersoft.iot.vmp.media.zlm.IStreamSendManager;
|
||||||
@ -52,6 +55,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ICommonGbChannelService commonGbChannelService;
|
private ICommonGbChannelService commonGbChannelService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISIPCommanderForPlatform commanderForPlatform;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IRedisCatchStorage redisCatchStorage;
|
private IRedisCatchStorage redisCatchStorage;
|
||||||
|
|
||||||
@ -64,6 +70,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IDeviceService deviceService;
|
private IDeviceService deviceService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AudioBroadcastManager audioBroadcastManager;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IDeviceChannelService channelService;
|
private IDeviceChannelService channelService;
|
||||||
|
|
||||||
@ -82,6 +91,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
|||||||
@Autowired
|
@Autowired
|
||||||
private VideoStreamSessionManager streamSession;
|
private VideoStreamSessionManager streamSession;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IPlayService playService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserSetting userSetting;
|
private UserSetting userSetting;
|
||||||
|
|
||||||
@ -117,7 +129,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
|||||||
param.put("app",sendRtpItem.getApp());
|
param.put("app",sendRtpItem.getApp());
|
||||||
param.put("stream",streamId);
|
param.put("stream",streamId);
|
||||||
param.put("ssrc",sendRtpItem.getSsrc());
|
param.put("ssrc",sendRtpItem.getSsrc());
|
||||||
logger.info("[收到bye] 停止向上级推流:{}", streamId);
|
logger.info("[收到bye] 停止推流:{}", streamId);
|
||||||
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
|
||||||
zlmServerFactory.stopSendRtpStream(mediaInfo, param);
|
zlmServerFactory.stopSendRtpStream(mediaInfo, param);
|
||||||
streamSendManager.remove(sendRtpItem);
|
streamSendManager.remove(sendRtpItem);
|
||||||
@ -138,10 +150,35 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
|||||||
messageForPushChannel.setPlatFormIndex(platform.getId());
|
messageForPushChannel.setPlatFormIndex(platform.getId());
|
||||||
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
|
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
|
||||||
}else {
|
}else {
|
||||||
logger.info("[上级平台停止观看] 未找到平台{}的信息,发送redis消息失败", sendRtpItem.getDestId());
|
logger.info("[上级平台停止观看] 未找到平台{}的信息,发送redis消息失败", sendRtpItem.getPlatformId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) {
|
||||||
|
Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
|
||||||
|
if (device == null) {
|
||||||
|
logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
logger.info("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
|
||||||
|
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), streamId, null);
|
||||||
|
} catch (InvalidArgumentException | ParseException | SipException |
|
||||||
|
SsrcTransactionNotFoundException e) {
|
||||||
|
logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else {
|
|
||||||
|
|
||||||
// 可能是设备发送的停止
|
// 可能是设备发送的停止
|
||||||
SsrcTransaction ssrcTransaction = streamSession.getSsrcTransactionByCallId(callIdHeader.getCallId());
|
SsrcTransaction ssrcTransaction = streamSession.getSsrcTransactionByCallId(callIdHeader.getCallId());
|
||||||
@ -150,6 +187,23 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
|||||||
}
|
}
|
||||||
logger.info("[收到bye] 来自设备:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId());
|
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());
|
Device device = deviceService.getDevice(ssrcTransaction.getDeviceId());
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
logger.info("[收到bye] 未找到设备:{} ", ssrcTransaction.getDeviceId());
|
logger.info("[收到bye] 未找到设备:{} ", ssrcTransaction.getDeviceId());
|
||||||
@ -174,6 +228,19 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
|
|||||||
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
|
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc());
|
||||||
}
|
}
|
||||||
streamSession.removeByCallId(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getCallId());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,20 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
|||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.genersoft.iot.vmp.common.CommonGbChannel;
|
import com.genersoft.iot.vmp.common.CommonGbChannel;
|
||||||
|
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.DynamicTask;
|
||||||
|
import com.genersoft.iot.vmp.conf.SipConfig;
|
||||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
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.SSRCFactory;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.transmit.SIPSender;
|
||||||
|
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.ISIPRequestProcessor;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent;
|
||||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||||
@ -66,7 +74,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||||||
private final String method = "INVITE";
|
private final String method = "INVITE";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SIPCommanderFroPlatform cmderFroPlatform;
|
private ISIPCommanderForPlatform cmderFroPlatform;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IVideoManagerStorage storager;
|
private IVideoManagerStorage storager;
|
||||||
@ -76,6 +84,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IStreamPushService streamPushService;
|
private IStreamPushService streamPushService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IStreamProxyService streamProxyService;
|
private IStreamProxyService streamProxyService;
|
||||||
|
|
||||||
@ -97,6 +106,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||||||
@Autowired
|
@Autowired
|
||||||
private Map<String, IResourceService> resourceServiceMap;
|
private Map<String, IResourceService> resourceServiceMap;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AudioBroadcastManager audioBroadcastManager;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ZLMServerFactory zlmServerFactory;
|
private ZLMServerFactory zlmServerFactory;
|
||||||
|
|
||||||
@ -115,9 +127,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ZLMMediaListManager mediaListManager;
|
private ZLMMediaListManager mediaListManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SipConfig config;
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisGbPlayMsgListener redisGbPlayMsgListener;
|
private RedisGbPlayMsgListener redisGbPlayMsgListener;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VideoStreamSessionManager streamSession;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
@ -168,7 +187,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||||||
// 查询请求是否来自上级平台\设备
|
// 查询请求是否来自上级平台\设备
|
||||||
ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
|
ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
|
||||||
if (platform == null) {
|
if (platform == null) {
|
||||||
inviteFromDeviceHandle(request, requesterId);
|
inviteFromDeviceHandle(request, requesterId, channelId);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 查询平台下是否有该通道
|
// 查询平台下是否有该通道
|
||||||
@ -465,6 +484,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||||||
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
|
mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知流上线
|
* 通知流上线
|
||||||
*/
|
*/
|
||||||
@ -666,8 +686,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||||||
content.append("t=0 0\r\n");
|
content.append("t=0 0\r\n");
|
||||||
// 非严格模式端口不统一, 增加兼容性,修改为一个不为0的端口
|
// 非严格模式端口不统一, 增加兼容性,修改为一个不为0的端口
|
||||||
int localPort = sendRtpItem.getLocalPort();
|
int localPort = sendRtpItem.getLocalPort();
|
||||||
if(localPort == 0)
|
if (localPort == 0) {
|
||||||
{
|
|
||||||
localPort = new Random().nextInt(65535) + 1;
|
localPort = new Random().nextInt(65535) + 1;
|
||||||
}
|
}
|
||||||
content.append("m=video " + localPort + " RTP/AVP 96\r\n");
|
content.append("m=video " + localPort + " RTP/AVP 96\r\n");
|
||||||
@ -686,39 +705,75 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
return responseSdpAck(request, content.toString(), platform);
|
return responseSdpAck(request, content.toString(), platform);
|
||||||
} catch (SipException e) {
|
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||||
logger.error("未处理的异常 ", e);
|
|
||||||
} catch (InvalidArgumentException e) {
|
|
||||||
logger.error("未处理的异常 ", e);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
logger.error("未处理的异常 ", e);
|
logger.error("未处理的异常 ", e);
|
||||||
}
|
}
|
||||||
return null;
|
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);
|
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) {
|
if (device != null) {
|
||||||
logger.info("收到设备" + requesterId + "的语音广播Invite请求");
|
logger.info("收到设备" + requesterId + "的语音广播Invite请求");
|
||||||
|
String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + broadcastCatch.getChannelId();
|
||||||
|
dynamicTask.stop(key);
|
||||||
try {
|
try {
|
||||||
responseAck(request, Response.TRYING);
|
responseAck(request, Response.TRYING);
|
||||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||||
logger.error("[命令发送失败] invite BAD_REQUEST: {}", e.getMessage());
|
logger.error("[命令发送失败] invite BAD_REQUEST: {}", e.getMessage());
|
||||||
|
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
String contentString = new String(request.getRawContent());
|
String contentString = new String(request.getRawContent());
|
||||||
// jainSip不支持y=字段, 移除移除以解析。
|
|
||||||
String ssrc = "0000000404";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
|
Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString);
|
||||||
SessionDescription sdp = gb28181Sdp.getBaseSdb();
|
SessionDescription sdp = gb28181Sdp.getBaseSdb();
|
||||||
// 获取支持的格式
|
// 获取支持的格式
|
||||||
Vector mediaDescriptions = sdp.getMediaDescriptions(true);
|
Vector mediaDescriptions = sdp.getMediaDescriptions(true);
|
||||||
|
|
||||||
// 查看是否支持PS 负载96
|
// 查看是否支持PS 负载96
|
||||||
int port = -1;
|
int port = -1;
|
||||||
//boolean recvonly = false;
|
|
||||||
boolean mediaTransmissionTCP = false;
|
boolean mediaTransmissionTCP = false;
|
||||||
Boolean tcpActive = null;
|
Boolean tcpActive = null;
|
||||||
for (int i = 0; i < mediaDescriptions.size(); i++) {
|
for (int i = 0; i < mediaDescriptions.size(); i++) {
|
||||||
@ -750,26 +805,147 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
|
|||||||
try {
|
try {
|
||||||
responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
|
responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415
|
||||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||||
logger.error("[命令发送失败] invite 不支持的媒体格式,返回415, {}", e.getMessage());
|
logger.error("[命令发送失败] invite 不支持的媒体格式: {}", e.getMessage());
|
||||||
|
playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String username = sdp.getOrigin().getUsername();
|
String addressStr = sdp.getOrigin().getAddress();
|
||||||
String addressStr = sdp.getConnection().getAddress();
|
logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}, {}", requesterId, addressStr, port, gb28181Sdp.getSsrc(),
|
||||||
logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc);
|
mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP");
|
||||||
} catch (SdpException e) {
|
|
||||||
logger.error("[SDP解析异常]", e);
|
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 {
|
} else {
|
||||||
logger.warn("来自无效设备/平台的请求");
|
logger.warn("来自无效设备/平台的请求");
|
||||||
try {
|
try {
|
||||||
responseAck(request, Response.BAD_REQUEST);; // 不支持的格式,发415
|
responseAck(request, Response.BAD_REQUEST);
|
||||||
|
; // 不支持的格式,发415
|
||||||
} catch (SipException | InvalidArgumentException | ParseException e) {
|
} catch (SipException | InvalidArgumentException | ParseException e) {
|
||||||
logger.error("[命令发送失败] invite 来自无效设备/平台的请求, {}", e.getMessage());
|
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.stereotype.Component;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
import javax.sip.*;
|
||||||
|
import javax.sip.header.*;
|
||||||
|
import javax.sip.message.Request;
|
||||||
import javax.sip.RequestEvent;
|
import javax.sip.RequestEvent;
|
||||||
import javax.sip.SipException;
|
import javax.sip.SipException;
|
||||||
import javax.sip.header.AuthorizationHeader;
|
import javax.sip.header.AuthorizationHeader;
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl;
|
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.CmdType;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
|
import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder;
|
||||||
|
|||||||
@ -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.SIPRequestProcessorParent;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd.CatalogQueryMessageHandler;
|
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd.CatalogQueryMessageHandler;
|
||||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||||
|
import gov.nist.javax.sip.message.SIPRequest;
|
||||||
import org.dom4j.Element;
|
import org.dom4j.Element;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import javax.sip.InvalidArgumentException;
|
||||||
import javax.sip.RequestEvent;
|
import javax.sip.RequestEvent;
|
||||||
|
import javax.sip.SipException;
|
||||||
|
import javax.sip.message.Response;
|
||||||
|
import java.text.ParseException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
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{
|
public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent implements IMessageHandler{
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(MessageHandlerAbstract.class);
|
||||||
|
|
||||||
public Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
|
public Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -28,6 +37,14 @@ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent i
|
|||||||
@Override
|
@Override
|
||||||
public void handForDevice(RequestEvent evt, Device device, Element element) {
|
public void handForDevice(RequestEvent evt, Device device, Element element) {
|
||||||
String cmd = getText(element, "CmdType");
|
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);
|
IMessageHandler messageHandler = messageHandlerMap.get(cmd);
|
||||||
|
|
||||||
if (messageHandler != null) {
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -99,7 +99,7 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
cmder.streamByeCmd(device, ssrcTransaction.getChannelId(), null, callIdHeader.getCallId());
|
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());
|
logger.error("[录像流]推送完毕,收到关流通知, 发送BYE失败 {}", e.getMessage());
|
||||||
}
|
}
|
||||||
// 去除监听流注销自动停止下载的监听
|
// 去除监听流注销自动停止下载的监听
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd;
|
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.Device;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
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.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.SIPRequestProcessorParent;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler;
|
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.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 gov.nist.javax.sip.message.SIPRequest;
|
||||||
import org.dom4j.Element;
|
import org.dom4j.Element;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -35,7 +38,13 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
|
|||||||
private ResponseMessageHandler responseMessageHandler;
|
private ResponseMessageHandler responseMessageHandler;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private DeferredResultHolder deferredResultHolder;
|
private DynamicTask dynamicTask;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AudioBroadcastManager audioBroadcastManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IPlayService playService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
@ -44,23 +53,38 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
|
public void handForDevice(RequestEvent evt, Device device, Element rootElement) {
|
||||||
|
|
||||||
|
SIPRequest request = (SIPRequest) evt.getRequest();
|
||||||
try {
|
try {
|
||||||
String channelId = getText(rootElement, "DeviceID");
|
String channelId = getText(rootElement, "DeviceID");
|
||||||
String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId() + channelId;
|
if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
|
||||||
// 回复200 OK
|
// 回复410
|
||||||
responseAck((SIPRequest) evt.getRequest(), Response.OK);
|
responseAck((SIPRequest) evt.getRequest(), Response.GONE);
|
||||||
// 此处是对本平台发出Broadcast指令的应答
|
return;
|
||||||
JSONObject json = new JSONObject();
|
|
||||||
XmlUtil.node2Json(rootElement, json);
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(json.toJSONString());
|
|
||||||
}
|
}
|
||||||
RequestMessage msg = new RequestMessage();
|
String result = getText(rootElement, "Result");
|
||||||
msg.setKey(key);
|
Element infoElement = rootElement.element("Info");
|
||||||
msg.setData(json);
|
String reason = null;
|
||||||
deferredResultHolder.invokeAllResult(msg);
|
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) {
|
} catch (ParseException | SipException | InvalidArgumentException e) {
|
||||||
logger.error("[命令发送失败] 国标级联 语音喊话: {}", e.getMessage());
|
logger.error("[命令发送失败] 国标级联 语音喊话: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
|
public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler {
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class);
|
private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class);
|
||||||
|
|
||||||
private final String cmdType = "Catalog";
|
private final String cmdType = "Catalog";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|||||||
@ -20,6 +20,9 @@ import javax.sdp.SessionDescription;
|
|||||||
import javax.sip.InvalidArgumentException;
|
import javax.sip.InvalidArgumentException;
|
||||||
import javax.sip.ResponseEvent;
|
import javax.sip.ResponseEvent;
|
||||||
import javax.sip.SipException;
|
import javax.sip.SipException;
|
||||||
|
import javax.sip.InvalidArgumentException;
|
||||||
|
import javax.sip.ResponseEvent;
|
||||||
|
import javax.sip.SipException;
|
||||||
import javax.sip.SipFactory;
|
import javax.sip.SipFactory;
|
||||||
import javax.sip.address.SipURI;
|
import javax.sip.address.SipURI;
|
||||||
import javax.sip.message.Request;
|
import javax.sip.message.Request;
|
||||||
|
|||||||
@ -155,6 +155,26 @@ public class SipUtils {
|
|||||||
return builder.toString();
|
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地址和端口号
|
* 从请求中获取设备ip地址和端口号
|
||||||
* @param request 请求
|
* @param request 请求
|
||||||
|
|||||||
@ -77,6 +77,50 @@ public class XmlUtil {
|
|||||||
return null == e ? null : e.getText().trim();
|
return null == e ? null : e.getText().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取element对象的text的值
|
||||||
|
*
|
||||||
|
* @param em 节点的对象
|
||||||
|
* @param tag 节点的tag
|
||||||
|
* @return 节点
|
||||||
|
*/
|
||||||
|
public static Double getDouble(Element em, String tag) {
|
||||||
|
if (null == em) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Element e = em.element(tag);
|
||||||
|
if (null == e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String text = e.getText().trim();
|
||||||
|
if (ObjectUtils.isEmpty(text) || !NumberUtils.isParsable(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Double.parseDouble(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取element对象的text的值
|
||||||
|
*
|
||||||
|
* @param em 节点的对象
|
||||||
|
* @param tag 节点的tag
|
||||||
|
* @return 节点
|
||||||
|
*/
|
||||||
|
public static Integer getInteger(Element em, String tag) {
|
||||||
|
if (null == em) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Element e = em.element(tag);
|
||||||
|
if (null == e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String text = e.getText().trim();
|
||||||
|
if (ObjectUtils.isEmpty(text) || !NumberUtils.isParsable(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Integer.parseInt(text);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 递归解析xml节点,适用于 多节点数据
|
* 递归解析xml节点,适用于 多节点数据
|
||||||
*
|
*
|
||||||
|
|||||||
@ -10,13 +10,20 @@ import com.genersoft.iot.vmp.conf.UserSetting;
|
|||||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||||
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
|
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.SSRCFactory;
|
||||||
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
|
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.DeferredResultHolder;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
|
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.SIPCommander;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.*;
|
import com.genersoft.iot.vmp.media.zlm.dto.*;
|
||||||
|
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;
|
||||||
|
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.hook.*;
|
import com.genersoft.iot.vmp.media.zlm.dto.hook.*;
|
||||||
import com.genersoft.iot.vmp.service.*;
|
import com.genersoft.iot.vmp.service.*;
|
||||||
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
|
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
|
||||||
@ -62,7 +69,13 @@ public class ZLMHttpHookListener {
|
|||||||
private SIPCommander cmder;
|
private SIPCommander cmder;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SIPCommanderFroPlatform commanderFroPlatform;
|
private ISIPCommanderForPlatform commanderFroPlatform;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AudioBroadcastManager audioBroadcastManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ZLMServerFactory zlmServerFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IPlayService playService;
|
private IPlayService playService;
|
||||||
@ -308,8 +321,16 @@ public class ZLMHttpHookListener {
|
|||||||
result.setModify_stamp(2);
|
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")) {
|
if (param.getApp().equalsIgnoreCase("rtp")) {
|
||||||
String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + param.getStream();
|
String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + param.getStream();
|
||||||
OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo) redisTemplate.opsForValue().get(receiveKey);
|
OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo) redisTemplate.opsForValue().get(receiveKey);
|
||||||
@ -340,7 +361,6 @@ public class ZLMHttpHookListener {
|
|||||||
OriginType.values()[param.getOriginType()].getType(), param.getApp(), param.getStream());
|
OriginType.values()[param.getOriginType()].getType(), param.getApp(), param.getStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
JSONObject json = (JSONObject) JSON.toJSON(param);
|
JSONObject json = (JSONObject) JSON.toJSON(param);
|
||||||
taskExecutor.execute(() -> {
|
taskExecutor.execute(() -> {
|
||||||
// 发送hook订阅通知
|
// 发送hook订阅通知
|
||||||
@ -370,15 +390,17 @@ public class ZLMHttpHookListener {
|
|||||||
redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
|
redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("rtsp".equals(param.getSchema())) {
|
if ("rtsp".equals(param.getSchema())) {
|
||||||
// 更新流媒体负载信息
|
logger.info("流变化:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream());
|
||||||
if (param.isRegist()) {
|
if (param.isRegist()) {
|
||||||
mediaServerService.addCount(param.getMediaServerId());
|
mediaServerService.addCount(param.getMediaServerId());
|
||||||
} else {
|
} else {
|
||||||
mediaServerService.removeCount(param.getMediaServerId());
|
mediaServerService.removeCount(param.getMediaServerId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int updateStatusResult = streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream());
|
||||||
|
if (updateStatusResult > 0) {
|
||||||
|
}
|
||||||
if ("rtp".equals(param.getApp())) {
|
if ("rtp".equals(param.getApp())) {
|
||||||
if (!param.isRegist()) {
|
if (!param.isRegist()) {
|
||||||
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
|
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream());
|
||||||
@ -388,6 +410,63 @@ public class ZLMHttpHookListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} 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 {
|
} else {
|
||||||
String type;
|
String type;
|
||||||
if (param.getOriginType() == 0) {
|
if (param.getOriginType() == 0) {
|
||||||
@ -458,10 +537,19 @@ public class ZLMHttpHookListener {
|
|||||||
streamSendManager.remove(sendRtpItem);
|
streamSendManager.remove(sendRtpItem);
|
||||||
} else {
|
} else {
|
||||||
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId());
|
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 |
|
} catch (SipException | InvalidArgumentException | ParseException |
|
||||||
SsrcTransactionNotFoundException e) {
|
SsrcTransactionNotFoundException e) {
|
||||||
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
|
logger.error("[命令发送失败] 发送BYE: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -469,7 +557,6 @@ public class ZLMHttpHookListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return HookResult.SUCCESS();
|
return HookResult.SUCCESS();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,6 +628,13 @@ public class ZLMHttpHookListener {
|
|||||||
storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
|
storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
|
||||||
return ret;
|
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 {
|
} else {
|
||||||
// 非国标流 推流/拉流代理
|
// 非国标流 推流/拉流代理
|
||||||
// 拉流代理
|
// 拉流代理
|
||||||
@ -662,7 +756,7 @@ public class ZLMHttpHookListener {
|
|||||||
|
|
||||||
if (!exist) {
|
if (!exist) {
|
||||||
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaInfo, param.getStream(), null,
|
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) -> {
|
playService.playBack(mediaInfo, ssrcInfo, deviceId, channelId, startTime, endTime, (code, message, data) -> {
|
||||||
msg.setData(new HookResult(code, message));
|
msg.setData(new HookResult(code, message));
|
||||||
resultHolder.invokeResult(msg);
|
resultHolder.invokeResult(msg);
|
||||||
@ -747,7 +841,8 @@ public class ZLMHttpHookListener {
|
|||||||
*/
|
*/
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8")
|
@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());
|
logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc());
|
||||||
|
|
||||||
taskExecutor.execute(() -> {
|
taskExecutor.execute(() -> {
|
||||||
|
|||||||
@ -97,6 +97,7 @@ public class ZLMRESTfulUtils {
|
|||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
try {
|
try {
|
||||||
Response response = client.newCall(request).execute();
|
Response response = client.newCall(request).execute();
|
||||||
|
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
ResponseBody responseBody = response.body();
|
ResponseBody responseBody = response.body();
|
||||||
if (responseBody != null) {
|
if (responseBody != null) {
|
||||||
@ -104,6 +105,8 @@ public class ZLMRESTfulUtils {
|
|||||||
responseJSON = JSON.parseObject(responseStr);
|
responseJSON = JSON.parseObject(responseStr);
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
|
System.out.println( 2222);
|
||||||
|
System.out.println( response.code());
|
||||||
response.close();
|
response.close();
|
||||||
Objects.requireNonNull(response.body()).close();
|
Objects.requireNonNull(response.body()).close();
|
||||||
}
|
}
|
||||||
@ -112,11 +115,11 @@ public class ZLMRESTfulUtils {
|
|||||||
|
|
||||||
if(e instanceof SocketTimeoutException){
|
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){
|
if(e instanceof ConnectException){
|
||||||
//判断连接异常,我这里是报Failed to connect to 10.7.5.144
|
//判断连接异常,我这里是报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){
|
}catch (Exception e){
|
||||||
@ -324,6 +327,10 @@ public class ZLMRESTfulUtils {
|
|||||||
return sendPost(mediaServerItem, "startSendRtpPassive",param, null);
|
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) {
|
public JSONObject stopSendRtp(MediaServerItem mediaServerItem, Map<String, Object> param) {
|
||||||
return sendPost(mediaServerItem, "stopSendRtp",param, null);
|
return sendPost(mediaServerItem, "stopSendRtp",param, null);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,7 @@ public class ZLMServerFactory {
|
|||||||
* @param tcpMode 0/null udp 模式,1 tcp 被动模式, 2 tcp 主动模式。
|
* @param tcpMode 0/null udp 模式,1 tcp 被动模式, 2 tcp 主动模式。
|
||||||
* @return
|
* @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;
|
int result = -1;
|
||||||
// 查询此rtp server 是否已经存在
|
// 查询此rtp server 是否已经存在
|
||||||
JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
|
JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
|
||||||
@ -58,7 +58,7 @@ public class ZLMServerFactory {
|
|||||||
JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerItem, param);
|
JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerItem, param);
|
||||||
if (jsonObject != null ) {
|
if (jsonObject != null ) {
|
||||||
if (jsonObject.getInteger("code") == 0) {
|
if (jsonObject.getInteger("code") == 0) {
|
||||||
return createRTPServer(mediaServerItem, streamId, ssrc, port, reUsePort, tcpMode);
|
return createRTPServer(mediaServerItem, streamId, ssrc, port,onlyAuto, reUsePort, tcpMode);
|
||||||
}else {
|
}else {
|
||||||
logger.warn("[开启rtpServer], 重启RtpServer错误");
|
logger.warn("[开启rtpServer], 重启RtpServer错误");
|
||||||
}
|
}
|
||||||
@ -86,6 +86,9 @@ public class ZLMServerFactory {
|
|||||||
}else {
|
}else {
|
||||||
param.put("port", port);
|
param.put("port", port);
|
||||||
}
|
}
|
||||||
|
if (onlyAuto != null) {
|
||||||
|
param.put("only_audio", onlyAuto?"1":"0");
|
||||||
|
}
|
||||||
if (ssrc != 0) {
|
if (ssrc != 0) {
|
||||||
param.put("ssrc", ssrc);
|
param.put("ssrc", ssrc);
|
||||||
}
|
}
|
||||||
@ -111,9 +114,10 @@ public class ZLMServerFactory {
|
|||||||
Map<String, Object> param = new HashMap<>();
|
Map<String, Object> param = new HashMap<>();
|
||||||
param.put("stream_id", streamId);
|
param.put("stream_id", streamId);
|
||||||
JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(serverItem, param);
|
JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(serverItem, param);
|
||||||
|
logger.info("关闭RTP Server " + jsonObject);
|
||||||
if (jsonObject != null ) {
|
if (jsonObject != null ) {
|
||||||
if (jsonObject.getInteger("code") == 0) {
|
if (jsonObject.getInteger("code") == 0) {
|
||||||
result = jsonObject.getInteger("hit") == 1;
|
result = jsonObject.getInteger("hit") >= 1;
|
||||||
}else {
|
}else {
|
||||||
logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
|
logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg"));
|
||||||
}
|
}
|
||||||
@ -205,8 +209,8 @@ public class ZLMServerFactory {
|
|||||||
sendRtpItem.setPort(port);
|
sendRtpItem.setPort(port);
|
||||||
sendRtpItem.setSsrc(ssrc);
|
sendRtpItem.setSsrc(ssrc);
|
||||||
sendRtpItem.setApp(app);
|
sendRtpItem.setApp(app);
|
||||||
sendRtpItem.setStreamId(stream);
|
|
||||||
sendRtpItem.setDestId(platformId);
|
sendRtpItem.setDestId(platformId);
|
||||||
|
sendRtpItem.setStream(stream);
|
||||||
sendRtpItem.setChannelId(channelId);
|
sendRtpItem.setChannelId(channelId);
|
||||||
sendRtpItem.setTcp(tcp);
|
sendRtpItem.setTcp(tcp);
|
||||||
sendRtpItem.setLocalPort(localPort);
|
sendRtpItem.setLocalPort(localPort);
|
||||||
@ -226,10 +230,14 @@ public class ZLMServerFactory {
|
|||||||
/**
|
/**
|
||||||
* 调用zlm RESTFUL API —— startSendRtpPassive
|
* 调用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);
|
return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Map<String, Object>param, ZLMRESTfulUtils.RequestCallback callback) {
|
||||||
|
return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param, callback);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询待转推的流是否就绪
|
* 查询待转推的流是否就绪
|
||||||
*/
|
*/
|
||||||
@ -288,13 +296,54 @@ public class ZLMServerFactory {
|
|||||||
result= true;
|
result= true;
|
||||||
logger.info("[停止RTP推流] 成功");
|
logger.info("[停止RTP推流] 成功");
|
||||||
} else {
|
} 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;
|
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) {
|
public Boolean updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc) {
|
||||||
|
|||||||
@ -39,6 +39,7 @@ public class ZlmHttpHookSubscribe {
|
|||||||
hookSubscribe.setExpires(expiresInstant);
|
hookSubscribe.setExpires(expiresInstant);
|
||||||
}
|
}
|
||||||
allSubscribes.computeIfAbsent(hookSubscribe.getHookType(), k -> new ConcurrentHashMap<>()).put(hookSubscribe, event);
|
allSubscribes.computeIfAbsent(hookSubscribe.getHookType(), k -> new ConcurrentHashMap<>()).put(hookSubscribe, event);
|
||||||
|
System.out.println(allSubscribes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZlmHttpHookSubscribe.Event sendNotify(HookType type, JSONObject hookResponse) {
|
public ZlmHttpHookSubscribe.Event sendNotify(HookType type, JSONObject hookResponse) {
|
||||||
@ -49,6 +50,7 @@ public class ZlmHttpHookSubscribe {
|
|||||||
}
|
}
|
||||||
for (IHookSubscribe key : eventMap.keySet()) {
|
for (IHookSubscribe key : eventMap.keySet()) {
|
||||||
Boolean result = null;
|
Boolean result = null;
|
||||||
|
|
||||||
for (String s : key.getContent().keySet()) {
|
for (String s : key.getContent().keySet()) {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = key.getContent().getString(s).equals(hookResponse.getString(s));
|
result = key.getContent().getString(s).equals(hookResponse.getString(s));
|
||||||
|
|||||||
@ -35,6 +35,21 @@ public class HookSubscribeFactory {
|
|||||||
return hookSubscribe;
|
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() {
|
public static HookSubscribeForServerStarted on_server_started() {
|
||||||
HookSubscribeForServerStarted hookSubscribe = new HookSubscribeForServerStarted();
|
HookSubscribeForServerStarted hookSubscribe = new HookSubscribeForServerStarted();
|
||||||
hookSubscribe.setContent(new JSONObject());
|
hookSubscribe.setContent(new JSONObject());
|
||||||
@ -52,4 +67,5 @@ public class HookSubscribeFactory {
|
|||||||
|
|
||||||
return hookSubscribe;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -47,8 +47,10 @@ public interface IMediaServerService {
|
|||||||
|
|
||||||
void updateVmServer(List<MediaServerItem> mediaServerItemList);
|
void updateVmServer(List<MediaServerItem> mediaServerItemList);
|
||||||
|
|
||||||
SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck,
|
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);
|
||||||
|
|
||||||
|
SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto);
|
||||||
|
|
||||||
void closeRTPServer(MediaServerItem mediaServerItem, String streamId);
|
void closeRTPServer(MediaServerItem mediaServerItem, String streamId);
|
||||||
|
|
||||||
|
|||||||
@ -53,4 +53,5 @@ public interface IMediaService {
|
|||||||
boolean closeStream(MediaServerItem mediaInfo, String app, String stream);
|
boolean closeStream(MediaServerItem mediaInfo, String app, String stream);
|
||||||
|
|
||||||
String getStreamType(String app, String stream);
|
String getStreamType(String app, String stream);
|
||||||
|
StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId, boolean isPlay);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,21 @@
|
|||||||
package com.genersoft.iot.vmp.service;
|
package com.genersoft.iot.vmp.service;
|
||||||
|
|
||||||
import com.genersoft.iot.vmp.common.CommonGbChannel;
|
import com.genersoft.iot.vmp.common.CommonGbChannel;
|
||||||
|
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
|
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.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.sip.InvalidArgumentException;
|
||||||
|
import javax.sip.SipException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 国标平台的业务类
|
* 国标平台的业务类
|
||||||
* @author lin
|
* @author lin
|
||||||
@ -59,6 +68,22 @@ public interface IPlatformService {
|
|||||||
*/
|
*/
|
||||||
void sendNotifyMobilePosition(Integer platformId);
|
void sendNotifyMobilePosition(Integer 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);
|
void addSimulatedSubscribeInfo(ParentPlatform parentPlatform);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,16 +1,25 @@
|
|||||||
package com.genersoft.iot.vmp.service;
|
package com.genersoft.iot.vmp.service;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||||
import com.genersoft.iot.vmp.conf.exception.ServiceException;
|
import com.genersoft.iot.vmp.conf.exception.ServiceException;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.Device;
|
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.gb28181.bean.DeviceChannel;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
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.ErrorCallback;
|
||||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
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.InvalidArgumentException;
|
||||||
import javax.sip.SipException;
|
import javax.sip.SipException;
|
||||||
|
import javax.sip.header.CallIdHeader;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 点播处理
|
* 点播处理
|
||||||
@ -21,6 +30,8 @@ public interface IPlayService {
|
|||||||
ErrorCallback<Object> callback);
|
ErrorCallback<Object> callback);
|
||||||
SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, 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);
|
MediaServerItem getNewMediaServerItem(Device device);
|
||||||
|
|
||||||
void playBack(String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback);
|
void playBack(String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback);
|
||||||
@ -34,10 +45,27 @@ public interface IPlayService {
|
|||||||
|
|
||||||
void zlmServerOnline(String mediaServerId);
|
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 pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException;
|
||||||
|
|
||||||
void resumeRtp(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);
|
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.DynamicTask;
|
||||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
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.session.VideoStreamSessionManager;
|
||||||
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
|
import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask;
|
||||||
import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
|
import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask;
|
||||||
@ -15,6 +16,12 @@ 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.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
|
||||||
import com.genersoft.iot.vmp.service.*;
|
import com.genersoft.iot.vmp.service.*;
|
||||||
import com.genersoft.iot.vmp.service.bean.Group;
|
import com.genersoft.iot.vmp.service.bean.Group;
|
||||||
|
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;
|
||||||
|
import com.genersoft.iot.vmp.service.IMediaServerService;
|
||||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||||
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
|
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
|
||||||
import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
|
import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
|
||||||
@ -37,9 +44,7 @@ import javax.sip.InvalidArgumentException;
|
|||||||
import javax.sip.SipException;
|
import javax.sip.SipException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,6 +104,12 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ICommonGbChannelService commonGbChannelService;
|
private ICommonGbChannelService commonGbChannelService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AudioBroadcastManager audioBroadcastManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ZLMRESTfulUtils zlmresTfulUtils;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void online(Device device, SipTransactionInfo sipTransactionInfo) {
|
public void online(Device device, SipTransactionInfo sipTransactionInfo) {
|
||||||
logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
|
logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
|
||||||
@ -238,6 +249,25 @@ public class DeviceServiceImpl implements IDeviceService {
|
|||||||
// 移除订阅
|
// 移除订阅
|
||||||
removeCatalogSubscribe(device, null);
|
removeCatalogSubscribe(device, null);
|
||||||
removeMobilePositionSubscribe(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
|
@Override
|
||||||
|
|||||||
@ -122,6 +122,8 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化
|
* 初始化
|
||||||
*/
|
*/
|
||||||
@ -148,7 +150,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck,
|
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) {
|
if (mediaServerItem == null || mediaServerItem.getId() == null) {
|
||||||
logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null");
|
logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null");
|
||||||
return null;
|
return null;
|
||||||
@ -174,13 +176,19 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
|||||||
}
|
}
|
||||||
int rtpServerPort;
|
int rtpServerPort;
|
||||||
if (mediaServerItem.isRtpEnable()) {
|
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 {
|
} else {
|
||||||
rtpServerPort = mediaServerItem.getRtpProxyPort();
|
rtpServerPort = mediaServerItem.getRtpProxyPort();
|
||||||
}
|
}
|
||||||
return new SSRCInfo(rtpServerPort, ssrc, streamId);
|
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
|
@Override
|
||||||
public void closeRTPServer(MediaServerItem mediaServerItem, String streamId) {
|
public void closeRTPServer(MediaServerItem mediaServerItem, String streamId) {
|
||||||
if (mediaServerItem == null) {
|
if (mediaServerItem == null) {
|
||||||
@ -309,6 +317,7 @@ public class MediaServerServiceImpl implements IMediaServerService {
|
|||||||
return JsonUtil.redisJsonToObject(redisTemplate, key, MediaServerItem.class);
|
return JsonUtil.redisJsonToObject(redisTemplate, key, MediaServerItem.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaServerItem getDefaultMediaServer() {
|
public MediaServerItem getDefaultMediaServer() {
|
||||||
return mediaServerMapper.queryDefault();
|
return mediaServerMapper.queryDefault();
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import com.alibaba.fastjson2.JSON;
|
|||||||
import com.alibaba.fastjson2.JSONArray;
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
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.conf.MediaConfig;
|
||||||
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
|
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.MediaServerItem;
|
||||||
@ -12,6 +11,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
|
|||||||
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxy;
|
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxy;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.StreamPush;
|
import com.genersoft.iot.vmp.media.zlm.dto.StreamPush;
|
||||||
import com.genersoft.iot.vmp.service.IMediaServerService;
|
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.IRedisCatchStorage;
|
||||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||||
import com.genersoft.iot.vmp.service.IMediaService;
|
import com.genersoft.iot.vmp.service.IMediaService;
|
||||||
@ -21,8 +21,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class MediaServiceImpl implements IMediaService {
|
public class MediaServiceImpl implements IMediaService {
|
||||||
|
|
||||||
@ -49,7 +47,7 @@ public class MediaServiceImpl implements IMediaService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String callId) {
|
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
|
@Override
|
||||||
@ -77,9 +75,9 @@ public class MediaServiceImpl implements IMediaService {
|
|||||||
JSONObject mediaJSON = data.getJSONObject(0);
|
JSONObject mediaJSON = data.getJSONObject(0);
|
||||||
JSONArray tracks = mediaJSON.getJSONArray("tracks");
|
JSONArray tracks = mediaJSON.getJSONArray("tracks");
|
||||||
if (authority) {
|
if (authority) {
|
||||||
streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld);
|
streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld, true);
|
||||||
}else {
|
}else {
|
||||||
streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null);
|
streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +92,7 @@ public class MediaServiceImpl implements IMediaService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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();
|
StreamInfo streamInfoResult = new StreamInfo();
|
||||||
streamInfoResult.setStream(stream);
|
streamInfoResult.setStream(stream);
|
||||||
streamInfoResult.setApp(app);
|
streamInfoResult.setApp(app);
|
||||||
@ -111,7 +109,7 @@ public class MediaServiceImpl implements IMediaService {
|
|||||||
streamInfoResult.setFmp4(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam);
|
streamInfoResult.setFmp4(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam);
|
||||||
streamInfoResult.setHls(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.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);
|
streamInfoResult.setTracks(tracks);
|
||||||
return streamInfoResult;
|
return streamInfoResult;
|
||||||
|
|||||||
@ -238,8 +238,6 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService {
|
|||||||
public CommonGbChannel queryChannelByPlatformIdAndChannelDeviceId(Integer platformId, String channelId) {
|
public CommonGbChannel queryChannelByPlatformIdAndChannelDeviceId(Integer platformId, String channelId) {
|
||||||
return platformChannelMapper.queryChannelByPlatformIdAndChannelDeviceId(platformId, channelId);
|
return platformChannelMapper.queryChannelByPlatformIdAndChannelDeviceId(platformId, channelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<CommonGbChannel> queryCommonGbChannellList(Integer platformId) {
|
public List<CommonGbChannel> queryCommonGbChannellList(Integer platformId) {
|
||||||
return platformChannelMapper.queryCommonGbChannellList(platformId);
|
return platformChannelMapper.queryCommonGbChannellList(platformId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,40 +2,64 @@ package com.genersoft.iot.vmp.service.impl;
|
|||||||
|
|
||||||
import com.genersoft.iot.vmp.common.CommonGbChannel;
|
import com.genersoft.iot.vmp.common.CommonGbChannel;
|
||||||
import com.genersoft.iot.vmp.common.VideoManagerConstants;
|
import com.genersoft.iot.vmp.common.VideoManagerConstants;
|
||||||
|
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.baomidou.dynamic.datasource.annotation.DS;
|
||||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
import com.genersoft.iot.vmp.conf.DynamicTask;
|
||||||
import com.genersoft.iot.vmp.conf.UserSetting;
|
import com.genersoft.iot.vmp.conf.UserSetting;
|
||||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||||
|
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
||||||
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
|
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.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||||
import com.genersoft.iot.vmp.media.zlm.IStreamSendManager;
|
import com.genersoft.iot.vmp.media.zlm.IStreamSendManager;
|
||||||
|
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.ZLMServerFactory;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
|
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.IMediaServerService;
|
||||||
import com.genersoft.iot.vmp.service.IPlatformChannelService;
|
import com.genersoft.iot.vmp.service.IPlatformChannelService;
|
||||||
import com.genersoft.iot.vmp.service.IPlatformService;
|
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.IRedisCatchStorage;
|
||||||
import com.genersoft.iot.vmp.storager.dao.*;
|
import com.genersoft.iot.vmp.storager.dao.*;
|
||||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||||
import com.github.pagehelper.PageHelper;
|
import com.github.pagehelper.PageHelper;
|
||||||
import com.github.pagehelper.PageInfo;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.sdp.*;
|
||||||
import javax.sip.InvalidArgumentException;
|
import javax.sip.InvalidArgumentException;
|
||||||
|
import javax.sip.ResponseEvent;
|
||||||
|
import javax.sip.PeerUnavailableException;
|
||||||
import javax.sip.SipException;
|
import javax.sip.SipException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
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
|
* @author lin
|
||||||
@ -62,6 +86,8 @@ public class PlatformServiceImpl implements IPlatformService {
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IStreamSendManager streamSendManager;
|
private IStreamSendManager streamSendManager;
|
||||||
|
@Autowired
|
||||||
|
private IRedisCatchStorage redisCatchStorage;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SSRCFactory ssrcFactory;
|
private SSRCFactory ssrcFactory;
|
||||||
@ -87,6 +113,21 @@ public class PlatformServiceImpl implements IPlatformService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IPlatformChannelService platformChannelService;
|
private IPlatformChannelService platformChannelService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ZlmHttpHookSubscribe subscribe;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VideoStreamSessionManager streamSession;
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IPlayService playService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IInviteStreamService inviteStreamService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ZLMRESTfulUtils zlmresTfulUtils;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -369,7 +410,7 @@ public class PlatformServiceImpl implements IPlatformService {
|
|||||||
Map<String, Object> param = new HashMap<>(3);
|
Map<String, Object> param = new HashMap<>(3);
|
||||||
param.put("vhost", "__defaultVhost__");
|
param.put("vhost", "__defaultVhost__");
|
||||||
param.put("app", sendRtpItem.getApp());
|
param.put("app", sendRtpItem.getApp());
|
||||||
param.put("stream", sendRtpItem.getStreamId());
|
param.put("stream", sendRtpItem.getStream());
|
||||||
zlmServerFactory.stopSendRtpStream(mediaInfo, param);
|
zlmServerFactory.stopSendRtpStream(mediaInfo, param);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -487,4 +528,319 @@ public class PlatformServiceImpl implements IPlatformService {
|
|||||||
public List<ParentPlatform> querySharePlatform(List<CommonGbChannel> channel, List<Integer> platformIdList) {
|
public List<ParentPlatform> querySharePlatform(List<CommonGbChannel> channel, List<Integer> platformIdList) {
|
||||||
return platformMapper.querySharePlatform(channel, platformIdList);
|
return platformMapper.querySharePlatform(channel, platformIdList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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,13 +8,18 @@ import com.genersoft.iot.vmp.common.InviteSessionStatus;
|
|||||||
import com.genersoft.iot.vmp.common.InviteSessionType;
|
import com.genersoft.iot.vmp.common.InviteSessionType;
|
||||||
import com.genersoft.iot.vmp.common.StreamInfo;
|
import com.genersoft.iot.vmp.common.StreamInfo;
|
||||||
import com.genersoft.iot.vmp.conf.DynamicTask;
|
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.UserSetting;
|
||||||
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
import com.genersoft.iot.vmp.conf.exception.ControllerException;
|
||||||
import com.genersoft.iot.vmp.conf.exception.ServiceException;
|
import com.genersoft.iot.vmp.conf.exception.ServiceException;
|
||||||
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
|
||||||
import com.genersoft.iot.vmp.gb28181.bean.*;
|
import com.genersoft.iot.vmp.gb28181.bean.*;
|
||||||
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
|
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.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.ISIPCommander;
|
||||||
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
|
||||||
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
|
||||||
@ -22,6 +27,11 @@ import com.genersoft.iot.vmp.media.zlm.IStreamSendManager;
|
|||||||
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
|
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
|
||||||
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
|
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
|
||||||
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
|
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.HookSubscribeFactory;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRecordMp4;
|
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRecordMp4;
|
||||||
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
|
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
|
||||||
@ -38,6 +48,12 @@ import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
|||||||
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
||||||
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
|
||||||
import com.genersoft.iot.vmp.service.*;
|
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.DownloadFileInfo;
|
||||||
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
|
||||||
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
||||||
@ -46,13 +62,20 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
|||||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||||
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
|
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
|
||||||
import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
|
import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
|
||||||
|
import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
|
||||||
import com.genersoft.iot.vmp.utils.CloudRecordUtils;
|
import com.genersoft.iot.vmp.utils.CloudRecordUtils;
|
||||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
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.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 gov.nist.javax.sip.message.SIPResponse;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.stereotype.Service;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
@ -60,13 +83,12 @@ import javax.sdp.*;
|
|||||||
import javax.sip.InvalidArgumentException;
|
import javax.sip.InvalidArgumentException;
|
||||||
import javax.sip.ResponseEvent;
|
import javax.sip.ResponseEvent;
|
||||||
import javax.sip.SipException;
|
import javax.sip.SipException;
|
||||||
|
import javax.sip.header.CallIdHeader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
@SuppressWarnings(value = {"rawtypes", "unchecked"})
|
@SuppressWarnings(value = {"rawtypes", "unchecked"})
|
||||||
@Service
|
@Service
|
||||||
@ -82,7 +104,13 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
private ISIPCommander cmder;
|
private ISIPCommander cmder;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SIPCommanderFroPlatform sipCommanderFroPlatform;
|
private AudioBroadcastManager audioBroadcastManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IDeviceService deviceService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISIPCommanderForPlatform sipCommanderFroPlatform;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IRedisCatchStorage redisCatchStorage;
|
private IRedisCatchStorage redisCatchStorage;
|
||||||
@ -90,6 +118,9 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IStreamSendManager streamSendManager;
|
private IStreamSendManager streamSendManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ZLMServerFactory zlmServerFactory;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IInviteStreamService inviteStreamService;
|
private IInviteStreamService inviteStreamService;
|
||||||
|
|
||||||
@ -97,10 +128,10 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
private ZlmHttpHookSubscribe subscribe;
|
private ZlmHttpHookSubscribe subscribe;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ZLMRESTfulUtils zlmresTfulUtils;
|
private SendRtpPortManager sendRtpPortManager;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ZLMServerFactory zlmServerFactory;
|
private ZLMRESTfulUtils zlmresTfulUtils;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IMediaService mediaService;
|
private IMediaService mediaService;
|
||||||
@ -113,12 +144,13 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private DeviceMapper deviceMapper;
|
private DeviceMapper deviceMapper;
|
||||||
|
private UserSetting userSetting;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IDeviceChannelService channelService;
|
private IDeviceChannelService channelService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserSetting userSetting;
|
private SipConfig sipConfig;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private DynamicTask dynamicTask;
|
private DynamicTask dynamicTask;
|
||||||
@ -126,6 +158,29 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private DeviceChannelMapper deviceChannelMapper;
|
private DeviceChannelMapper deviceChannelMapper;
|
||||||
|
|
||||||
|
@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
|
@Override
|
||||||
public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback) {
|
public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback) {
|
||||||
@ -183,7 +238,7 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
String streamId = String.format("%s_%s", device.getDeviceId(), channelId);;
|
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) {
|
if (ssrcInfo == null) {
|
||||||
callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null);
|
callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null);
|
||||||
inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
|
inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
|
||||||
@ -196,6 +251,147 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
return ssrcInfo;
|
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
|
@Override
|
||||||
public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel,
|
public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel,
|
||||||
@ -428,6 +624,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());
|
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());
|
JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
|
||||||
logger.info("[TCP主动连接对方] 结果: {}" , jsonObject);
|
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) {
|
} catch (SdpException e) {
|
||||||
logger.error("[TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
|
logger.error("[TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e);
|
||||||
dynamicTask.stop(timeOutTaskKey);
|
dynamicTask.stop(timeOutTaskKey);
|
||||||
@ -439,7 +650,7 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
|
|
||||||
callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
|
callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(),
|
||||||
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
|
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.getCode(),
|
||||||
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
|
InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null);
|
||||||
}
|
}
|
||||||
@ -467,7 +678,7 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
|
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;
|
StreamInfo streamInfo = null;
|
||||||
Device device = redisCatchStorage.getDevice(deviceId);
|
Device device = redisCatchStorage.getDevice(deviceId);
|
||||||
OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam)hookParam;
|
OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam)hookParam;
|
||||||
@ -551,7 +762,7 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
.replace(":", "")
|
.replace(":", "")
|
||||||
.replace(" ", "");
|
.replace(" ", "");
|
||||||
String stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr;
|
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);
|
playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -746,7 +957,7 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
return;
|
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);
|
download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -987,6 +1198,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
|
@Override
|
||||||
public void zlmServerOnline(String mediaServerId) {
|
public void zlmServerOnline(String mediaServerId) {
|
||||||
// TODO 查找之前的点播,流如果不存在则给下级发送bye
|
// TODO 查找之前的点播,流如果不存在则给下级发送bye
|
||||||
@ -1098,6 +1445,199 @@ public class PlayServiceImpl implements IPlayService {
|
|||||||
cmder.playResumeCmd(device, inviteInfo.getStreamInfo());
|
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
|
@Override
|
||||||
public void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback) {
|
public void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback) {
|
||||||
Device device = deviceMapper.getDeviceByDeviceId(deviceId);
|
Device device = deviceMapper.getDeviceByDeviceId(deviceId);
|
||||||
|
|||||||
@ -91,7 +91,7 @@ public class RedisPushStreamCloseResponseListener implements MessageListener {
|
|||||||
}
|
}
|
||||||
if (push.isSelf()) {
|
if (push.isSelf()) {
|
||||||
// 停止向上级推流
|
// 停止向上级推流
|
||||||
String streamId = sendRtpItem.getStreamId();
|
String streamId = sendRtpItem.getStream();
|
||||||
Map<String, Object> param = new HashMap<>();
|
Map<String, Object> param = new HashMap<>();
|
||||||
param.put("vhost","__defaultVhost__");
|
param.put("vhost","__defaultVhost__");
|
||||||
param.put("app",sendRtpItem.getApp());
|
param.put("app",sendRtpItem.getApp());
|
||||||
|
|||||||
@ -44,6 +44,7 @@ public interface DeviceMapper {
|
|||||||
"geo_coord_sys," +
|
"geo_coord_sys," +
|
||||||
"on_line," +
|
"on_line," +
|
||||||
"media_server_id," +
|
"media_server_id," +
|
||||||
|
"broadcast_push_after_ack," +
|
||||||
"(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+
|
"(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+
|
||||||
" FROM wvp_device WHERE device_id = #{deviceId}")
|
" FROM wvp_device WHERE device_id = #{deviceId}")
|
||||||
Device getDeviceByDeviceId(String deviceId);
|
Device getDeviceByDeviceId(String deviceId);
|
||||||
@ -74,6 +75,7 @@ public interface DeviceMapper {
|
|||||||
"subscribe_cycle_for_alarm,"+
|
"subscribe_cycle_for_alarm,"+
|
||||||
"ssrc_check,"+
|
"ssrc_check,"+
|
||||||
"as_message_channel,"+
|
"as_message_channel,"+
|
||||||
|
"broadcast_push_after_ack,"+
|
||||||
"geo_coord_sys,"+
|
"geo_coord_sys,"+
|
||||||
"on_line"+
|
"on_line"+
|
||||||
") VALUES (" +
|
") VALUES (" +
|
||||||
@ -102,6 +104,7 @@ public interface DeviceMapper {
|
|||||||
"#{subscribeCycleForAlarm}," +
|
"#{subscribeCycleForAlarm}," +
|
||||||
"#{ssrcCheck}," +
|
"#{ssrcCheck}," +
|
||||||
"#{asMessageChannel}," +
|
"#{asMessageChannel}," +
|
||||||
|
"#{broadcastPushAfterAck}," +
|
||||||
"#{geoCoordSys}," +
|
"#{geoCoordSys}," +
|
||||||
"#{onLine}" +
|
"#{onLine}" +
|
||||||
")")
|
")")
|
||||||
@ -157,6 +160,7 @@ public interface DeviceMapper {
|
|||||||
"subscribe_cycle_for_alarm,"+
|
"subscribe_cycle_for_alarm,"+
|
||||||
"ssrc_check,"+
|
"ssrc_check,"+
|
||||||
"as_message_channel,"+
|
"as_message_channel,"+
|
||||||
|
"broadcast_push_after_ack,"+
|
||||||
"geo_coord_sys,"+
|
"geo_coord_sys,"+
|
||||||
"on_line,"+
|
"on_line,"+
|
||||||
"media_server_id,"+
|
"media_server_id,"+
|
||||||
@ -197,6 +201,7 @@ public interface DeviceMapper {
|
|||||||
"subscribe_cycle_for_alarm,"+
|
"subscribe_cycle_for_alarm,"+
|
||||||
"ssrc_check,"+
|
"ssrc_check,"+
|
||||||
"as_message_channel,"+
|
"as_message_channel,"+
|
||||||
|
"broadcast_push_after_ack,"+
|
||||||
"geo_coord_sys,"+
|
"geo_coord_sys,"+
|
||||||
"on_line"+
|
"on_line"+
|
||||||
" FROM wvp_device WHERE on_line = true")
|
" FROM wvp_device WHERE on_line = true")
|
||||||
@ -227,6 +232,7 @@ public interface DeviceMapper {
|
|||||||
"subscribe_cycle_for_alarm,"+
|
"subscribe_cycle_for_alarm,"+
|
||||||
"ssrc_check,"+
|
"ssrc_check,"+
|
||||||
"as_message_channel,"+
|
"as_message_channel,"+
|
||||||
|
"broadcast_push_after_ack,"+
|
||||||
"geo_coord_sys,"+
|
"geo_coord_sys,"+
|
||||||
"on_line"+
|
"on_line"+
|
||||||
" FROM wvp_device WHERE ip = #{host} AND port=#{port}")
|
" FROM wvp_device WHERE ip = #{host} AND port=#{port}")
|
||||||
@ -248,6 +254,7 @@ public interface DeviceMapper {
|
|||||||
"<if test=\"subscribeCycleForAlarm != null\">, subscribe_cycle_for_alarm=#{subscribeCycleForAlarm}</if>" +
|
"<if test=\"subscribeCycleForAlarm != null\">, subscribe_cycle_for_alarm=#{subscribeCycleForAlarm}</if>" +
|
||||||
"<if test=\"ssrcCheck != null\">, ssrc_check=#{ssrcCheck}</if>" +
|
"<if test=\"ssrcCheck != null\">, ssrc_check=#{ssrcCheck}</if>" +
|
||||||
"<if test=\"asMessageChannel != null\">, as_message_channel=#{asMessageChannel}</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=\"geoCoordSys != null\">, geo_coord_sys=#{geoCoordSys}</if>" +
|
||||||
"<if test=\"mediaServerId != null\">, media_server_id=#{mediaServerId}</if>" +
|
"<if test=\"mediaServerId != null\">, media_server_id=#{mediaServerId}</if>" +
|
||||||
"WHERE device_id=#{deviceId}"+
|
"WHERE device_id=#{deviceId}"+
|
||||||
@ -264,6 +271,7 @@ public interface DeviceMapper {
|
|||||||
"charset,"+
|
"charset,"+
|
||||||
"ssrc_check,"+
|
"ssrc_check,"+
|
||||||
"as_message_channel,"+
|
"as_message_channel,"+
|
||||||
|
"broadcastPushAfterAck,"+
|
||||||
"geo_coord_sys,"+
|
"geo_coord_sys,"+
|
||||||
"on_line,"+
|
"on_line,"+
|
||||||
"media_server_id"+
|
"media_server_id"+
|
||||||
@ -277,6 +285,7 @@ public interface DeviceMapper {
|
|||||||
"#{charset}," +
|
"#{charset}," +
|
||||||
"#{ssrcCheck}," +
|
"#{ssrcCheck}," +
|
||||||
"#{asMessageChannel}," +
|
"#{asMessageChannel}," +
|
||||||
|
"#{broadcastPushAfterAck}," +
|
||||||
"#{geoCoordSys}," +
|
"#{geoCoordSys}," +
|
||||||
"#{onLine}," +
|
"#{onLine}," +
|
||||||
"#{mediaServerId}" +
|
"#{mediaServerId}" +
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,6 +24,7 @@ import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
|
|||||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||||
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
|
||||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
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.ErrorCode;
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
|
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
|
||||||
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||||
@ -46,6 +47,10 @@ import java.text.ParseException;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lin
|
||||||
|
*/
|
||||||
@Tag(name = "国标设备点播")
|
@Tag(name = "国标设备点播")
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@ -249,68 +254,42 @@ public class PlayController {
|
|||||||
|
|
||||||
@Operation(summary = "语音广播命令", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
@Operation(summary = "语音广播命令", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||||
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
|
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
|
||||||
@GetMapping("/broadcast/{deviceId}")
|
@Parameter(name = "deviceId", description = "通道国标编号", required = true)
|
||||||
@PostMapping("/broadcast/{deviceId}")
|
@Parameter(name = "timeout", description = "推流超时时间(秒)", required = true)
|
||||||
public DeferredResult<String> broadcastApi(@PathVariable String deviceId) {
|
@GetMapping("/broadcast/{deviceId}/{channelId}")
|
||||||
|
@PostMapping("/broadcast/{deviceId}/{channelId}")
|
||||||
|
public AudioBroadcastResult broadcastApi(@PathVariable String deviceId, @PathVariable String channelId, Integer timeout, Boolean broadcastMode) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("语音广播API调用");
|
logger.debug("语音广播API调用");
|
||||||
}
|
}
|
||||||
Device device = storager.queryVideoDevice(deviceId);
|
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) {
|
if (device == null) {
|
||||||
|
throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到设备: " + deviceId);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
try {
|
if (channelId == null) {
|
||||||
cmder.audioBroadcastCmd(device, (event) -> {
|
throw new ControllerException(ErrorCode.ERROR400.getCode(), "未找到通道: " + channelId);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.onTimeout(() -> {
|
return playService.audioBroadcast(device, channelId, broadcastMode);
|
||||||
logger.warn("语音广播操作超时, 设备未返回应答指令");
|
|
||||||
RequestMessage msg = new RequestMessage();
|
}
|
||||||
msg.setKey(key);
|
|
||||||
msg.setId(uuid);
|
@Operation(summary = "停止语音广播")
|
||||||
JSONObject json = new JSONObject();
|
@Parameter(name = "deviceId", description = "设备Id", required = true)
|
||||||
json.put("DeviceID", deviceId);
|
@Parameter(name = "channelId", description = "通道Id", required = true)
|
||||||
json.put("CmdType", "Broadcast");
|
@GetMapping("/broadcast/stop/{deviceId}/{channelId}")
|
||||||
json.put("Result", "Failed");
|
@PostMapping("/broadcast/stop/{deviceId}/{channelId}")
|
||||||
json.put("Error", "Timeout. Device did not response to broadcast command.");
|
public void stopBroadcast(@PathVariable String deviceId, @PathVariable String channelId) {
|
||||||
msg.setData(json);
|
if (logger.isDebugEnabled()) {
|
||||||
resultHolder.invokeResult(msg);
|
logger.debug("停止语音广播API调用");
|
||||||
});
|
}
|
||||||
resultHolder.put(key, uuid, result);
|
// try {
|
||||||
return result;
|
// 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))
|
@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;
|
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) {
|
if (localPort == 0) {
|
||||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取端口失败");
|
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;
|
String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + callId + "_" + stream;
|
||||||
int localPortForVideo = zlmServerFactory.createRTPServer(mediaServerItem, stream, 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, tcpMode);
|
int localPortForAudio = zlmServerFactory.createRTPServer(mediaServerItem, stream + "_a" , ssrcInt, null, false, false, tcpMode);
|
||||||
if (localPortForVideo == 0 || localPortForAudio == 0) {
|
if (localPortForVideo == 0 || localPortForAudio == 0) {
|
||||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取端口失败");
|
throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取端口失败");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import javax.security.sasl.AuthenticationException;
|
import javax.security.sasl.AuthenticationException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Tag(name = "用户管理")
|
@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
|
push-authority: true
|
||||||
# 设备上线时是否自动同步通道
|
# 设备上线时是否自动同步通道
|
||||||
sync-channel-on-device-online: false
|
sync-channel-on-device-online: false
|
||||||
|
# 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式
|
||||||
|
broadcast-for-platform: UDP
|
||||||
# 是否使用设备来源Ip作为回复IP, 不设置则为 false
|
# 是否使用设备来源Ip作为回复IP, 不设置则为 false
|
||||||
sip-use-source-ip-as-remote-address: false
|
sip-use-source-ip-as-remote-address: false
|
||||||
# 是否开启sip日志
|
# 是否开启sip日志
|
||||||
sip-log: true
|
sip-log: true
|
||||||
# 是否开启sql日志
|
# 是否开启sql日志
|
||||||
sql-log: true
|
sql-log: true
|
||||||
|
# 收到ack消息后开始发流,默认false, 回复200ok后直接开始发流
|
||||||
|
push-stream-after-ack: false
|
||||||
# 消息通道功能-缺少国标ID是否给所有上级发送消息
|
# 消息通道功能-缺少国标ID是否给所有上级发送消息
|
||||||
send-to-platforms-when-id-lost: true
|
send-to-platforms-when-id-lost: true
|
||||||
# 保持通道状态,不接受notify通道状态变化, 兼容海康平台发送错误消息
|
# 保持通道状态,不接受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: '/',
|
assetsPublicPath: '/',
|
||||||
proxyTable: {
|
proxyTable: {
|
||||||
'/debug': {
|
'/debug': {
|
||||||
target: 'http://localhost:18080',
|
target: 'http://127.0.0.1:18082',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
pathRewrite: {
|
pathRewrite: {
|
||||||
'^/debug': '/'
|
'^/debug': '/'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'/static/snap': {
|
'/static/snap': {
|
||||||
target: 'http://localhost:18080',
|
target: 'http://127.0.0.1:18082',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
// pathRewrite: {
|
// pathRewrite: {
|
||||||
// '^/static/snap': '/static/snap'
|
// '^/static/snap': '/static/snap'
|
||||||
|
|||||||
@ -43,6 +43,9 @@
|
|||||||
<el-option key="UTF-8" label="UTF-8" value="utf-8"></el-option>
|
<el-option key="UTF-8" label="UTF-8" value="utf-8"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</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-form-item label="地理坐标系" prop="geoCoordSys" >
|
||||||
<el-select v-model="form.geoCoordSys" style="float: left; width: 100%" >
|
<el-select v-model="form.geoCoordSys" style="float: left; width: 100%" >
|
||||||
<el-option key="WGS84" label="WGS84" value="WGS84"></el-option>
|
<el-option key="WGS84" label="WGS84" value="WGS84"></el-option>
|
||||||
@ -61,6 +64,7 @@
|
|||||||
<el-form-item label="其他选项">
|
<el-form-item label="其他选项">
|
||||||
<el-checkbox label="SSRC校验" v-model="form.ssrcCheck" style="float: left"></el-checkbox>
|
<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="作为消息通道" 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>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<div style="float: right;">
|
<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">
|
<el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" @close="close()" v-if="showVideoDialog">
|
||||||
<div style="width: 100%; height: 100%">
|
<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-tabs type="card" :stretch="true" v-model="activePlayer" @tab-click="changePlayer"
|
||||||
<!-- <el-tab-pane label="LivePlayer" name="livePlayer">-->
|
v-if="Object.keys(this.player).length > 1">
|
||||||
<!-- <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-tab-pane label="Jessibuca" name="jessibuca">
|
<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>
|
||||||
<el-tab-pane label="WebRTC" name="webRTC">
|
<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>
|
||||||
<el-tab-pane label="h265web">h265web敬请期待</el-tab-pane>
|
<el-tab-pane label="h265web">h265web敬请期待</el-tab-pane>
|
||||||
</el-tabs>
|
</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>
|
<jessibucaPlayer v-if="Object.keys(this.player).length == 1 && this.player.jessibuca" ref="jessibuca"
|
||||||
<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>
|
: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>
|
||||||
<div id="shared" style="text-align: right; margin-top: 1rem;">
|
<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>
|
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">播放地址:</span>
|
||||||
<el-input v-model="getPlayerShared.sharedUrl" :disabled="true">
|
<el-input v-model="getPlayerShared.sharedUrl" :disabled="true">
|
||||||
<template slot="append">
|
<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>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
@ -34,14 +41,17 @@
|
|||||||
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe:</span>
|
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">iframe:</span>
|
||||||
<el-input v-model="getPlayerShared.sharedIframe" :disabled="true">
|
<el-input v-model="getPlayerShared.sharedIframe" :disabled="true">
|
||||||
<template slot="append">
|
<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>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
|
<div style="display: flex; margin-bottom: 0.5rem; height: 2.5rem;">
|
||||||
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址:</span>
|
<span style="width: 5rem; line-height: 2.5rem; text-align: right;">资源地址:</span>
|
||||||
<el-input v-model="getPlayerShared.sharedRtmp" :disabled="true">
|
<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-dropdown slot="prepend" v-if="streamInfo" trigger="click" @command="copyUrl">
|
||||||
<el-button>
|
<el-button>
|
||||||
更多地址<i class="el-icon-arrow-down el-icon--right"></i>
|
更多地址<i class="el-icon-arrow-down el-icon--right"></i>
|
||||||
@ -165,8 +175,12 @@
|
|||||||
<div class="control-round">
|
<div class="control-round">
|
||||||
<div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
|
<div class="control-round-inner"><i class="fa fa-pause-circle"></i></div>
|
||||||
</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: 1.25rem" @mousedown="ptzCamera('zoomin')"
|
||||||
<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>
|
@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;">
|
<div class="contro-speed" style="position: absolute; left: 4px; top: 7rem; width: 9rem;">
|
||||||
<el-slider v-model="controSpeed" :max="255"></el-slider>
|
<el-slider v-model="controSpeed" :max="255"></el-slider>
|
||||||
</div>
|
</div>
|
||||||
@ -174,32 +188,84 @@
|
|||||||
|
|
||||||
<div class="control-panel">
|
<div class="control-panel">
|
||||||
<el-button-group>
|
<el-button-group>
|
||||||
<el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center" size="medium">预置位编号</el-tag>
|
<el-tag style="position :absolute; left: 0rem; top: 0rem; width: 5rem; text-align: center"
|
||||||
<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>
|
size="medium">预置位编号
|
||||||
<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-tag>
|
||||||
<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-input-number style="position: absolute; left: 5rem; top: 0rem; width: 6rem" size="mini"
|
||||||
<el-button style="position: absolute; left: 16rem; top: 0rem; width: 5rem" size="mini" icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除</el-button>
|
v-model="presetPos" controls-position="right" :precision="0" :step="1" :min="1"
|
||||||
<el-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">巡航速度</el-tag>
|
:max="255"></el-input-number>
|
||||||
<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: 0rem; width: 5rem" size="mini"
|
||||||
<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>
|
icon="el-icon-add-location" @click="presetPosition(129, presetPos)">设置
|
||||||
<el-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center" size="medium">停留时间</el-tag>
|
</el-button>
|
||||||
<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: 0rem; width: 5rem" size="mini" type="primary"
|
||||||
<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>
|
icon="el-icon-place" @click="presetPosition(130, presetPos)">调用
|
||||||
<el-tag style="position :absolute; left: 0rem; top: 4.5rem; width: 5rem; text-align: center" size="medium">巡航组编号</el-tag>
|
</el-button>
|
||||||
<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: 16rem; top: 0rem; width: 5rem" size="mini"
|
||||||
<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>
|
icon="el-icon-delete-location" @click="presetPosition(131, presetPos)">删除
|
||||||
<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>
|
||||||
<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-tag style="position :absolute; left: 0rem; top: 2.5rem; width: 5rem; text-align: center"
|
||||||
<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>
|
size="medium">巡航速度
|
||||||
<el-tag style="position :absolute; left: 0rem; top: 7rem; width: 5rem; text-align: center" size="medium">扫描速度</el-tag>
|
</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-input-number style="position: absolute; left: 5rem; top: 2.5rem; width: 6rem" size="mini"
|
||||||
<el-button style="position: absolute; left: 11rem; top: 7rem; width: 5rem" size="mini" icon="el-icon-loading" @click="setSpeedOrTime(138, scanGroup, scanSpeed)">设置</el-button>
|
v-model="cruisingSpeed" controls-position="right" :precision="0" :min="1"
|
||||||
<el-tag style="position :absolute; left: 0rem; top: 9rem; width: 5rem; text-align: center" size="medium">扫描组编号</el-tag>
|
:max="4095"></el-input-number>
|
||||||
<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: 2.5rem; width: 5rem" size="mini"
|
||||||
<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>
|
icon="el-icon-loading" @click="setSpeedOrTime(134, cruisingGroup, cruisingSpeed)">设置
|
||||||
<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>
|
||||||
<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-tag style="position :absolute; left: 16rem; top: 2.5rem; width: 5rem; text-align: center"
|
||||||
<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>
|
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>
|
</el-button-group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -231,6 +297,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</el-tab-pane>
|
</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>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
@ -241,7 +325,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import rtcPlayer from '../dialog/rtcPlayer.vue'
|
import rtcPlayer from '../dialog/rtcPlayer.vue'
|
||||||
import LivePlayer from '@liveqing/liveplayer'
|
import LivePlayer from '@liveqing/liveplayer'
|
||||||
|
import crypto from 'crypto'
|
||||||
import jessibucaPlayer from '../common/jessibuca.vue'
|
import jessibucaPlayer from '../common/jessibuca.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'devicePlayer',
|
name: 'devicePlayer',
|
||||||
props: {},
|
props: {},
|
||||||
@ -258,7 +344,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
console.log("created")
|
||||||
console.log(this.player)
|
console.log(this.player)
|
||||||
|
this.broadcastStatus = -1;
|
||||||
if (Object.keys(this.player).length === 1) {
|
if (Object.keys(this.player).length === 1) {
|
||||||
this.activePlayer = Object.keys(this.player)[0]
|
this.activePlayer = Object.keys(this.player)[0]
|
||||||
}
|
}
|
||||||
@ -271,7 +359,6 @@ export default {
|
|||||||
// 如何你只是用一种播放器,直接注释掉不用的部分即可
|
// 如何你只是用一种播放器,直接注释掉不用的部分即可
|
||||||
player: {
|
player: {
|
||||||
jessibuca: ["ws_flv", "wss_flv"],
|
jessibuca: ["ws_flv", "wss_flv"],
|
||||||
livePlayer : ["ws_flv", "wss_flv"],
|
|
||||||
webRTC: ["rtc", "rtcs"],
|
webRTC: ["rtc", "rtcs"],
|
||||||
},
|
},
|
||||||
showVideoDialog: false,
|
showVideoDialog: false,
|
||||||
@ -307,6 +394,9 @@ export default {
|
|||||||
recordStartTime: 0,
|
recordStartTime: 0,
|
||||||
showTimeText: "00:00:00",
|
showTimeText: "00:00:00",
|
||||||
streamInfo: null,
|
streamInfo: null,
|
||||||
|
broadcastMode: true,
|
||||||
|
broadcastRtc: null,
|
||||||
|
broadcastStatus: -1, // -2 正在释放资源 -1 默认状态 0 等待接通 1 接通成功
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -332,7 +422,8 @@ export default {
|
|||||||
type: 'warning'
|
type: 'warning'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(function (e) {});
|
}).catch(function (e) {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
changePlayer: function (tab) {
|
changePlayer: function (tab) {
|
||||||
@ -441,7 +532,8 @@ export default {
|
|||||||
console.error(res.data.msg)
|
console.error(res.data.msg)
|
||||||
}
|
}
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
}).catch(function (e) {});
|
}).catch(function (e) {
|
||||||
|
});
|
||||||
that.coverPlaying = false;
|
that.coverPlaying = false;
|
||||||
that.convertKey = "";
|
that.convertKey = "";
|
||||||
// if (callback )callback();
|
// if (callback )callback();
|
||||||
@ -457,8 +549,6 @@ export default {
|
|||||||
this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
|
this.$refs[this.activePlayer].play(this.getUrlByStreamInfo(streamInfo))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function () {
|
||||||
console.log('关闭视频');
|
console.log('关闭视频');
|
||||||
@ -472,6 +562,7 @@ export default {
|
|||||||
this.convertStop();
|
this.convertStop();
|
||||||
}
|
}
|
||||||
this.convertKey = ''
|
this.convertKey = ''
|
||||||
|
this.stopBroadcast()
|
||||||
},
|
},
|
||||||
|
|
||||||
copySharedInfo: function (data) {
|
copySharedInfo: function (data) {
|
||||||
@ -502,7 +593,8 @@ export default {
|
|||||||
this.$axios({
|
this.$axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: '/api/ptz/control/' + this.deviceId + '/' + this.channelId + '?command=' + command + '&horizonSpeed=' + this.controSpeed + '&verticalSpeed=' + this.controSpeed + '&zoomSpeed=' + this.controSpeed
|
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) {
|
videoError: function (e) {
|
||||||
@ -514,7 +606,8 @@ export default {
|
|||||||
this.$axios({
|
this.$axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=0¶meter2=' + presetPos + '&combindCode2=0'
|
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) {
|
setSpeedOrTime: function (cmdCode, groupNum, parameter) {
|
||||||
let that = this;
|
let that = this;
|
||||||
@ -524,7 +617,8 @@ export default {
|
|||||||
this.$axios({
|
this.$axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=' + groupNum + '¶meter2=' + parameter2 + '&combindCode2=' + combindCode2
|
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) {
|
setCommand: function (cmdCode, groupNum, parameter) {
|
||||||
let that = this;
|
let that = this;
|
||||||
@ -532,7 +626,8 @@ export default {
|
|||||||
this.$axios({
|
this.$axios({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: '/api/ptz/front_end_command/' + this.deviceId + '/' + this.channelId + '?cmdCode=' + cmdCode + '¶meter1=' + groupNum + '¶meter2=' + parameter + '&combindCode2=0'
|
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) {
|
copyUrl: function (dropdownItem) {
|
||||||
console.log(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>
|
</script>
|
||||||
@ -581,6 +835,7 @@ export default {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
transition: all 0.3s linear;
|
transition: all 0.3s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-btn:hover {
|
.control-btn:hover {
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
}
|
}
|
||||||
@ -592,9 +847,11 @@ export default {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-btn i:hover {
|
.control-btn i:hover {
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-zoom-btn:hover {
|
.control-zoom-btn:hover {
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
}
|
}
|
||||||
@ -724,6 +981,7 @@ export default {
|
|||||||
.control-bottom .fa {
|
.control-bottom .fa {
|
||||||
transform: rotate(-45deg) translateY(7px);
|
transform: rotate(-45deg) translateY(7px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.trank {
|
.trank {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
height: 180px;
|
height: 180px;
|
||||||
@ -731,6 +989,7 @@ export default {
|
|||||||
padding: 0 10%;
|
padding: 0 10%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trankInfo {
|
.trankInfo {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
padding: 0 10%;
|
padding: 0 10%;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -92,6 +92,7 @@ create table wvp_device_channel (
|
|||||||
latitude_wgs84 double precision,
|
latitude_wgs84 double precision,
|
||||||
business_group_id character varying(50),
|
business_group_id character varying(50),
|
||||||
gps_time character varying(50),
|
gps_time character varying(50),
|
||||||
|
stream_identification character varying(50),
|
||||||
constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id)
|
constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -93,6 +93,7 @@ create table wvp_device_channel (
|
|||||||
business_group_id character varying(50),
|
business_group_id character varying(50),
|
||||||
gps_time character varying(50),
|
gps_time character varying(50),
|
||||||
common_gb_channel_id integer,
|
common_gb_channel_id integer,
|
||||||
|
stream_identification character varying(50),
|
||||||
constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id)
|
constraint uk_wvp_device_channel_unique_device_channel unique (device_id, channel_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user