diff --git a/README.md b/README.md index bbe44d2d1..a64a1c1bc 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 支持电子地图,支持接入WGS84和GCJ02两种坐标系,并且自动转化为合适的坐标系进行展示和分发 - [X] 接入设备 - [X] 视频预览 + - [X] 支持主码流子码流切换 - [X] 无限制接入路数,能接入多少设备只取决于你的服务器性能 - [X] 云台控制,控制设备转向,拉近,拉远 - [X] 预置位查询,使用与设置 @@ -66,6 +67,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 支持国标网络校时 - [X] 支持播放H264和H265 - [X] 报警信息处理,支持向前端推送报警信息 + - [X] 语音对讲 - [X] 支持订阅与通知方法 - [X] 移动位置订阅 - [X] 移动位置通知处理 @@ -92,6 +94,7 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 目录订阅与通知 - [X] 录像查看与播放 - [X] GPS订阅与通知(直播推流) + - [X] 语音对讲 - [X] 支持自动配置ZLM媒体服务, 减少因配置问题所出现的问题; - [X] 多流媒体节点,自动选择负载最低的节点使用。 - [X] 支持启用udp多端口模式, 提高udp模式下媒体传输性能; @@ -133,3 +136,11 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git [mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001) +ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/broadcast/34020000001320000101_34020000001310000001 + +ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/talk/34020000001320000011_34020000001370000001 + + + +ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/talk/34020000001320000101_34020000001310000001 + diff --git a/doc/README.md b/doc/README.md index c2cbfba4e..cdf3a0810 100644 --- a/doc/README.md +++ b/doc/README.md @@ -55,8 +55,8 @@ - [X] 移动设备位置订阅 - [X] 报警订阅 - [X] 目录订阅 -- [ ] 语音广播 -- [ ] 语音对讲 +- [X] 语音广播 +- [X] 语音喊话 **作为下级平台** - [X] 注册 @@ -94,8 +94,8 @@ - [X] 移动设备位置订阅 - [ ] 报警订阅 - [X] 目录订阅 -- [ ] 语音广播 -- [ ] 语音对讲 +- [X] 语音广播 +- [X] 语音喊话 diff --git a/doc/_content/ability/continuous_broadcast.md b/doc/_content/ability/continuous_broadcast.md new file mode 100644 index 000000000..7af3afeb0 --- /dev/null +++ b/doc/_content/ability/continuous_broadcast.md @@ -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` \ No newline at end of file diff --git a/doc/_content/broadcast.md b/doc/_content/broadcast.md new file mode 100644 index 000000000..3a6b20d70 --- /dev/null +++ b/doc/_content/broadcast.md @@ -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 +``` \ No newline at end of file diff --git a/doc/_content/introduction/config.md b/doc/_content/introduction/config.md index 9e8ac0f63..29f00a4b8 100644 --- a/doc/_content/introduction/config.md +++ b/doc/_content/introduction/config.md @@ -153,11 +153,11 @@ user-settings: # 国标是否录制 record-sip: true # 是否将日志存储进数据库 - logInDatebase: true + logInDatabase: true # 第三方匹配,用于从stream钟获取有效信息 thirdPartyGBIdReg: [\s\S]* ``` 如果配置信息无误,你可以启动zlm,再启动wvp来测试了,启动成功的话,你可以在wvp的日志下看到zlm已连接的提示。 -接下来[部署到服务器](./_content/introduction/deployment.md), 如何你只是本地运行直接再本地运行即可。 +接下来[部署到服务器](./_content/introduction/deployment.md), 如果你只是本地运行直接在本地运行即可。 diff --git a/doc/_content/introduction/deployment.md b/doc/_content/introduction/deployment.md index 188efb049..45c1a83a6 100644 --- a/doc/_content/introduction/deployment.md +++ b/doc/_content/introduction/deployment.md @@ -21,9 +21,9 @@ 4. WVP-PRO与ZLM支持分开部署,但是wvp-pro-assist必须与zlm部署在同一台主机; 5. 生产环境按需开放端口,但是建议修改默认端口,尤其是5060端口,易受到攻击; 6. zlm使用docker部署的情况,要求端口映射一致,比如映射5060,应将外部端口也映射为5060端口; -7. 启动服务,以linux为例 -### 启动WVP-PRO -**jar包:** +7. zlm与wvp会保持高频率的通信,所以不要去将wvp与zlm分属在两个网络,比如wvp在内网,zlm却在公网的情况。 +8. 启动服务,以linux为例 +**启动WVP-PRO** ```shell nohup java -jar wvp-pro-*.jar & ``` diff --git a/doc/_content/theory/broadcast_cascade.md b/doc/_content/theory/broadcast_cascade.md new file mode 100644 index 000000000..e59b8c251 --- /dev/null +++ b/doc/_content/theory/broadcast_cascade.md @@ -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,会话结束成功。 diff --git a/doc/_sidebar.md b/doc/_sidebar.md index e49ff915a..bdf595527 100644 --- a/doc/_sidebar.md +++ b/doc/_sidebar.md @@ -15,11 +15,13 @@ * [节点管理](_content/ability/node_manger.md) * [云端录像](_content/ability/cloud_record.md) * [不间断录像](_content/ability/continuous_recording.md) + * [语音对讲](_content/ability/continuous_broadcast.md) * **流程与原理** * [统一编码规则](_content/theory/code.md) * [树形结构](_content/theory/channel_tree.md) * [注册流程](_content/theory/register.md) * [点播流程](_content/theory/play.md) + * [级联语音喊话流程](_content/theory/broadcast_cascade.md) * **必备技巧** * [抓包](_content/skill/tcpdump.md) diff --git a/libs/jdbc-x86/bcprov-jdk15on-1.70.jar b/libs/jdbc-x86/bcprov-jdk15on-1.70.jar new file mode 100644 index 000000000..0e4198e5a Binary files /dev/null and b/libs/jdbc-x86/bcprov-jdk15on-1.70.jar differ diff --git a/libs/jdbc-x86/kingbase8-8.6.0.jar b/libs/jdbc-x86/kingbase8-8.6.0.jar new file mode 100644 index 000000000..ff4664ef2 Binary files /dev/null and b/libs/jdbc-x86/kingbase8-8.6.0.jar differ diff --git a/libs/jdbc-x86/kingbase8-8.6.0.jre6.jar b/libs/jdbc-x86/kingbase8-8.6.0.jre6.jar new file mode 100644 index 000000000..fcdf628ca Binary files /dev/null and b/libs/jdbc-x86/kingbase8-8.6.0.jre6.jar differ diff --git a/libs/jdbc-x86/kingbase8-8.6.0.jre7.jar b/libs/jdbc-x86/kingbase8-8.6.0.jre7.jar new file mode 100644 index 000000000..a0393586a Binary files /dev/null and b/libs/jdbc-x86/kingbase8-8.6.0.jre7.jar differ diff --git a/libs/jdbc-x86/postgresql-42.2.9.jar b/libs/jdbc-x86/postgresql-42.2.9.jar new file mode 100644 index 000000000..487cfc1e0 Binary files /dev/null and b/libs/jdbc-x86/postgresql-42.2.9.jar differ diff --git a/libs/jdbc-x86/postgresql-42.2.9.jre6.jar b/libs/jdbc-x86/postgresql-42.2.9.jre6.jar new file mode 100644 index 000000000..11e9f9976 Binary files /dev/null and b/libs/jdbc-x86/postgresql-42.2.9.jre6.jar differ diff --git a/libs/jdbc-x86/postgresql-42.2.9.jre7.jar b/libs/jdbc-x86/postgresql-42.2.9.jre7.jar new file mode 100644 index 000000000..522738dda Binary files /dev/null and b/libs/jdbc-x86/postgresql-42.2.9.jre7.jar differ diff --git a/pom.xml b/pom.xml index 2ecfad302..b316e9807 100644 --- a/pom.xml +++ b/pom.xml @@ -1,146 +1,147 @@ - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.7.2 - + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.17 + - com.genersoft - wvp-pro - 2.6.9 - web video platform - 国标28181视频平台 - ${project.packaging} + com.genersoft + wvp-pro + 2.7.0 + web video platform + 国标28181视频平台 + ${project.packaging} - - - nexus-aliyun - Nexus aliyun - https://maven.aliyun.com/repository/public - default - - false - - - true - - - - - - nexus-aliyun - Nexus aliyun - https://maven.aliyun.com/repository/public - - false - - - true - - - + + + nexus-aliyun + Nexus aliyun + https://maven.aliyun.com/repository/public + default + + false + + + true + + + - - UTF-8 - MMddHHmm - 3.1.1 + + + nexus-aliyun + Nexus aliyun + https://maven.aliyun.com/repository/public + + false + + + true + + + - - ${project.build.directory}/generated-snippets - ${project.basedir}/docs/asciidoc - ${project.build.directory}/asciidoc - ${project.build.directory}/asciidoc/html - ${project.build.directory}/asciidoc/pdf - + + UTF-8 + MMddHHmm + 3.1.1 - - - jar - - true - - - jar - - - - war - - war - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-jetty - - - - - javax.servlet - javax.servlet-api - 3.1.0 - provided - - - - + + ${project.build.directory}/generated-snippets + ${project.basedir}/docs/asciidoc + ${project.build.directory}/asciidoc + ${project.build.directory}/asciidoc/html + ${project.build.directory}/asciidoc/pdf + - - - org.springframework.boot - spring-boot-starter-data-redis - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - 2.2.2 - - - com.zaxxer - HikariCP - - - - - org.springframework.boot - spring-boot-starter-security - + + + jar + + true + + + jar + + + + war + + war + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-jetty + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + - - org.springframework.boot - spring-boot-starter-jdbc - + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.2.2 + + + com.zaxxer + HikariCP + + + + + org.springframework.boot + spring-boot-starter-security + - - - mysql - mysql-connector-java - 8.0.30 - + + org.springframework.boot + spring-boot-starter-jdbc + - - - org.postgresql - postgresql - 42.5.1 - + + + com.mysql + mysql-connector-j + 8.2.0 + + + + + org.postgresql + postgresql + 42.5.1 + @@ -153,14 +154,41 @@ system ${basedir}/libs/jdbc-aarch/kingbase8-8.6.0.jar - - - com.github.pagehelper - pagehelper-spring-boot-starter - 1.4.6 + com.kingbase + kingbase8 + 8.6.0 + system + ${basedir}/libs/jdbc-x86/kingbase8-8.6.0.jar + + + com.github.pagehelper + pagehelper-spring-boot-starter + 1.4.6 + + + + + + org.springdoc + springdoc-openapi-ui + 1.6.10 + + + org.springdoc + springdoc-openapi-security + 1.6.10 + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 3.6.1 + + + org.springdoc @@ -168,195 +196,203 @@ 1.6.10 - - com.github.xiaoymin - knife4j-springdoc-ui - 3.0.3 - + + com.github.xiaoymin + knife4j-springdoc-ui + 3.0.3 + - - - javax.validation - validation-api - + + + javax.validation + validation-api + - - - org.springframework.boot - spring-boot-starter-aop - + + + org.springframework.boot + spring-boot-starter-aop + - - - javax.sip - jain-sip-ri - 1.3.0-91 - + + + javax.sip + jain-sip-ri + 1.3.0-91 + - - - org.slf4j - log4j-over-slf4j - 1.7.36 - + + + org.slf4j + log4j-over-slf4j + 1.7.36 + - - - org.dom4j - dom4j - 2.1.3 - + + + org.dom4j + dom4j + 2.1.3 + - - - com.alibaba.fastjson2 - fastjson2 - 2.0.17 - - - com.alibaba.fastjson2 - fastjson2-extension - 2.0.17 - + + + com.alibaba.fastjson2 + fastjson2 + 2.0.17 + + + com.alibaba.fastjson2 + fastjson2-extension + 2.0.17 + - - - com.squareup.okhttp3 - okhttp - 4.10.0 - + + + com.squareup.okhttp3 + okhttp + 4.10.0 + - - - com.squareup.okhttp3 - logging-interceptor - 4.10.0 - + + + com.squareup.okhttp3 + logging-interceptor + 4.10.0 + - - - io.github.rburgst - okhttp-digest - 2.7 - + + + io.github.rburgst + okhttp-digest + 2.7 + - - - - - - + + + + + + - - - org.bitbucket.b_c - jose4j - 0.9.3 - + + + org.bitbucket.b_c + jose4j + 0.9.3 + - - - org.mitre.dsmiley.httpproxy - smiley-http-proxy-servlet - 1.12.1 - + + + org.mitre.dsmiley.httpproxy + smiley-http-proxy-servlet + 1.12.1 + - - - com.alibaba - easyexcel - 3.1.1 - + + + com.alibaba + easyexcel + 3.3.2 + + + org.apache.commons + commons-compress + + + + + org.apache.commons + commons-compress + 1.24.0 + - - - com.github.oshi - oshi-core - 6.2.2 - + + + com.github.oshi + oshi-core + 6.2.2 + - - org.springframework.session - spring-session-core - + + org.springframework.session + spring-session-core + - - - - - - - + + + + + + + - - - com.google.guava - guava - 31.1-jre - + + + com.google.guava + guava + 32.1.3-jre + + + org.springframework.boot + spring-boot-starter-test + test + + - - org.springframework.boot - spring-boot-starter-test - - + + ${project.artifactId}-${project.version}-${maven.build.timestamp} + + + org.springframework.boot + spring-boot-maven-plugin + 2.7.2 + + true + + - + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + pl.project13.maven + git-commit-id-plugin + 3.0.1 + + true + false + yyyyMMdd + + - - ${project.artifactId}-${project.version}-${maven.build.timestamp} - - - org.springframework.boot - spring-boot-maven-plugin - 2.7.2 - - true - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - - pl.project13.maven - git-commit-id-plugin - 3.0.1 - - true - false - yyyyMMdd - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.22.2 - - true - - - - - - - src/main/resources - - - src/main/java - - **/*.xml - - - - + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + true + + + + + + src/main/resources + + + src/main/java + + **/*.xml + + + + diff --git a/sql/2.6.9更新.sql b/sql/2.6.9更新.sql deleted file mode 100644 index 2e047948c..000000000 --- a/sql/2.6.9更新.sql +++ /dev/null @@ -1,2 +0,0 @@ -alter table wvp_device_channel - change stream_id stream_id varying(255) \ No newline at end of file diff --git a/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java b/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java index 886d6fa17..be573166b 100644 --- a/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java +++ b/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java @@ -1,63 +1,63 @@ -package com.genersoft.iot.vmp; - -import com.genersoft.iot.vmp.utils.GitUtil; -import com.genersoft.iot.vmp.utils.SpringBeanFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.web.servlet.ServletComponentScan; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.scheduling.annotation.EnableScheduling; - -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.SessionCookieConfig; -import javax.servlet.SessionTrackingMode; -import java.util.Collections; - -/** - * 启动类 - */ -@ServletComponentScan("com.genersoft.iot.vmp.conf") -@SpringBootApplication -@EnableScheduling -public class VManageBootstrap extends SpringBootServletInitializer { - - private final static Logger logger = LoggerFactory.getLogger(VManageBootstrap.class); - - private static String[] args; - private static ConfigurableApplicationContext context; - public static void main(String[] args) { - VManageBootstrap.args = args; - VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args); - GitUtil gitUtil1 = SpringBeanFactory.getBean("gitUtil"); - logger.info("构建版本: {}", gitUtil1.getBuildVersion()); - logger.info("构建时间: {}", gitUtil1.getBuildDate()); - logger.info("GIT最后提交时间: {}", gitUtil1.getCommitTime()); - } - // 项目重启 - public static void restart() { - context.close(); - VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args); - } - - @Override - protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { - return application.sources(VManageBootstrap.class); - } - - @Override - public void onStartup(ServletContext servletContext) throws ServletException { - super.onStartup(servletContext); - - servletContext.setSessionTrackingModes( - Collections.singleton(SessionTrackingMode.COOKIE) - ); - SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig(); - sessionCookieConfig.setHttpOnly(true); - - } -} +package com.genersoft.iot.vmp; + +import com.genersoft.iot.vmp.utils.GitUtil; +import com.genersoft.iot.vmp.utils.SpringBeanFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.scheduling.annotation.EnableScheduling; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import java.util.Collections; + +/** + * 启动类 + */ +@ServletComponentScan("com.genersoft.iot.vmp.conf") +@SpringBootApplication +@EnableScheduling +public class VManageBootstrap extends SpringBootServletInitializer { + + private final static Logger logger = LoggerFactory.getLogger(VManageBootstrap.class); + + private static String[] args; + private static ConfigurableApplicationContext context; + public static void main(String[] args) { + VManageBootstrap.args = args; + VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args); + GitUtil gitUtil1 = SpringBeanFactory.getBean("gitUtil"); + logger.info("构建版本: {}", gitUtil1.getBuildVersion()); + logger.info("构建时间: {}", gitUtil1.getBuildDate()); + logger.info("GIT最后提交时间: {}", gitUtil1.getCommitTime()); + } + // 项目重启 + public static void restart() { + context.close(); + VManageBootstrap.context = SpringApplication.run(VManageBootstrap.class, args); + } + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(VManageBootstrap.class); + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + super.onStartup(servletContext); + + servletContext.setSessionTrackingModes( + Collections.singleton(SessionTrackingMode.COOKIE) + ); + SessionCookieConfig sessionCookieConfig = servletContext.getSessionCookieConfig(); + sessionCookieConfig.setHttpOnly(true); + + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java b/src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java index dabdb4f07..dbe9e090a 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java @@ -1,7 +1,6 @@ package com.genersoft.iot.vmp.common; import com.genersoft.iot.vmp.service.bean.SSRCInfo; -import io.swagger.v3.oas.annotations.media.Schema; /** * 记录每次发送invite消息的状态 @@ -125,20 +124,4 @@ public class InviteInfo { this.streamMode = streamMode; } - - /*=========================设备主子码流逻辑START====================*/ - @Schema(description = "是否为子码流(true-是,false-主码流)") - private boolean subStream; - - public boolean isSubStream() { - return subStream; - } - - public void setSubStream(boolean subStream) { - this.subStream = subStream; - } - - - - } diff --git a/src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java b/src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java index 5a6eb85b7..924130577 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java +++ b/src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java @@ -3,5 +3,7 @@ package com.genersoft.iot.vmp.common; public enum InviteSessionType { PLAY, PLAYBACK, - DOWNLOAD + DOWNLOAD, + BROADCAST, + TALK } diff --git a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java index cde235bc7..979f54a51 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.common; +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import io.swagger.v3.oas.annotations.media.Schema; import java.io.Serializable; @@ -76,6 +77,8 @@ public class StreamInfo implements Serializable, Cloneable{ private String endTime; @Schema(description = "进度(录像下载使用)") private double progress; + @Schema(description = "文件下载地址(录像下载使用)") + private DownloadFileInfo downLoadFilePath; @Schema(description = "是否暂停(录像回放使用)") private boolean pause; @@ -237,11 +240,11 @@ public class StreamInfo implements Serializable, Cloneable{ } } - public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam) { + public void setRtc(String host, int port, int sslPort, String app, String stream, String callIdParam, boolean isPlay) { if (callIdParam != null) { callIdParam = Objects.equals(callIdParam, "") ? callIdParam : callIdParam.replace("?", "&"); } - String file = String.format("index/api/webrtc?app=%s&stream=%s&type=play%s", app, stream, callIdParam); + String file = String.format("index/api/webrtc?app=%s&stream=%s&type=%s%s", app, stream, isPlay?"play":"push", callIdParam); if (port > 0) { this.rtc = new StreamURL("http", host, port, file); } @@ -523,6 +526,69 @@ public class StreamInfo implements Serializable, Cloneable{ StreamInfo instance = null; try{ instance = (StreamInfo)super.clone(); + if (this.flv != null) { + instance.flv=this.flv.clone(); + } + if (this.ws_flv != null ){ + instance.ws_flv= this.ws_flv.clone(); + } + if (this.hls != null ) { + instance.hls= this.hls.clone(); + } + if (this.ws_hls != null ) { + instance.ws_hls= this.ws_hls.clone(); + } + if (this.ts != null ) { + instance.ts= this.ts.clone(); + } + if (this.ws_ts != null ) { + instance.ws_ts= this.ws_ts.clone(); + } + if (this.fmp4 != null ) { + instance.fmp4= this.fmp4.clone(); + } + if (this.ws_fmp4 != null ) { + instance.ws_fmp4= this.ws_fmp4.clone(); + } + if (this.rtc != null ) { + instance.rtc= this.rtc.clone(); + } + if (this.https_flv != null) { + instance.https_flv= this.https_flv.clone(); + } + if (this.wss_flv != null) { + instance.wss_flv= this.wss_flv.clone(); + } + if (this.https_hls != null) { + instance.https_hls= this.https_hls.clone(); + } + if (this.wss_hls != null) { + instance.wss_hls= this.wss_hls.clone(); + } + if (this.wss_ts != null) { + instance.wss_ts= this.wss_ts.clone(); + } + if (this.https_fmp4 != null) { + instance.https_fmp4= this.https_fmp4.clone(); + } + if (this.wss_fmp4 != null) { + instance.wss_fmp4= this.wss_fmp4.clone(); + } + if (this.rtcs != null) { + instance.rtcs= this.rtcs.clone(); + } + if (this.rtsp != null) { + instance.rtsp= this.rtsp.clone(); + } + if (this.rtsps != null) { + instance.rtsps= this.rtsps.clone(); + } + if (this.rtmp != null) { + instance.rtmp= this.rtmp.clone(); + } + if (this.rtmps != null) { + instance.rtmps= this.rtmps.clone(); + } }catch(CloneNotSupportedException e) { e.printStackTrace(); } @@ -542,5 +608,11 @@ public class StreamInfo implements Serializable, Cloneable{ this.subStream = subStream; } + public DownloadFileInfo getDownLoadFilePath() { + return downLoadFilePath; + } + public void setDownLoadFilePath(DownloadFileInfo downLoadFilePath) { + this.downLoadFilePath = downLoadFilePath; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/common/StreamURL.java b/src/main/java/com/genersoft/iot/vmp/common/StreamURL.java index eecf469ff..40bd7e250 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/StreamURL.java +++ b/src/main/java/com/genersoft/iot/vmp/common/StreamURL.java @@ -6,7 +6,7 @@ import java.io.Serializable; @Schema(description = "流地址信息") -public class StreamURL implements Serializable { +public class StreamURL implements Serializable,Cloneable { @Schema(description = "协议") private String protocol; @@ -77,4 +77,8 @@ public class StreamURL implements Serializable { return null; } } + @Override + public StreamURL clone() throws CloneNotSupportedException { + return (StreamURL) super.clone(); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java index a90670d87..d19b8f051 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java +++ b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java @@ -1,174 +1,178 @@ -package com.genersoft.iot.vmp.common; - -/** - * @description: 定义常量 - * @author: swwheihei - * @date: 2019年5月30日 下午3:04:04 - * - */ -public class VideoManagerConstants { - - public static final String WVP_SERVER_PREFIX = "VMP_SIGNALLING_SERVER_INFO_"; - - public static final String WVP_SERVER_STREAM_PREFIX = "VMP_SIGNALLING_STREAM_"; - - public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_"; - - public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS_"; - - public static final String DEVICE_PREFIX = "VMP_DEVICE_"; - - // 设备同步完成 - public static final String DEVICE_SYNC_PREFIX = "VMP_DEVICE_SYNC_"; - - public static final String CACHEKEY_PREFIX = "VMP_CHANNEL_"; - - public static final String KEEPLIVEKEY_PREFIX = "VMP_KEEPALIVE_"; - - // TODO 此处多了一个_,暂不修改 - public static final String INVITE_PREFIX = "VMP_INVITE"; - public static final String PLAYER_PREFIX = "VMP_INVITE_PLAY_"; - public static final String PLAY_BLACK_PREFIX = "VMP_INVITE_PLAYBACK_"; - public static final String DOWNLOAD_PREFIX = "VMP_INVITE_DOWNLOAD_"; - - public static final String PLATFORM_KEEPALIVE_PREFIX = "VMP_PLATFORM_KEEPALIVE_"; - - public static final String PLATFORM_CATCH_PREFIX = "VMP_PLATFORM_CATCH_"; - - public static final String PLATFORM_REGISTER_PREFIX = "VMP_PLATFORM_REGISTER_"; - - public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_PLATFORM_REGISTER_INFO_"; - - public static final String PLATFORM_SEND_RTP_INFO_PREFIX = "VMP_PLATFORM_SEND_RTP_INFO_"; - - public static final String EVENT_ONLINE_REGISTER = "1"; - - public static final String EVENT_ONLINE_MESSAGE = "3"; - - public static final String EVENT_OUTLINE_UNREGISTER = "1"; - - public static final String EVENT_OUTLINE_TIMEOUT = "2"; - - public static final String MEDIA_SSRC_USED_PREFIX = "VMP_MEDIA_USED_SSRC_"; - - public static final String MEDIA_TRANSACTION_USED_PREFIX = "VMP_MEDIA_TRANSACTION_"; - - public static final String MEDIA_STREAM_AUTHORITY = "MEDIA_STREAM_AUTHORITY_"; - - public static final String SIP_CSEQ_PREFIX = "VMP_SIP_CSEQ_"; - - public static final String SIP_SN_PREFIX = "VMP_SIP_SN_"; - - public static final String SIP_SUBSCRIBE_PREFIX = "VMP_SIP_SUBSCRIBE_"; - - public static final String SYSTEM_INFO_CPU_PREFIX = "VMP_SYSTEM_INFO_CPU_"; - - public static final String SYSTEM_INFO_MEM_PREFIX = "VMP_SYSTEM_INFO_MEM_"; - - 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 REGISTER_EXPIRE_TASK_KEY_PREFIX = "VMP_device_register_expire_"; - - - - - //************************** redis 消息********************************* - - /** - * 流变化的通知 - */ - public static final String WVP_MSG_STREAM_CHANGE_PREFIX = "WVP_MSG_STREAM_CHANGE_"; - - /** - * 接收推流设备的GPS变化通知 - */ - public static final String VM_MSG_GPS = "VM_MSG_GPS"; - - /** - * 接收推流设备的GPS变化通知 - */ - public static final String VM_MSG_PUSH_STREAM_STATUS_CHANGE = "VM_MSG_PUSH_STREAM_STATUS_CHANGE"; - /** - * 接收推流设备列表更新变化通知 - */ - public static final String VM_MSG_PUSH_STREAM_LIST_CHANGE = "VM_MSG_PUSH_STREAM_LIST_CHANGE"; - - /** - * redis 消息通知设备推流到平台 - */ - public static final String VM_MSG_STREAM_PUSH_REQUESTED = "VM_MSG_STREAM_PUSH_REQUESTED"; - - /** - * redis 消息通知上级平台开始观看流 - */ - public static final String VM_MSG_STREAM_START_PLAY_NOTIFY = "VM_MSG_STREAM_START_PLAY_NOTIFY"; - - /** - * redis 消息通知上级平台停止观看流 - */ - public static final String VM_MSG_STREAM_STOP_PLAY_NOTIFY = "VM_MSG_STREAM_STOP_PLAY_NOTIFY"; - - /** - * redis 消息接收关闭一个推流 - */ - public static final String VM_MSG_STREAM_PUSH_CLOSE_REQUESTED = "VM_MSG_STREAM_PUSH_CLOSE_REQUESTED"; - - - /** - * redis 消息通知平台通知设备推流结果 - */ - public static final String VM_MSG_STREAM_PUSH_RESPONSE = "VM_MSG_STREAM_PUSH_RESPONSE"; - - /** - * redis 通知平台关闭推流 - */ - public static final String VM_MSG_STREAM_PUSH_CLOSE = "VM_MSG_STREAM_PUSH_CLOSE"; - - /** - * redis 消息请求所有的在线通道 - */ - public static final String VM_MSG_GET_ALL_ONLINE_REQUESTED = "VM_MSG_GET_ALL_ONLINE_REQUESTED"; - - /** - * 移动位置订阅通知 - */ - public static final String VM_MSG_SUBSCRIBE_MOBILE_POSITION = "mobileposition"; - - /** - * 报警订阅的通知(收到报警向redis发出通知) - */ - public static final String VM_MSG_SUBSCRIBE_ALARM = "alarm"; - - - /** - * 报警通知的发送 (收到redis发出的通知,转发给其他平台) - */ - public static final String VM_MSG_SUBSCRIBE_ALARM_RECEIVE= "alarm_receive"; - - /** - * 设备状态订阅的通知 - */ - public static final String VM_MSG_SUBSCRIBE_DEVICE_STATUS = "device"; - - - //************************** 第三方 **************************************** - - public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_"; - public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_"; - public static final String WVP_OTHER_SEND_RTP_INFO = "VMP_OTHER_SEND_RTP_INFO_"; - public static final String WVP_OTHER_RECEIVE_RTP_INFO = "VMP_OTHER_RECEIVE_RTP_INFO_"; - - /** - * Redis Const - * 设备录像信息结果前缀 - */ - public static final String REDIS_RECORD_INFO_RES_PRE = "GB_RECORD_INFO_RES_"; - /** - * Redis Const - * 设备录像信息结果前缀 - */ - public static final String REDIS_RECORD_INFO_RES_COUNT_PRE = "GB_RECORD_INFO_RES_COUNT:"; - -} +package com.genersoft.iot.vmp.common; + +/** + * @description: 定义常量 + * @author: swwheihei + * @date: 2019年5月30日 下午3:04:04 + * + */ +public class VideoManagerConstants { + + public static final String WVP_SERVER_PREFIX = "VMP_SIGNALLING_SERVER_INFO_"; + + public static final String WVP_SERVER_STREAM_PREFIX = "VMP_SIGNALLING_STREAM_"; + + public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_"; + + public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS_"; + + public static final String DEVICE_PREFIX = "VMP_DEVICE_"; + + // 设备同步完成 + public static final String DEVICE_SYNC_PREFIX = "VMP_DEVICE_SYNC_"; + + public static final String CACHEKEY_PREFIX = "VMP_CHANNEL_"; + + public static final String KEEPLIVEKEY_PREFIX = "VMP_KEEPALIVE_"; + + // TODO 此处多了一个_,暂不修改 + public static final String INVITE_PREFIX = "VMP_INVITE"; + public static final String PLAYER_PREFIX = "VMP_INVITE_PLAY_"; + public static final String PLAY_BLACK_PREFIX = "VMP_INVITE_PLAYBACK_"; + public static final String DOWNLOAD_PREFIX = "VMP_INVITE_DOWNLOAD_"; + + public static final String PLATFORM_KEEPALIVE_PREFIX = "VMP_PLATFORM_KEEPALIVE_"; + + public static final String PLATFORM_CATCH_PREFIX = "VMP_PLATFORM_CATCH_"; + + public static final String PLATFORM_REGISTER_PREFIX = "VMP_PLATFORM_REGISTER_"; + + public static final String PLATFORM_REGISTER_INFO_PREFIX = "VMP_PLATFORM_REGISTER_INFO_"; + + public static final String PLATFORM_SEND_RTP_INFO_PREFIX = "VMP_PLATFORM_SEND_RTP_INFO_"; + + public static final String EVENT_ONLINE_REGISTER = "1"; + + public static final String EVENT_ONLINE_MESSAGE = "3"; + + public static final String EVENT_OUTLINE_UNREGISTER = "1"; + + public static final String EVENT_OUTLINE_TIMEOUT = "2"; + + public static final String MEDIA_SSRC_USED_PREFIX = "VMP_MEDIA_USED_SSRC_"; + + public static final String MEDIA_TRANSACTION_USED_PREFIX = "VMP_MEDIA_TRANSACTION_"; + + public static final String MEDIA_STREAM_AUTHORITY = "VMP_MEDIA_STREAM_AUTHORITY_"; + + public static final String SIP_CSEQ_PREFIX = "VMP_SIP_CSEQ_"; + + public static final String SIP_SN_PREFIX = "VMP_SIP_SN_"; + + public static final String SIP_SUBSCRIBE_PREFIX = "VMP_SIP_SUBSCRIBE_"; + + public static final String SYSTEM_INFO_CPU_PREFIX = "VMP_SYSTEM_INFO_CPU_"; + + public static final String SYSTEM_INFO_MEM_PREFIX = "VMP_SYSTEM_INFO_MEM_"; + + public static final String SYSTEM_INFO_NET_PREFIX = "VMP_SYSTEM_INFO_NET_"; + + public static final String SYSTEM_INFO_DISK_PREFIX = "VMP_SYSTEM_INFO_DISK_"; + public static final String BROADCAST_WAITE_INVITE = "task_broadcast_waite_invite_"; + + public static final String REGISTER_EXPIRE_TASK_KEY_PREFIX = "VMP_device_register_expire_"; + public static final String PUSH_STREAM_LIST = "VMP_PUSH_STREAM_LIST_"; + + + + + //************************** redis 消息********************************* + + /** + * 流变化的通知 + */ + public static final String WVP_MSG_STREAM_CHANGE_PREFIX = "WVP_MSG_STREAM_CHANGE_"; + + /** + * 接收推流设备的GPS变化通知 + */ + public static final String VM_MSG_GPS = "VM_MSG_GPS"; + + /** + * 接收推流设备的GPS变化通知 + */ + public static final String VM_MSG_PUSH_STREAM_STATUS_CHANGE = "VM_MSG_PUSH_STREAM_STATUS_CHANGE"; + /** + * 接收推流设备列表更新变化通知 + */ + public static final String VM_MSG_PUSH_STREAM_LIST_CHANGE = "VM_MSG_PUSH_STREAM_LIST_CHANGE"; + + /** + * redis 消息通知设备推流到平台 + */ + public static final String VM_MSG_STREAM_PUSH_REQUESTED = "VM_MSG_STREAM_PUSH_REQUESTED"; + + /** + * redis 消息通知上级平台开始观看流 + */ + public static final String VM_MSG_STREAM_START_PLAY_NOTIFY = "VM_MSG_STREAM_START_PLAY_NOTIFY"; + + /** + * redis 消息通知上级平台停止观看流 + */ + public static final String VM_MSG_STREAM_STOP_PLAY_NOTIFY = "VM_MSG_STREAM_STOP_PLAY_NOTIFY"; + + /** + * redis 消息接收关闭一个推流 + */ + public static final String VM_MSG_STREAM_PUSH_CLOSE_REQUESTED = "VM_MSG_STREAM_PUSH_CLOSE_REQUESTED"; + + + /** + * redis 消息通知平台通知设备推流结果 + */ + public static final String VM_MSG_STREAM_PUSH_RESPONSE = "VM_MSG_STREAM_PUSH_RESPONSE"; + + /** + * redis 通知平台关闭推流 + */ + public static final String VM_MSG_STREAM_PUSH_CLOSE = "VM_MSG_STREAM_PUSH_CLOSE"; + + /** + * redis 消息请求所有的在线通道 + */ + public static final String VM_MSG_GET_ALL_ONLINE_REQUESTED = "VM_MSG_GET_ALL_ONLINE_REQUESTED"; + + /** + * 移动位置订阅通知 + */ + public static final String VM_MSG_SUBSCRIBE_MOBILE_POSITION = "mobileposition"; + + /** + * 报警订阅的通知(收到报警向redis发出通知) + */ + public static final String VM_MSG_SUBSCRIBE_ALARM = "alarm"; + + + /** + * 报警通知的发送 (收到redis发出的通知,转发给其他平台) + */ + public static final String VM_MSG_SUBSCRIBE_ALARM_RECEIVE= "alarm_receive"; + + /** + * 设备状态订阅的通知 + */ + public static final String VM_MSG_SUBSCRIBE_DEVICE_STATUS = "device"; + + + //************************** 第三方 **************************************** + + public static final String WVP_STREAM_GB_ID_PREFIX = "memberNo_"; + public static final String WVP_STREAM_GPS_MSG_PREFIX = "WVP_STREAM_GPS_MSG_"; + public static final String WVP_OTHER_SEND_RTP_INFO = "VMP_OTHER_SEND_RTP_INFO_"; + public static final String WVP_OTHER_SEND_PS_INFO = "VMP_OTHER_SEND_PS_INFO_"; + public static final String WVP_OTHER_RECEIVE_RTP_INFO = "VMP_OTHER_RECEIVE_RTP_INFO_"; + public static final String WVP_OTHER_RECEIVE_PS_INFO = "VMP_OTHER_RECEIVE_PS_INFO_"; + + /** + * Redis Const + * 设备录像信息结果前缀 + */ + public static final String REDIS_RECORD_INFO_RES_PRE = "GB_RECORD_INFO_RES_"; + /** + * Redis Const + * 设备录像信息结果前缀 + */ + public static final String REDIS_RECORD_INFO_RES_COUNT_PRE = "GB_RECORD_INFO_RES_COUNT:"; + +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java b/src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java index 4d885e86c..b449b1b7e 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/ApiAccessFilter.java @@ -51,7 +51,7 @@ public class ApiAccessFilter extends OncePerRequestFilter { filterChain.doFilter(servletRequest, servletResponse); - if (uriName != null && userSetting != null && userSetting.getLogInDatebase() != null && userSetting.getLogInDatebase()) { + if (uriName != null && userSetting != null && userSetting.getLogInDatabase() != null && userSetting.getLogInDatabase()) { LogDto logDto = new LogDto(); logDto.setName(uriName); diff --git a/src/main/java/com/genersoft/iot/vmp/conf/CivilCodeFileConf.java b/src/main/java/com/genersoft/iot/vmp/conf/CivilCodeFileConf.java index 39e0a70a4..20b6eef77 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/CivilCodeFileConf.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/CivilCodeFileConf.java @@ -12,7 +12,10 @@ import org.springframework.core.annotation.Order; import org.springframework.core.io.ClassPathResource; import org.springframework.util.ObjectUtils; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.file.Files; import java.util.Map; diff --git a/src/main/java/com/genersoft/iot/vmp/conf/CloudRecordTimer.java b/src/main/java/com/genersoft/iot/vmp/conf/CloudRecordTimer.java new file mode 100644 index 000000000..f98977fa4 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/CloudRecordTimer.java @@ -0,0 +1,83 @@ +package com.genersoft.iot.vmp.conf; + + +import com.alibaba.fastjson2.JSONObject; +import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; +import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.service.IMediaServerService; +import com.genersoft.iot.vmp.service.bean.CloudRecordItem; +import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; +import com.genersoft.iot.vmp.vmanager.cloudRecord.CloudRecordController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +/** + * 录像文件定时删除 + */ +@Component +public class CloudRecordTimer { + + private final static Logger logger = LoggerFactory.getLogger(CloudRecordTimer.class); + + @Autowired + private IMediaServerService mediaServerService; + + @Autowired + private CloudRecordServiceMapper cloudRecordServiceMapper; + + @Autowired + private ZLMRESTfulUtils zlmresTfulUtils; + + /** + * 定时查询待删除的录像文件 + */ +// @Scheduled(fixedRate = 10000) //每五秒执行一次,方便测试 + @Scheduled(cron = "0 0 0 * * ?") //每天的0点执行 + public void execute(){ + logger.info("[录像文件定时清理] 开始清理过期录像文件"); + // 获取配置了assist的流媒体节点 + List mediaServerItemList = mediaServerService.getAllOnline(); + if (mediaServerItemList.isEmpty()) { + return; + } + long result = 0; + for (MediaServerItem mediaServerItem : mediaServerItemList) { + + Calendar lastCalendar = Calendar.getInstance(); + if (mediaServerItem.getRecordDay() > 0) { + lastCalendar.setTime(new Date()); + // 获取保存的最后截至日[期,因为每个节点都有一个日期,也就是支持每个节点设置不同的保存日期, + lastCalendar.add(Calendar.DAY_OF_MONTH, -mediaServerItem.getRecordDay()); + Long lastDate = lastCalendar.getTimeInMillis(); + + // 获取到截至日期之前的录像文件列表,文件列表满足未被收藏和保持的。这两个字段目前共能一致, + // 为我自己业务系统相关的代码,大家使用的时候直接使用收藏(collect)这一个类型即可 + List cloudRecordItemList = cloudRecordServiceMapper.queryRecordListForDelete(lastDate, mediaServerItem.getId()); + if (cloudRecordItemList.isEmpty()) { + continue; + } + // TODO 后续可以删除空了的过期日期文件夹 + for (CloudRecordItem cloudRecordItem : cloudRecordItemList) { + String date = new File(cloudRecordItem.getFilePath()).getParentFile().getName(); + JSONObject jsonObject = zlmresTfulUtils.deleteRecordDirectory(mediaServerItem, cloudRecordItem.getApp(), + cloudRecordItem.getStream(), date, cloudRecordItem.getFileName()); + if (jsonObject.getInteger("code") != 0) { + logger.warn("[录像文件定时清理] 删除磁盘文件错误: {}:{}", cloudRecordItem.getFilePath(), jsonObject); + } + } + result += cloudRecordServiceMapper.deleteList(cloudRecordItemList); + } + } + logger.info("[录像文件定时清理] 共清理{}个过期录像文件", result); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java b/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java index 041d73884..5a451e35f 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.conf; +import org.apache.commons.lang3.ObjectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; @@ -45,6 +46,9 @@ public class DynamicTask { * @return */ public void startCron(String key, Runnable task, int cycleForCatalog) { + if(ObjectUtils.isEmpty(key)) { + return; + } ScheduledFuture future = futureMap.get(key); if (future != null) { if (future.isCancelled()) { @@ -73,6 +77,9 @@ public class DynamicTask { * @return */ public void startDelay(String key, Runnable task, int delay) { + if(ObjectUtils.isEmpty(key)) { + return; + } stop(key); // 获取执行的时刻 @@ -99,8 +106,11 @@ public class DynamicTask { } public boolean stop(String key) { + if(ObjectUtils.isEmpty(key)) { + return false; + } boolean result = false; - if (futureMap.get(key) != null && !futureMap.get(key).isCancelled() && !futureMap.get(key).isDone()) { + if (!ObjectUtils.isEmpty(futureMap.get(key)) && !futureMap.get(key).isCancelled() && !futureMap.get(key).isDone()) { result = futureMap.get(key).cancel(false); futureMap.remove(key); runnableMap.remove(key); @@ -109,6 +119,9 @@ public class DynamicTask { } public boolean contains(String key) { + if(ObjectUtils.isEmpty(key)) { + return false; + } return futureMap.get(key) != null; } @@ -117,6 +130,9 @@ public class DynamicTask { } public Runnable get(String key) { + if(ObjectUtils.isEmpty(key)) { + return null; + } return runnableMap.get(key); } @@ -127,7 +143,8 @@ public class DynamicTask { public void execute(){ if (futureMap.size() > 0) { for (String key : futureMap.keySet()) { - if (futureMap.get(key).isDone() || futureMap.get(key).isCancelled()) { + ScheduledFuture future = futureMap.get(key); + if (future.isDone() || future.isCancelled()) { futureMap.remove(key); runnableMap.remove(key); } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/GlobalResponseAdvice.java b/src/main/java/com/genersoft/iot/vmp/conf/GlobalResponseAdvice.java index c8b84fe9f..33fdd23df 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/GlobalResponseAdvice.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/GlobalResponseAdvice.java @@ -32,7 +32,7 @@ public class GlobalResponseAdvice implements ResponseBodyAdvice { @Override public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType, @NotNull Class> selectedConverterType, @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) { // 排除api文档的接口,这个接口不需要统一 - String[] excludePath = {"/v3/api-docs","/api/v1","/index/hook"}; + String[] excludePath = {"/v3/api-docs","/api/v1","/index/hook","/api/video-"}; for (String path : excludePath) { if (request.getURI().getPath().startsWith(path)) { return body; @@ -59,8 +59,8 @@ public class GlobalResponseAdvice implements ResponseBodyAdvice { * 防止返回string时出错 * @return */ - @Bean - public HttpMessageConverters fast() { + /*@Bean + public HttpMessageConverters custHttpMessageConverter() { return new HttpMessageConverters(new FastJsonHttpMessageConverter()); - } + }*/ } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java index 20ed8cb1f..884036ae9 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java @@ -81,6 +81,12 @@ public class MediaConfig{ @Value("${media.record-assist-port:0}") private Integer recordAssistPort = 0; + @Value("${media.record-day:7}") + private Integer recordDay; + + @Value("${media.record-path:}") + private String recordPath; + public String getId() { return id; } @@ -91,7 +97,7 @@ public class MediaConfig{ public String getHookIp() { if (ObjectUtils.isEmpty(hookIp)){ - return sipIp.split(",")[0]; + return sipIp; }else { return hookIp; } @@ -212,13 +218,32 @@ public class MediaConfig{ mediaServerItem.setSendRtpPortRange(rtpSendPortRange); mediaServerItem.setRecordAssistPort(recordAssistPort); mediaServerItem.setHookAliveInterval(30.00f); - + mediaServerItem.setRecordDay(recordDay); + if (recordPath != null) { + mediaServerItem.setRecordPath(recordPath); + } mediaServerItem.setCreateTime(DateUtil.getNow()); mediaServerItem.setUpdateTime(DateUtil.getNow()); return mediaServerItem; } + public Integer getRecordDay() { + return recordDay; + } + + public void setRecordDay(Integer recordDay) { + this.recordDay = recordDay; + } + + public String getRecordPath() { + return recordPath; + } + + public void setRecordPath(String recordPath) { + this.recordPath = recordPath; + } + public String getRtpSendPortRange() { return rtpSendPortRange; } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java index f3fd1d80a..a7416a8e7 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java @@ -18,6 +18,7 @@ import org.springframework.util.ObjectUtils; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.ConnectException; @@ -64,6 +65,18 @@ public class ProxyServletConfig { return queryStr; } + + @Override + protected HttpResponse doExecute(HttpServletRequest servletRequest, HttpServletResponse servletResponse, + HttpRequest proxyRequest) throws IOException { + HttpResponse response = super.doExecute(servletRequest, servletResponse, proxyRequest); + response.removeHeaders("Access-Control-Allow-Origin"); + response.setHeader("Access-Control-Allow-Credentials","true"); + response.removeHeaders("Access-Control-Allow-Credentials"); + + return response; + } + /** * 异常处理 */ @@ -181,6 +194,18 @@ public class ProxyServletConfig { return queryStr; } + + @Override + protected HttpResponse doExecute(HttpServletRequest servletRequest, HttpServletResponse servletResponse, + HttpRequest proxyRequest) throws IOException { + HttpResponse response = super.doExecute(servletRequest, servletResponse, proxyRequest); + String origin = servletRequest.getHeader("origin"); + response.setHeader("Access-Control-Allow-Origin",origin); + response.setHeader("Access-Control-Allow-Credentials","true"); + + return response; + } + /** * 异常处理 */ diff --git a/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java b/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java index eb1b157a3..7760bddfa 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java @@ -4,8 +4,11 @@ import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.service.IPlatformService; +import com.genersoft.iot.vmp.service.impl.PlatformServiceImpl; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; @@ -33,6 +36,7 @@ public class SipPlatformRunner implements CommandLineRunner { @Autowired private ISIPCommanderForPlatform sipCommanderForPlatform; + private final static Logger logger = LoggerFactory.getLogger(PlatformServiceImpl.class); @Override public void run(String... args) throws Exception { @@ -50,9 +54,15 @@ public class SipPlatformRunner implements CommandLineRunner { redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch); if (parentPlatformCatchOld != null) { // 取消订阅 - sipCommanderForPlatform.unregister(parentPlatform, parentPlatformCatchOld.getSipTransactionInfo(), null, (eventResult)->{ - platformService.login(parentPlatform); - }); + try { + sipCommanderForPlatform.unregister(parentPlatform, parentPlatformCatchOld.getSipTransactionInfo(), null, (eventResult)->{ + platformService.login(parentPlatform); + }); + } catch (Exception e) { + logger.error("[命令发送失败] 国标级联 注销: {}", e.getMessage()); + platformService.offline(parentPlatform, true); + continue; + } } // 设置所有平台离线 diff --git a/src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java index 587518f17..0a472f826 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/SpringDocConfig.java @@ -1,9 +1,12 @@ package com.genersoft.iot.vmp.conf; +import com.genersoft.iot.vmp.conf.security.JwtUtils; +import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.security.SecurityScheme; import org.springframework.core.annotation.Order; import org.springdoc.core.GroupedOpenApi; import org.springframework.beans.factory.annotation.Value; @@ -26,10 +29,14 @@ public class SpringDocConfig { contact.setName("pan"); contact.setEmail("648540858@qq.com"); return new OpenAPI() + .components(new Components() + .addSecuritySchemes(JwtUtils.HEADER, new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .bearerFormat("JWT"))) .info(new Info().title("WVP-PRO 接口文档") .contact(contact) .description("开箱即用的28181协议视频平台") - .version("v2.0") + .version("v3.1.0") .license(new License().name("Apache 2.0").url("http://springdoc.org"))); } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/SystemInfoTimerTask.java b/src/main/java/com/genersoft/iot/vmp/conf/SystemInfoTimerTask.java index 9bc86261d..40803cd1d 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/SystemInfoTimerTask.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/SystemInfoTimerTask.java @@ -39,4 +39,6 @@ public class SystemInfoTimerTask { } } + + } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java b/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java index 55bf371e3..a9f5c88a5 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java @@ -23,7 +23,7 @@ public class UserSetting { private Integer playTimeout = 18000; - private int platformPlayTimeout = 60000; + private int platformPlayTimeout = 20000; private Boolean interfaceAuthentication = Boolean.TRUE; @@ -31,9 +31,9 @@ public class UserSetting { private Boolean recordSip = Boolean.TRUE; - private Boolean logInDatebase = Boolean.TRUE; + private Boolean logInDatabase = Boolean.TRUE; - private Boolean usePushingAsStatus = Boolean.TRUE; + private Boolean usePushingAsStatus = Boolean.FALSE; private Boolean useSourceIpAsStreamIp = Boolean.FALSE; @@ -43,8 +43,6 @@ public class UserSetting { private Boolean pushAuthority = Boolean.TRUE; - private Boolean gbSendStreamStrict = Boolean.FALSE; - private Boolean syncChannelOnDeviceOnline = Boolean.FALSE; private Boolean sipLog = Boolean.FALSE; @@ -53,15 +51,15 @@ public class UserSetting { private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE; - private Boolean deviceStatusNotify = Boolean.FALSE; + private Boolean deviceStatusNotify = Boolean.TRUE; private Boolean useCustomSsrcForParentInvite = Boolean.TRUE; private String serverId = "000000"; - private String recordPath = null; - private String thirdPartyGBIdReg = "[\\s\\S]*"; + private String broadcastForPlatform = "UDP"; + private String civilCodeFile = "classpath:civilCode.csv"; private List interfaceAuthenticationExcludes = new ArrayList<>(); @@ -134,12 +132,12 @@ public class UserSetting { this.interfaceAuthenticationExcludes = interfaceAuthenticationExcludes; } - public Boolean getLogInDatebase() { - return logInDatebase; + public Boolean getLogInDatabase() { + return logInDatabase; } - public void setLogInDatebase(Boolean logInDatebase) { - this.logInDatebase = logInDatebase; + public void setLogInDatabase(Boolean logInDatabase) { + this.logInDatabase = logInDatabase; } public String getServerId() { @@ -206,14 +204,6 @@ public class UserSetting { this.pushAuthority = pushAuthority; } - public Boolean getGbSendStreamStrict() { - return gbSendStreamStrict; - } - - public void setGbSendStreamStrict(Boolean gbSendStreamStrict) { - this.gbSendStreamStrict = gbSendStreamStrict; - } - public Boolean getSyncChannelOnDeviceOnline() { return syncChannelOnDeviceOnline; } @@ -222,6 +212,14 @@ public class UserSetting { this.syncChannelOnDeviceOnline = syncChannelOnDeviceOnline; } + public String getBroadcastForPlatform() { + return broadcastForPlatform; + } + + public void setBroadcastForPlatform(String broadcastForPlatform) { + this.broadcastForPlatform = broadcastForPlatform; + } + public Boolean getSipUseSourceIpAsRemoteAddress() { return sipUseSourceIpAsRemoteAddress; } @@ -262,14 +260,6 @@ public class UserSetting { this.refuseChannelStatusChannelFormNotify = refuseChannelStatusChannelFormNotify; } - public String getRecordPath() { - return recordPath; - } - - public void setRecordPath(String recordPath) { - this.recordPath = recordPath; - } - public int getMaxNotifyCountQueue() { return maxNotifyCountQueue; } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/redis/RedisConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/redis/RedisConfig.java new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java index f35b5bd86..f45f89a13 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java @@ -78,6 +78,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录 User user = new User(); + user.setId(jwtUser.getUserId()); user.setUsername(jwtUser.getUserName()); user.setPassword(jwtUser.getPassword()); Role role = new Role(); diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java index c9c7b680f..fcd194614 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java @@ -1,8 +1,10 @@ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.security.dto.JwtUser; -import org.jose4j.json.JsonUtil; +import com.genersoft.iot.vmp.service.IUserService; +import com.genersoft.iot.vmp.storager.dao.dto.User; import org.jose4j.jwk.RsaJsonWebKey; +import org.jose4j.jwk.RsaJwkGenerator; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; @@ -14,45 +16,69 @@ import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.lang.JoseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; -import java.security.PrivateKey; +import javax.annotation.Resource; import java.time.LocalDateTime; import java.time.ZoneOffset; -public class JwtUtils { +@Component +public class JwtUtils implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class); - private static final String HEADER = "access-token"; + public static final String HEADER = "access-token"; + private static final String AUDIENCE = "Audience"; - private static final long EXPIRED_THRESHOLD = 10 * 60; - private static final String keyId = "3e79646c4dbc408383a9eed09f2b85ae"; - private static final String privateKeyStr = "{\"kty\":\"RSA\",\"kid\":\"3e79646c4dbc408383a9eed09f2b85ae\",\"alg\":\"RS256\",\"n\":\"gndmVdiOTSJ5et2HIeTM5f1m61x5ojLUi5HDfvr-jRrESQ5kbKuySGHVwR4QhwinpY1wQqBnwc80tx7cb_6SSqsTOoGln6T_l3k2Pb54ClVnGWiW_u1kmX78V2TZOsVmZmwtdZCMi-2zWIyAdIEXE-gncIehoAgEoq2VAhaCURbJWro_EwzzQwNmCTkDodLAx4npXRd_qSu0Ayp0txym9OFovBXBULRvk4DPiy3i_bPUmCDxzC46pTtFOe9p82uybTehZfULZtXXqRm85FL9n5zkrsTllPNAyEGhgb0RK9sE5nK1m_wNNysDyfLC4EFf1VXTrKm14XNVjc2vqLb7Mw\",\"e\":\"AQAB\",\"d\":\"ed7U_k3rJ4yTk70JtRSIfjKGiEb67BO1TabcymnljKO7RU8nage84zZYuSu_XpQsHk6P1f0Gzxkicghm_Er-FrfVn2pp70Xu52z3yRd6BJUgWLDFk97ngScIyw5OiULKU9SrZk2frDpftNCSUcIgb50F8m0QAnBa_CdPsQKbuuhLv8V8tBAV7F_lAwvSBgu56wRo3hPz5dWH8YeXM7XBfQ9viFMNEKd21sP_j5C7ueUnXT66nBxe3ZJEU3iuMYM6D6dB_KW2GfZC6WmTgvGhhxJD0h7aYmfjkD99MDleB7SkpbvoODOqiQ5Epb7Nyh6kv5u4KUv2CJYtATLZkUeMkQ\",\"p\":\"uBUjWPWtlGksmOqsqCNWksfqJvMcnP_8TDYN7e4-WnHL4N-9HjRuPDnp6kHvCIEi9SEfxm7gNxlRcWegvNQr3IZCz7TnCTexXc5NOklB9OavWFla6u-s3Thn6Tz45-EUjpJr0VJMxhO-KxGmuTwUXBBp4vN6K2qV6rQNFmgkWzk\",\"q\":\"tW_i7cCec56bHkhITL_79dXHz_PLC_f7xlynmlZJGU_d6mqOKmLBNBbTMLnYW8uAFiFzWxDeDHh1o5uF0mSQR-Z1Fg35OftnpbWpy0Cbc2la5WgXQjOwtG1eLYIY2BD3-wQ1VYDBCvowr4FDi-sngxwLqvwmrJ0xjhi99O-Gzcs\",\"dp\":\"q1d5jE85Hz_6M-eTh_lEluEf0NtPEc-vvhw-QO4V-cecNpbrCBdTWBmr4dE3NdpFeJc5ZVFEv-SACyei1MBEh0ItI_pFZi4BmMfy2ELh8ptaMMkTOESYyVy8U7veDq9RnBcr5i1Nqr0rsBkA77-9T6gzdvycBZdzLYAkAmwzEvk\",\"dq\":\"q29A2K08Crs-jmp2Bi8Q_8QzvIX6wSBbwZ4ir24AO-5_HNP56IrPS0yV2GCB0pqCOGb6_Hz_koDvhtuYoqdqvMVAtMoXR3YJBUaVXPt65p4RyNmFwIPe31zHs_BNUTsXVRMw4c16mci03-Af1sEm4HdLfxAp6sfM3xr5wcnhcek\",\"qi\":\"rHPgVTyHUHuYzcxfouyBfb1XAY8nshwn0ddo81o1BccD4Z7zo5It6SefDHjxCAbcmbiCcXBSooLcY-NF5FMv3fg19UE21VyLQltHcVjRRp2tRs4OHcM8yaXIU2x6N6Z6BP2tOksHb9MOBY1wAQzFOAKg_G4Sxev6-_6ud6RISuc\"}"; - private static final String publicKeyStr = "{\"kty\":\"RSA\",\"kid\":\"3e79646c4dbc408383a9eed09f2b85ae\",\"alg\":\"RS256\",\"n\":\"gndmVdiOTSJ5et2HIeTM5f1m61x5ojLUi5HDfvr-jRrESQ5kbKuySGHVwR4QhwinpY1wQqBnwc80tx7cb_6SSqsTOoGln6T_l3k2Pb54ClVnGWiW_u1kmX78V2TZOsVmZmwtdZCMi-2zWIyAdIEXE-gncIehoAgEoq2VAhaCURbJWro_EwzzQwNmCTkDodLAx4npXRd_qSu0Ayp0txym9OFovBXBULRvk4DPiy3i_bPUmCDxzC46pTtFOe9p82uybTehZfULZtXXqRm85FL9n5zkrsTllPNAyEGhgb0RK9sE5nK1m_wNNysDyfLC4EFf1VXTrKm14XNVjc2vqLb7Mw\",\"e\":\"AQAB\"}"; /** * token过期时间(分钟) */ - public static final long expirationTime = 30; + public static final long expirationTime = 30 * 24 * 60; - public static String createToken(String username, String password, Integer roleId) { + private static RsaJsonWebKey rsaJsonWebKey; + + private static IUserService userService; + + @Resource + public void setUserService(IUserService userService) { + JwtUtils.userService = userService; + } + + @Override + public void afterPropertiesSet() { try { - /** + rsaJsonWebKey = generateRsaJsonWebKey(); + } catch (JoseException e) { + logger.error("生成RsaJsonWebKey报错。", e); + } + } + + /** + * 创建密钥对 + * @throws JoseException JoseException + */ + private RsaJsonWebKey generateRsaJsonWebKey() throws JoseException { + // 生成一个RSA密钥对,该密钥对将用于JWT的签名和验证,包装在JWK中 + RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048); + // 给JWK一个密钥ID + rsaJsonWebKey.setKeyId(keyId); + return rsaJsonWebKey; + } + + public static String createToken(String username) { + try { + /* * “iss” (issuer) 发行人 - * * “sub” (subject) 主题 - * * “aud” (audience) 接收方 用户 - * * “exp” (expiration time) 到期时间 - * * “nbf” (not before) 在此之前不可用 - * * “iat” (issued at) jwt的签发时间 */ - //Payload JwtClaims claims = new JwtClaims(); claims.setGeneratedJwtId(); claims.setIssuedAtToNow(); @@ -62,9 +88,7 @@ public class JwtUtils { claims.setSubject("login"); claims.setAudience(AUDIENCE); //添加自定义参数,必须是字符串类型 - claims.setClaim("username", username); - claims.setClaim("password", password); - claims.setClaim("roleId", roleId); + claims.setClaim("userName", username); //jws JsonWebSignature jws = new JsonWebSignature(); @@ -73,12 +97,10 @@ public class JwtUtils { jws.setKeyIdHeaderValue(keyId); jws.setPayload(claims.toJson()); - PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyStr)).getPrivateKey(); - jws.setKey(privateKey); + jws.setKey(rsaJsonWebKey.getPrivateKey()); //get token - String idToken = jws.getCompactSerialization(); - return idToken; + return jws.getCompactSerialization(); } catch (JoseException e) { logger.error("[Token生成失败]: {}", e.getMessage()); } @@ -90,7 +112,6 @@ public class JwtUtils { return HEADER; } - public static JwtUser verifyToken(String token) { JwtUser jwtUser = new JwtUser(); @@ -103,7 +124,7 @@ public class JwtUtils { .setRequireSubject() //.setExpectedIssuer("") .setExpectedAudience(AUDIENCE) - .setVerificationKey(new RsaJsonWebKey(JsonUtil.parseJson(publicKeyStr)).getPublicKey()) + .setVerificationKey(rsaJsonWebKey.getPublicKey()) .build(); JwtClaims claims = consumer.processToClaims(token); @@ -113,26 +134,27 @@ public class JwtUtils { long timeRemaining = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8)) - expirationTime.getValue(); if (timeRemaining < 5 * 60) { jwtUser.setStatus(JwtUser.TokenStatus.EXPIRING_SOON); - }else { + } else { jwtUser.setStatus(JwtUser.TokenStatus.NORMAL); } - String username = (String) claims.getClaimValue("username"); - String password = (String) claims.getClaimValue("password"); - Long roleId = (Long) claims.getClaimValue("roleId"); + String username = (String) claims.getClaimValue("userName"); + User user = userService.getUserByUsername(username); + jwtUser.setUserName(username); - jwtUser.setPassword(password); - jwtUser.setRoleId(roleId.intValue()); + jwtUser.setPassword(user.getPassword()); + jwtUser.setRoleId(user.getRole().getId()); + jwtUser.setUserId(user.getId()); return jwtUser; } catch (InvalidJwtException e) { if (e.hasErrorCode(ErrorCodes.EXPIRED)) { jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED); - }else { + } else { jwtUser.setStatus(JwtUser.TokenStatus.EXCEPTION); } return jwtUser; - }catch (Exception e) { + } catch (Exception e) { logger.error("[Token解析失败]: {}", e.getMessage()); jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED); return jwtUser; diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java index 6a247359b..ad959d622 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/WebSecurityConfig.java @@ -1,12 +1,12 @@ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.UserSetting; -import org.springframework.core.annotation.Order; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; @@ -25,9 +25,11 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; /** * 配置Spring Security + * * @author lin */ @Configuration @@ -67,6 +69,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { matchers.add("/"); matchers.add("/#/**"); matchers.add("/static/**"); + matchers.add("/swagger-ui.html"); + matchers.add("/swagger-ui/"); matchers.add("/index.html"); matchers.add("/doc.html"); matchers.add("/webjars/**"); @@ -75,7 +79,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { matchers.add("/js/**"); matchers.add("/api/device/query/snap/**"); matchers.add("/record_proxy/*/**"); - matchers.addAll(userSetting.getInterfaceAuthenticationExcludes()); + matchers.add("/api/emit"); + matchers.add("/favicon.ico"); // 可以直接访问的静态数据 web.ignoring().antMatchers(matchers.toArray(new String[0])); } @@ -83,6 +88,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { /** * 配置认证方式 + * * @param auth * @throws Exception */ @@ -111,7 +117,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .authorizeRequests() .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() .antMatchers(userSetting.getInterfaceAuthenticationExcludes().toArray(new String[0])).permitAll() - .antMatchers("/api/user/login","/index/hook/**","/zlm_Proxy/FhTuMYqB2HeCuNOb/record/t/1/2023-03-25/16:35:07-16:35:16-9353.mp4").permitAll() + .antMatchers("/api/user/login", "/index/hook/**", "/swagger-ui/**", "/doc.html").permitAll() .anyRequest().authenticated() // 异常处理器 .and() @@ -124,18 +130,24 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { } - CorsConfigurationSource configurationSource(){ + CorsConfigurationSource configurationSource() { // 配置跨域 CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedHeaders(Arrays.asList("*")); corsConfiguration.setAllowedMethods(Arrays.asList("*")); corsConfiguration.setMaxAge(3600L); - corsConfiguration.setAllowCredentials(true); - corsConfiguration.setAllowedOrigins(userSetting.getAllowedOrigins()); + if (userSetting.getAllowedOrigins() != null && !userSetting.getAllowedOrigins().isEmpty()) { + corsConfiguration.setAllowCredentials(true); + corsConfiguration.setAllowedOrigins(userSetting.getAllowedOrigins()); + }else { + corsConfiguration.setAllowCredentials(false); + corsConfiguration.setAllowedOrigins(Collections.singletonList(CorsConfiguration.ALL)); + } + corsConfiguration.setExposedHeaders(Arrays.asList(JwtUtils.getHeader())); UrlBasedCorsConfigurationSource url = new UrlBasedCorsConfigurationSource(); - url.registerCorsConfiguration("/**",corsConfiguration); + url.registerCorsConfiguration("/**", corsConfiguration); return url; } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/dto/JwtUser.java b/src/main/java/com/genersoft/iot/vmp/conf/security/dto/JwtUser.java index 8921a3083..df29c333a 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/dto/JwtUser.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/dto/JwtUser.java @@ -21,6 +21,7 @@ public class JwtUser { EXCEPTION } + private int userId; private String userName; private String password; @@ -29,6 +30,14 @@ public class JwtUser { private TokenStatus status; + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + public String getUserName() { return userName; } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java b/src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java index 61bd6fa6f..67075bc8d 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/dto/LoginUser.java @@ -100,6 +100,9 @@ public class LoginUser implements UserDetails, CredentialsContainer { return user.getRole(); } + public String getPushKey() { + return user.getPushKey(); + } public String getAccessToken() { return accessToken; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java index 0c77fc183..56663f18d 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java @@ -1,141 +1,141 @@ -package com.genersoft.iot.vmp.gb28181; - -import com.genersoft.iot.vmp.conf.SipConfig; -import com.genersoft.iot.vmp.conf.UserSetting; -import com.genersoft.iot.vmp.gb28181.bean.GbStringMsgParserFactory; -import com.genersoft.iot.vmp.gb28181.conf.DefaultProperties; -import com.genersoft.iot.vmp.gb28181.transmit.ISIPProcessorObserver; -import gov.nist.javax.sip.SipProviderImpl; -import gov.nist.javax.sip.SipStackImpl; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Component; -import org.springframework.util.ObjectUtils; - -import javax.sip.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -@Component -@Order(value=10) -public class SipLayer implements CommandLineRunner { - - private final static Logger logger = LoggerFactory.getLogger(SipLayer.class); - - @Autowired - private SipConfig sipConfig; - - @Autowired - private ISIPProcessorObserver sipProcessorObserver; - - @Autowired - private UserSetting userSetting; - - private final Map tcpSipProviderMap = new ConcurrentHashMap<>(); - private final Map udpSipProviderMap = new ConcurrentHashMap<>(); - - @Override - public void run(String... args) { - List monitorIps = new ArrayList<>(); - // 使用逗号分割多个ip - String separator = ","; - if (sipConfig.getIp().indexOf(separator) > 0) { - String[] split = sipConfig.getIp().split(separator); - monitorIps.addAll(Arrays.asList(split)); - }else { - monitorIps.add(sipConfig.getIp()); - } - - SipFactory.getInstance().setPathName("gov.nist"); - if (monitorIps.size() > 0) { - for (String monitorIp : monitorIps) { - addListeningPoint(monitorIp, sipConfig.getPort()); - } - if (udpSipProviderMap.size() + tcpSipProviderMap.size() == 0) { - System.exit(1); - } - } - } - - private void addListeningPoint(String monitorIp, int port){ - SipStackImpl sipStack; - try { - sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties("GB28181_SIP", userSetting.getSipLog())); - sipStack.setMessageParserFactory(new GbStringMsgParserFactory()); - } catch (PeerUnavailableException e) { - logger.error("[SIP SERVER] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp); - return; - } - - try { - ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "TCP"); - SipProviderImpl tcpSipProvider = (SipProviderImpl)sipStack.createSipProvider(tcpListeningPoint); - - tcpSipProvider.setDialogErrorsAutomaticallyHandled(); - tcpSipProvider.addSipListener(sipProcessorObserver); - tcpSipProviderMap.put(monitorIp, tcpSipProvider); - logger.info("[SIP SERVER] tcp://{}:{} 启动成功", monitorIp, port); - } catch (TransportNotSupportedException - | TooManyListenersException - | ObjectInUseException - | InvalidArgumentException e) { - logger.error("[SIP SERVER] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" - , monitorIp, port); - } - - try { - ListeningPoint udpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "UDP"); - - SipProviderImpl udpSipProvider = (SipProviderImpl)sipStack.createSipProvider(udpListeningPoint); - udpSipProvider.addSipListener(sipProcessorObserver); - - udpSipProviderMap.put(monitorIp, udpSipProvider); - - logger.info("[SIP SERVER] udp://{}:{} 启动成功", monitorIp, port); - } catch (TransportNotSupportedException - | TooManyListenersException - | ObjectInUseException - | InvalidArgumentException e) { - logger.error("[SIP SERVER] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" - , monitorIp, port); - } - } - - public SipProviderImpl getUdpSipProvider(String ip) { - if (ObjectUtils.isEmpty(ip)) { - return null; - } - return udpSipProviderMap.get(ip); - } - - public SipProviderImpl getUdpSipProvider() { - if (udpSipProviderMap.size() != 1) { - return null; - } - return udpSipProviderMap.values().stream().findFirst().get(); - } - - public SipProviderImpl getTcpSipProvider() { - if (tcpSipProviderMap.size() != 1) { - return null; - } - return tcpSipProviderMap.values().stream().findFirst().get(); - } - - public SipProviderImpl getTcpSipProvider(String ip) { - if (ObjectUtils.isEmpty(ip)) { - return null; - } - return tcpSipProviderMap.get(ip); - } - - public String getLocalIp(String deviceLocalIp) { - if (!ObjectUtils.isEmpty(deviceLocalIp)) { - return deviceLocalIp; - } - return getUdpSipProvider().getListeningPoint().getIPAddress(); - } -} +package com.genersoft.iot.vmp.gb28181; + +import com.genersoft.iot.vmp.conf.SipConfig; +import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.gb28181.bean.GbStringMsgParserFactory; +import com.genersoft.iot.vmp.gb28181.conf.DefaultProperties; +import com.genersoft.iot.vmp.gb28181.transmit.ISIPProcessorObserver; +import gov.nist.javax.sip.SipProviderImpl; +import gov.nist.javax.sip.SipStackImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; + +import javax.sip.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +@Component +@Order(value=10) +public class SipLayer implements CommandLineRunner { + + private final static Logger logger = LoggerFactory.getLogger(SipLayer.class); + + @Autowired + private SipConfig sipConfig; + + @Autowired + private ISIPProcessorObserver sipProcessorObserver; + + @Autowired + private UserSetting userSetting; + + private final Map tcpSipProviderMap = new ConcurrentHashMap<>(); + private final Map udpSipProviderMap = new ConcurrentHashMap<>(); + + @Override + public void run(String... args) { + List monitorIps = new ArrayList<>(); + // 使用逗号分割多个ip + String separator = ","; + if (sipConfig.getIp().indexOf(separator) > 0) { + String[] split = sipConfig.getIp().split(separator); + monitorIps.addAll(Arrays.asList(split)); + }else { + monitorIps.add(sipConfig.getIp()); + } + + SipFactory.getInstance().setPathName("gov.nist"); + if (monitorIps.size() > 0) { + for (String monitorIp : monitorIps) { + addListeningPoint(monitorIp, sipConfig.getPort()); + } + if (udpSipProviderMap.size() + tcpSipProviderMap.size() == 0) { + System.exit(1); + } + } + } + + private void addListeningPoint(String monitorIp, int port){ + SipStackImpl sipStack; + try { + sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties("GB28181_SIP", userSetting.getSipLog())); + sipStack.setMessageParserFactory(new GbStringMsgParserFactory()); + } catch (PeerUnavailableException e) { + logger.error("[SIP SERVER] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp); + return; + } + + try { + ListeningPoint tcpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "TCP"); + SipProviderImpl tcpSipProvider = (SipProviderImpl)sipStack.createSipProvider(tcpListeningPoint); + + tcpSipProvider.setDialogErrorsAutomaticallyHandled(); + tcpSipProvider.addSipListener(sipProcessorObserver); + tcpSipProviderMap.put(monitorIp, tcpSipProvider); + logger.info("[SIP SERVER] tcp://{}:{} 启动成功", monitorIp, port); + } catch (TransportNotSupportedException + | TooManyListenersException + | ObjectInUseException + | InvalidArgumentException e) { + logger.error("[SIP SERVER] tcp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" + , monitorIp, port); + } + + try { + ListeningPoint udpListeningPoint = sipStack.createListeningPoint(monitorIp, port, "UDP"); + + SipProviderImpl udpSipProvider = (SipProviderImpl)sipStack.createSipProvider(udpListeningPoint); + udpSipProvider.addSipListener(sipProcessorObserver); + + udpSipProviderMap.put(monitorIp, udpSipProvider); + + logger.info("[SIP SERVER] udp://{}:{} 启动成功", monitorIp, port); + } catch (TransportNotSupportedException + | TooManyListenersException + | ObjectInUseException + | InvalidArgumentException e) { + logger.error("[SIP SERVER] udp://{}:{} SIP服务启动失败,请检查端口是否被占用或者ip是否正确" + , monitorIp, port); + } + } + + public SipProviderImpl getUdpSipProvider(String ip) { + if (ObjectUtils.isEmpty(ip)) { + return null; + } + return udpSipProviderMap.get(ip); + } + + public SipProviderImpl getUdpSipProvider() { + if (udpSipProviderMap.size() != 1) { + return null; + } + return udpSipProviderMap.values().stream().findFirst().get(); + } + + public SipProviderImpl getTcpSipProvider() { + if (tcpSipProviderMap.size() != 1) { + return null; + } + return tcpSipProviderMap.values().stream().findFirst().get(); + } + + public SipProviderImpl getTcpSipProvider(String ip) { + if (ObjectUtils.isEmpty(ip)) { + return null; + } + return tcpSipProviderMap.get(ip); + } + + public String getLocalIp(String deviceLocalIp) { + if (!ObjectUtils.isEmpty(deviceLocalIp)) { + return deviceLocalIp; + } + return getUdpSipProvider().getListeningPoint().getIPAddress(); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java new file mode 100644 index 000000000..814d9847a --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatch.java @@ -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); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatchStatus.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatchStatus.java new file mode 100644 index 000000000..7d4f7c837 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/AudioBroadcastCatchStatus.java @@ -0,0 +1,15 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +/** + * 语音广播状态 + * @author lin + */ +public enum AudioBroadcastCatchStatus { + + // 发送语音广播消息等待对方回复语音广播 + Ready, + // 收到回复等待invite消息 + WaiteInvite, + // 收到invite消息 + Ok, +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java index 1c7baac9c..a9752f127 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Device.java @@ -165,7 +165,7 @@ public class Device { * 是否开启ssrc校验,默认关闭,开启可以防止串流 */ @Schema(description = "是否开启ssrc校验,默认关闭,开启可以防止串流") - private boolean ssrcCheck = true; + private boolean ssrcCheck = false; /** * 地理坐标系, 目前支持 WGS84,GCJ02 @@ -188,8 +188,8 @@ public class Device { @Schema(description = "设备注册的事务信息") private SipTransactionInfo sipTransactionInfo; - - + @Schema(description = "控制语音对讲流程,释放收到ACK后发流") + private boolean broadcastPushAfterAck; public String getDeviceId() { return deviceId; @@ -452,20 +452,11 @@ public class Device { public void setSipTransactionInfo(SipTransactionInfo sipTransactionInfo) { this.sipTransactionInfo = sipTransactionInfo; } + public boolean isBroadcastPushAfterAck() { + return broadcastPushAfterAck; + } - /*======================设备主子码流逻辑START=========================*/ - @Schema(description = "开启主子码流切换的开关(false-不开启,true-开启)") - private boolean switchPrimarySubStream; - - public boolean isSwitchPrimarySubStream() { - return switchPrimarySubStream; - } - - public void setSwitchPrimarySubStream(boolean switchPrimarySubStream) { - this.switchPrimarySubStream = switchPrimarySubStream; - } - - /*======================设备主子码流逻辑END=========================*/ - - + public void setBroadcastPushAfterAck(boolean broadcastPushAfterAck) { + this.broadcastPushAfterAck = broadcastPushAfterAck; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java index bfc97b555..371134160 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceAlarm.java @@ -1,187 +1,187 @@ -package com.genersoft.iot.vmp.gb28181.bean; - -import io.swagger.v3.oas.annotations.media.Schema; - -/** - * @author lin - */ -@Schema(description = "报警信息") -public class DeviceAlarm { - - /** - * 数据库id - */ - @Schema(description = "数据库id") - private String id; - - /** - * 设备Id - */ - @Schema(description = "设备的国标编号") - private String deviceId; - - /** - * 通道Id - */ - @Schema(description = "通道的国标编号") - private String channelId; - - /** - * 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情 - */ - @Schema(description = "报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情") - private String alarmPriority; - - /** - * 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警, - * 7其他报警;可以为直接组合如12为电话报警或 设备报警- - */ - @Schema(description = "报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,\n" + - "\t * 7其他报警;可以为直接组合如12为电话报警或设备报警") - private String alarmMethod; - - /** - * 报警时间 - */ - @Schema(description = "报警时间") - private String alarmTime; - - /** - * 报警内容描述 - */ - @Schema(description = "报警内容描述") - private String alarmDescription; - - /** - * 经度 - */ - @Schema(description = "经度") - private double longitude; - - /** - * 纬度 - */ - @Schema(description = "纬度") - private double latitude; - - /** - * 报警类型, - * 报警方式为2时,不携带 AlarmType为默认的报警设备报警, - * 携带 AlarmType取值及对应报警类型如下: - * 1-视频丢失报警; - * 2-设备防拆报警; - * 3-存储设备磁盘满报警; - * 4-设备高温报警; - * 5-设备低温报警。 - * 报警方式为5时,取值如下: - * 1-人工视频报警; - * 2-运动目标检测报警; - * 3-遗留物检测报警; - * 4-物体移除检测报警; - * 5-绊线检测报警; - * 6-入侵检测报警; - * 7-逆行检测报警; - * 8-徘徊检测报警; - * 9-流量统计报警; - * 10-密度检测报警; - * 11-视频异常检测报警; - * 12-快速移动报警。 - * 报警方式为6时,取值下: - * 1-存储设备磁盘故障报警; - * 2-存储设备风扇故障报警。 - */ - @Schema(description = "报警类型") - private String alarmType; - - @Schema(description = "创建时间") - private String createTime; - - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getDeviceId() { - return deviceId; - } - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - public String getAlarmPriority() { - return alarmPriority; - } - - public void setAlarmPriority(String alarmPriority) { - this.alarmPriority = alarmPriority; - } - - public String getAlarmMethod() { - return alarmMethod; - } - - public void setAlarmMethod(String alarmMethod) { - this.alarmMethod = alarmMethod; - } - - public String getAlarmTime() { - return alarmTime; - } - - public void setAlarmTime(String alarmTime) { - this.alarmTime = alarmTime; - } - - public String getAlarmDescription() { - return alarmDescription; - } - - public void setAlarmDescription(String alarmDescription) { - this.alarmDescription = alarmDescription; - } - - public double getLongitude() { - return longitude; - } - - public void setLongitude(double longitude) { - this.longitude = longitude; - } - - public double getLatitude() { - return latitude; - } - - public void setLatitude(double latitude) { - this.latitude = latitude; - } - - public String getAlarmType() { - return alarmType; - } - - public void setAlarmType(String alarmType) { - this.alarmType = alarmType; - } - - public String getChannelId() { - return channelId; - } - - public void setChannelId(String channelId) { - this.channelId = channelId; - } - - public String getCreateTime() { - return createTime; - } - - public void setCreateTime(String createTime) { - this.createTime = createTime; - } -} +package com.genersoft.iot.vmp.gb28181.bean; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * @author lin + */ +@Schema(description = "报警信息") +public class DeviceAlarm { + + /** + * 数据库id + */ + @Schema(description = "数据库id") + private String id; + + /** + * 设备Id + */ + @Schema(description = "设备的国标编号") + private String deviceId; + + /** + * 通道Id + */ + @Schema(description = "通道的国标编号") + private String channelId; + + /** + * 报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情 + */ + @Schema(description = "报警级别, 1为一级警情, 2为二级警情, 3为三级警情, 4为四级警情") + private String alarmPriority; + + /** + * 报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警, + * 7其他报警;可以为直接组合如12为电话报警或 设备报警- + */ + @Schema(description = "报警方式 , 1为电话报警, 2为设备报警, 3为短信报警, 4为 GPS报警, 5为视频报警, 6为设备故障报警,\n" + + "\t * 7其他报警;可以为直接组合如12为电话报警或设备报警") + private String alarmMethod; + + /** + * 报警时间 + */ + @Schema(description = "报警时间") + private String alarmTime; + + /** + * 报警内容描述 + */ + @Schema(description = "报警内容描述") + private String alarmDescription; + + /** + * 经度 + */ + @Schema(description = "经度") + private double longitude; + + /** + * 纬度 + */ + @Schema(description = "纬度") + private double latitude; + + /** + * 报警类型, + * 报警方式为2时,不携带 AlarmType为默认的报警设备报警, + * 携带 AlarmType取值及对应报警类型如下: + * 1-视频丢失报警; + * 2-设备防拆报警; + * 3-存储设备磁盘满报警; + * 4-设备高温报警; + * 5-设备低温报警。 + * 报警方式为5时,取值如下: + * 1-人工视频报警; + * 2-运动目标检测报警; + * 3-遗留物检测报警; + * 4-物体移除检测报警; + * 5-绊线检测报警; + * 6-入侵检测报警; + * 7-逆行检测报警; + * 8-徘徊检测报警; + * 9-流量统计报警; + * 10-密度检测报警; + * 11-视频异常检测报警; + * 12-快速移动报警。 + * 报警方式为6时,取值下: + * 1-存储设备磁盘故障报警; + * 2-存储设备风扇故障报警。 + */ + @Schema(description = "报警类型") + private String alarmType; + + @Schema(description = "创建时间") + private String createTime; + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getAlarmPriority() { + return alarmPriority; + } + + public void setAlarmPriority(String alarmPriority) { + this.alarmPriority = alarmPriority; + } + + public String getAlarmMethod() { + return alarmMethod; + } + + public void setAlarmMethod(String alarmMethod) { + this.alarmMethod = alarmMethod; + } + + public String getAlarmTime() { + return alarmTime; + } + + public void setAlarmTime(String alarmTime) { + this.alarmTime = alarmTime; + } + + public String getAlarmDescription() { + return alarmDescription; + } + + public void setAlarmDescription(String alarmDescription) { + this.alarmDescription = alarmDescription; + } + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + public String getAlarmType() { + return alarmType; + } + + public void setAlarmType(String alarmType) { + this.alarmType = alarmType; + } + + public String getChannelId() { + return channelId; + } + + public void setChannelId(String channelId) { + this.channelId = channelId; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java index f7ef12d32..d58abcb35 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java @@ -246,6 +246,10 @@ public class DeviceChannel { @Schema(description = "GPS的更新时间") private String gpsTime; + @Schema(description = "码流标识,优先级高于设备中码流标识," + + "用于选择码流时组成码流标识。默认为null,不设置。可选值: stream/streamnumber/streamprofile/streamMode") + private String streamIdentification; + public int getId() { return id; } @@ -574,4 +578,12 @@ public class DeviceChannel { public void setGpsTime(String gpsTime) { this.gpsTime = gpsTime; } + + public String getStreamIdentification() { + return streamIdentification; + } + + public void setStreamIdentification(String streamIdentification) { + this.streamIdentification = streamIdentification; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GBStringMsgParser.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GBStringMsgParser.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbSteamIdentification.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbSteamIdentification.java new file mode 100644 index 000000000..63c17a80c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbSteamIdentification.java @@ -0,0 +1,44 @@ +package com.genersoft.iot.vmp.gb28181.bean; + +/** + * 码流索引标识 + */ +public enum GbSteamIdentification { + /** + * 主码流 stream:0 + * 子码流 stream:1s + */ + streamMain("stream", new String[]{"0","1"}), + /** + * 国标28181-2022定义的方式 + * 主码流 streamnumber:0 + * 子码流 streamnumber:1 + */ + streamnumber("streamnumber", new String[]{"0","1"}), + /** + * 主码流 streamprofile:0 + * 子码流 streamprofile:1 + */ + streamprofile("streamprofile", new String[]{"0","1"}), + /** + * 适用的品牌: TP-LINK + */ + streamMode("streamMode", new String[]{"main","sub"}), + ; + + GbSteamIdentification(String value, String[] indexArray) { + this.value = value; + this.indexArray = indexArray; + } + + private String value; + private String[] indexArray; + + public String getValue() { + return value; + } + + public String[] getIndexArray() { + return indexArray; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStream.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStream.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStringMsgParserFactory.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/GbStringMsgParserFactory.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/HandlerCatchData.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/HandlerCatchData.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/HomePositionRequest.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Host.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/Host.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamCallback.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamCallback.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamInfo.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java old mode 100644 new mode 100755 index 677dcd71c..4f62c6687 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/InviteStreamType.java @@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.gb28181.bean; public enum InviteStreamType { - PLAY,PLAYBACK,DOWNLOAD,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY + PLAY,PLAYBACK,DOWNLOAD,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,BROADCAST,TALK } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/MobilePosition.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/MobilePosition.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java old mode 100644 new mode 100755 index fbc95ed96..7de5098d8 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatform.java @@ -66,7 +66,7 @@ public class ParentPlatform { * 设备端口 */ @Schema(description = "设备端口") - private String devicePort; + private int devicePort; /** * SIP认证用户名(默认使用设备国标编号) @@ -186,6 +186,9 @@ public class ParentPlatform { @Schema(description = "是否作为消息通道") private boolean asMessageChannel; + @Schema(description = "是否作为消息通道") + private boolean autoPushChannel; + public Integer getId() { return id; } @@ -258,11 +261,11 @@ public class ParentPlatform { this.deviceIp = deviceIp; } - public String getDevicePort() { + public int getDevicePort() { return devicePort; } - public void setDevicePort(String devicePort) { + public void setDevicePort(int devicePort) { this.devicePort = devicePort; } @@ -425,4 +428,12 @@ public class ParentPlatform { public void setAsMessageChannel(boolean asMessageChannel) { this.asMessageChannel = asMessageChannel; } + + public boolean isAutoPushChannel() { + return autoPushChannel; + } + + public void setAutoPushChannel(boolean autoPushChannel) { + this.autoPushChannel = autoPushChannel; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatformCatch.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/ParentPlatformCatch.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformCatalog.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformCatalog.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformGbStream.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformGbStream.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformRegister.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PlatformRegister.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PresetQuerySipReq.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/PresetQuerySipReq.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java old mode 100644 new mode 100755 index 7ff528306..5b524cf67 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java @@ -1,102 +1,102 @@ -package com.genersoft.iot.vmp.gb28181.bean; - - -import io.swagger.v3.oas.annotations.media.Schema; - -import java.time.Instant; -import java.util.List; - -/** - * @description:设备录像信息bean - * @author: swwheihei - * @date: 2020年5月8日 下午2:05:56 - */ -@Schema(description = "设备录像查询结果信息") -public class RecordInfo { - - @Schema(description = "设备编号") - private String deviceId; - - @Schema(description = "通道编号") - private String channelId; - - @Schema(description = "命令序列号") - private String sn; - - @Schema(description = "设备名称") - private String name; - - @Schema(description = "列表总数") - private int sumNum; - - private int count; - - private Instant lastTime; - - @Schema(description = "") - private List recordList; - - public String getDeviceId() { - return deviceId; - } - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getSumNum() { - return sumNum; - } - - public void setSumNum(int sumNum) { - this.sumNum = sumNum; - } - - public List getRecordList() { - return recordList; - } - - public void setRecordList(List recordList) { - this.recordList = recordList; - } - - public String getChannelId() { - return channelId; - } - - public void setChannelId(String channelId) { - this.channelId = channelId; - } - - public String getSn() { - return sn; - } - - public void setSn(String sn) { - this.sn = sn; - } - - public Instant getLastTime() { - return lastTime; - } - - public void setLastTime(Instant lastTime) { - this.lastTime = lastTime; - } - - public int getCount() { - return count; - } - - public void setCount(int count) { - this.count = count; - } -} +package com.genersoft.iot.vmp.gb28181.bean; + + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.Instant; +import java.util.List; + +/** + * @description:设备录像信息bean + * @author: swwheihei + * @date: 2020年5月8日 下午2:05:56 + */ +@Schema(description = "设备录像查询结果信息") +public class RecordInfo { + + @Schema(description = "设备编号") + private String deviceId; + + @Schema(description = "通道编号") + private String channelId; + + @Schema(description = "命令序列号") + private String sn; + + @Schema(description = "设备名称") + private String name; + + @Schema(description = "列表总数") + private int sumNum; + + private int count; + + private Instant lastTime; + + @Schema(description = "") + private List recordList; + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getSumNum() { + return sumNum; + } + + public void setSumNum(int sumNum) { + this.sumNum = sumNum; + } + + public List getRecordList() { + return recordList; + } + + public void setRecordList(List recordList) { + this.recordList = recordList; + } + + public String getChannelId() { + return channelId; + } + + public void setChannelId(String channelId) { + this.channelId = channelId; + } + + public String getSn() { + return sn; + } + + public void setSn(String sn) { + this.sn = sn; + } + + public Instant getLastTime() { + return lastTime; + } + + public void setLastTime(Instant lastTime) { + this.lastTime = lastTime; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java old mode 100644 new mode 100755 index 07e559c81..452e13886 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordItem.java @@ -1,144 +1,144 @@ -package com.genersoft.iot.vmp.gb28181.bean; - - -import com.genersoft.iot.vmp.utils.DateUtil; -import io.swagger.v3.oas.annotations.media.Schema; -import org.jetbrains.annotations.NotNull; - -import java.time.Instant; -import java.time.temporal.TemporalAccessor; - -/** - * @description:设备录像bean - * @author: swwheihei - * @date: 2020年5月8日 下午2:06:54 - */ -@Schema(description = "设备录像详情") -public class RecordItem implements Comparable{ - - @Schema(description = "设备编号") - private String deviceId; - - @Schema(description = "名称") - private String name; - - @Schema(description = "文件路径名 (可选)") - private String filePath; - - @Schema(description = "录像文件大小,单位:Byte(可选)") - private String fileSize; - - @Schema(description = "录像地址(可选)") - private String address; - - @Schema(description = "录像开始时间(可选)") - private String startTime; - - @Schema(description = "录像结束时间(可选)") - private String endTime; - - @Schema(description = "保密属性(必选)缺省为0;0:不涉密,1:涉密") - private int secrecy; - - @Schema(description = "录像产生类型(可选)time或alarm 或 manua") - private String type; - - @Schema(description = "录像触发者ID(可选)") - private String recorderId; - - public String getDeviceId() { - return deviceId; - } - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getFilePath() { - return filePath; - } - - public void setFilePath(String filePath) { - this.filePath = filePath; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public String getStartTime() { - return startTime; - } - - public void setStartTime(String startTime) { - this.startTime = startTime; - } - - public String getEndTime() { - return endTime; - } - - public void setEndTime(String endTime) { - this.endTime = endTime; - } - - public int getSecrecy() { - return secrecy; - } - - public void setSecrecy(int secrecy) { - this.secrecy = secrecy; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getRecorderId() { - return recorderId; - } - - public void setRecorderId(String recorderId) { - this.recorderId = recorderId; - } - - public String getFileSize() { - return fileSize; - } - - public void setFileSize(String fileSize) { - this.fileSize = fileSize; - } - - @Override - public int compareTo(@NotNull RecordItem recordItem) { - TemporalAccessor startTimeNow = DateUtil.formatter.parse(startTime); - TemporalAccessor startTimeParam = DateUtil.formatter.parse(recordItem.getStartTime()); - Instant startTimeParamInstant = Instant.from(startTimeParam); - Instant startTimeNowInstant = Instant.from(startTimeNow); - if (startTimeNowInstant.equals(startTimeParamInstant)) { - return 0; - }else if (Instant.from(startTimeParam).isAfter(Instant.from(startTimeNow)) ) { - return -1; - }else { - return 1; - } - - } -} +package com.genersoft.iot.vmp.gb28181.bean; + + +import com.genersoft.iot.vmp.utils.DateUtil; +import io.swagger.v3.oas.annotations.media.Schema; +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; +import java.time.temporal.TemporalAccessor; + +/** + * @description:设备录像bean + * @author: swwheihei + * @date: 2020年5月8日 下午2:06:54 + */ +@Schema(description = "设备录像详情") +public class RecordItem implements Comparable{ + + @Schema(description = "设备编号") + private String deviceId; + + @Schema(description = "名称") + private String name; + + @Schema(description = "文件路径名 (可选)") + private String filePath; + + @Schema(description = "录像文件大小,单位:Byte(可选)") + private String fileSize; + + @Schema(description = "录像地址(可选)") + private String address; + + @Schema(description = "录像开始时间(可选)") + private String startTime; + + @Schema(description = "录像结束时间(可选)") + private String endTime; + + @Schema(description = "保密属性(必选)缺省为0;0:不涉密,1:涉密") + private int secrecy; + + @Schema(description = "录像产生类型(可选)time或alarm 或 manua") + private String type; + + @Schema(description = "录像触发者ID(可选)") + private String recorderId; + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public int getSecrecy() { + return secrecy; + } + + public void setSecrecy(int secrecy) { + this.secrecy = secrecy; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getRecorderId() { + return recorderId; + } + + public void setRecorderId(String recorderId) { + this.recorderId = recorderId; + } + + public String getFileSize() { + return fileSize; + } + + public void setFileSize(String fileSize) { + this.fileSize = fileSize; + } + + @Override + public int compareTo(@NotNull RecordItem recordItem) { + TemporalAccessor startTimeNow = DateUtil.formatter.parse(startTime); + TemporalAccessor startTimeParam = DateUtil.formatter.parse(recordItem.getStartTime()); + Instant startTimeParamInstant = Instant.from(startTimeParam); + Instant startTimeNowInstant = Instant.from(startTimeNow); + if (startTimeNowInstant.equals(startTimeParamInstant)) { + return 0; + }else if (Instant.from(startTimeParam).isAfter(Instant.from(startTimeNow)) ) { + return -1; + }else { + return 1; + } + + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RemoteAddressInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RemoteAddressInfo.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SDPInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SDPInfo.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java old mode 100644 new mode 100755 index c1fe2c1fe..361bdc6df --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SendRtpItem.java @@ -49,7 +49,7 @@ public class SendRtpItem { /** * 设备推流的streamId */ - private String streamId; + private String stream; /** * 是否为tcp @@ -117,6 +117,11 @@ public class SendRtpItem { */ private InviteStreamType playType; + /** + * 发流的同时收流 + */ + private String receiveStream; + public String getIp() { return ip; } @@ -181,12 +186,12 @@ public class SendRtpItem { this.app = app; } - public String getStreamId() { - return streamId; + public String getStream() { + return stream; } - public void setStreamId(String streamId) { - this.streamId = streamId; + public void setStream(String stream) { + this.stream = stream; } public boolean isTcp() { @@ -292,4 +297,12 @@ public class SendRtpItem { public void setRtcp(boolean rtcp) { this.rtcp = rtcp; } + + public String getReceiveStream() { + return receiveStream; + } + + public void setReceiveStream(String receiveStream) { + this.receiveStream = receiveStream; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipMsgInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipMsgInfo.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java old mode 100644 new mode 100755 index c04a6959a..885843980 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SipTransactionInfo.java @@ -1,6 +1,5 @@ package com.genersoft.iot.vmp.gb28181.bean; -import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; public class SipTransactionInfo { @@ -10,11 +9,23 @@ public class SipTransactionInfo { private String toTag; private String viaBranch; + // 自己是否媒体流发送者 + private boolean asSender; + + public SipTransactionInfo(SIPResponse response, boolean asSender) { + this.callId = response.getCallIdHeader().getCallId(); + this.fromTag = response.getFromTag(); + this.toTag = response.getToTag(); + this.viaBranch = response.getTopmostViaHeader().getBranch(); + this.asSender = asSender; + } + public SipTransactionInfo(SIPResponse response) { this.callId = response.getCallIdHeader().getCallId(); this.fromTag = response.getFromTag(); this.toTag = response.getToTag(); this.viaBranch = response.getTopmostViaHeader().getBranch(); + this.asSender = false; } public SipTransactionInfo() { @@ -51,4 +62,12 @@ public class SipTransactionInfo { public void setViaBranch(String viaBranch) { this.viaBranch = viaBranch; } + + public boolean isAsSender() { + return asSender; + } + + public void setAsSender(boolean asSender) { + this.asSender = asSender; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java old mode 100644 new mode 100755 index ba905b50b..e7b7ab800 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeHolder.java @@ -2,12 +2,9 @@ package com.genersoft.iot.vmp.gb28181.bean; 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.task.ISubscribeTask; import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeHandlerTask; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; -import com.genersoft.iot.vmp.service.IPlatformService; -import com.genersoft.iot.vmp.storager.IRedisCatchStorage; -import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -24,6 +21,9 @@ public class SubscribeHolder { @Autowired private DynamicTask dynamicTask; + @Autowired + private UserSetting userSetting; + private final String taskOverduePrefix = "subscribe_overdue_"; private static ConcurrentHashMap catalogMap = new ConcurrentHashMap<>(); @@ -32,11 +32,13 @@ public class SubscribeHolder { public void putCatalogSubscribe(String platformId, SubscribeInfo subscribeInfo) { catalogMap.put(platformId, subscribeInfo); - // 添加订阅到期 - String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId; - // 添加任务处理订阅过期 - dynamicTask.startDelay(taskOverdueKey, () -> removeCatalogSubscribe(subscribeInfo.getId()), - subscribeInfo.getExpires() * 1000); + if (subscribeInfo.getExpires() > 0) { + // 添加订阅到期 + String taskOverdueKey = taskOverduePrefix + "catalog_" + platformId; + // 添加任务处理订阅过期 + dynamicTask.startDelay(taskOverdueKey, () -> removeCatalogSubscribe(subscribeInfo.getId()), + subscribeInfo.getExpires() * 1000); + } } public SubscribeInfo getCatalogSubscribe(String platformId) { @@ -50,7 +52,7 @@ public class SubscribeHolder { Runnable runnable = dynamicTask.get(taskOverdueKey); if (runnable instanceof ISubscribeTask) { ISubscribeTask subscribeTask = (ISubscribeTask) runnable; - subscribeTask.stop(); + subscribeTask.stop(null); } // 添加任务处理订阅过期 dynamicTask.stop(taskOverdueKey); @@ -58,16 +60,18 @@ public class SubscribeHolder { public void putMobilePositionSubscribe(String platformId, SubscribeInfo subscribeInfo) { mobilePositionMap.put(platformId, subscribeInfo); - String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + "MobilePosition_" + platformId; + String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetting.getServerId() + "MobilePosition_" + platformId; // 添加任务处理GPS定时推送 dynamicTask.startCron(key, new MobilePositionSubscribeHandlerTask(platformId), subscribeInfo.getGpsInterval() * 1000); String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId; - // 添加任务处理订阅过期 - dynamicTask.startDelay(taskOverdueKey, () -> { - removeMobilePositionSubscribe(subscribeInfo.getId()); - }, - subscribeInfo.getExpires() * 1000); + if (subscribeInfo.getExpires() > 0) { + // 添加任务处理订阅过期 + dynamicTask.startDelay(taskOverdueKey, () -> { + removeMobilePositionSubscribe(subscribeInfo.getId()); + }, + subscribeInfo.getExpires() * 1000); + } } public SubscribeInfo getMobilePositionSubscribe(String platformId) { @@ -76,14 +80,14 @@ public class SubscribeHolder { public void removeMobilePositionSubscribe(String platformId) { mobilePositionMap.remove(platformId); - String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + "MobilePosition_" + platformId; + String key = VideoManagerConstants.SIP_SUBSCRIBE_PREFIX + userSetting.getServerId() + "MobilePosition_" + platformId; // 结束任务处理GPS定时推送 dynamicTask.stop(key); String taskOverdueKey = taskOverduePrefix + "MobilePosition_" + platformId; Runnable runnable = dynamicTask.get(taskOverdueKey); if (runnable instanceof ISubscribeTask) { ISubscribeTask subscribeTask = (ISubscribeTask) runnable; - subscribeTask.stop(); + subscribeTask.stop(null); } // 添加任务处理订阅过期 dynamicTask.stop(taskOverdueKey); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java old mode 100644 new mode 100755 index 07176f210..e5c504559 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SubscribeInfo.java @@ -18,6 +18,9 @@ public class SubscribeInfo { } + public SubscribeInfo() { + } + private String id; private SIPRequest request; @@ -33,6 +36,21 @@ public class SubscribeInfo { private String sn; private int gpsInterval; + /** + * 模拟的FromTag + */ + private String simulatedFromTag; + + /** + * 模拟的ToTag + */ + private String simulatedToTag; + + /** + * 模拟的CallID + */ + private String simulatedCallId; + public String getId() { return id; } @@ -96,4 +114,28 @@ public class SubscribeInfo { public void setGpsInterval(int gpsInterval) { this.gpsInterval = gpsInterval; } + + public String getSimulatedFromTag() { + return simulatedFromTag; + } + + public void setSimulatedFromTag(String simulatedFromTag) { + this.simulatedFromTag = simulatedFromTag; + } + + public String getSimulatedCallId() { + return simulatedCallId; + } + + public void setSimulatedCallId(String simulatedCallId) { + this.simulatedCallId = simulatedCallId; + } + + public String getSimulatedToTag() { + return simulatedToTag; + } + + public void setSimulatedToTag(String simulatedToTag) { + this.simulatedToTag = simulatedToTag; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SyncStatus.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SyncStatus.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/conf/DefaultProperties.java b/src/main/java/com/genersoft/iot/vmp/gb28181/conf/DefaultProperties.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/conf/ServerLoggerImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/conf/ServerLoggerImpl.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/conf/StackLoggerImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/conf/StackLoggerImpl.java old mode 100644 new mode 100755 index 5e67bdba1..bab02856a --- a/src/main/java/com/genersoft/iot/vmp/gb28181/conf/StackLoggerImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/conf/StackLoggerImpl.java @@ -1,8 +1,8 @@ package com.genersoft.iot.vmp.gb28181.conf; import gov.nist.core.StackLogger; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.spi.LocationAwareLogger; import org.springframework.stereotype.Component; import java.util.Properties; @@ -10,100 +10,132 @@ import java.util.Properties; @Component public class StackLoggerImpl implements StackLogger { - private final static Logger logger = LoggerFactory.getLogger(StackLoggerImpl.class); + /** + * 完全限定类名(Fully Qualified Class Name),用于定位日志位置 + */ + private static final String FQCN = StackLoggerImpl.class.getName(); - @Override - public void logStackTrace() { + /** + * 获取栈中类信息(以便底层日志记录系统能够提取正确的位置信息(方法名、行号)) + * @return LocationAwareLogger + */ + private static LocationAwareLogger getLocationAwareLogger() { + return (LocationAwareLogger) LoggerFactory.getLogger(new Throwable().getStackTrace()[4].getClassName()); + } - } - @Override - public void logStackTrace(int traceLevel) { - System.out.println("traceLevel: " + traceLevel); - } + /** + * 封装打印日志的位置信息 + * @param level 日志级别 + * @param message 日志事件的消息 + */ + private static void log(int level, String message) { + LocationAwareLogger locationAwareLogger = getLocationAwareLogger(); + locationAwareLogger.log(null, FQCN, level, message, null, null); + } - @Override - public int getLineCount() { - return 0; - } + /** + * 封装打印日志的位置信息 + * @param level 日志级别 + * @param message 日志事件的消息 + */ + private static void log(int level, String message, Throwable throwable) { + LocationAwareLogger locationAwareLogger = getLocationAwareLogger(); + locationAwareLogger.log(null, FQCN, level, message, null, throwable); + } - @Override - public void logException(Throwable ex) { + @Override + public void logStackTrace() { - } + } - @Override - public void logDebug(String message) { -// logger.debug(message); - } + @Override + public void logStackTrace(int traceLevel) { + System.out.println("traceLevel: " + traceLevel); + } - @Override - public void logDebug(String message, Exception ex) { -// logger.debug(message); - } + @Override + public int getLineCount() { + return 0; + } - @Override - public void logTrace(String message) { - logger.trace(message); - } + @Override + public void logException(Throwable ex) { - @Override - public void logFatalError(String message) { -// logger.error(message); - } + } - @Override - public void logError(String message) { -// logger.error(message); - } + @Override + public void logDebug(String message) { + log(LocationAwareLogger.INFO_INT, message); + } - @Override - public boolean isLoggingEnabled() { - return true; - } + @Override + public void logDebug(String message, Exception ex) { + log(LocationAwareLogger.INFO_INT, message, ex); + } - @Override - public boolean isLoggingEnabled(int logLevel) { - return true; - } + @Override + public void logTrace(String message) { + log(LocationAwareLogger.INFO_INT, message); + } - @Override - public void logError(String message, Exception ex) { -// logger.error(message); - } + @Override + public void logFatalError(String message) { + log(LocationAwareLogger.INFO_INT, message); + } - @Override - public void logWarning(String message) { - logger.warn(message); - } + @Override + public void logError(String message) { + log(LocationAwareLogger.INFO_INT, message); + } - @Override - public void logInfo(String message) { - logger.info(message); - } + @Override + public boolean isLoggingEnabled() { + return true; + } - @Override - public void disableLogging() { + @Override + public boolean isLoggingEnabled(int logLevel) { + return true; + } - } + @Override + public void logError(String message, Exception ex) { + log(LocationAwareLogger.INFO_INT, message, ex); + } - @Override - public void enableLogging() { + @Override + public void logWarning(String message) { + log(LocationAwareLogger.INFO_INT, message); + } - } + @Override + public void logInfo(String message) { + log(LocationAwareLogger.INFO_INT, message); + } - @Override - public void setBuildTimeStamp(String buildTimeStamp) { + @Override + public void disableLogging() { - } + } - @Override - public void setStackProperties(Properties stackProperties) { + @Override + public void enableLogging() { - } + } - @Override - public String getLoggerName() { - return null; - } + @Override + public void setBuildTimeStamp(String buildTimeStamp) { + + } + + @Override + public void setStackProperties(Properties stackProperties) { + + } + + @Override + public String getLoggerName() { + return null; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java old mode 100644 new mode 100755 index 26ababd4c..d56e744da --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/EventPublisher.java @@ -1,118 +1,118 @@ -package com.genersoft.iot.vmp.gb28181.event; - -import com.genersoft.iot.vmp.gb28181.bean.*; -import com.genersoft.iot.vmp.gb28181.event.device.RequestTimeoutEvent; -import com.genersoft.iot.vmp.gb28181.event.record.RecordEndEvent; -import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; -import com.genersoft.iot.vmp.media.zlm.event.ZLMOfflineEvent; -import com.genersoft.iot.vmp.media.zlm.event.ZLMOnlineEvent; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Component; - -import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEvent; - -import javax.sip.TimeoutEvent; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * @description:Event事件通知推送器,支持推送在线事件、离线事件 - * @author: swwheihei - * @date: 2020年5月6日 上午11:30:50 - */ -@Component -public class EventPublisher { - - @Autowired - private ApplicationEventPublisher applicationEventPublisher; - - /** - * 设备报警事件 - * @param deviceAlarm - */ - public void deviceAlarmEventPublish(DeviceAlarm deviceAlarm) { - AlarmEvent alarmEvent = new AlarmEvent(this); - alarmEvent.setAlarmInfo(deviceAlarm); - applicationEventPublisher.publishEvent(alarmEvent); - } - - public void zlmOfflineEventPublish(String mediaServerId){ - ZLMOfflineEvent outEvent = new ZLMOfflineEvent(this); - outEvent.setMediaServerId(mediaServerId); - applicationEventPublisher.publishEvent(outEvent); - } - - public void zlmOnlineEventPublish(String mediaServerId) { - ZLMOnlineEvent outEvent = new ZLMOnlineEvent(this); - outEvent.setMediaServerId(mediaServerId); - applicationEventPublisher.publishEvent(outEvent); - } - - - public void catalogEventPublish(String platformId, DeviceChannel deviceChannel, String type) { - List deviceChannelList = new ArrayList<>(); - deviceChannelList.add(deviceChannel); - catalogEventPublish(platformId, deviceChannelList, type); - } - - - public void requestTimeOut(TimeoutEvent timeoutEvent) { - RequestTimeoutEvent requestTimeoutEvent = new RequestTimeoutEvent(this); - requestTimeoutEvent.setTimeoutEvent(timeoutEvent); - applicationEventPublisher.publishEvent(requestTimeoutEvent); - } - - - /** - * - * @param platformId - * @param deviceChannels - * @param type - */ - public void catalogEventPublish(String platformId, List deviceChannels, String type) { - CatalogEvent outEvent = new CatalogEvent(this); - List channels = new ArrayList<>(); - if (deviceChannels.size() > 1) { - // 数据去重 - Set gbIdSet = new HashSet<>(); - for (DeviceChannel deviceChannel : deviceChannels) { - if (!gbIdSet.contains(deviceChannel.getChannelId())) { - gbIdSet.add(deviceChannel.getChannelId()); - channels.add(deviceChannel); - } - } - }else { - channels = deviceChannels; - } - outEvent.setDeviceChannels(channels); - outEvent.setType(type); - outEvent.setPlatformId(platformId); - applicationEventPublisher.publishEvent(outEvent); - } - - - public void catalogEventPublishForStream(String platformId, List gbStreams, String type) { - CatalogEvent outEvent = new CatalogEvent(this); - outEvent.setGbStreams(gbStreams); - outEvent.setType(type); - outEvent.setPlatformId(platformId); - applicationEventPublisher.publishEvent(outEvent); - } - - - public void catalogEventPublishForStream(String platformId, GbStream gbStream, String type) { - List gbStreamList = new ArrayList<>(); - gbStreamList.add(gbStream); - catalogEventPublishForStream(platformId, gbStreamList, type); - } - - public void recordEndEventPush(RecordInfo recordInfo) { - RecordEndEvent outEvent = new RecordEndEvent(this); - outEvent.setRecordInfo(recordInfo); - applicationEventPublisher.publishEvent(outEvent); - } - -} +package com.genersoft.iot.vmp.gb28181.event; + +import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.gb28181.event.device.RequestTimeoutEvent; +import com.genersoft.iot.vmp.gb28181.event.record.RecordEndEvent; +import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; +import com.genersoft.iot.vmp.media.zlm.event.ZLMOfflineEvent; +import com.genersoft.iot.vmp.media.zlm.event.ZLMOnlineEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +import com.genersoft.iot.vmp.gb28181.event.alarm.AlarmEvent; + +import javax.sip.TimeoutEvent; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @description:Event事件通知推送器,支持推送在线事件、离线事件 + * @author: swwheihei + * @date: 2020年5月6日 上午11:30:50 + */ +@Component +public class EventPublisher { + + @Autowired + private ApplicationEventPublisher applicationEventPublisher; + + /** + * 设备报警事件 + * @param deviceAlarm + */ + public void deviceAlarmEventPublish(DeviceAlarm deviceAlarm) { + AlarmEvent alarmEvent = new AlarmEvent(this); + alarmEvent.setAlarmInfo(deviceAlarm); + applicationEventPublisher.publishEvent(alarmEvent); + } + + public void zlmOfflineEventPublish(String mediaServerId){ + ZLMOfflineEvent outEvent = new ZLMOfflineEvent(this); + outEvent.setMediaServerId(mediaServerId); + applicationEventPublisher.publishEvent(outEvent); + } + + public void zlmOnlineEventPublish(String mediaServerId) { + ZLMOnlineEvent outEvent = new ZLMOnlineEvent(this); + outEvent.setMediaServerId(mediaServerId); + applicationEventPublisher.publishEvent(outEvent); + } + + + public void catalogEventPublish(String platformId, DeviceChannel deviceChannel, String type) { + List deviceChannelList = new ArrayList<>(); + deviceChannelList.add(deviceChannel); + catalogEventPublish(platformId, deviceChannelList, type); + } + + + public void requestTimeOut(TimeoutEvent timeoutEvent) { + RequestTimeoutEvent requestTimeoutEvent = new RequestTimeoutEvent(this); + requestTimeoutEvent.setTimeoutEvent(timeoutEvent); + applicationEventPublisher.publishEvent(requestTimeoutEvent); + } + + + /** + * + * @param platformId + * @param deviceChannels + * @param type + */ + public void catalogEventPublish(String platformId, List deviceChannels, String type) { + CatalogEvent outEvent = new CatalogEvent(this); + List channels = new ArrayList<>(); + if (deviceChannels.size() > 1) { + // 数据去重 + Set gbIdSet = new HashSet<>(); + for (DeviceChannel deviceChannel : deviceChannels) { + if (!gbIdSet.contains(deviceChannel.getChannelId())) { + gbIdSet.add(deviceChannel.getChannelId()); + channels.add(deviceChannel); + } + } + }else { + channels = deviceChannels; + } + outEvent.setDeviceChannels(channels); + outEvent.setType(type); + outEvent.setPlatformId(platformId); + applicationEventPublisher.publishEvent(outEvent); + } + + + public void catalogEventPublishForStream(String platformId, List gbStreams, String type) { + CatalogEvent outEvent = new CatalogEvent(this); + outEvent.setGbStreams(gbStreams); + outEvent.setType(type); + outEvent.setPlatformId(platformId); + applicationEventPublisher.publishEvent(outEvent); + } + + + public void catalogEventPublishForStream(String platformId, GbStream gbStream, String type) { + List gbStreamList = new ArrayList<>(); + gbStreamList.add(gbStream); + catalogEventPublishForStream(platformId, gbStreamList, type); + } + + public void recordEndEventPush(RecordInfo recordInfo) { + RecordEndEvent outEvent = new RecordEndEvent(this); + outEvent.setRecordInfo(recordInfo); + applicationEventPublisher.publishEvent(outEvent); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java old mode 100644 new mode 100755 index 75751ad2e..e07761a91 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java @@ -2,6 +2,8 @@ package com.genersoft.iot.vmp.gb28181.event; import com.genersoft.iot.vmp.gb28181.bean.DeviceNotFoundEvent; import gov.nist.javax.sip.message.SIPRequest; +import gov.nist.javax.sip.message.SIPResponse; +import org.apache.commons.lang3.ObjectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; @@ -11,8 +13,7 @@ import javax.sip.DialogTerminatedEvent; import javax.sip.ResponseEvent; import javax.sip.TimeoutEvent; import javax.sip.TransactionTerminatedEvent; -import javax.sip.header.CallIdHeader; -import javax.sip.message.Response; +import javax.sip.header.WarningHeader; import java.time.Instant; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -60,7 +61,7 @@ public class SipSubscribe { logger.debug("errorSubscribes.size:{}",errorSubscribes.size()); } - public interface Event { void response(EventResult eventResult) ; + public interface Event { void response(EventResult eventResult); } /** @@ -77,8 +78,10 @@ public class SipSubscribe { dialogTerminated, // 设备未找到 deviceNotFoundEvent, - // 设备未找到 - cmdSendFailEvent + // 消息发送失败 + cmdSendFailEvent, + // 消息发送失败 + failedToGetPort } public static class EventResult{ @@ -95,14 +98,27 @@ public class SipSubscribe { this.event = event; if (event instanceof ResponseEvent) { ResponseEvent responseEvent = (ResponseEvent)event; - Response response = responseEvent.getResponse(); + SIPResponse response = (SIPResponse)responseEvent.getResponse(); this.type = EventResultType.response; if (response != null) { - this.msg = response.getReasonPhrase(); + WarningHeader warningHeader = (WarningHeader)response.getHeader(WarningHeader.NAME); + if (warningHeader != null && !ObjectUtils.isEmpty(warningHeader.getText())) { + this.msg = ""; + if (warningHeader.getCode() > 0) { + this.msg += warningHeader.getCode() + ":"; + } + if (warningHeader.getAgent() != null) { + this.msg += warningHeader.getCode() + ":"; + } + if (warningHeader.getText() != null) { + this.msg += warningHeader.getText(); + } + }else { + this.msg = response.getReasonPhrase(); + } this.statusCode = response.getStatusCode(); + this.callId = response.getCallIdHeader().getCallId(); } - this.callId = ((CallIdHeader)response.getHeader(CallIdHeader.NAME)).getCallId(); - }else if (event instanceof TimeoutEvent) { TimeoutEvent timeoutEvent = (TimeoutEvent)event; this.type = EventResultType.timeout; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEvent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEvent.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEventListener.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEventListener.java old mode 100644 new mode 100755 index 9ee647731..aef590763 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEventListener.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/alarm/AlarmEventListener.java @@ -1,55 +1,68 @@ package com.genersoft.iot.vmp.gb28181.event.alarm; -import org.springframework.context.ApplicationListener; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import java.io.IOException; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.Map; - +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** - * @description: 报警事件监听 - * @author: lawrencehj - * @data: 2021-01-20 + * 报警事件监听器. + * + * @author lawrencehj + * @author xiaoQQya + * @since 2021/01/20 */ - @Component public class AlarmEventListener implements ApplicationListener { - private final static Logger logger = LoggerFactory.getLogger(AlarmEventListener.class); + private static final Logger logger = LoggerFactory.getLogger(AlarmEventListener.class); - private static Map sseEmitters = new Hashtable<>(); + private static final Map SSE_CACHE = new ConcurrentHashMap<>(); - public void addSseEmitters(String browserId, SseEmitter sseEmitter) { - sseEmitters.put(browserId, sseEmitter); + public void addSseEmitter(String browserId, PrintWriter writer) { + SSE_CACHE.put(browserId, writer); + logger.info("SSE 在线数量: {}", SSE_CACHE.size()); + } + + public void removeSseEmitter(String browserId, PrintWriter writer) { + SSE_CACHE.remove(browserId, writer); + logger.info("SSE 在线数量: {}", SSE_CACHE.size()); } @Override - public void onApplicationEvent(AlarmEvent event) { + public void onApplicationEvent(@NotNull AlarmEvent event) { if (logger.isDebugEnabled()) { - logger.debug("设备报警事件触发,deviceId:" + event.getAlarmInfo().getDeviceId() + ", " - + event.getAlarmInfo().getAlarmDescription()); + logger.debug("设备报警事件触发, deviceId: {}, {}", event.getAlarmInfo().getDeviceId(), event.getAlarmInfo().getAlarmDescription()); } - String msg = "设备编码: " + event.getAlarmInfo().getDeviceId() + "" - + "
报警描述: " + event.getAlarmInfo().getAlarmDescription() + "" - + "
报警时间: " + event.getAlarmInfo().getAlarmTime() + "" - + "
报警位置: " + event.getAlarmInfo().getLongitude() + "" - + ", " + event.getAlarmInfo().getLatitude() + ""; - for (Iterator> it = sseEmitters.entrySet().iterator(); it.hasNext();) { - Map.Entry emitter = it.next(); - logger.info("推送到SSE连接,浏览器ID: " + emitter.getKey()); + String msg = "设备编号: " + event.getAlarmInfo().getDeviceId() + "" + + "
通道编号: " + event.getAlarmInfo().getChannelId() + "" + + "
报警描述: " + event.getAlarmInfo().getAlarmDescription() + "" + + "
报警时间: " + event.getAlarmInfo().getAlarmTime() + ""; + + for (Iterator> it = SSE_CACHE.entrySet().iterator(); it.hasNext(); ) { + Map.Entry response = it.next(); + logger.info("推送到 SSE 连接, 浏览器 ID: {}", response.getKey()); try { - emitter.getValue().send(msg); - } catch (IOException | IllegalStateException e) { - if (logger.isDebugEnabled()) { - logger.debug("SSE连接已关闭"); + PrintWriter writer = response.getValue(); + + if (writer.checkError()) { + it.remove(); + continue; } - // 移除已关闭的连接 + + String sseMsg = "event:message\n" + + "data:" + msg + "\n" + + "\n"; + writer.write(sseMsg); + writer.flush(); + } catch (Exception e) { it.remove(); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEvent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEvent.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEventImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/device/RequestTimeoutEventImpl.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEvent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEvent.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java old mode 100644 new mode 100755 index cb4682304..167b9f201 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/record/RecordEndEventListener.java @@ -35,7 +35,7 @@ public class RecordEndEventListener implements ApplicationListener 0) { + if (!handlerMap.isEmpty()) { RecordEndEventHandler handler = handlerMap.get(deviceId + channelId); if (handler !=null){ handler.handler(event.getRecordInfo()); @@ -43,6 +43,9 @@ public class RecordEndEventListener implements ApplicationListener { } if (event.getGbStreams() != null && event.getGbStreams().size() > 0){ for (GbStream gbStream : event.getGbStreams()) { - if (gbStream.getStreamType().equals("push") && !userSetting.isUsePushingAsStatus()) { + if (gbStream != null + && gbStream.getStreamType() != null + && gbStream.getStreamType().equals("push") + && !userSetting.isUsePushingAsStatus()) { continue; } DeviceChannel deviceChannelByStream = gbStreamService.getDeviceChannelListByStream(gbStream, gbStream.getCatalogId(), parentPlatform); @@ -145,13 +148,13 @@ public class CatalogEventLister implements ApplicationListener { if (event.getDeviceChannels() != null) { deviceChannelList.addAll(event.getDeviceChannels()); } - if (event.getGbStreams() != null && event.getGbStreams().size() > 0){ + if (event.getGbStreams() != null && !event.getGbStreams().isEmpty()){ for (GbStream gbStream : event.getGbStreams()) { deviceChannelList.add( gbStreamService.getDeviceChannelListByStreamWithStatus(gbStream, gbStream.getCatalogId(), parentPlatform)); } } - if (deviceChannelList.size() > 0) { + if (!deviceChannelList.isEmpty()) { logger.info("[Catalog事件: {}]平台:{},影响通道{}个", event.getType(), event.getPlatformId(), deviceChannelList.size()); try { sipCommanderFroPlatform.sendNotifyForCatalogAddOrUpdate(event.getType(), parentPlatform, deviceChannelList, subscribe, null); @@ -160,10 +163,10 @@ public class CatalogEventLister implements ApplicationListener { logger.error("[命令发送失败] 国标级联 Catalog通知: {}", e.getMessage()); } } - }else if (parentPlatformMap.keySet().size() > 0) { + }else if (!parentPlatformMap.keySet().isEmpty()) { for (String gbId : parentPlatformMap.keySet()) { List parentPlatforms = parentPlatformMap.get(gbId); - if (parentPlatforms != null && parentPlatforms.size() > 0) { + if (parentPlatforms != null && !parentPlatforms.isEmpty()) { for (ParentPlatform platform : parentPlatforms) { SubscribeInfo subscribeInfo = subscribeHolder.getCatalogSubscribe(platform.getServerGBId()); if (subscribeInfo == null) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java new file mode 100644 index 000000000..03aa7497c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/AudioBroadcastManager.java @@ -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 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 getAll(){ + Collection 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 allAudioBroadcastCatchStreamForDevice = data.values().stream().filter( + audioBroadcastCatchItem -> Objects.equals(audioBroadcastCatchItem.getDeviceId(), deviceId)); + List audioBroadcastCatchList = allAudioBroadcastCatchStreamForDevice.collect(Collectors.toList()); + if (audioBroadcastCatchList.size() == 1 && Objects.equals(config.getId(), channelId)) { + audioBroadcastCatch = audioBroadcastCatchList.get(0); + } + } + + return audioBroadcastCatch; + } + + public List get(String deviceId) { + List 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; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/CatalogDataCatch.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/CommonSessionManager.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/CommonSessionManager.java new file mode 100755 index 000000000..2d8c7e171 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/CommonSessionManager.java @@ -0,0 +1,86 @@ +package com.genersoft.iot.vmp.gb28181.session; + +import com.genersoft.iot.vmp.common.CommonCallback; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Calendar; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 通用回调管理 + */ +@Component +public class CommonSessionManager { + + public static Map callbackMap = new ConcurrentHashMap<>(); + + /** + * 存储回调相关的信息 + */ + class CommonSession{ + public String session; + public long createTime; + public int timeout; + + public CommonCallback callback; + public CommonCallback timeoutCallback; + } + + /** + * 添加回调 + * @param sessionId 唯一标识 + * @param callback 回调 + * @param timeout 超时时间, 单位分钟 + */ + public void add(String sessionId, CommonCallback callback, CommonCallback timeoutCallback, + Integer timeout) { + CommonSession commonSession = new CommonSession(); + commonSession.session = sessionId; + commonSession.callback = callback; + commonSession.createTime = System.currentTimeMillis(); + if (timeoutCallback != null) { + commonSession.timeoutCallback = timeoutCallback; + } + if (timeout != null) { + commonSession.timeout = timeout; + } + callbackMap.put(sessionId, commonSession); + } + + public void add(String sessionId, CommonCallback callback) { + add(sessionId, callback, null, 1); + } + + public CommonCallback get(String sessionId, boolean destroy) { + CommonSession commonSession = callbackMap.get(sessionId); + if (destroy) { + callbackMap.remove(sessionId); + } + return commonSession.callback; + } + + public CommonCallback get(String sessionId) { + return get(sessionId, false); + } + + public void delete(String sessionID) { + callbackMap.remove(sessionID); + } + + @Scheduled(fixedRate= 60) //每分钟执行一次 + public void execute(){ + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MINUTE, -1); + for (String session : callbackMap.keySet()) { + if (callbackMap.get(session).createTime < cal.getTimeInMillis()) { + // 超时 + if (callbackMap.get(session).timeoutCallback != null) { + callbackMap.get(session).timeoutCallback.run("timeout"); + } + callbackMap.remove(session); + } + } + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/RecordDataCatch.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java old mode 100644 new mode 100755 index 657bb2fa9..2422b8a76 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java @@ -8,6 +8,7 @@ import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -37,7 +38,8 @@ public class SSRCFactory { public void initMediaServerSSRC(String mediaServerId, Set usedSet) { - String ssrcPrefix = sipConfig.getDomain().substring(3, 8); + String sipDomain = sipConfig.getDomain(); + String ssrcPrefix = sipDomain.length() >= 8 ? sipDomain.substring(3, 8) : sipDomain; String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId; List ssrcList = new ArrayList<>(); for (int i = 1; i < MAX_STREAM_COUNT; i++) { @@ -118,7 +120,7 @@ public class SSRCFactory { */ public boolean hasMediaServerSSRC(String mediaServerId) { String redisKey = SSRC_INFO_KEY + userSetting.getServerId() + "_" + mediaServerId; - return redisTemplate.opsForSet().members(redisKey) != null; + return Boolean.TRUE.equals(redisTemplate.hasKey(redisKey)); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java old mode 100644 new mode 100755 index a5da0186b..24d4ef326 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/VideoStreamSessionManager.java @@ -1,139 +1,177 @@ -package com.genersoft.iot.vmp.gb28181.session; - -import com.genersoft.iot.vmp.common.InviteSessionType; -import com.genersoft.iot.vmp.common.VideoManagerConstants; -import com.genersoft.iot.vmp.conf.UserSetting; -import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; -import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; -import com.genersoft.iot.vmp.utils.JsonUtil; -import com.genersoft.iot.vmp.utils.redis.RedisUtil; -import gov.nist.javax.sip.message.SIPResponse; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Component; -import org.springframework.util.ObjectUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * 视频流session管理器,管理视频预览、预览回放的通信句柄 - */ -@Component -public class VideoStreamSessionManager { - - @Autowired - private UserSetting userSetting; - - @Autowired - private RedisTemplate redisTemplate; - - /** - * 添加一个点播/回放的事务信息 - * 后续可以通过流Id/callID - * @param deviceId 设备ID - * @param channelId 通道ID - * @param callId 一次请求的CallID - * @param stream 流名称 - * @param mediaServerId 所使用的流媒体ID - * @param response 回复 - */ - public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, SIPResponse response, InviteSessionType type){ - SsrcTransaction ssrcTransaction = new SsrcTransaction(); - ssrcTransaction.setDeviceId(deviceId); - ssrcTransaction.setChannelId(channelId); - ssrcTransaction.setStream(stream); - ssrcTransaction.setSipTransactionInfo(new SipTransactionInfo(response)); - ssrcTransaction.setCallId(callId); - ssrcTransaction.setSsrc(ssrc); - ssrcTransaction.setMediaServerId(mediaServerId); - ssrcTransaction.setType(type); - - redisTemplate.opsForValue().set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() - + "_" + deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction); - } - - public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){ - - if (ObjectUtils.isEmpty(deviceId)) { - deviceId ="*"; - } - if (ObjectUtils.isEmpty(channelId)) { - channelId ="*"; - } - if (ObjectUtils.isEmpty(callId)) { - callId ="*"; - } - if (ObjectUtils.isEmpty(stream)) { - stream ="*"; - } - String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream; - List scanResult = RedisUtil.scan(redisTemplate, key); - if (scanResult.size() == 0) { - return null; - } - return (SsrcTransaction)redisTemplate.opsForValue().get(scanResult.get(0)); - } - - public List getSsrcTransactionForAll(String deviceId, String channelId, String callId, String stream){ - if (ObjectUtils.isEmpty(deviceId)) { - deviceId ="*"; - } - if (ObjectUtils.isEmpty(channelId)) { - channelId ="*"; - } - if (ObjectUtils.isEmpty(callId)) { - callId ="*"; - } - if (ObjectUtils.isEmpty(stream)) { - stream ="*"; - } - String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream; - List scanResult = RedisUtil.scan(redisTemplate, key); - if (scanResult.size() == 0) { - return null; - } - List result = new ArrayList<>(); - for (Object keyObj : scanResult) { - result.add((SsrcTransaction)redisTemplate.opsForValue().get(keyObj)); - } - return result; - } - - public String getMediaServerId(String deviceId, String channelId, String stream){ - SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream); - if (ssrcTransaction == null) { - return null; - } - return ssrcTransaction.getMediaServerId(); - } - - public String getSSRC(String deviceId, String channelId, String stream){ - SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream); - if (ssrcTransaction == null) { - return null; - } - return ssrcTransaction.getSsrc(); - } - - public void remove(String deviceId, String channelId, String stream) { - SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream); - if (ssrcTransaction == null) { - return; - } - redisTemplate.delete(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" - + deviceId + "_" + channelId + "_" + ssrcTransaction.getCallId() + "_" + ssrcTransaction.getStream()); - } - - - public List getAllSsrc() { - List ssrcTransactionKeys = RedisUtil.scan(redisTemplate, String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId())); - List result= new ArrayList<>(); - for (Object ssrcTransactionKey : ssrcTransactionKeys) { - String key = (String) ssrcTransactionKey; - SsrcTransaction ssrcTransaction = JsonUtil.redisJsonToObject(redisTemplate, key, SsrcTransaction.class); - result.add(ssrcTransaction); - } - return result; - } -} +package com.genersoft.iot.vmp.gb28181.session; + +import com.genersoft.iot.vmp.common.InviteSessionType; +import com.genersoft.iot.vmp.common.VideoManagerConstants; +import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; +import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; +import com.genersoft.iot.vmp.utils.JsonUtil; +import com.genersoft.iot.vmp.utils.redis.RedisUtil; +import gov.nist.javax.sip.message.SIPResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 视频流session管理器,管理视频预览、预览回放的通信句柄 + */ +@Component +public class VideoStreamSessionManager { + + @Autowired + private UserSetting userSetting; + + @Autowired + private RedisTemplate redisTemplate; + + /** + * 添加一个点播/回放的事务信息 + * 后续可以通过流Id/callID + * @param deviceId 设备ID + * @param channelId 通道ID + * @param callId 一次请求的CallID + * @param stream 流名称 + * @param mediaServerId 所使用的流媒体ID + * @param response 回复 + */ + public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, SIPResponse response, InviteSessionType type){ + SsrcTransaction ssrcTransaction = new SsrcTransaction(); + ssrcTransaction.setDeviceId(deviceId); + ssrcTransaction.setChannelId(channelId); + ssrcTransaction.setStream(stream); + ssrcTransaction.setSipTransactionInfo(new SipTransactionInfo(response)); + ssrcTransaction.setCallId(callId); + ssrcTransaction.setSsrc(ssrc); + ssrcTransaction.setMediaServerId(mediaServerId); + ssrcTransaction.setType(type); + + redisTemplate.opsForValue().set(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + + "_" + deviceId + "_" + channelId + "_" + callId + "_" + stream, ssrcTransaction); + } + + public SsrcTransaction getSsrcTransaction(String deviceId, String channelId, String callId, String stream){ + + if (ObjectUtils.isEmpty(deviceId)) { + deviceId ="*"; + } + if (ObjectUtils.isEmpty(channelId)) { + channelId ="*"; + } + if (ObjectUtils.isEmpty(callId)) { + callId ="*"; + } + if (ObjectUtils.isEmpty(stream)) { + stream ="*"; + } + String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream; + List scanResult = RedisUtil.scan(redisTemplate, key); + if (scanResult.size() == 0) { + return null; + } + return (SsrcTransaction)redisTemplate.opsForValue().get(scanResult.get(0)); + } + + public SsrcTransaction getSsrcTransactionByCallId(String callId){ + + if (ObjectUtils.isEmpty(callId)) { + return null; + } + String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_*_*_" + callId+ "_*"; + List scanResult = RedisUtil.scan(redisTemplate, key); + if (!scanResult.isEmpty()) { + return (SsrcTransaction)redisTemplate.opsForValue().get(scanResult.get(0)); + }else { + key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_*_*_play_*"; + scanResult = RedisUtil.scan(redisTemplate, key); + if (scanResult.isEmpty()) { + return null; + } + for (Object keyObj : scanResult) { + SsrcTransaction ssrcTransaction = (SsrcTransaction)redisTemplate.opsForValue().get(keyObj); + if (ssrcTransaction.getSipTransactionInfo() != null && + ssrcTransaction.getSipTransactionInfo().getCallId().equals(callId)) { + return ssrcTransaction; + } + } + return null; + } + + } + + public List getSsrcTransactionForAll(String deviceId, String channelId, String callId, String stream){ + if (ObjectUtils.isEmpty(deviceId)) { + deviceId ="*"; + } + if (ObjectUtils.isEmpty(channelId)) { + channelId ="*"; + } + if (ObjectUtils.isEmpty(callId)) { + callId ="*"; + } + if (ObjectUtils.isEmpty(stream)) { + stream ="*"; + } + String key = VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + deviceId + "_" + channelId + "_" + callId+ "_" + stream; + List scanResult = RedisUtil.scan(redisTemplate, key); + if (scanResult.size() == 0) { + return null; + } + List result = new ArrayList<>(); + for (Object keyObj : scanResult) { + result.add((SsrcTransaction)redisTemplate.opsForValue().get(keyObj)); + } + return result; + } + + public String getMediaServerId(String deviceId, String channelId, String stream){ + SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream); + if (ssrcTransaction == null) { + return null; + } + return ssrcTransaction.getMediaServerId(); + } + + public String getSSRC(String deviceId, String channelId, String stream){ + SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, null, stream); + if (ssrcTransaction == null) { + return null; + } + return ssrcTransaction.getSsrc(); + } + + public void remove(String deviceId, String channelId, String stream) { + List ssrcTransactionList = getSsrcTransactionForAll(deviceId, channelId, null, stream); + if (ssrcTransactionList == null || ssrcTransactionList.isEmpty()) { + return; + } + for (SsrcTransaction ssrcTransaction : ssrcTransactionList) { + redisTemplate.delete(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + + deviceId + "_" + channelId + "_" + ssrcTransaction.getCallId() + "_" + ssrcTransaction.getStream()); + } + } + + public void removeByCallId(String deviceId, String channelId, String callId) { + SsrcTransaction ssrcTransaction = getSsrcTransaction(deviceId, channelId, callId, null); + if (ssrcTransaction == null ) { + return; + } + redisTemplate.delete(VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX + userSetting.getServerId() + "_" + + deviceId + "_" + channelId + "_" + ssrcTransaction.getCallId() + "_" + ssrcTransaction.getStream()); + } + + + public List getAllSsrc() { + List ssrcTransactionKeys = RedisUtil.scan(redisTemplate, String.format("%s_*_*_*_*", VideoManagerConstants.MEDIA_TRANSACTION_USED_PREFIX+ userSetting.getServerId())); + List result= new ArrayList<>(); + for (Object ssrcTransactionKey : ssrcTransactionKeys) { + String key = (String) ssrcTransactionKey; + SsrcTransaction ssrcTransaction = JsonUtil.redisJsonToObject(redisTemplate, key, SsrcTransaction.class); + result.add(ssrcTransaction); + } + return result; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/ISubscribeTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/ISubscribeTask.java old mode 100644 new mode 100755 index a4e711d9e..8d1c7d2ee --- a/src/main/java/com/genersoft/iot/vmp/gb28181/task/ISubscribeTask.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/ISubscribeTask.java @@ -1,10 +1,10 @@ package com.genersoft.iot.vmp.gb28181.task; -import javax.sip.DialogState; +import com.genersoft.iot.vmp.common.CommonCallback; /** * @author lin */ public interface ISubscribeTask extends Runnable{ - void stop(); + void stop(CommonCallback callback); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java old mode 100644 new mode 100755 index 17c23a00d..5ca95a0d8 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java @@ -12,13 +12,19 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.IDeviceService; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IPlatformService; +import com.genersoft.iot.vmp.service.impl.PlatformServiceImpl; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; +import javax.sip.InvalidArgumentException; +import javax.sip.SipException; +import java.text.ParseException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -59,6 +65,8 @@ public class SipRunner implements CommandLineRunner { @Autowired private ISIPCommanderForPlatform commanderForPlatform; + private final static Logger logger = LoggerFactory.getLogger(PlatformServiceImpl.class); + @Override public void run(String... args) throws Exception { List deviceList = deviceService.getAllOnlineDevice(); @@ -98,23 +106,29 @@ public class SipRunner implements CommandLineRunner { if (sendRtpItems.size() > 0) { for (SendRtpItem sendRtpItem : sendRtpItems) { MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId()); - redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(),sendRtpItem.getChannelId(), sendRtpItem.getCallId(),sendRtpItem.getStreamId()); + redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(),sendRtpItem.getChannelId(), sendRtpItem.getCallId(),sendRtpItem.getStream()); if (mediaServerItem != null) { ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); Map param = new HashMap<>(); param.put("vhost","__defaultVhost__"); param.put("app",sendRtpItem.getApp()); - param.put("stream",sendRtpItem.getStreamId()); + param.put("stream",sendRtpItem.getStream()); param.put("ssrc",sendRtpItem.getSsrc()); JSONObject jsonObject = zlmresTfulUtils.stopSendRtp(mediaServerItem, param); if (jsonObject != null && jsonObject.getInteger("code") == 0) { ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId()); if (platform != null) { - commanderForPlatform.streamByeCmd(platform, sendRtpItem.getCallId()); + try { + commanderForPlatform.streamByeCmd(platform, sendRtpItem.getCallId()); + } catch (InvalidArgumentException | ParseException | SipException e) { + logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); + } } } } } } } + + } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java old mode 100644 new mode 100755 index 39dff931d..d9270bbf0 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/CatalogSubscribeTask.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.gb28181.task.impl; +import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; @@ -7,14 +8,13 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import gov.nist.javax.sip.message.SIPRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.scheduling.annotation.Async; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import javax.sip.*; +import javax.sip.DialogState; +import javax.sip.InvalidArgumentException; +import javax.sip.ResponseEvent; +import javax.sip.SipException; import javax.sip.header.ToHeader; import java.text.ParseException; -import java.util.Timer; -import java.util.TimerTask; /** * 目录订阅任务 @@ -71,7 +71,7 @@ public class CatalogSubscribeTask implements ISubscribeTask { } @Override - public void stop() { + public void stop(CommonCallback callback) { /** * dialog 的各个状态 * EARLY-> Early state状态-初始请求发送以后,收到了一个临时响应消息 @@ -89,17 +89,20 @@ public class CatalogSubscribeTask implements ISubscribeTask { ResponseEvent event = (ResponseEvent) eventResult.event; if (event.getResponse().getRawContent() != null) { // 成功 - logger.info("[取消目录订阅订阅]成功: {}", device.getDeviceId()); + logger.info("[取消目录订阅]成功: {}", device.getDeviceId()); }else { // 成功 - logger.info("[取消目录订阅订阅]成功: {}", device.getDeviceId()); + logger.info("[取消目录订阅]成功: {}", device.getDeviceId()); + } + if (callback != null) { + callback.run(event.getResponse().getRawContent() != null); } },eventResult -> { // 失败 - logger.warn("[取消目录订阅订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); + logger.warn("[取消目录订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); }); } catch (InvalidArgumentException | SipException | ParseException e) { - logger.error("[命令发送失败] 取消目录订阅订阅: {}", e.getMessage()); + logger.error("[命令发送失败] 取消目录订阅: {}", e.getMessage()); } } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java old mode 100644 new mode 100755 index 2e792c1c9..a4512f35b --- a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeHandlerTask.java @@ -1,20 +1,9 @@ package com.genersoft.iot.vmp.gb28181.task.impl; -import com.genersoft.iot.vmp.conf.DynamicTask; -import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.service.IPlatformService; -import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; -import com.genersoft.iot.vmp.storager.IRedisCatchStorage; -import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.utils.SpringBeanFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.scheduling.annotation.Async; - -import javax.sip.DialogState; -import java.util.List; /** * 向已经订阅(移动位置)的上级发送MobilePosition消息 @@ -38,7 +27,7 @@ public class MobilePositionSubscribeHandlerTask implements ISubscribeTask { } @Override - public void stop() { + public void stop(CommonCallback callback) { } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeTask.java b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeTask.java old mode 100644 new mode 100755 index 0abd3cae3..9fed0793b --- a/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeTask.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/impl/MobilePositionSubscribeTask.java @@ -1,21 +1,19 @@ package com.genersoft.iot.vmp.gb28181.task.impl; +import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import gov.nist.javax.sip.message.SIPRequest; -import gov.nist.javax.sip.message.SIPResponse; -import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.scheduling.annotation.Async; -import javax.sip.*; +import javax.sip.InvalidArgumentException; +import javax.sip.ResponseEvent; +import javax.sip.SipException; import javax.sip.header.ToHeader; import java.text.ParseException; -import java.util.Timer; -import java.util.TimerTask; /** * 移动位置订阅的定时更新 @@ -70,7 +68,7 @@ public class MobilePositionSubscribeTask implements ISubscribeTask { } @Override - public void stop() { + public void stop(CommonCallback callback) { /** * dialog 的各个状态 * EARLY-> Early state状态-初始请求发送以后,收到了一个临时响应消息 @@ -92,6 +90,9 @@ public class MobilePositionSubscribeTask implements ISubscribeTask { // 成功 logger.info("[取消移动位置订阅]成功: {}", device.getDeviceId()); } + if (callback != null) { + callback.run(event.getResponse().getRawContent() != null); + } },eventResult -> { // 失败 logger.warn("[取消移动位置订阅]失败,信令发送失败: {}-{} ", device.getDeviceId(), eventResult.msg); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java index e8066b755..ce1074bd6 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/SIPSender.java @@ -66,17 +66,17 @@ public class SIPSender { // 添加错误订阅 if (errorEvent != null) { sipSubscribe.addErrorSubscribe(callIdHeader.getCallId(), (eventResult -> { - errorEvent.response(eventResult); sipSubscribe.removeErrorSubscribe(eventResult.callId); sipSubscribe.removeOkSubscribe(eventResult.callId); + errorEvent.response(eventResult); })); } // 添加订阅 if (okEvent != null) { sipSubscribe.addOkSubscribe(callIdHeader.getCallId(), eventResult -> { - okEvent.response(eventResult); sipSubscribe.removeOkSubscribe(eventResult.callId); sipSubscribe.removeErrorSubscribe(eventResult.callId); + okEvent.response(eventResult); }); } if ("TCP".equals(transport)) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java old mode 100644 new mode 100755 index 8a6218281..8d0ed7fb4 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/DeferredResultHolder.java @@ -1,160 +1,160 @@ -package com.genersoft.iot.vmp.gb28181.transmit.callback; - -import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx; -import org.springframework.stereotype.Component; -import org.springframework.util.ObjectUtils; -import org.springframework.web.context.request.async.DeferredResult; - -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @description: 异步请求处理 - * @author: swwheihei - * @date: 2020年5月8日 下午7:59:05 - */ -@SuppressWarnings(value = {"rawtypes", "unchecked"}) -@Component -public class DeferredResultHolder { - - public static final String CALLBACK_CMD_DEVICESTATUS = "CALLBACK_DEVICESTATUS"; - - public static final String CALLBACK_CMD_DEVICEINFO = "CALLBACK_DEVICEINFO"; - - public static final String CALLBACK_CMD_DEVICECONTROL = "CALLBACK_DEVICECONTROL"; - - public static final String CALLBACK_CMD_DEVICECONFIG = "CALLBACK_DEVICECONFIG"; - - public static final String CALLBACK_CMD_CONFIGDOWNLOAD = "CALLBACK_CONFIGDOWNLOAD"; - - public static final String CALLBACK_CMD_CATALOG = "CALLBACK_CATALOG"; - - public static final String CALLBACK_CMD_RECORDINFO = "CALLBACK_RECORDINFO"; - - public static final String CALLBACK_CMD_PLAY = "CALLBACK_PLAY"; - - public static final String CALLBACK_CMD_PLAYBACK = "CALLBACK_PLAYBACK"; - - public static final String CALLBACK_CMD_DOWNLOAD = "CALLBACK_DOWNLOAD"; - - public static final String CALLBACK_CMD_PROXY = "CALLBACK_PROXY"; - - public static final String CALLBACK_CMD_STOP = "CALLBACK_STOP"; - - public static final String UPLOAD_FILE_CHANNEL = "UPLOAD_FILE_CHANNEL"; - - public static final String CALLBACK_CMD_MOBILE_POSITION = "CALLBACK_CMD_MOBILE_POSITION"; - - public static final String CALLBACK_CMD_PRESETQUERY = "CALLBACK_PRESETQUERY"; - - public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM"; - - public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST"; - - public static final String CALLBACK_CMD_SNAP= "CALLBACK_SNAP"; - - private Map> map = new ConcurrentHashMap<>(); - - - public void put(String key, String id, DeferredResultEx result) { - Map deferredResultMap = map.get(key); - if (deferredResultMap == null) { - deferredResultMap = new ConcurrentHashMap<>(); - map.put(key, deferredResultMap); - } - deferredResultMap.put(id, result); - } - - public void put(String key, String id, DeferredResult result) { - Map deferredResultMap = map.get(key); - if (deferredResultMap == null) { - deferredResultMap = new ConcurrentHashMap<>(); - map.put(key, deferredResultMap); - } - deferredResultMap.put(id, new DeferredResultEx(result)); - } - - public DeferredResultEx get(String key, String id) { - Map deferredResultMap = map.get(key); - if (deferredResultMap == null || ObjectUtils.isEmpty(id)) { - return null; - } - return deferredResultMap.get(id); - } - - public Collection getAllByKey(String key) { - Map deferredResultMap = map.get(key); - if (deferredResultMap == null) { - return null; - } - return deferredResultMap.values(); - } - - public boolean exist(String key, String id){ - if (key == null) { - return false; - } - Map deferredResultMap = map.get(key); - if (id == null) { - return deferredResultMap != null; - }else { - return deferredResultMap != null && deferredResultMap.get(id) != null; - } - } - - /** - * 释放单个请求 - * @param msg - */ - public void invokeResult(RequestMessage msg) { - Map deferredResultMap = map.get(msg.getKey()); - if (deferredResultMap == null) { - return; - } - DeferredResultEx result = deferredResultMap.get(msg.getId()); - if (result == null) { - return; - } - result.getDeferredResult().setResult(msg.getData()); - deferredResultMap.remove(msg.getId()); - if (deferredResultMap.size() == 0) { - map.remove(msg.getKey()); - } - } - - /** - * 释放所有的请求 - * @param msg - */ - public void invokeAllResult(RequestMessage msg) { - Map deferredResultMap = map.get(msg.getKey()); - if (deferredResultMap == null) { - return; - } - synchronized (this) { - deferredResultMap = map.get(msg.getKey()); - if (deferredResultMap == null) { - return; - } - Set ids = deferredResultMap.keySet(); - for (String id : ids) { - DeferredResultEx result = deferredResultMap.get(id); - if (result == null) { - return; - } - if (result.getFilter() != null) { - Object handler = result.getFilter().handler(msg.getData()); - result.getDeferredResult().setResult(handler); - }else { - result.getDeferredResult().setResult(msg.getData()); - } - - } - map.remove(msg.getKey()); - } - } - - -} +package com.genersoft.iot.vmp.gb28181.transmit.callback; + +import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; +import org.springframework.web.context.request.async.DeferredResult; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @description: 异步请求处理 + * @author: swwheihei + * @date: 2020年5月8日 下午7:59:05 + */ +@SuppressWarnings(value = {"rawtypes", "unchecked"}) +@Component +public class DeferredResultHolder { + + public static final String CALLBACK_CMD_DEVICESTATUS = "CALLBACK_DEVICESTATUS"; + + public static final String CALLBACK_CMD_DEVICEINFO = "CALLBACK_DEVICEINFO"; + + public static final String CALLBACK_CMD_DEVICECONTROL = "CALLBACK_DEVICECONTROL"; + + public static final String CALLBACK_CMD_DEVICECONFIG = "CALLBACK_DEVICECONFIG"; + + public static final String CALLBACK_CMD_CONFIGDOWNLOAD = "CALLBACK_CONFIGDOWNLOAD"; + + public static final String CALLBACK_CMD_CATALOG = "CALLBACK_CATALOG"; + + public static final String CALLBACK_CMD_RECORDINFO = "CALLBACK_RECORDINFO"; + + public static final String CALLBACK_CMD_PLAY = "CALLBACK_PLAY"; + + public static final String CALLBACK_CMD_PLAYBACK = "CALLBACK_PLAYBACK"; + + public static final String CALLBACK_CMD_DOWNLOAD = "CALLBACK_DOWNLOAD"; + + public static final String CALLBACK_CMD_PROXY = "CALLBACK_PROXY"; + + public static final String CALLBACK_CMD_STOP = "CALLBACK_STOP"; + + public static final String UPLOAD_FILE_CHANNEL = "UPLOAD_FILE_CHANNEL"; + + public static final String CALLBACK_CMD_MOBILE_POSITION = "CALLBACK_CMD_MOBILE_POSITION"; + + public static final String CALLBACK_CMD_PRESETQUERY = "CALLBACK_PRESETQUERY"; + + public static final String CALLBACK_CMD_ALARM = "CALLBACK_ALARM"; + + public static final String CALLBACK_CMD_BROADCAST = "CALLBACK_BROADCAST"; + + public static final String CALLBACK_CMD_SNAP= "CALLBACK_SNAP"; + + private Map> map = new ConcurrentHashMap<>(); + + + public void put(String key, String id, DeferredResultEx result) { + Map deferredResultMap = map.get(key); + if (deferredResultMap == null) { + deferredResultMap = new ConcurrentHashMap<>(); + map.put(key, deferredResultMap); + } + deferredResultMap.put(id, result); + } + + public void put(String key, String id, DeferredResult result) { + Map deferredResultMap = map.get(key); + if (deferredResultMap == null) { + deferredResultMap = new ConcurrentHashMap<>(); + map.put(key, deferredResultMap); + } + deferredResultMap.put(id, new DeferredResultEx(result)); + } + + public DeferredResultEx get(String key, String id) { + Map deferredResultMap = map.get(key); + if (deferredResultMap == null || ObjectUtils.isEmpty(id)) { + return null; + } + return deferredResultMap.get(id); + } + + public Collection getAllByKey(String key) { + Map deferredResultMap = map.get(key); + if (deferredResultMap == null) { + return null; + } + return deferredResultMap.values(); + } + + public boolean exist(String key, String id){ + if (key == null) { + return false; + } + Map deferredResultMap = map.get(key); + if (id == null) { + return deferredResultMap != null; + }else { + return deferredResultMap != null && deferredResultMap.get(id) != null; + } + } + + /** + * 释放单个请求 + * @param msg + */ + public void invokeResult(RequestMessage msg) { + Map deferredResultMap = map.get(msg.getKey()); + if (deferredResultMap == null) { + return; + } + DeferredResultEx result = deferredResultMap.get(msg.getId()); + if (result == null) { + return; + } + result.getDeferredResult().setResult(msg.getData()); + deferredResultMap.remove(msg.getId()); + if (deferredResultMap.size() == 0) { + map.remove(msg.getKey()); + } + } + + /** + * 释放所有的请求 + * @param msg + */ + public void invokeAllResult(RequestMessage msg) { + Map deferredResultMap = map.get(msg.getKey()); + if (deferredResultMap == null) { + return; + } + synchronized (this) { + deferredResultMap = map.get(msg.getKey()); + if (deferredResultMap == null) { + return; + } + Set ids = deferredResultMap.keySet(); + for (String id : ids) { + DeferredResultEx result = deferredResultMap.get(id); + if (result == null) { + return; + } + if (result.getFilter() != null) { + Object handler = result.getFilter().handler(msg.getData()); + result.getDeferredResult().setResult(handler); + }else { + result.getDeferredResult().setResult(msg.getData()); + } + + } + map.remove(msg.getKey()); + } + } + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java old mode 100644 new mode 100755 index 42ae577ad..f4e2d2013 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/callback/RequestMessage.java @@ -1,39 +1,39 @@ -package com.genersoft.iot.vmp.gb28181.transmit.callback; - -/** - * @description: 请求信息定义 - * @author: swwheihei - * @date: 2020年5月8日 下午1:09:18 - */ -public class RequestMessage { - - private String id; - - private String key; - - private Object data; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public void setKey(String key) { - this.key = key; - } - - public String getKey() { - return key; - } - - public Object getData() { - return data; - } - - public void setData(Object data) { - this.data = data; - } -} +package com.genersoft.iot.vmp.gb28181.transmit.callback; + +/** + * @description: 请求信息定义 + * @author: swwheihei + * @date: 2020年5月8日 下午1:09:18 + */ +public class RequestMessage { + + private String id; + + private String key; + + private Object data; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public void setKey(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java old mode 100644 new mode 100755 index d68405632..d48e3a8b2 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java @@ -1,364 +1,367 @@ -package com.genersoft.iot.vmp.gb28181.transmit.cmd; - -import com.genersoft.iot.vmp.common.StreamInfo; -import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; -import com.genersoft.iot.vmp.gb28181.bean.Device; -import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; -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.SSRCInfo; -import gov.nist.javax.sip.message.SIPRequest; - -import javax.sip.InvalidArgumentException; -import javax.sip.SipException; -import java.text.ParseException; - -/** - * @description:设备能力接口,用于定义设备的控制、查询能力 - * @author: swwheihei - * @date: 2020年5月3日 下午9:16:34 - */ -public interface ISIPCommander { - - /** - * 云台方向放控制,使用配置文件中的默认镜头移动速度 - * - * @param device 控制设备 - * @param channelId 预览通道 - * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移 - * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 - */ - void ptzdirectCmd(Device device,String channelId,int leftRight, int upDown) throws InvalidArgumentException, ParseException, SipException; - - /** - * 云台方向放控制 - * - * @param device 控制设备 - * @param channelId 预览通道 - * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移 - * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 - * @param moveSpeed 镜头移动速度 - */ - void ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed) throws InvalidArgumentException, ParseException, SipException; - - /** - * 云台缩放控制,使用配置文件中的默认镜头缩放速度 - * - * @param device 控制设备 - * @param channelId 预览通道 - * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 - */ - void ptzZoomCmd(Device device,String channelId,int inOut) throws InvalidArgumentException, ParseException, SipException; - - /** - * 云台缩放控制 - * - * @param device 控制设备 - * @param channelId 预览通道 - * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 - */ - void ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed) throws InvalidArgumentException, ParseException, SipException; - - /** - * 云台控制,支持方向与缩放控制 - * - * @param device 控制设备 - * @param channelId 预览通道 - * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移 - * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 - * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 - * @param moveSpeed 镜头移动速度 - * @param zoomSpeed 镜头缩放速度 - */ - void ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) throws InvalidArgumentException, SipException, ParseException; - - /** - * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令 - * - * @param device 控制设备 - * @param channelId 预览通道 - * @param cmdCode 指令码 - * @param parameter1 数据1 - * @param parameter2 数据2 - * @param combineCode2 组合码2 - */ - void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException; - - /** - * 前端控制指令(用于转发上级指令) - * @param device 控制设备 - * @param channelId 预览通道 - * @param cmdString 前端控制指令串 - */ - void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 请求预览视频流 - * @param device 视频设备 - * @param channelId 预览通道 - */ - void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 请求回放视频流 - * - * @param device 视频设备 - * @param channelId 预览通道 - * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss - * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss - */ - void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 请求历史媒体下载 - * - * @param device 视频设备 - * @param channelId 预览通道 - * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss - * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss - * @param downloadSpeed 下载倍速参数 - */ - void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, - String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent, - SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 视频流停止 - */ - void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException; - - void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException; - - /** - * 回放暂停 - */ - void playPauseCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException; - - /** - * 回放恢复 - */ - void playResumeCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException; - - /** - * 回放拖动播放 - */ - void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) throws InvalidArgumentException, ParseException, SipException; - - /** - * 回放倍速播放 - */ - void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException; - - /** - * 回放控制 - * @param device - * @param streamInfo - * @param content - */ - void playbackControlCmd(Device device, StreamInfo streamInfo, String content,SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException; - - - /** - * 语音广播 - * - * @param device 视频设备 - * @param channelId 预览通道 - */ - void audioBroadcastCmd(Device device,String channelId); - - /** - * 语音广播 - * - * @param device 视频设备 - */ - void audioBroadcastCmd(Device device, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; - void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException; - - /** - * 音视频录像控制 - * - * @param device 视频设备 - * @param channelId 预览通道 - * @param recordCmdStr 录像命令:Record / StopRecord - */ - void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 远程启动控制命令 - * - * @param device 视频设备 - */ - void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException; - - /** - * 报警布防/撤防命令 - * - * @param device 视频设备 - */ - void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 报警复位命令 - * - * @param device 视频设备 - * @param alarmMethod 报警方式(可选) - * @param alarmType 报警类型(可选) - */ - void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧 - * - * @param device 视频设备 - * @param channelId 预览通道 - */ - void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException; - - /** - * 看守位控制命令 - * - * @param device 视频设备 - * @param channelId 通道id,非通道则是设备本身 - * @param enabled 看守位使能:1 = 开启,0 = 关闭 - * @param resetTime 自动归位时间间隔,开启看守位时使用,单位:秒(s) - * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255 - */ - void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 设备配置命令 - * - * @param device 视频设备 - */ - void deviceConfigCmd(Device device); - - /** - * 设备配置命令:basicParam - * - * @param device 视频设备 - * @param channelId 通道编码(可选) - * @param name 设备/通道名称(可选) - * @param expiration 注册过期时间(可选) - * @param heartBeatInterval 心跳间隔时间(可选) - * @param heartBeatCount 心跳超时次数(可选) - */ - void deviceBasicConfigCmd(Device device, String channelId, String name, String expiration, String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 查询设备状态 - * - * @param device 视频设备 - */ - void deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 查询设备信息 - * - * @param device 视频设备 - * @return - */ - void deviceInfoQuery(Device device) throws InvalidArgumentException, SipException, ParseException; - - /** - * 查询目录列表 - * - * @param device 视频设备 - */ - void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException; - - /** - * 查询录像信息 - * - * @param device 视频设备 - * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss - * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss - * @param sn - */ - void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer Secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 查询报警信息 - * - * @param device 视频设备 - * @param startPriority 报警起始级别(可选) - * @param endPriority 报警终止级别(可选) - * @param alarmMethod 报警方式条件(可选) - * @param alarmType 报警类型 - * @param startTime 报警发生起始时间(可选) - * @param endTime 报警发生终止时间(可选) - * @return true = 命令发送成功 - */ - void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, - String alarmType, String startTime, String endTime, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 查询设备配置 - * - * @param device 视频设备 - * @param channelId 通道编码(可选) - * @param configType 配置类型: - */ - void deviceConfigQuery(Device device, String channelId, String configType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 查询设备预置位置 - * - * @param device 视频设备 - */ - void presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 查询移动设备位置数据 - * - * @param device 视频设备 - */ - void mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 订阅、取消订阅移动位置 - * - * @param device 视频设备 - * @return true = 命令发送成功 - */ - SIPRequest mobilePositionSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 订阅、取消订阅报警信息 - * @param device 视频设备 - * @param expires 订阅过期时间(0 = 取消订阅) - * @param startPriority 报警起始级别(可选) - * @param endPriority 报警终止级别(可选) - * @param alarmType 报警类型 - * @param startTime 报警发生起始时间(可选) - * @param endTime 报警发生终止时间(可选) - * @return true = 命令发送成功 - */ - void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException; - - /** - * 订阅、取消订阅目录信息 - * @param device 视频设备 - * @return true = 命令发送成功 - */ - SIPRequest catalogSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; - - /** - * 拉框控制命令 - * - * @param device 控制设备 - * @param channelId 通道id - * @param cmdString 前端控制指令串 - */ - void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException; - - - /** - * 向设备发送报警NOTIFY消息, 用于互联结构下,此时将设备当成一个平级平台看待 - * @param device 设备 - * @param deviceAlarm 报警信息信息 - * @return - */ - void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException; - -} +package com.genersoft.iot.vmp.gb28181.transmit.cmd; + +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; +import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.gb28181.bean.Device; +import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; +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.SSRCInfo; +import gov.nist.javax.sip.message.SIPRequest; + +import javax.sip.InvalidArgumentException; +import javax.sip.SipException; +import java.text.ParseException; + +/** + * @description:设备能力接口,用于定义设备的控制、查询能力 + * @author: swwheihei + * @date: 2020年5月3日 下午9:16:34 + */ +public interface ISIPCommander { + + /** + * 云台方向放控制,使用配置文件中的默认镜头移动速度 + * + * @param device 控制设备 + * @param channelId 预览通道 + * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移 + * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 + */ + void ptzdirectCmd(Device device,String channelId,int leftRight, int upDown) throws InvalidArgumentException, ParseException, SipException; + + /** + * 云台方向放控制 + * + * @param device 控制设备 + * @param channelId 预览通道 + * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移 + * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 + * @param moveSpeed 镜头移动速度 + */ + void ptzdirectCmd(Device device,String channelId,int leftRight, int upDown, int moveSpeed) throws InvalidArgumentException, ParseException, SipException; + + /** + * 云台缩放控制,使用配置文件中的默认镜头缩放速度 + * + * @param device 控制设备 + * @param channelId 预览通道 + * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 + */ + void ptzZoomCmd(Device device,String channelId,int inOut) throws InvalidArgumentException, ParseException, SipException; + + /** + * 云台缩放控制 + * + * @param device 控制设备 + * @param channelId 预览通道 + * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 + */ + void ptzZoomCmd(Device device,String channelId,int inOut, int moveSpeed) throws InvalidArgumentException, ParseException, SipException; + + /** + * 云台控制,支持方向与缩放控制 + * + * @param device 控制设备 + * @param channelId 预览通道 + * @param leftRight 镜头左移右移 0:停止 1:左移 2:右移 + * @param upDown 镜头上移下移 0:停止 1:上移 2:下移 + * @param inOut 镜头放大缩小 0:停止 1:缩小 2:放大 + * @param moveSpeed 镜头移动速度 + * @param zoomSpeed 镜头缩放速度 + */ + void ptzCmd(Device device,String channelId,int leftRight, int upDown, int inOut, int moveSpeed, int zoomSpeed) throws InvalidArgumentException, SipException, ParseException; + + /** + * 前端控制,包括PTZ指令、FI指令、预置位指令、巡航指令、扫描指令和辅助开关指令 + * + * @param device 控制设备 + * @param channelId 预览通道 + * @param cmdCode 指令码 + * @param parameter1 数据1 + * @param parameter2 数据2 + * @param combineCode2 组合码2 + */ + void frontEndCmd(Device device, String channelId, int cmdCode, int parameter1, int parameter2, int combineCode2) throws SipException, InvalidArgumentException, ParseException; + + /** + * 前端控制指令(用于转发上级指令) + * @param device 控制设备 + * @param channelId 预览通道 + * @param cmdString 前端控制指令串 + */ + void fronEndCmd(Device device, String channelId, String cmdString, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 请求预览视频流 + * @param device 视频设备 + * @param channel 预览通道 + */ + void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 请求回放视频流 + * + * @param device 视频设备 + * @param channelId 预览通道 + * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss + * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss + */ + void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInf, Device device, String channelId, String startTime, String endTime,ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 请求历史媒体下载 + * + * @param device 视频设备 + * @param channelId 预览通道 + * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss + * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss + * @param downloadSpeed 下载倍速参数 + */ + void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, + String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent, + SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; + + + /** + * 视频流停止 + */ + void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException; + + void talkStreamCmd(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, Device device, String channelId, String callId, ZlmHttpHookSubscribe.Event event, ZlmHttpHookSubscribe.Event eventForPush, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + + void streamByeCmd(Device device, String channelId, String stream, String callId) throws InvalidArgumentException, ParseException, SipException, SsrcTransactionNotFoundException; + + void streamByeCmd(Device device, String channelId, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException; + + /** + * 回放暂停 + */ + void playPauseCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException; + + /** + * 回放恢复 + */ + void playResumeCmd(Device device, StreamInfo streamInfo) throws InvalidArgumentException, ParseException, SipException; + + /** + * 回放拖动播放 + */ + void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) throws InvalidArgumentException, ParseException, SipException; + + /** + * 回放倍速播放 + */ + void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) throws InvalidArgumentException, ParseException, SipException; + + /** + * 回放控制 + * @param device + * @param streamInfo + * @param content + */ + 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 视频设备 + */ + void audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 音视频录像控制 + * + * @param device 视频设备 + * @param channelId 预览通道 + * @param recordCmdStr 录像命令:Record / StopRecord + */ + void recordCmd(Device device, String channelId, String recordCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 远程启动控制命令 + * + * @param device 视频设备 + */ + void teleBootCmd(Device device) throws InvalidArgumentException, SipException, ParseException; + + /** + * 报警布防/撤防命令 + * + * @param device 视频设备 + */ + void guardCmd(Device device, String guardCmdStr, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 报警复位命令 + * + * @param device 视频设备 + * @param alarmMethod 报警方式(可选) + * @param alarmType 报警类型(可选) + */ + void alarmCmd(Device device, String alarmMethod, String alarmType, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 强制关键帧命令,设备收到此命令应立刻发送一个IDR帧 + * + * @param device 视频设备 + * @param channelId 预览通道 + */ + void iFrameCmd(Device device, String channelId) throws InvalidArgumentException, SipException, ParseException; + + /** + * 看守位控制命令 + * + * @param device 视频设备 + * @param channelId 通道id,非通道则是设备本身 + * @param enabled 看守位使能:1 = 开启,0 = 关闭 + * @param resetTime 自动归位时间间隔,开启看守位时使用,单位:秒(s) + * @param presetIndex 调用预置位编号,开启看守位时使用,取值范围0~255 + */ + void homePositionCmd(Device device, String channelId, String enabled, String resetTime, String presetIndex, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 设备配置命令 + * + * @param device 视频设备 + */ + void deviceConfigCmd(Device device); + + /** + * 设备配置命令:basicParam + * + * @param device 视频设备 + * @param channelId 通道编码(可选) + * @param name 设备/通道名称(可选) + * @param expiration 注册过期时间(可选) + * @param heartBeatInterval 心跳间隔时间(可选) + * @param heartBeatCount 心跳超时次数(可选) + */ + void deviceBasicConfigCmd(Device device, String channelId, String name, String expiration, String heartBeatInterval, String heartBeatCount, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 查询设备状态 + * + * @param device 视频设备 + */ + void deviceStatusQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 查询设备信息 + * + * @param device 视频设备 + * @return + */ + void deviceInfoQuery(Device device) throws InvalidArgumentException, SipException, ParseException; + + /** + * 查询目录列表 + * + * @param device 视频设备 + */ + void catalogQuery(Device device, int sn, SipSubscribe.Event errorEvent) throws SipException, InvalidArgumentException, ParseException; + + /** + * 查询录像信息 + * + * @param device 视频设备 + * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss + * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss + * @param sn + */ + void recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, Integer Secrecy, String type, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 查询报警信息 + * + * @param device 视频设备 + * @param startPriority 报警起始级别(可选) + * @param endPriority 报警终止级别(可选) + * @param alarmMethod 报警方式条件(可选) + * @param alarmType 报警类型 + * @param startTime 报警发生起始时间(可选) + * @param endTime 报警发生终止时间(可选) + * @return true = 命令发送成功 + */ + void alarmInfoQuery(Device device, String startPriority, String endPriority, String alarmMethod, + String alarmType, String startTime, String endTime, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 查询设备配置 + * + * @param device 视频设备 + * @param channelId 通道编码(可选) + * @param configType 配置类型: + */ + void deviceConfigQuery(Device device, String channelId, String configType, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 查询设备预置位置 + * + * @param device 视频设备 + */ + void presetQuery(Device device, String channelId, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 查询移动设备位置数据 + * + * @param device 视频设备 + */ + void mobilePostitionQuery(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 订阅、取消订阅移动位置 + * + * @param device 视频设备 + * @return true = 命令发送成功 + */ + SIPRequest mobilePositionSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent , SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 订阅、取消订阅报警信息 + * @param device 视频设备 + * @param expires 订阅过期时间(0 = 取消订阅) + * @param startPriority 报警起始级别(可选) + * @param endPriority 报警终止级别(可选) + * @param alarmType 报警类型 + * @param startTime 报警发生起始时间(可选) + * @param endTime 报警发生终止时间(可选) + * @return true = 命令发送成功 + */ + void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException; + + /** + * 订阅、取消订阅目录信息 + * @param device 视频设备 + * @return true = 命令发送成功 + */ + SIPRequest catalogSubscribe(Device device, SIPRequest request, SipSubscribe.Event okEvent ,SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + + /** + * 拉框控制命令 + * + * @param device 控制设备 + * @param channelId 通道id + * @param cmdString 前端控制指令串 + */ + void dragZoomCmd(Device device, String channelId, String cmdString) throws InvalidArgumentException, SipException, ParseException; + + + /** + * 向设备发送报警NOTIFY消息, 用于互联结构下,此时将设备当成一个平级平台看待 + * @param device 设备 + * @param deviceAlarm 报警信息信息 + * @return + */ + void sendAlarmMessage(Device device, DeviceAlarm deviceAlarm) throws InvalidArgumentException, SipException, ParseException; + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java old mode 100644 new mode 100755 index 0332aabe6..a412a6c74 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommanderForPlatform.java @@ -1,8 +1,12 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd; +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; +import com.genersoft.iot.vmp.service.bean.SSRCInfo; import javax.sip.InvalidArgumentException; import javax.sip.SipException; @@ -14,16 +18,20 @@ public interface ISIPCommanderForPlatform { /** * 向上级平台注册 + * * @param parentPlatform * @return */ void register(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException; void register(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws InvalidArgumentException, ParseException, SipException; + + void register(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo, WWWAuthenticateHeader www, SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent, boolean isRegister) throws SipException, InvalidArgumentException, ParseException; /** * 向上级平台注销 + * * @param parentPlatform * @return */ @@ -32,26 +40,33 @@ public interface ISIPCommanderForPlatform { /** * 向上级平发送心跳信息 + * * @param parentPlatform * @return callId(作为接受回复的判定) */ - String keepalive(ParentPlatform parentPlatform,SipSubscribe.Event errorEvent , SipSubscribe.Event okEvent) throws SipException, InvalidArgumentException, ParseException; + String keepalive(ParentPlatform parentPlatform, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) + throws SipException, InvalidArgumentException, ParseException; /** * 向上级回复通道信息 - * @param channel 通道信息 + * + * @param channel 通道信息 * @param parentPlatform 平台信息 * @param sn * @param fromTag * @param size * @return */ - void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) throws SipException, InvalidArgumentException, ParseException; - void catalogQuery(List channels, ParentPlatform parentPlatform, String sn, String fromTag) throws InvalidArgumentException, ParseException, SipException; + void catalogQuery(DeviceChannel channel, ParentPlatform parentPlatform, String sn, String fromTag, int size) + throws SipException, InvalidArgumentException, ParseException; + + void catalogQuery(List channels, ParentPlatform parentPlatform, String sn, String fromTag) + throws InvalidArgumentException, ParseException, SipException; /** * 向上级回复DeviceInfo查询信息 + * * @param parentPlatform 平台信息 * @param sn SN * @param fromTag FROM头的tag信息 @@ -61,6 +76,7 @@ public interface ISIPCommanderForPlatform { /** * 向上级回复DeviceStatus查询信息 + * * @param parentPlatform 平台信息 * @param sn * @param fromTag @@ -70,23 +86,27 @@ public interface ISIPCommanderForPlatform { /** * 向上级回复移动位置订阅消息 + * * @param parentPlatform 平台信息 - * @param gpsMsgInfo GPS信息 - * @param subscribeInfo 订阅相关的信息 + * @param gpsMsgInfo GPS信息 + * @param subscribeInfo 订阅相关的信息 * @return */ - void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException; + void sendNotifyMobilePosition(ParentPlatform parentPlatform, GPSMsgInfo gpsMsgInfo, SubscribeInfo subscribeInfo) + throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException; /** * 向上级回复报警消息 + * * @param parentPlatform 平台信息 - * @param deviceAlarm 报警信息信息 + * @param deviceAlarm 报警信息信息 * @return */ void sendAlarmMessage(ParentPlatform parentPlatform, DeviceAlarm deviceAlarm) throws SipException, InvalidArgumentException, ParseException; /** * 回复catalog事件-增加/更新 + * * @param parentPlatform * @param deviceChannels */ @@ -94,22 +114,28 @@ public interface ISIPCommanderForPlatform { /** * 回复catalog事件-删除 + * * @param parentPlatform * @param deviceChannels */ - void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException; + void sendNotifyForCatalogOther(String type, ParentPlatform parentPlatform, List deviceChannels, + SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, + ParseException, NoSuchFieldException, SipException, IllegalAccessException; /** * 回复recordInfo - * @param deviceChannel 通道信息 + * + * @param deviceChannel 通道信息 * @param parentPlatform 平台信息 - * @param fromTag fromTag - * @param recordInfo 录像信息 + * @param fromTag fromTag + * @param recordInfo 录像信息 */ - void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) throws SipException, InvalidArgumentException, ParseException; + void recordInfo(DeviceChannel deviceChannel, ParentPlatform parentPlatform, String fromTag, RecordInfo recordInfo) + throws SipException, InvalidArgumentException, ParseException; /** * 录像播放推送完成时发送MediaStatus消息 + * * @param platform * @param sendRtpItem * @return @@ -118,9 +144,19 @@ public interface ISIPCommanderForPlatform { /** * 向发起点播的上级回复bye + * * @param platform 平台信息 - * @param callId callId + * @param callId callId */ void streamByeCmd(ParentPlatform platform, String callId) throws SipException, InvalidArgumentException, ParseException; + void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException; + + void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException; + + void broadcastInviteCmd(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, + SSRCInfo ssrcInfo, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, + SipSubscribe.Event errorEvent) throws ParseException, SipException, InvalidArgumentException; + + void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException; } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java old mode 100644 new mode 100755 index fe130a549..543072ac0 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderPlarformProvider.java @@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.gb28181.SipLayer; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; +import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.genersoft.iot.vmp.gb28181.bean.SubscribeInfo; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; @@ -55,7 +56,7 @@ public class SIPRequestHeaderPlarformProvider { //via ArrayList viaHeaders = new ArrayList(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), - Integer.parseInt(parentPlatform.getDevicePort()), parentPlatform.getTransport(), SipUtils.getNewViaTag()); + parentPlatform.getDevicePort(), parentPlatform.getTransport(), SipUtils.getNewViaTag()); viaHeader.setRPort(); viaHeaders.add(viaHeader); //from @@ -166,6 +167,7 @@ public class SIPRequestHeaderPlarformProvider { public Request createMessageRequest(ParentPlatform parentPlatform, String content, SendRtpItem sendRtpItem) throws PeerUnavailableException, ParseException, InvalidArgumentException { CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId()); + callIdHeader.setCallId(sendRtpItem.getCallId()); return createMessageRequest(parentPlatform, content, sendRtpItem.getToTag(), SipUtils.getNewViaTag(), sendRtpItem.getFromTag(), callIdHeader); } @@ -181,7 +183,7 @@ public class SIPRequestHeaderPlarformProvider { SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); // via ArrayList 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(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); @@ -218,7 +220,7 @@ public class SIPRequestHeaderPlarformProvider { SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort()); // via ArrayList viaHeaders = new ArrayList<>(); - ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), Integer.parseInt(parentPlatform.getDevicePort()), + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(), parentPlatform.getTransport(), SipUtils.getNewViaTag()); viaHeader.setRPort(); viaHeaders.add(viaHeader); @@ -226,11 +228,11 @@ public class SIPRequestHeaderPlarformProvider { SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort()); Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getResponse().getToTag()); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getResponse() != null ? subscribeInfo.getResponse().getToTag(): subscribeInfo.getSimulatedToTag()); // to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, subscribeInfo.getRequest().getFromTag()); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, subscribeInfo.getRequest() != null ?subscribeInfo.getRequest().getFromTag(): subscribeInfo.getSimulatedFromTag()); // Forwards MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); @@ -240,7 +242,7 @@ public class SIPRequestHeaderPlarformProvider { // 设置编码, 防止中文乱码 messageFactory.setDefaultContentEncodingCharset("gb2312"); - CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(subscribeInfo.getRequest().getCallIdHeader().getCallId()); + CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(subscribeInfo.getRequest() != null ? subscribeInfo.getRequest().getCallIdHeader().getCallId(): subscribeInfo.getSimulatedCallId()); request = (SIPRequest) messageFactory.createRequest(requestURI, Request.NOTIFY, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); @@ -278,7 +280,7 @@ public class SIPRequestHeaderPlarformProvider { SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort()); // via ArrayList viaHeaders = new ArrayList<>(); - ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(platform.getDeviceIp(), Integer.parseInt(platform.getDevicePort()), + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag()); viaHeader.setRPort(); viaHeaders.add(viaHeader); @@ -310,6 +312,82 @@ public class SIPRequestHeaderPlarformProvider { request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); + return request; + } + + public Request createInviteRequest(ParentPlatform platform, String channelId, String content, String viaTag, String fromTag, String ssrc, CallIdHeader callIdHeader) throws PeerUnavailableException, ParseException, InvalidArgumentException { + Request request = null; + //请求行 + String platformHostAddress = platform.getServerIP() + ":" + platform.getServerPort(); + String localHostAddress = sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort(); + SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, platformHostAddress); + //via + ArrayList viaHeaders = new ArrayList(); + 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 viaHeaders = new ArrayList(); + 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; } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java old mode 100644 new mode 100755 index 899643700..3a9f5fb37 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/SIPRequestHeaderProvider.java @@ -164,6 +164,7 @@ public class SIPRequestHeaderProvider { Request request = null; //请求行 SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); +// SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag()); @@ -174,6 +175,7 @@ public class SIPRequestHeaderProvider { FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag()); //to SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress()); +// SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(),device.getHostAddress()); Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, transactionInfo.getToTag()); @@ -195,6 +197,41 @@ public class SIPRequestHeaderProvider { return request; } + public Request createByteRequestForDeviceInvite(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException { + Request request = null; + //请求行 + SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); + // via + ArrayList viaHeaders = new ArrayList(); + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag()); + viaHeaders.add(viaHeader); + //from + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain()); + Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getToTag()); + //to + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress()); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, transactionInfo.getFromTag()); + + //Forwards + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); + + //ceq + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE); + CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId()); + request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); + + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); + + Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort())); + request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); + + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); + + return request; + } + public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; // sipuri @@ -313,6 +350,38 @@ public class SIPRequestHeaderProvider { request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); + return request; + } + public Request createBroadcastMessageRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { + Request request = null; + // sipuri + SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); + // via + ArrayList viaHeaders = new ArrayList(); + 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; } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java old mode 100644 new mode 100755 index 782dc06a3..723c715fa --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java @@ -6,8 +6,10 @@ import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.SipLayer; +import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.SsrcTransaction; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; @@ -16,9 +18,11 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderProvider; import com.genersoft.iot.vmp.gb28181.utils.NumericUtil; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; +import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamPush; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam; import com.genersoft.iot.vmp.service.IMediaServerService; @@ -40,6 +44,8 @@ import javax.sip.SipFactory; import javax.sip.header.CallIdHeader; import javax.sip.message.Request; import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; /** * @description:设备能力接口,用于定义设备的控制、查询能力 @@ -60,7 +66,7 @@ public class SIPCommander implements ISIPCommander { @Autowired private SIPSender sipSender; - + @Autowired private SIPRequestHeaderProvider headerProvider; @@ -73,11 +79,12 @@ public class SIPCommander implements ISIPCommander { @Autowired private ZlmHttpHookSubscribe subscribe; - - @Autowired private IMediaServerService mediaServerService; + @Autowired + private ZLMServerFactory zlmServerFactory; + /** * 云台方向放控制,使用配置文件中的默认镜头移动速度 @@ -189,7 +196,7 @@ public class SIPCommander implements ISIPCommander { ptzXml.append("5\r\n"); ptzXml.append("\r\n"); ptzXml.append("\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); @@ -252,8 +259,8 @@ public class SIPCommander implements ISIPCommander { ptzXml.append("5\r\n"); ptzXml.append("\r\n"); ptzXml.append("\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, errorEvent, okEvent); @@ -263,12 +270,12 @@ public class SIPCommander implements ISIPCommander { * 请求预览视频流 * * @param device 视频设备 - * @param channelId 预览通道 + * @param channel 预览通道 * @param event hook订阅 * @param errorEvent sip错误订阅 */ @Override - public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, + public void playStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { String stream = ssrcInfo.getStream(); @@ -292,7 +299,7 @@ public class SIPCommander implements ISIPCommander { } StringBuffer content = new StringBuffer(200); content.append("v=0\r\n"); - content.append("o=" + channelId + " 0 0 IN IP4 " + sdpIp + "\r\n"); + content.append("o=" + channel.getChannelId() + " 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"); @@ -343,20 +350,8 @@ public class SIPCommander implements ISIPCommander { } } - if( device.isSwitchPrimarySubStream() ){ - if("TP-LINK".equals(device.getManufacturer())){ - if (device.isSwitchPrimarySubStream()){ - content.append("a=streamMode:sub\r\n"); - }else { - content.append("a=streamMode:main\r\n"); - } - }else { - if (device.isSwitchPrimarySubStream()){ - content.append("a=streamprofile:1\r\n"); - }else { - content.append("a=streamprofile:0\r\n"); - } - } + if (!ObjectUtils.isEmpty(channel.getStreamIdentification())) { + content.append("a=" + channel.getStreamIdentification() + "\r\n"); } content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc @@ -365,15 +360,16 @@ public class SIPCommander implements ISIPCommander { - Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); + Request request = headerProvider.createInviteRequest(device, channel.getChannelId(), content.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, ssrcInfo.getSsrc(),sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> { - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream()); mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); errorEvent.response(e); }), e -> { ResponseEvent responseEvent = (ResponseEvent) e.event; SIPResponse response = (SIPResponse) responseEvent.getResponse(); - streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, + String callId = response.getCallIdHeader().getCallId(); + streamSession.put(device.getDeviceId(), channel.getChannelId(), callId, stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAY); okEvent.response(e); }); @@ -598,6 +594,70 @@ public class SIPCommander implements ISIPCommander { }); } + @Override + public void talkStreamCmd(MediaServerItem mediaServerItem, SendRtpItem sendRtpItem, Device device, String channelId, String callId, ZlmHttpHookSubscribe.Event event, ZlmHttpHookSubscribe.Event eventForPush, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { + + String stream = sendRtpItem.getStream(); + + if (device == null) { + return; + } + if (!mediaServerItem.isRtpEnable()) { + // 单端口暂不支持语音喊话 + logger.info("[语音喊话] 单端口暂不支持此操作"); + return; + } + + logger.info("[语音喊话] {} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), sendRtpItem.getPort()); + HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId()); + subscribe.addSubscribe(hookSubscribeForStreamChange, (mediaServerItemInUse, hookParam) -> { + if (event != null) { + event.response(mediaServerItemInUse, hookParam); + subscribe.removeSubscribe(hookSubscribeForStreamChange); + } + }); + + CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport()); + callIdHeader.setCallId(callId); + HookSubscribeForStreamPush hookSubscribeForStreamPush = HookSubscribeFactory.on_publish("rtp", stream, null, mediaServerItem.getId()); + subscribe.addSubscribe(hookSubscribeForStreamPush, (mediaServerItemInUse, hookParam) -> { + if (eventForPush != null) { + eventForPush.response(mediaServerItemInUse, hookParam); + } + }); + // + StringBuffer content = new StringBuffer(200); + content.append("v=0\r\n"); + content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); + content.append("s=Talk\r\n"); + content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); + content.append("t=0 0\r\n"); + + content.append("m=audio " + sendRtpItem.getPort() + " TCP/RTP/AVP 8\r\n"); + content.append("a=setup:passive\r\n"); + content.append("a=connection:new\r\n"); + content.append("a=sendrecv\r\n"); + content.append("a=rtpmap:8 PCMA/8000\r\n"); + + content.append("y=" + sendRtpItem.getSsrc() + "\r\n");//ssrc + // f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率 + content.append("f=v/////a/1/8/1" + "\r\n"); + + Request request = headerProvider.createInviteRequest(device, channelId, content.toString(), + SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null, sendRtpItem.getSsrc(), callIdHeader); + sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, (e -> { + streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream()); + mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc()); + errorEvent.response(e); + }), e -> { + // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 + ResponseEvent responseEvent = (ResponseEvent) e.event; + SIPResponse response = (SIPResponse) responseEvent.getResponse(); + streamSession.put(device.getDeviceId(), channelId, "talk", stream, sendRtpItem.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.TALK); + okEvent.response(e); + }); + } + /** * 视频流停止, 不使用回调 */ @@ -611,27 +671,37 @@ public class SIPCommander implements ISIPCommander { */ @Override public void streamByeCmd(Device device, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException { - SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callId, stream); - if (ssrcTransaction == null) { + if (device == null) { + logger.warn("[发送BYE] device为null"); + return; + } + List ssrcTransactionList = streamSession.getSsrcTransactionForAll(device.getDeviceId(), channelId, callId, stream); + if (ssrcTransactionList == null || ssrcTransactionList.isEmpty()) { + logger.info("[发送BYE] 未找到事务信息,设备: device: {}, channel: {}", device.getDeviceId(), channelId); throw new SsrcTransactionNotFoundException(device.getDeviceId(), channelId, callId, stream); } - mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc()); - mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream()); - streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); + for (SsrcTransaction ssrcTransaction : ssrcTransactionList) { + logger.info("[发送BYE] 设备: device: {}, channel: {}, callId: {}", device.getDeviceId(), channelId, ssrcTransaction.getCallId()); + mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc()); - Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo()); + mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream()); + streamSession.removeByCallId(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getCallId()); + Request byteRequest = headerProvider.createByteRequest(device, channelId, ssrcTransaction.getSipTransactionInfo()); + sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), byteRequest, null, okEvent); + } + } + + @Override + 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); } - /** - * 语音广播 - * - * @param device 视频设备 - * @param channelId 预览通道 - */ @Override - public void audioBroadcastCmd(Device device, String channelId) { + 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); } /** @@ -639,43 +709,20 @@ public class SIPCommander implements ISIPCommander { * * @param device 视频设备 */ - @Override - public void audioBroadcastCmd(Device device) throws InvalidArgumentException, SipException, ParseException { - + @Override + public void audioBroadcastCmd(Device device, String channelId, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { StringBuffer broadcastXml = new StringBuffer(200); String charset = device.getCharset(); broadcastXml.append("\r\n"); broadcastXml.append("\r\n"); broadcastXml.append("Broadcast\r\n"); - broadcastXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); + broadcastXml.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); broadcastXml.append("" + sipConfig.getId() + "\r\n"); - broadcastXml.append("" + device.getDeviceId() + "\r\n"); + broadcastXml.append("" + channelId + "\r\n"); broadcastXml.append("\r\n"); - - Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); - sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request); - - } - - @Override - public void audioBroadcastCmd(Device device, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { - - StringBuffer broadcastXml = new StringBuffer(200); - String charset = device.getCharset(); - broadcastXml.append("\r\n"); - broadcastXml.append("\r\n"); - broadcastXml.append("Broadcast\r\n"); - broadcastXml.append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n"); - broadcastXml.append("" + sipConfig.getId() + "\r\n"); - broadcastXml.append("" + device.getDeviceId() + "\r\n"); - broadcastXml.append("\r\n"); - - - - Request request = headerProvider.createMessageRequest(device, broadcastXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); - sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); + sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, okEvent); } @@ -703,7 +750,7 @@ public class SIPCommander implements ISIPCommander { cmdXml.append("" + recordCmdStr + "\r\n"); cmdXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent); @@ -727,7 +774,7 @@ public class SIPCommander implements ISIPCommander { cmdXml.append("Boot\r\n"); cmdXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request); @@ -752,6 +799,8 @@ public class SIPCommander implements ISIPCommander { cmdXml.append("" + guardCmdStr + "\r\n"); cmdXml.append("\r\n"); + + Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent); } @@ -786,7 +835,7 @@ public class SIPCommander implements ISIPCommander { } cmdXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent); @@ -815,7 +864,7 @@ public class SIPCommander implements ISIPCommander { cmdXml.append("Send\r\n"); cmdXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request); @@ -863,7 +912,7 @@ public class SIPCommander implements ISIPCommander { cmdXml.append("\r\n"); cmdXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent,okEvent); @@ -926,7 +975,7 @@ public class SIPCommander implements ISIPCommander { cmdXml.append("\r\n"); cmdXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); @@ -949,6 +998,8 @@ public class SIPCommander implements ISIPCommander { catalogXml.append("" + device.getDeviceId() + "\r\n"); catalogXml.append("\r\n"); + + Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); @@ -971,7 +1022,7 @@ public class SIPCommander implements ISIPCommander { catalogXml.append("" + device.getDeviceId() + "\r\n"); catalogXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); @@ -996,7 +1047,7 @@ public class SIPCommander implements ISIPCommander { catalogXml.append(" " + device.getDeviceId() + "\r\n"); catalogXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, catalogXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); @@ -1041,7 +1092,7 @@ public class SIPCommander implements ISIPCommander { } recordInfoXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); @@ -1092,7 +1143,7 @@ public class SIPCommander implements ISIPCommander { } cmdXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); @@ -1122,7 +1173,7 @@ public class SIPCommander implements ISIPCommander { cmdXml.append("" + configType + "\r\n"); cmdXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, cmdXml.toString(), null, SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent); @@ -1172,7 +1223,7 @@ public class SIPCommander implements ISIPCommander { mobilePostitionXml.append("60\r\n"); mobilePostitionXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, mobilePostitionXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); @@ -1228,7 +1279,7 @@ public class SIPCommander implements ISIPCommander { * @return true = 命令发送成功 */ @Override - public void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String alarmType, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException { + public void alarmSubscribe(Device device, int expires, String startPriority, String endPriority, String alarmMethod, String startTime, String endTime) throws InvalidArgumentException, SipException, ParseException { StringBuffer cmdXml = new StringBuffer(200); String charset = device.getCharset(); @@ -1246,9 +1297,6 @@ public class SIPCommander implements ISIPCommander { if (!ObjectUtils.isEmpty(alarmMethod)) { cmdXml.append("" + alarmMethod + "\r\n"); } - if (!ObjectUtils.isEmpty(alarmType)) { - cmdXml.append("" + alarmType + "\r\n"); - } if (!ObjectUtils.isEmpty(startTime)) { cmdXml.append("" + startTime + "\r\n"); } @@ -1257,7 +1305,7 @@ public class SIPCommander implements ISIPCommander { } cmdXml.append("\r\n"); - + Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), null, expires, "presence",sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request); @@ -1307,15 +1355,13 @@ public class SIPCommander implements ISIPCommander { } dragXml.append(cmdString); dragXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, dragXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); logger.debug("拉框信令: " + request.toString()); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request); } - - /** * 回放暂停 @@ -1418,7 +1464,7 @@ public class SIPCommander implements ISIPCommander { deviceStatusXml.append("\r\n"); deviceStatusXml.append("\r\n"); - + Request request = headerProvider.createMessageRequest(device, deviceStatusXml.toString(), SipUtils.getNewViaTag(), SipUtils.getNewFromTag(), null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport())); sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()),request); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java old mode 100644 new mode 100755 index 06a08e243..abd32fbaa --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderFroPlatform.java @@ -1,24 +1,34 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; import com.alibaba.fastjson2.JSON; +import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.SipLayer; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.cmd.SIPRequestHeaderPlarformProvider; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; +import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.GitUtil; import gov.nist.javax.sip.message.MessageFactoryImpl; import gov.nist.javax.sip.message.SIPRequest; +import gov.nist.javax.sip.message.SIPResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +38,7 @@ import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.InvalidArgumentException; +import javax.sip.ResponseEvent; import javax.sip.SipException; import javax.sip.SipFactory; import javax.sip.header.CallIdHeader; @@ -64,6 +75,16 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { @Autowired private SIPSender sipSender; + @Autowired + private ZlmHttpHookSubscribe subscribe; + + @Autowired + private UserSetting userSetting; + + + @Autowired + private VideoStreamSessionManager streamSession; + @Autowired private DynamicTask dynamicTask; @@ -148,13 +169,13 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); - Request request = headerProviderPlatformProvider.createMessageRequest( - parentPlatform, - keepaliveXml.toString(), - SipUtils.getNewFromTag(), - SipUtils.getNewViaTag(), - callIdHeader); - sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, errorEvent, okEvent); + Request request = headerProviderPlatformProvider.createMessageRequest( + parentPlatform, + keepaliveXml.toString(), + SipUtils.getNewFromTag(), + SipUtils.getNewViaTag(), + callIdHeader); + sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, errorEvent, okEvent); return callIdHeader.getCallId(); } @@ -462,6 +483,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { sipSender.transmitRequest(parentPlatform.getDeviceIp(), request); } + /** * 向上级回复DeviceStatus查询信息 * @param parentPlatform 平台信息 @@ -557,7 +579,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { @Override public void sendNotifyForCatalogAddOrUpdate(String type, ParentPlatform parentPlatform, List deviceChannels, SubscribeInfo subscribeInfo, Integer index) throws InvalidArgumentException, ParseException, NoSuchFieldException, SipException, IllegalAccessException { - if (parentPlatform == null || deviceChannels == null || deviceChannels.size() == 0 || subscribeInfo == null) { + if (parentPlatform == null || deviceChannels == null || deviceChannels.isEmpty() || subscribeInfo == null) { return; } if (index == null) { @@ -575,6 +597,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { Integer finalIndex = index; String catalogXmlContent = getCatalogXmlContentForCatalogAddOrUpdate(parentPlatform, channels, deviceChannels.size(), type, subscribeInfo); + logger.info("[发送NOTIFY通知]类型: {},发送数量: {}", type, channels.size()); sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> { logger.error("发送NOTIFY通知消息失败。错误:{} {}", eventResult.statusCode, eventResult.msg); }, (eventResult -> { @@ -598,7 +621,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { SIPRequest notifyRequest = headerProviderPlatformProvider.createNotifyRequest(parentPlatform, catalogXmlContent, subscribeInfo); - sipSender.transmitRequest(parentPlatform.getDeviceIp(), notifyRequest); + sipSender.transmitRequest(parentPlatform.getDeviceIp(), notifyRequest, errorEvent, okEvent); } private String getCatalogXmlContentForCatalogAddOrUpdate(ParentPlatform parentPlatform, List channels, int sumNum, String type, SubscribeInfo subscribeInfo) { @@ -610,9 +633,9 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { .append("Catalog\r\n") .append("" + (int) ((Math.random() * 9 + 1) * 100000) + "\r\n") .append("" + parentPlatform.getDeviceGBId() + "\r\n") - .append("1\r\n") + .append(""+ sumNum +"\r\n") .append("\r\n"); - if (channels.size() > 0) { + if (!channels.isEmpty()) { for (DeviceChannel channel : channels) { if (parentPlatform.getServerGBId().equals(channel.getParentId())) { channel.setParentId(parentPlatform.getDeviceGBId()); @@ -679,6 +702,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { }else { channels = deviceChannels.subList(index, deviceChannels.size()); } + logger.info("[发送NOTIFY通知]类型: {},发送数量: {}", type, channels.size()); Integer finalIndex = index; String catalogXmlContent = getCatalogXmlContentForCatalogOther(parentPlatform, channels, type); sendNotify(parentPlatform, catalogXmlContent, subscribeInfo, eventResult -> { @@ -725,13 +749,14 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { if ( parentPlatform ==null) { return ; } + logger.info("[国标级联] 发送录像数据通道: {}", recordInfo.getChannelId()); String characterSet = parentPlatform.getCharacterSet(); StringBuffer recordXml = new StringBuffer(600); recordXml.append("\r\n") .append("\r\n") .append("RecordInfo\r\n") .append("" +recordInfo.getSn() + "\r\n") - .append("" + recordInfo.getChannelId() + "\r\n") + .append("" + deviceChannel.getChannelId() + "\r\n") .append("" + recordInfo.getSumNum() + "\r\n"); if (recordInfo.getRecordList() == null ) { recordXml.append("\r\n"); @@ -741,7 +766,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { for (RecordItem recordItem : recordInfo.getRecordList()) { recordXml.append("\r\n"); if (deviceChannel != null) { - recordXml.append("" + recordItem.getDeviceId() + "\r\n") + recordXml.append("" + deviceChannel.getChannelId() + "\r\n") .append("" + recordItem.getName() + "\r\n") .append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getStartTime()) + "\r\n") .append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(recordItem.getEndTime()) + "\r\n") @@ -761,12 +786,14 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { recordXml.append("\r\n") .append("\r\n"); - + logger.info("[国标级联] 发送录像数据通道:{}, 内容: {}", recordInfo.getChannelId(), recordXml); // callid CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport()); Request request = headerProviderPlatformProvider.createMessageRequest(parentPlatform, recordXml.toString(), fromTag, SipUtils.getNewViaTag(), callIdHeader); - sipSender.transmitRequest(parentPlatform.getDeviceIp(), request); + sipSender.transmitRequest(parentPlatform.getDeviceIp(), request, null, eventResult -> { + logger.info("[国标级联] 发送录像数据通道:{}, 发送成功", recordInfo.getChannelId()); + }); } @@ -806,26 +833,129 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { } @Override - public void streamByeCmd(ParentPlatform parentPlatform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException { + public synchronized void streamByeCmd(ParentPlatform platform, SendRtpItem sendRtpItem) throws SipException, InvalidArgumentException, ParseException { if (sendRtpItem == null ) { logger.info("[向上级发送BYE], sendRtpItem 为NULL"); return; } - if (parentPlatform == null) { + if (platform == null) { logger.info("[向上级发送BYE], platform 为NULL"); return; } - logger.info("[向上级发送BYE], {}/{}", parentPlatform.getServerGBId(), sendRtpItem.getChannelId()); + logger.info("[向上级发送BYE], {}/{}", platform.getServerGBId(), sendRtpItem.getChannelId()); String mediaServerId = sendRtpItem.getMediaServerId(); MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); if (mediaServerItem != null) { mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc()); - zlmServerFactory.closeRtpServer(mediaServerItem, sendRtpItem.getStreamId()); + zlmServerFactory.closeRtpServer(mediaServerItem, sendRtpItem.getStream()); } - SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(parentPlatform, sendRtpItem); + SIPRequest byeRequest = headerProviderPlatformProvider.createByeRequest(platform, sendRtpItem); if (byeRequest == null) { logger.warn("[向上级发送bye]:无法创建 byeRequest"); } - sipSender.transmitRequest(parentPlatform.getDeviceIp(),byeRequest); + sipSender.transmitRequest(platform.getDeviceIp(),byeRequest); + } + + @Override + public void streamByeCmd(ParentPlatform platform, String channelId, String stream, String callId, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException, SsrcTransactionNotFoundException { + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(platform.getServerGBId(), channelId, callId, stream); + if (ssrcTransaction == null) { + throw new SsrcTransactionNotFoundException(platform.getServerGBId(), channelId, callId, stream); + } + + mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc()); + mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream()); + streamSession.remove(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); + + Request byteRequest = headerProviderPlatformProvider.createByteRequest(platform, channelId, ssrcTransaction.getSipTransactionInfo()); + sipSender.transmitRequest(sipLayer.getLocalIp(platform.getDeviceIp()), byteRequest, null, okEvent); + } + + @Override + public void broadcastResultCmd(ParentPlatform platform, DeviceChannel deviceChannel, String sn, boolean result, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException { + if (platform == null || deviceChannel == null) { + return; + } + String characterSet = platform.getCharacterSet(); + StringBuffer mediaStatusXml = new StringBuffer(200); + mediaStatusXml.append("\r\n"); + mediaStatusXml.append("\r\n"); + mediaStatusXml.append("Broadcast\r\n"); + mediaStatusXml.append("" + sn + "\r\n"); + mediaStatusXml.append("" + deviceChannel.getChannelId() + "\r\n"); + mediaStatusXml.append("" + (result?"OK":"ERROR") + "\r\n"); + mediaStatusXml.append("\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); + }); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/ISIPRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/ISIPRequestProcessor.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java old mode 100644 new mode 100755 index d52f4f6a1..7cbfe70e4 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/SIPRequestProcessorParent.java @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; +import com.google.common.primitives.Bytes; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; import org.apache.commons.lang3.ArrayUtils; @@ -81,6 +82,7 @@ public abstract class SIPRequestProcessorParent { return responseAck(sipRequest, statusCode, msg, null); } + public SIPResponse responseAck(SIPRequest sipRequest, int statusCode, String msg, ResponseAckExtraParam responseAckExtraParam) throws SipException, InvalidArgumentException, ParseException { if (sipRequest.getToHeader().getTag() == null) { sipRequest.getToHeader().setTag(SipUtils.getNewTag()); @@ -123,6 +125,8 @@ public abstract class SIPRequestProcessorParent { return response; } + + /** * 回复带sdp的200 */ @@ -140,7 +144,10 @@ public abstract class SIPRequestProcessorParent { responseAckExtraParam.content = sdp; responseAckExtraParam.sipURI = sipURI; - return responseAck(request, Response.OK, null, responseAckExtraParam); + SIPResponse sipResponse = responseAck(request, Response.OK, null, responseAckExtraParam); + + + return sipResponse; } /** @@ -173,7 +180,8 @@ public abstract class SIPRequestProcessorParent { reader.setEncoding(charset); // 对海康出现的未转义字符做处理。 String[] destStrArray = new String[]{"<",">","&","'","""}; - char despChar = '&'; // 或许可扩展兼容其他字符 + // 或许可扩展兼容其他字符 + char despChar = '&'; byte destBye = (byte) despChar; List result = new ArrayList<>(); byte[] rawContent = request.getRawContent(); @@ -196,15 +204,14 @@ public abstract class SIPRequestProcessorParent { result.add(rawContent[i]); } } - Byte[] bytes = new Byte[0]; - byte[] bytesResult = ArrayUtils.toPrimitive(result.toArray(bytes)); + byte[] bytesResult = Bytes.toArray(result); Document xml; try { xml = reader.read(new ByteArrayInputStream(bytesResult)); }catch (DocumentException e) { - logger.warn("[xml解析异常]: 愿文如下: \r\n{}", new String(bytesResult)); - logger.warn("[xml解析异常]: 愿文如下: 尝试兼容性处理"); + logger.warn("[xml解析异常]: 原文如下: \r\n{}", new String(bytesResult)); + logger.warn("[xml解析异常]: 原文如下: 尝试兼容性处理"); String[] xmlLineArray = new String(bytesResult).split("\\r?\\n"); // 兼容海康的address字段带有<破换xml结构导致无法解析xml的问题 @@ -220,4 +227,5 @@ public abstract class SIPRequestProcessorParent { return xml.getRootElement(); } + } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java index 61a600410..70048209f 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java @@ -1,22 +1,20 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; -import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; -import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType; +import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.service.IDeviceService; import com.genersoft.iot.vmp.service.IMediaServerService; -import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; +import com.genersoft.iot.vmp.service.IPlayService; import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg; import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; @@ -27,25 +25,23 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; -import javax.sip.SipException; import javax.sip.address.SipURI; import javax.sip.header.CallIdHeader; import javax.sip.header.FromHeader; import javax.sip.header.HeaderAddress; import javax.sip.header.ToHeader; -import java.text.ParseException; import java.util.HashMap; import java.util.Map; /** * SIP命令类型: ACK请求 + * @author lin */ @Component public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { - private Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class); + private final Logger logger = LoggerFactory.getLogger(AckRequestProcessor.class); private final String method = "ACK"; @Autowired @@ -66,6 +62,9 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In @Autowired private IVideoManagerStorage storager; + @Autowired + private IDeviceService deviceService; + @Autowired private ZLMServerFactory zlmServerFactory; @@ -75,106 +74,126 @@ public class AckRequestProcessor extends SIPRequestProcessorParent implements In @Autowired private IMediaServerService mediaServerService; - @Autowired - private ZlmHttpHookSubscribe subscribe; - @Autowired private DynamicTask dynamicTask; - @Autowired - private ISIPCommander cmder; - - @Autowired - private ISIPCommanderForPlatform commanderForPlatform; - @Autowired private RedisGbPlayMsgListener redisGbPlayMsgListener; + @Autowired + private IPlayService playService; + /** * 处理 ACK请求 - * - * @param evt */ @Override public void process(RequestEvent evt) { CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); - - String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser(); - logger.info("[收到ACK]: platformGbId->{}", platformGbId); - ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(platformGbId); - // 取消设置的超时任务 dynamicTask.stop(callIdHeader.getCallId()); - String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser(); - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId()); + String fromUserId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser(); + String toUserId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser(); + logger.info("[收到ACK]: 来自->{}", fromUserId); + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId()); if (sendRtpItem == null) { - logger.warn("[收到ACK]:未找到通道({})的推流信息", channelId); + logger.warn("[收到ACK]:未找到来自{},目标为({})的推流信息",fromUserId, toUserId); + return; + } + // tcp主动时,此时是级联下级平台,在回复200ok时,本地已经请求zlm开启监听,跳过下面步骤 + if (sendRtpItem.isTcpActive()) { + logger.info("收到ACK,rtp/{} TCP主动方式后续处理", sendRtpItem.getStream()); return; } - String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); - logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}", sendRtpItem.getStreamId(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc()); + logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, 协议:{}", + sendRtpItem.getStream(), + sendRtpItem.getIp(), + sendRtpItem.getPort(), + sendRtpItem.getSsrc(), + sendRtpItem.isTcp()?(sendRtpItem.isTcpActive()?"TCP主动":"TCP被动"):"UDP" + ); + ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(fromUserId); + + if (parentPlatform != null) { + Map 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 param = getSendRtpParam(sendRtpItem); + JSONObject startSendRtpStreamResult = sendRtp(sendRtpItem, mediaInfo, param); + if (startSendRtpStreamResult != null) { + playService.startSendRtpStreamHand(sendRtpItem, device, startSendRtpStreamResult, param, callIdHeader); + } + } + } + + private Map getSendRtpParam(SendRtpItem sendRtpItem) { + String isUdp = sendRtpItem.isTcp() ? "0" : "1"; Map param = new HashMap<>(12); param.put("vhost","__defaultVhost__"); param.put("app",sendRtpItem.getApp()); - param.put("stream",sendRtpItem.getStreamId()); + param.put("stream",sendRtpItem.getStream()); param.put("ssrc", sendRtpItem.getSsrc()); param.put("dst_url",sendRtpItem.getIp()); param.put("dst_port", sendRtpItem.getPort()); - param.put("is_udp", is_Udp); param.put("src_port", sendRtpItem.getLocalPort()); param.put("pt", sendRtpItem.getPt()); param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); + param.put("is_udp", isUdp); if (!sendRtpItem.isTcp()) { - // 开启rtcp保活 + // udp模式下开启rtcp保活 param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0"); } - // tcp主动时,此时是级联下级平台,在回复200ok时,本地已经请求zlm开启监听,跳过下面步骤 - if (sendRtpItem.isTcpActive()) { - return; - } - if (mediaInfo == null) { - RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance( - sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(), - sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(), - sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio()); - redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, jsonObject->{ - startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, jsonObject, param, callIdHeader); - }); - }else { - JSONObject startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param); - if (startSendRtpStreamResult != null) { - startSendRtpStreamHand(evt, sendRtpItem, parentPlatform, startSendRtpStreamResult, param, callIdHeader); - } - } + return param; } - private void startSendRtpStreamHand(RequestEvent evt, SendRtpItem sendRtpItem, ParentPlatform parentPlatform, - JSONObject jsonObject, Map 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 { - logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param)); - if (sendRtpItem.isOnlyAudio()) { - // TODO 可能是语音对讲 + + private JSONObject sendRtp(SendRtpItem sendRtpItem, MediaServerItem mediaInfo, Map param){ + JSONObject startSendRtpStreamResult = null; + if (sendRtpItem.getLocalPort() != 0) { + if (sendRtpItem.isTcpActive()) { + startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param); }else { - // 向上级平台 - try { - commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId()); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); - } + param.put("dst_url", sendRtpItem.getIp()); + param.put("dst_port", sendRtpItem.getPort()); + startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param); + } + }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); } } + return startSendRtpStreamResult; + } + } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java old mode 100644 new mode 100755 index 374e5dcc0..f66e3c380 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java @@ -5,10 +5,12 @@ import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; @@ -45,6 +47,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In @Autowired private ISIPCommander cmder; + @Autowired + private ISIPCommanderForPlatform commanderForPlatform; + @Autowired private IRedisCatchStorage redisCatchStorage; @@ -57,6 +62,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In @Autowired private IDeviceService deviceService; + @Autowired + private AudioBroadcastManager audioBroadcastManager; + @Autowired private IDeviceChannelService channelService; @@ -78,6 +86,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In @Autowired private VideoStreamSessionManager streamSession; + @Autowired + private IPlayService playService; + @Autowired private UserSetting userSetting; @@ -100,27 +111,31 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In logger.error("[回复BYE信息失败],{}", e.getMessage()); } CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId()); + // 收流端发送的停止 if (sendRtpItem != null){ - logger.info("[收到bye] 来自平台{}, 停止通道:{}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId()); - String streamId = sendRtpItem.getStreamId(); + logger.info("[收到bye] 来自{},停止通道:{}, 类型: {}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getPlayType()); + + String streamId = sendRtpItem.getStream(); Map param = new HashMap<>(); param.put("vhost","__defaultVhost__"); param.put("app",sendRtpItem.getApp()); param.put("stream",streamId); param.put("ssrc",sendRtpItem.getSsrc()); - logger.info("[收到bye] 停止向上级推流:{}", streamId); + logger.info("[收到bye] 停止推流:{}", streamId); MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), callIdHeader.getCallId(), null); zlmServerFactory.stopSendRtpStream(mediaInfo, param); + if (userSetting.getUseCustomSsrcForParentInvite()) { + mediaServerService.releaseSsrc(mediaInfo.getId(), sendRtpItem.getSsrc()); + } if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) { ParentPlatform platform = platformService.queryPlatformByServerGBId(sendRtpItem.getPlatformId()); if (platform != null) { MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, - sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(), + sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(), sendRtpItem.getPlatformId(), platform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId()); messageForPushChannel.setPlatFormIndex(platform.getId()); redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel); @@ -129,6 +144,13 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In } } + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); + if (audioBroadcastCatch != null && audioBroadcastCatch.getSipTransactionInfo().getCallId().equals(callIdHeader.getCallId())) { + // 来自上级平台的停止对讲 + logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); + audioBroadcastManager.del(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); + } + int totalReaderCount = zlmServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId); if (totalReaderCount <= 0) { logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId); @@ -138,7 +160,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId); } try { - logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); + logger.info("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); cmder.streamByeCmd(device, sendRtpItem.getChannelId(), streamId, null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { @@ -146,15 +168,32 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In } } } - }else { + } // 可能是设备发送的停止 - SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransactionByCallId(callIdHeader.getCallId()); if (ssrcTransaction == null) { return; } logger.info("[收到bye] 来自设备:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); + ParentPlatform platform = platformService.queryPlatformByServerGBId(ssrcTransaction.getDeviceId()); + if (platform != null ) { + if (ssrcTransaction.getType().equals(InviteSessionType.BROADCAST)) { + logger.info("[收到bye] 上级停止语音对讲,来自:{}, 通道已停止推流: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); + DeviceChannel channel = storager.queryChannelInParentPlatform(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); + if (channel == null) { + logger.info("[收到bye] 未找到通道,设备:{}, 通道:{}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); + return; + } + String mediaServerId = ssrcTransaction.getMediaServerId(); + platformService.stopBroadcast(platform, channel, ssrcTransaction.getStream(), false, + mediaServerService.getOne(mediaServerId)); + + playService.stopAudioBroadcast(channel.getDeviceId(), channel.getChannelId()); + } + + }else { Device device = deviceService.getDevice(ssrcTransaction.getDeviceId()); if (device == null) { logger.info("[收到bye] 未找到设备:{} ", ssrcTransaction.getDeviceId()); @@ -178,7 +217,20 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In if (mediaServerItem != null) { mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransaction.getSsrc()); } - streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcTransaction.getStream()); + 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()); + } } } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java old mode 100644 new mode 100755 index 397a4a0f7..4646b38d6 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java @@ -2,14 +2,19 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; +import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; @@ -18,10 +23,7 @@ import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.dto.*; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; -import com.genersoft.iot.vmp.service.IMediaServerService; -import com.genersoft.iot.vmp.service.IPlayService; -import com.genersoft.iot.vmp.service.IStreamProxyService; -import com.genersoft.iot.vmp.service.IStreamPushService; +import com.genersoft.iot.vmp.service.*; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; @@ -33,8 +35,10 @@ import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.utils.DateUtil; import gov.nist.javax.sdp.TimeDescriptionImpl; import gov.nist.javax.sdp.fields.TimeField; +import gov.nist.javax.sdp.fields.URIField; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; @@ -66,19 +70,23 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements private final String method = "INVITE"; @Autowired - private SIPCommanderFroPlatform cmderFroPlatform; + private ISIPCommanderForPlatform cmderFroPlatform; @Autowired private IVideoManagerStorage storager; @Autowired private IStreamPushService streamPushService; + @Autowired private IStreamProxyService streamProxyService; @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private IInviteStreamService inviteStreamService; + @Autowired private SSRCFactory ssrcFactory; @@ -94,6 +102,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements @Autowired private SIPSender sipSender; + @Autowired + private AudioBroadcastManager audioBroadcastManager; + @Autowired private ZLMServerFactory zlmServerFactory; @@ -112,10 +123,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements @Autowired private ZLMMediaListManager mediaListManager; + @Autowired + private SipConfig config; + @Autowired private RedisGbPlayMsgListener redisGbPlayMsgListener; + @Autowired + private VideoStreamSessionManager streamSession; + @Override public void afterPropertiesSet() throws Exception { @@ -133,11 +150,24 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements // Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令 try { SIPRequest request = (SIPRequest)evt.getRequest(); - String channelId = SipUtils.getChannelIdFromRequest(request); + String channelIdFromSub = SipUtils.getChannelIdFromRequest(request); + + // 解析sdp消息, 使用jainsip 自带的sdp解析方式 + String contentString = new String(request.getRawContent()); + Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString); + SessionDescription sdp = gb28181Sdp.getBaseSdb(); + String sessionName = sdp.getSessionName().getValue(); + String channelIdFromSdp = null; + if(StringUtils.equalsIgnoreCase("Playback", sessionName)){ + URIField uriField = (URIField)sdp.getURI(); + channelIdFromSdp = uriField.getURI().split(":")[0]; + } + final String channelId = StringUtils.isNotBlank(channelIdFromSdp) ? channelIdFromSdp : channelIdFromSub; + String requesterId = SipUtils.getUserIdFromFromHeader(request); CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); if (requesterId == null || channelId == null) { - logger.info("无法从FromHeader的Address中获取到平台id,返回400"); + logger.info("无法从请求中获取到平台id,返回400"); // 参数不全, 发400,请求错误 try { responseAck(request, Response.BAD_REQUEST); @@ -147,11 +177,13 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements return; } + logger.info("[INVITE] requesterId: {}, callId: {}, 来自:{}:{}", + requesterId, callIdHeader.getCallId(), request.getRemoteAddress(), request.getRemotePort()); // 查询请求是否来自上级平台\设备 ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId); if (platform == null) { - inviteFromDeviceHandle(request, requesterId); + inviteFromDeviceHandle(request, requesterId, channelId); } else { // 查询平台下是否有该通道 @@ -161,7 +193,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements MediaServerItem mediaServerItem = null; StreamPushItem streamPushItem = null; - StreamProxyItem proxyByAppAndStream =null; + StreamProxyItem proxyByAppAndStream = null; // 不是通道可能是直播流 if (channel != null && gbStream == null) { // 通道存在,发100,TRYING @@ -204,7 +236,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } return; } - }else if("proxy".equals(gbStream.getStreamType())){ + } else if ("proxy".equals(gbStream.getStreamType())) { proxyByAppAndStream = streamProxyService.getStreamProxyByAppAndStream(gbStream.getApp(), gbStream.getStream()); if (proxyByAppAndStream == null) { logger.info("[ app={}, stream={} ]找不到zlm {},返回410", gbStream.getApp(), gbStream.getStream(), mediaServerId); @@ -240,12 +272,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } return; } - // 解析sdp消息, 使用jainsip 自带的sdp解析方式 - String contentString = new String(request.getRawContent()); - - Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString); - SessionDescription sdp = gb28181Sdp.getBaseSdb(); - String sessionName = sdp.getSessionName().getValue(); Long startTime = null; Long stopTime = null; @@ -342,10 +368,10 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements if (mediaTransmissionTCP) { if (tcpActive) { streamTypeStr = "TCP-ACTIVE"; - }else { + } else { streamTypeStr = "TCP-PASSIVE"; } - }else { + } else { streamTypeStr = "UDP"; } logger.info("[上级Invite] {}, 平台:{}, 通道:{}, 收流地址:{}:{},收流方式:{}, ssrc:{}", sessionName, username, channelId, addressStr, port, streamTypeStr, ssrc); @@ -394,7 +420,16 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements // 非严格模式端口不统一, 增加兼容性,修改为一个不为0的端口 localPort = new Random().nextInt(65535) + 1; } - content.append("m=video " + localPort + " RTP/AVP 96\r\n"); + if (sendRtpItem.isTcp()) { + content.append("m=video " + localPort + " TCP/RTP/AVP 96\r\n"); + if (!sendRtpItem.isTcpActive()) { + content.append("a=setup:active\r\n"); + } else { + content.append("a=setup:passive\r\n"); + } + }else { + content.append("m=video " + localPort + " RTP/AVP 96\r\n"); + } content.append("a=sendonly\r\n"); content.append("a=rtpmap:96 PS/90000\r\n"); content.append("y=" + sendRtpItem.getSsrc() + "\r\n"); @@ -420,7 +455,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements Map param = new HashMap<>(12); param.put("vhost","__defaultVhost__"); param.put("app",sendRtpItem.getApp()); - param.put("stream",sendRtpItem.getStreamId()); + param.put("stream",sendRtpItem.getStream()); param.put("ssrc", sendRtpItem.getSsrc()); if (!sendRtpItem.isTcpActive()) { param.put("dst_url",sendRtpItem.getIp()); @@ -436,7 +471,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements // 开启rtcp保活 param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0"); } - JSONObject startSendRtpStreamResult = zlmServerFactory.startSendRtpStreamForPassive(mediaInfo, param); + JSONObject startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param); if (startSendRtpStreamResult != null) { startSendRtpStreamHand(evt, sendRtpItem, null, startSendRtpStreamResult, param, callIdHeader); } @@ -452,81 +487,78 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements Response response = getMessageFactory().createResponse(statusCode, evt.getRequest()); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); } - } catch (ParseException | SipException e) { + } catch (ParseException | SipException e) { logger.error("未处理的异常 ", e); } }); sendRtpItem.setApp("rtp"); if ("Playback".equalsIgnoreCase(sessionName)) { sendRtpItem.setPlayType(InviteStreamType.PLAYBACK); - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam()); - sendRtpItem.setStreamId(ssrcInfo.getStream()); + String startTimeStr = DateUtil.urlFormatter.format(start); + String endTimeStr = DateUtil.urlFormatter.format(end); + String stream = device.getDeviceId() + "_" + channelId + "_" + startTimeStr + "_" + endTimeStr; + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, stream, null, device.isSsrcCheck(), true, 0,false, false, device.getStreamModeForParam()); // 写入redis, 超时时回复 redisCatchStorage.updateSendRTPSever(sendRtpItem); playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start), DateUtil.formatter.format(end), (code, msg, data) -> { - if (code == InviteErrorCode.SUCCESS.getCode()){ + if (code == InviteErrorCode.SUCCESS.getCode()) { hookEvent.run(code, msg, data); - }else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()){ + } else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()) { logger.info("[录像回放]超时, 用户:{}, 通道:{}", username, channelId); redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); errorEvent.run(code, msg, data); - }else { + } else { errorEvent.run(code, msg, data); } }); - }else if ("Download".equalsIgnoreCase(sessionName)) { + } else if ("Download".equalsIgnoreCase(sessionName)) { // 获取指定的下载速度 Vector sdpMediaDescriptions = sdp.getMediaDescriptions(true); MediaDescription mediaDescription = null; String downloadSpeed = "1"; if (sdpMediaDescriptions.size() > 0) { - mediaDescription = (MediaDescription)sdpMediaDescriptions.get(0); + mediaDescription = (MediaDescription) sdpMediaDescriptions.get(0); } if (mediaDescription != null) { downloadSpeed = mediaDescription.getAttribute("downloadspeed"); } sendRtpItem.setPlayType(InviteStreamType.DOWNLOAD); - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam()); - sendRtpItem.setStreamId(ssrcInfo.getStream()); + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam()); + sendRtpItem.setStream(ssrcInfo.getStream()); // 写入redis, 超时时回复 redisCatchStorage.updateSendRTPSever(sendRtpItem); playService.download(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start), DateUtil.formatter.format(end), Integer.parseInt(downloadSpeed), (code, msg, data) -> { - if (code == InviteErrorCode.SUCCESS.getCode()){ + if (code == InviteErrorCode.SUCCESS.getCode()) { hookEvent.run(code, msg, data); - }else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()){ + } else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()) { logger.info("[录像下载]超时, 用户:{}, 通道:{}", username, channelId); redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); errorEvent.run(code, msg, data); - }else { + } else { errorEvent.run(code, msg, data); } }); - }else { - + } else { + sendRtpItem.setPlayType(InviteStreamType.PLAY); + String streamId = String.format("%s_%s", device.getDeviceId(), channelId); + sendRtpItem.setStream(streamId); + redisCatchStorage.updateSendRTPSever(sendRtpItem); SSRCInfo ssrcInfo = playService.play(mediaServerItem, device.getDeviceId(), channelId, ssrc, ((code, msg, data) -> { - if (code == InviteErrorCode.SUCCESS.getCode()){ + if (code == InviteErrorCode.SUCCESS.getCode()) { hookEvent.run(code, msg, data); - }else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()){ + } else if (code == InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode() || code == InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode()) { logger.info("[上级点播]超时, 用户:{}, 通道:{}", username, channelId); redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); errorEvent.run(code, msg, data); - }else { + } else { errorEvent.run(code, msg, data); } })); - sendRtpItem.setPlayType(InviteStreamType.PLAY); - String streamId = null; - if (mediaServerItem.isRtpEnable()) { - streamId = String.format("%s_%s", device.getDeviceId(), channelId); - }else { - streamId = String.format("%08x", Integer.parseInt(ssrcInfo.getSsrc())).toUpperCase(); - } - sendRtpItem.setStreamId(streamId); sendRtpItem.setSsrc(ssrcInfo.getSsrc()); redisCatchStorage.updateSendRTPSever(sendRtpItem); @@ -541,24 +573,24 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements ssrc = gb28181Sdp.getSsrc(); } - if("push".equals(gbStream.getStreamType())) { + if ("push".equals(gbStream.getStreamType())) { if (streamPushItem != null && streamPushItem.isPushIng()) { // 推流状态 pushStream(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); } else { // 未推流 拉起 - notifyStreamOnline(evt, request,gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, + notifyStreamOnline(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); } - }else if ("proxy".equals(gbStream.getStreamType())){ + } else if ("proxy".equals(gbStream.getStreamType())) { if (null != proxyByAppAndStream) { - if(proxyByAppAndStream.isStatus()){ - pushProxyStream(evt, request, gbStream, platform, callIdHeader, mediaServerItem, port, tcpActive, + if (proxyByAppAndStream.isStatus()) { + pushProxyStream(evt, request, gbStream, platform, callIdHeader, mediaServerItem, port, tcpActive, mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); - }else{ + } else { //开启代理拉流 - notifyStreamOnline(evt, request,gbStream, null, platform, callIdHeader, mediaServerItem, port, tcpActive, + notifyStreamOnline(evt, request, gbStream, null, platform, callIdHeader, mediaServerItem, port, tcpActive, mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); } } @@ -599,33 +631,34 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements SendRtpItem sendRtpItem = zlmServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp()); - if (sendRtpItem == null) { - logger.warn("服务器端口资源不足"); - try { - responseAck(request, Response.BUSY_HERE); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage()); - } - return; + if (sendRtpItem == null) { + logger.warn("服务器端口资源不足"); + try { + responseAck(request, Response.BUSY_HERE); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage()); } - if (tcpActive != null) { - sendRtpItem.setTcpActive(tcpActive); - } - sendRtpItem.setPlayType(InviteStreamType.PUSH); - // 写入redis, 超时时回复 - sendRtpItem.setStatus(1); - sendRtpItem.setCallId(callIdHeader.getCallId()); - sendRtpItem.setFromTag(request.getFromTag()); + return; + } + if (tcpActive != null) { + sendRtpItem.setTcpActive(tcpActive); + } + sendRtpItem.setPlayType(InviteStreamType.PUSH); + // 写入redis, 超时时回复 + sendRtpItem.setStatus(1); + sendRtpItem.setCallId(callIdHeader.getCallId()); + sendRtpItem.setFromTag(request.getFromTag()); - SIPResponse response = sendStreamAck(mediaServerItem, request, sendRtpItem, platform, evt); - if (response != null) { - sendRtpItem.setToTag(response.getToTag()); - } - redisCatchStorage.updateSendRTPSever(sendRtpItem); + SIPResponse response = sendStreamAck(mediaServerItem, request, sendRtpItem, platform, evt); + if (response != null) { + sendRtpItem.setToTag(response.getToTag()); + } + redisCatchStorage.updateSendRTPSever(sendRtpItem); } } + private void pushStream(RequestEvent evt, SIPRequest request, GbStream gbStream, StreamPushItem streamPushItem, ParentPlatform platform, CallIdHeader callIdHeader, MediaServerItem mediaServerItem, int port, Boolean tcpActive, boolean mediaTransmissionTCP, @@ -660,12 +693,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements if (response != null) { sendRtpItem.setToTag(response.getToTag()); } - redisCatchStorage.updateSendRTPSever(sendRtpItem); } else { // 不在线 拉起 - notifyStreamOnline(evt, request,gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, + notifyStreamOnline(evt, request, gbStream, streamPushItem, platform, callIdHeader, mediaServerItem, port, tcpActive, mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); } @@ -675,6 +707,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); } } + /** * 通知流上线 */ @@ -691,7 +724,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam)hookParam; logger.info("[上级点播]拉流代理已经就绪, {}/{}", streamChangedHookParam.getApp(), streamChangedHookParam.getStream()); dynamicTask.stop(callIdHeader.getCallId()); - pushProxyStream(evt, request, gbStream, platform, callIdHeader, mediaServerItem, port, tcpActive, + pushProxyStream(evt, request, gbStream, platform, callIdHeader, mediaServerItem, port, tcpActive, mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); }); dynamicTask.startDelay(callIdHeader.getCallId(), () -> { @@ -708,9 +741,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements zlmHttpHookSubscribe.removeSubscribe(hookSubscribe); dynamicTask.stop(callIdHeader.getCallId()); } - - - } else if ("push".equals(gbStream.getStreamType())) { if (!platform.isStartOfflinePush()) { // 平台设置中关闭了拉起离线的推流则直接回复 @@ -733,13 +763,10 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements dynamicTask.startDelay(callIdHeader.getCallId(), () -> { logger.info("[ app={}, stream={} ] 等待设备开始推流超时", gbStream.getApp(), gbStream.getStream()); try { + redisPushStreamResponseListener.removeEvent(gbStream.getApp(), gbStream.getStream()); mediaListManager.removedChannelOnlineEventLister(gbStream.getApp(), gbStream.getStream()); responseAck(request, Response.REQUEST_TIMEOUT); // 超时 - } catch (SipException e) { - logger.error("未处理的异常 ", e); - } catch (InvalidArgumentException e) { - logger.error("未处理的异常 ", e); - } catch (ParseException e) { + } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("未处理的异常 ", e); } }, userSetting.getPlatformPlayTimeout()); @@ -750,6 +777,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements // 添加在本机上线的通知 mediaListManager.addChannelOnlineEventLister(gbStream.getApp(), gbStream.getStream(), (app, stream, serverId) -> { dynamicTask.stop(callIdHeader.getCallId()); + redisPushStreamResponseListener.removeEvent(gbStream.getApp(), gbStream.getStream()); if (serverId.equals(userSetting.getServerId())) { SendRtpItem sendRtpItem = zlmServerFactory.createSendRtpItem(mediaServerItem, addressStr, finalPort, ssrc, requesterId, app, stream, channelId, mediaTransmissionTCP, platform.isRtcp()); @@ -814,7 +842,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements // 发送redis消息 redisGbPlayMsgListener.sendMsg(streamPushItem.getServerId(), streamPushItem.getMediaServerId(), streamPushItem.getApp(), streamPushItem.getStream(), addressStr, port, ssrc, requesterId, - channelId, mediaTransmissionTCP, platform.isRtcp(),null, responseSendItemMsg -> { + channelId, mediaTransmissionTCP, platform.isRtcp(),platform.getName(), responseSendItemMsg -> { SendRtpItem sendRtpItem = responseSendItemMsg.getSendRtpItem(); if (sendRtpItem == null || responseSendItemMsg.getMediaServerItem() == null) { logger.warn("服务器端口资源不足"); @@ -839,7 +867,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements sendRtpItem.setCallId(callIdHeader.getCallId()); sendRtpItem.setFromTag(request.getFromTag()); - SIPResponse response = sendStreamAck(responseSendItemMsg.getMediaServerItem(), request,sendRtpItem, platform, evt); + SIPResponse response = sendStreamAck(responseSendItemMsg.getMediaServerItem(), request, sendRtpItem, platform, evt); if (response != null) { sendRtpItem.setToTag(response.getToTag()); } @@ -880,8 +908,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements content.append("t=0 0\r\n"); // 非严格模式端口不统一, 增加兼容性,修改为一个不为0的端口 int localPort = sendRtpItem.getLocalPort(); - if(localPort == 0) - { + if (localPort == 0) { localPort = new Random().nextInt(65535) + 1; } content.append("m=video " + localPort + " RTP/AVP 96\r\n"); @@ -900,39 +927,75 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements try { return responseSdpAck(request, content.toString(), platform); - } catch (SipException e) { - logger.error("未处理的异常 ", e); - } catch (InvalidArgumentException e) { - logger.error("未处理的异常 ", e); - } catch (ParseException e) { + } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("未处理的异常 ", e); } return null; } - public void inviteFromDeviceHandle(SIPRequest request, String requesterId) { + public void inviteFromDeviceHandle(SIPRequest request, String requesterId, String channelId) { + + String realChannelId = null; // 非上级平台请求,查询是否设备请求(通常为接收语音广播的设备) Device device = redisCatchStorage.getDevice(requesterId); + // 判断requesterId是设备还是通道 + if (device == null) { + device = storager.queryVideoDeviceByChannelId(requesterId); + realChannelId = requesterId; + }else { + realChannelId = channelId; + } + if (device == null) { + // 检查channelID是否可用 + device = redisCatchStorage.getDevice(channelId); + if (device == null) { + device = storager.queryVideoDeviceByChannelId(channelId); + realChannelId = channelId; + } + } + + if (device == null) { + logger.warn("来自设备的Invite请求,无法从请求信息中确定所属设备,已忽略,requesterId: {}/{}", requesterId, channelId); + try { + responseAck(request, Response.FORBIDDEN); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 来自设备的Invite请求,无法从请求信息中确定所属设备 FORBIDDEN: {}", e.getMessage()); + } + return; + } + + AudioBroadcastCatch broadcastCatch = audioBroadcastManager.get(device.getDeviceId(), realChannelId); + if (broadcastCatch == null) { + logger.warn("来自设备的Invite请求非语音广播,已忽略,requesterId: {}/{}", requesterId, channelId); + try { + responseAck(request, Response.FORBIDDEN); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 来自设备的Invite请求非语音广播 FORBIDDEN: {}", e.getMessage()); + } + return; + } if (device != null) { logger.info("收到设备" + requesterId + "的语音广播Invite请求"); + String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + broadcastCatch.getChannelId(); + dynamicTask.stop(key); try { responseAck(request, Response.TRYING); } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("[命令发送失败] invite BAD_REQUEST: {}", e.getMessage()); + playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId()); + return; } String contentString = new String(request.getRawContent()); - // jainSip不支持y=字段, 移除移除以解析。 - String ssrc = "0000000404"; try { Gb28181Sdp gb28181Sdp = SipUtils.parseSDP(contentString); SessionDescription sdp = gb28181Sdp.getBaseSdb(); // 获取支持的格式 Vector mediaDescriptions = sdp.getMediaDescriptions(true); + // 查看是否支持PS 负载96 int port = -1; - //boolean recvonly = false; boolean mediaTransmissionTCP = false; Boolean tcpActive = null; for (int i = 0; i < mediaDescriptions.size(); i++) { @@ -964,26 +1027,147 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements try { responseAck(request, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式,发415 } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] invite 不支持的媒体格式,返回415, {}", e.getMessage()); + logger.error("[命令发送失败] invite 不支持的媒体格式: {}", e.getMessage()); + playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId()); + return; } return; } - String username = sdp.getOrigin().getUsername(); - String addressStr = sdp.getConnection().getAddress(); - logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}", username, addressStr, port, ssrc); + String addressStr = sdp.getOrigin().getAddress(); + logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}, {}", requesterId, addressStr, port, gb28181Sdp.getSsrc(), + mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP"); + + MediaServerItem mediaServerItem = broadcastCatch.getMediaServerItem(); + if (mediaServerItem == null) { + logger.warn("未找到语音喊话使用的zlm"); + try { + responseAck(request, Response.BUSY_HERE); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] invite 未找到可用的zlm: {}", e.getMessage()); + playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId()); + } + return; + } + logger.info("设备{}请求语音流, 收流地址:{}:{},ssrc:{}, {}, 对讲方式:{}", requesterId, addressStr, port, gb28181Sdp.getSsrc(), + mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP", sdp.getSessionName().getValue()); + CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); + + SendRtpItem sendRtpItem = zlmServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, gb28181Sdp.getSsrc(), requesterId, + device.getDeviceId(), broadcastCatch.getChannelId(), + mediaTransmissionTCP, false); + + if (sendRtpItem == null) { + logger.warn("服务器端口资源不足"); + try { + responseAck(request, Response.BUSY_HERE); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] invite 服务器端口资源不足: {}", e.getMessage()); + playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId()); + return; + } + return; + } + + + sendRtpItem.setPlayType(InviteStreamType.BROADCAST); + sendRtpItem.setCallId(callIdHeader.getCallId()); + sendRtpItem.setPlatformId(requesterId); + sendRtpItem.setStatus(1); + sendRtpItem.setApp(broadcastCatch.getApp()); + sendRtpItem.setStream(broadcastCatch.getStream()); + sendRtpItem.setPt(8); + sendRtpItem.setUsePs(false); + sendRtpItem.setRtcp(false); + sendRtpItem.setOnlyAudio(true); + sendRtpItem.setTcp(mediaTransmissionTCP); + if (tcpActive != null) { + sendRtpItem.setTcpActive(tcpActive); + } + + redisCatchStorage.updateSendRTPSever(sendRtpItem); + + Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, broadcastCatch.getApp(), broadcastCatch.getStream()); + if (streamReady) { + sendOk(device, sendRtpItem, sdp, request, mediaServerItem, mediaTransmissionTCP, gb28181Sdp.getSsrc()); + } else { + logger.warn("[语音通话], 未发现待推送的流,app={},stream={}", broadcastCatch.getApp(), broadcastCatch.getStream()); + try { + responseAck(request, Response.GONE); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 语音通话 回复410失败, {}", e.getMessage()); + return; + } + playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId()); + } } catch (SdpException e) { logger.error("[SDP解析异常]", e); + playService.stopAudioBroadcast(device.getDeviceId(), broadcastCatch.getChannelId()); } - - - } else { logger.warn("来自无效设备/平台的请求"); try { - responseAck(request, Response.BAD_REQUEST);; // 不支持的格式,发415 + responseAck(request, Response.BAD_REQUEST); + ; // 不支持的格式,发415 } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("[命令发送失败] invite 来自无效设备/平台的请求, {}", e.getMessage()); } } } + + SIPResponse sendOk(Device device, SendRtpItem sendRtpItem, SessionDescription sdp, SIPRequest request, MediaServerItem mediaServerItem, boolean mediaTransmissionTCP, String ssrc) { + SIPResponse sipResponse = null; + try { + sendRtpItem.setStatus(2); + redisCatchStorage.updateSendRTPSever(sendRtpItem); + StringBuffer content = new StringBuffer(200); + content.append("v=0\r\n"); + content.append("o=" + config.getId() + " " + sdp.getOrigin().getSessionId() + " " + sdp.getOrigin().getSessionVersion() + " IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); + content.append("s=Play\r\n"); + content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n"); + content.append("t=0 0\r\n"); + + if (mediaTransmissionTCP) { + content.append("m=audio " + sendRtpItem.getLocalPort() + " TCP/RTP/AVP 8\r\n"); + } else { + content.append("m=audio " + sendRtpItem.getLocalPort() + " RTP/AVP 8\r\n"); + } + + content.append("a=rtpmap:8 PCMA/8000/1\r\n"); + + content.append("a=sendonly\r\n"); + if (sendRtpItem.isTcp()) { + content.append("a=connection:new\r\n"); + if (!sendRtpItem.isTcpActive()) { + content.append("a=setup:active\r\n"); + } else { + content.append("a=setup:passive\r\n"); + } + } + content.append("y=" + ssrc + "\r\n"); + content.append("f=v/////a/1/8/1\r\n"); + + ParentPlatform parentPlatform = new ParentPlatform(); + parentPlatform.setServerIP(device.getIp()); + parentPlatform.setServerPort(device.getPort()); + parentPlatform.setServerGBId(device.getDeviceId()); + + sipResponse = responseSdpAck(request, content.toString(), parentPlatform); + + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), sendRtpItem.getChannelId()); + + audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.Ok); + audioBroadcastCatch.setSipTransactionInfoByRequset(sipResponse); + audioBroadcastManager.update(audioBroadcastCatch); + streamSession.put(device.getDeviceId(), sendRtpItem.getChannelId(), request.getCallIdHeader().getCallId(), sendRtpItem.getStream(), sendRtpItem.getSsrc(), sendRtpItem.getMediaServerId(), sipResponse, InviteSessionType.BROADCAST); + // 开启发流,大华在收到200OK后就会开始建立连接 + if (!device.isBroadcastPushAfterAck()) { + logger.info("[语音喊话] 回复200OK后发现 BroadcastPushAfterAck为False,现在开始推流"); + playService.startPushStream(sendRtpItem, sipResponse, parentPlatform, request.getCallIdHeader()); + } + + } catch (SipException | InvalidArgumentException | ParseException | SdpParseException e) { + logger.error("[命令发送失败] 语音喊话 回复200OK(SDP): {}", e.getMessage()); + } + return sipResponse; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java old mode 100644 new mode 100755 index a78404e26..4ea7667d2 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java @@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; import com.genersoft.iot.vmp.service.IDeviceChannelService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.utils.DateUtil; import org.dom4j.DocumentException; import org.dom4j.Element; import org.slf4j.Logger; @@ -185,6 +186,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent // 判断此通道是否存在 DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId, channel.getChannelId()); if (deviceChannel != null) { + logger.info("[增加通道] 已存在,不发送通知只更新,设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); channel.setId(deviceChannel.getId()); updateChannelMap.put(channel.getChannelId(), channel); if (updateChannelMap.keySet().size() > 300) { @@ -222,6 +224,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent DeviceChannel deviceChannelForUpdate = deviceChannelService.getOne(deviceId, channel.getChannelId()); if (deviceChannelForUpdate != null) { channel.setId(deviceChannelForUpdate.getId()); + channel.setUpdateTime(DateUtil.getNow()); updateChannelMap.put(channel.getChannelId(), channel); if (updateChannelMap.keySet().size() > 300) { executeSaveForUpdate(); @@ -244,11 +247,11 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent // 转发变化信息 eventPublisher.catalogEventPublish(null, channel, event); - if (updateChannelMap.keySet().size() > 0 - || addChannelMap.keySet().size() > 0 - || updateChannelOnlineList.size() > 0 - || updateChannelOfflineList.size() > 0 - || deleteChannelList.size() > 0) { + if (!updateChannelMap.keySet().isEmpty() + || !addChannelMap.keySet().isEmpty() + || !updateChannelOnlineList.isEmpty() + || !updateChannelOfflineList.isEmpty() + || !deleteChannelList.isEmpty()) { if (!dynamicTask.contains(talkKey)) { dynamicTask.startDelay(talkKey, this::executeSave, 1000); @@ -262,16 +265,36 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent } private void executeSave(){ - executeSaveForAdd(); - executeSaveForUpdate(); - executeSaveForDelete(); - executeSaveForOnline(); - executeSaveForOffline(); + try { + executeSaveForAdd(); + } catch (Exception e) { + logger.error("[存储收到的增加通道] 异常: ", e ); + } + try { + executeSaveForUpdate(); + } catch (Exception e) { + logger.error("[存储收到的更新通道] 异常: ", e ); + } + try { + executeSaveForDelete(); + } catch (Exception e) { + logger.error("[存储收到的删除通道] 异常: ", e ); + } + try { + executeSaveForOnline(); + } catch (Exception e) { + logger.error("[存储收到的通道上线] 异常: ", e ); + } + try { + executeSaveForOffline(); + } catch (Exception e) { + logger.error("[存储收到的通道离线] 异常: ", e ); + } dynamicTask.stop(talkKey); } private void executeSaveForUpdate(){ - if (updateChannelMap.values().size() > 0) { + if (!updateChannelMap.values().isEmpty()) { ArrayList deviceChannels = new ArrayList<>(updateChannelMap.values()); updateChannelMap.clear(); deviceChannelService.batchUpdateChannel(deviceChannels); @@ -280,7 +303,7 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent } private void executeSaveForAdd(){ - if (addChannelMap.values().size() > 0) { + if (!addChannelMap.values().isEmpty()) { ArrayList deviceChannels = new ArrayList<>(addChannelMap.values()); addChannelMap.clear(); deviceChannelService.batchAddChannel(deviceChannels); @@ -288,21 +311,21 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent } private void executeSaveForDelete(){ - if (deleteChannelList.size() > 0) { + if (!deleteChannelList.isEmpty()) { deviceChannelService.deleteChannels(deleteChannelList); deleteChannelList.clear(); } } private void executeSaveForOnline(){ - if (updateChannelOnlineList.size() > 0) { + if (!updateChannelOnlineList.isEmpty()) { deviceChannelService.channelsOnline(updateChannelOnlineList); updateChannelOnlineList.clear(); } } private void executeSaveForOffline(){ - if (updateChannelOfflineList.size() > 0) { + if (!updateChannelOfflineList.isEmpty()) { deviceChannelService.channelsOffline(updateChannelOfflineList); updateChannelOfflineList.clear(); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java old mode 100644 new mode 100755 index d44ff5a85..435f35f4d --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestProcessor.java @@ -6,7 +6,6 @@ import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; -import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; @@ -38,7 +37,6 @@ import javax.sip.SipException; import javax.sip.header.FromHeader; import javax.sip.message.Response; import java.text.ParseException; -import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; @@ -132,7 +130,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements if (CmdType.CATALOG.equals(cmd)) { logger.info("接收到Catalog通知"); -// processNotifyCatalogList(take.getEvt()); notifyRequestForCatalogProcessor.process(take.getEvt()); } else if (CmdType.ALARM.equals(cmd)) { logger.info("接收到Alarm通知"); @@ -223,7 +220,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements mobilePosition.getLongitude(), mobilePosition.getLatitude()); mobilePosition.setReportSource("Mobile Position"); - // 更新device channel 的经纬度 DeviceChannel deviceChannel = new DeviceChannel(); deviceChannel.setDeviceId(device.getDeviceId()); @@ -243,6 +239,8 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements } storager.updateChannelPosition(deviceChannel); + // 向关联了该通道并且开启移动位置订阅的上级平台发送移动位置订阅消息 + // 发送redis消息。 通知位置信息的变化 JSONObject jsonObject = new JSONObject(); @@ -319,6 +317,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements logger.info("[收到Notify-Alarm]:{}/{}", device.getDeviceId(), deviceAlarm.getChannelId()); if ("4".equals(deviceAlarm.getAlarmMethod())) { MobilePosition mobilePosition = new MobilePosition(); + mobilePosition.setChannelId(channelId); mobilePosition.setCreateTime(DateUtil.getNow()); mobilePosition.setDeviceId(deviceAlarm.getDeviceId()); mobilePosition.setTime(deviceAlarm.getAlarmTime()); @@ -370,114 +369,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements } } - /*** - * 处理catalog设备目录列表Notify - * - * @param evt - */ - private void processNotifyCatalogList(RequestEvent evt) { - try { - FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); - String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader); - - Device device = redisCatchStorage.getDevice(deviceId); - if (device == null || !device.isOnLine()) { - logger.warn("[收到目录订阅]:{}, 但是设备已经离线", (device != null ? device.getDeviceId():"" )); - return; - } - Element rootElement = getRootElement(evt, device.getCharset()); - if (rootElement == null) { - logger.warn("[ 收到目录订阅 ] content cannot be null, {}", evt.getRequest()); - return; - } - Element deviceListElement = rootElement.element("DeviceList"); - if (deviceListElement == null) { - return; - } - Iterator deviceListIterator = deviceListElement.elementIterator(); - if (deviceListIterator != null) { - - // 遍历DeviceList - while (deviceListIterator.hasNext()) { - Element itemDevice = deviceListIterator.next(); - Element channelDeviceElement = itemDevice.element("DeviceID"); - if (channelDeviceElement == null) { - continue; - } - Element eventElement = itemDevice.element("Event"); - String event; - if (eventElement == null) { - logger.warn("[收到目录订阅]:{}, 但是Event为空, 设为默认值 ADD", (device != null ? device.getDeviceId():"" )); - event = CatalogEvent.ADD; - }else { - event = eventElement.getText().toUpperCase(); - } - DeviceChannel channel = XmlUtil.channelContentHandler(itemDevice, device, event, civilCodeFileConf); - if (channel == null) { - logger.info("[收到目录订阅]:但是解析失败 {}", new String(evt.getRequest().getRawContent())); - continue; - } - if (channel.getParentId() != null && channel.getParentId().equals(sipConfig.getId())) { - channel.setParentId(null); - } - channel.setDeviceId(device.getDeviceId()); - logger.info("[收到目录订阅]:{}/{}", device.getDeviceId(), channel.getChannelId()); - switch (event) { - case CatalogEvent.ON: - // 上线 - logger.info("[收到通道上线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - storager.deviceChannelOnline(deviceId, channel.getChannelId()); - break; - case CatalogEvent.OFF : - // 离线 - logger.info("[收到通道离线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - if (userSetting.getRefuseChannelStatusChannelFormNotify()) { - storager.deviceChannelOffline(deviceId, channel.getChannelId()); - }else { - logger.info("[收到通道离线通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - } - break; - case CatalogEvent.VLOST: - // 视频丢失 - logger.info("[收到通道视频丢失通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - if (userSetting.getRefuseChannelStatusChannelFormNotify()) { - storager.deviceChannelOffline(deviceId, channel.getChannelId()); - }else { - logger.info("[收到通道视频丢失通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - } - break; - case CatalogEvent.DEFECT: - // 故障 - break; - case CatalogEvent.ADD: - // 增加 - logger.info("[收到增加通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - deviceChannelService.updateChannel(deviceId, channel); - break; - case CatalogEvent.DEL: - // 删除 - logger.info("[收到删除通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - storager.delChannel(deviceId, channel.getChannelId()); - break; - case CatalogEvent.UPDATE: - // 更新 - logger.info("[收到更新通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); - deviceChannelService.updateChannel(deviceId, channel); - break; - default: - logger.warn("[ NotifyCatalog ] event not found : {}", event ); - - } - // 转发变化信息 - eventPublisher.catalogEventPublish(null, channel, event); - - } - } - } catch (DocumentException e) { - logger.error("未处理的异常 ", e); - } - } - public void setCmder(SIPCommander cmder) { } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java old mode 100644 new mode 100755 index ceeb3b17e..69dc45514 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/RegisterRequestProcessor.java @@ -27,6 +27,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; +import javax.sip.*; +import javax.sip.header.*; +import javax.sip.message.Request; import javax.sip.RequestEvent; import javax.sip.SipException; import javax.sip.header.AuthorizationHeader; @@ -85,7 +88,11 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen Response response = null; boolean passwordCorrect = false; // 注册标志 - boolean registerFlag; + boolean registerFlag = true; + if (request.getExpires().getExpires() == 0) { + // 注销成功 + registerFlag = false; + } FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME); AddressImpl address = (AddressImpl) fromHeader.getAddress(); SipUri uri = (SipUri) address.getURI(); @@ -96,31 +103,36 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress()); String requestAddress = remoteAddressInfo.getIp() + ":" + remoteAddressInfo.getPort(); - logger.info("[注册请求] 设备:{}, 开始处理: {}", deviceId, requestAddress); + String title = registerFlag ? "[注册请求]": "[注销请求]"; + logger.info(title + "设备:{}, 开始处理: {}", deviceId, requestAddress); if (device != null && - device.getSipTransactionInfo() != null && - request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) { - logger.info("[注册请求] 设备:{}, 注册续订: {}",device.getDeviceId(), device.getDeviceId()); - device.setExpires(request.getExpires().getExpires()); - device.setIp(remoteAddressInfo.getIp()); - device.setPort(remoteAddressInfo.getPort()); - device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort()))); - device.setLocalIp(request.getLocalAddress().getHostAddress()); - Response registerOkResponse = getRegisterOkResponse(request); - // 判断TCP还是UDP - ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); - String transport = reqViaHeader.getTransport(); - device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP"); - sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), registerOkResponse); - device.setRegisterTime(DateUtil.getNow()); - SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse)registerOkResponse); - deviceService.online(device, sipTransactionInfo); + device.getSipTransactionInfo() != null && + request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) { + logger.info(title + "设备:{}, 注册续订: {}",device.getDeviceId(), device.getDeviceId()); + if (registerFlag) { + device.setExpires(request.getExpires().getExpires()); + device.setIp(remoteAddressInfo.getIp()); + device.setPort(remoteAddressInfo.getPort()); + device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort()))); + device.setLocalIp(request.getLocalAddress().getHostAddress()); + Response registerOkResponse = getRegisterOkResponse(request); + // 判断TCP还是UDP + ViaHeader reqViaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME); + String transport = reqViaHeader.getTransport(); + device.setTransport("TCP".equalsIgnoreCase(transport) ? "TCP" : "UDP"); + sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), registerOkResponse); + device.setRegisterTime(DateUtil.getNow()); + SipTransactionInfo sipTransactionInfo = new SipTransactionInfo((SIPResponse)registerOkResponse); + deviceService.online(device, sipTransactionInfo); + }else { + deviceService.offline(deviceId, "主动注销"); + } return; } String password = (device != null && !ObjectUtils.isEmpty(device.getPassword()))? device.getPassword() : sipConfig.getPassword(); AuthorizationHeader authHead = (AuthorizationHeader) request.getHeader(AuthorizationHeader.NAME); if (authHead == null && !ObjectUtils.isEmpty(password)) { - logger.info("[注册请求] 设备:{}, 回复401: {}",deviceId, requestAddress); + logger.info(title + " 设备:{}, 回复401: {}",deviceId, requestAddress); response = getMessageFactory().createResponse(Response.UNAUTHORIZED, request); new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getDomain()); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); @@ -135,7 +147,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen // 注册失败 response = getMessageFactory().createResponse(Response.FORBIDDEN, request); response.setReasonPhrase("wrong password"); - logger.info("[注册请求] 设备:{}, 密码/SIP服务器ID错误, 回复403: {}", deviceId, requestAddress); + logger.info(title + " 设备:{}, 密码/SIP服务器ID错误, 回复403: {}", deviceId, requestAddress); sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); return; } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java old mode 100644 new mode 100755 index 4ff4e980f..1580bafbf --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java @@ -1,5 +1,8 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; +import com.genersoft.iot.vmp.common.VideoManagerConstants; +import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.CmdType; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.gb28181.bean.SubscribeHolder; @@ -10,6 +13,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; +import com.genersoft.iot.vmp.service.IPlatformService; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPResponse; @@ -50,6 +54,10 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme @Autowired private SIPSender sipSender; + + @Autowired + private IPlatformService platformService; + @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 @@ -191,5 +199,8 @@ public class SubscribeRequestProcessor extends SIPRequestProcessorParent impleme } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("未处理的异常 ", e); } + if (subscribeHolder.getCatalogSubscribe(platformId) == null && platform.isAutoPushChannel()) { + platformService.addSimulatedSubscribeInfo(platform); + } } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java old mode 100644 new mode 100755 index fedf0ecad..92175ac29 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/info/InfoRequestProcessor.java @@ -1,149 +1,149 @@ -package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info; - -import com.genersoft.iot.vmp.common.InviteInfo; -import com.genersoft.iot.vmp.common.InviteSessionType; -import com.genersoft.iot.vmp.gb28181.bean.*; -import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; -import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; -import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; -import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; -import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; -import com.genersoft.iot.vmp.gb28181.utils.SipUtils; -import com.genersoft.iot.vmp.service.IInviteStreamService; -import com.genersoft.iot.vmp.storager.IRedisCatchStorage; -import com.genersoft.iot.vmp.storager.IVideoManagerStorage; -import gov.nist.javax.sip.message.SIPRequest; -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.header.CallIdHeader; -import javax.sip.header.ContentTypeHeader; -import javax.sip.message.Response; -import java.text.ParseException; - -@Component -public class InfoRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { - - private final static Logger logger = LoggerFactory.getLogger(InfoRequestProcessor.class); - - private final String method = "INFO"; - - @Autowired - private SIPProcessorObserver sipProcessorObserver; - - @Autowired - private IVideoManagerStorage storage; - - @Autowired - private SipSubscribe sipSubscribe; - - @Autowired - private IRedisCatchStorage redisCatchStorage; - - @Autowired - private IInviteStreamService inviteStreamService; - - @Autowired - private IVideoManagerStorage storager; - - @Autowired - private SIPCommander cmder; - - @Autowired - private VideoStreamSessionManager sessionManager; - - @Override - public void afterPropertiesSet() throws Exception { - // 添加消息处理的订阅 - sipProcessorObserver.addRequestProcessor(method, this); - } - - @Override - public void process(RequestEvent evt) { - logger.debug("接收到消息:" + evt.getRequest()); - SIPRequest request = (SIPRequest) evt.getRequest(); - String deviceId = SipUtils.getUserIdFromFromHeader(request); - CallIdHeader callIdHeader = request.getCallIdHeader(); - // 先从会话内查找 - SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); - - // 兼容海康 媒体通知 消息from字段不是设备ID的问题 - if (ssrcTransaction != null) { - deviceId = ssrcTransaction.getDeviceId(); - } - // 查询设备是否存在 - Device device = redisCatchStorage.getDevice(deviceId); - // 查询上级平台是否存在 - ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId); - try { - if (device != null && parentPlatform != null) { - logger.warn("[重复]平台与设备编号重复:{}", deviceId); - String hostAddress = request.getRemoteAddress().getHostAddress(); - int remotePort = request.getRemotePort(); - if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) { - parentPlatform = null; - }else { - device = null; - } - } - if (device == null && parentPlatform == null) { - // 不存在则回复404 - responseAck(request, Response.NOT_FOUND, "device "+ deviceId +" not found"); - logger.warn("[设备未找到 ]: {}", deviceId); - if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){ - DeviceNotFoundEvent deviceNotFoundEvent = new DeviceNotFoundEvent(evt.getDialog()); - deviceNotFoundEvent.setCallId(callIdHeader.getCallId()); - SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(deviceNotFoundEvent); - sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()).response(eventResult); - }; - }else { - ContentTypeHeader header = (ContentTypeHeader)evt.getRequest().getHeader(ContentTypeHeader.NAME); - String contentType = header.getContentType(); - String contentSubType = header.getContentSubType(); - if ("Application".equalsIgnoreCase(contentType) && "MANSRTSP".equalsIgnoreCase(contentSubType)) { - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId()); - String streamId = sendRtpItem.getStreamId(); - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); - if (null == inviteInfo) { - responseAck(request, Response.NOT_FOUND, "stream " + streamId + " not found"); - return; - } - Device device1 = storager.queryVideoDevice(inviteInfo.getDeviceId()); - if (inviteInfo.getStreamInfo() != null) { - cmder.playbackControlCmd(device1,inviteInfo.getStreamInfo(),new String(evt.getRequest().getRawContent()),eventResult -> { - // 失败的回复 - try { - responseAck(request, eventResult.statusCode, eventResult.msg); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage()); - } - }, eventResult -> { - // 成功的回复 - try { - responseAck(request, eventResult.statusCode); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage()); - } - }); - } - - } - } - } catch (SipException e) { - logger.warn("SIP 回复错误", e); - } catch (InvalidArgumentException e) { - logger.warn("参数无效", e); - } catch (ParseException e) { - logger.warn("SIP回复时解析异常", e); - } - } - - -} +package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info; + +import com.genersoft.iot.vmp.common.InviteInfo; +import com.genersoft.iot.vmp.common.InviteSessionType; +import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; +import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; +import com.genersoft.iot.vmp.gb28181.transmit.event.request.ISIPRequestProcessor; +import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; +import com.genersoft.iot.vmp.gb28181.utils.SipUtils; +import com.genersoft.iot.vmp.service.IInviteStreamService; +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; +import gov.nist.javax.sip.message.SIPRequest; +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.header.CallIdHeader; +import javax.sip.header.ContentTypeHeader; +import javax.sip.message.Response; +import java.text.ParseException; + +@Component +public class InfoRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { + + private final static Logger logger = LoggerFactory.getLogger(InfoRequestProcessor.class); + + private final String method = "INFO"; + + @Autowired + private SIPProcessorObserver sipProcessorObserver; + + @Autowired + private IVideoManagerStorage storage; + + @Autowired + private SipSubscribe sipSubscribe; + + @Autowired + private IRedisCatchStorage redisCatchStorage; + + @Autowired + private IInviteStreamService inviteStreamService; + + @Autowired + private IVideoManagerStorage storager; + + @Autowired + private SIPCommander cmder; + + @Autowired + private VideoStreamSessionManager sessionManager; + + @Override + public void afterPropertiesSet() throws Exception { + // 添加消息处理的订阅 + sipProcessorObserver.addRequestProcessor(method, this); + } + + @Override + public void process(RequestEvent evt) { + logger.debug("接收到消息:" + evt.getRequest()); + SIPRequest request = (SIPRequest) evt.getRequest(); + String deviceId = SipUtils.getUserIdFromFromHeader(request); + CallIdHeader callIdHeader = request.getCallIdHeader(); + // 先从会话内查找 + SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); + + // 兼容海康 媒体通知 消息from字段不是设备ID的问题 + if (ssrcTransaction != null) { + deviceId = ssrcTransaction.getDeviceId(); + } + // 查询设备是否存在 + Device device = redisCatchStorage.getDevice(deviceId); + // 查询上级平台是否存在 + ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(deviceId); + try { + if (device != null && parentPlatform != null) { + logger.warn("[重复]平台与设备编号重复:{}", deviceId); + String hostAddress = request.getRemoteAddress().getHostAddress(); + int remotePort = request.getRemotePort(); + if (device.getHostAddress().equals(hostAddress + ":" + remotePort)) { + parentPlatform = null; + }else { + device = null; + } + } + if (device == null && parentPlatform == null) { + // 不存在则回复404 + responseAck(request, Response.NOT_FOUND, "device "+ deviceId +" not found"); + logger.warn("[设备未找到 ]: {}", deviceId); + if (sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()) != null){ + DeviceNotFoundEvent deviceNotFoundEvent = new DeviceNotFoundEvent(evt.getDialog()); + deviceNotFoundEvent.setCallId(callIdHeader.getCallId()); + SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(deviceNotFoundEvent); + sipSubscribe.getErrorSubscribe(callIdHeader.getCallId()).response(eventResult); + }; + }else { + ContentTypeHeader header = (ContentTypeHeader)evt.getRequest().getHeader(ContentTypeHeader.NAME); + String contentType = header.getContentType(); + String contentSubType = header.getContentSubType(); + if ("Application".equalsIgnoreCase(contentType) && "MANSRTSP".equalsIgnoreCase(contentSubType)) { + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId()); + String streamId = sendRtpItem.getStream(); + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); + if (null == inviteInfo) { + responseAck(request, Response.NOT_FOUND, "stream " + streamId + " not found"); + return; + } + Device device1 = storager.queryVideoDevice(inviteInfo.getDeviceId()); + if (inviteInfo.getStreamInfo() != null) { + cmder.playbackControlCmd(device1,inviteInfo.getStreamInfo(),new String(evt.getRequest().getRawContent()),eventResult -> { + // 失败的回复 + try { + responseAck(request, eventResult.statusCode, eventResult.msg); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage()); + } + }, eventResult -> { + // 成功的回复 + try { + responseAck(request, eventResult.statusCode); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 国标级联 录像控制: {}", e.getMessage()); + } + }); + } + + } + } + } catch (SipException e) { + logger.warn("SIP 回复错误", e); + } catch (InvalidArgumentException e) { + logger.warn("参数无效", e); + } catch (ParseException e) { + logger.warn("SIP回复时解析异常", e); + } + } + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/IMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/IMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java old mode 100644 new mode 100755 index afaa7cb39..855395c0b --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java @@ -3,10 +3,19 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; +import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd.CatalogQueryMessageHandler; +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; +import gov.nist.javax.sip.message.SIPRequest; import org.dom4j.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import javax.sip.InvalidArgumentException; import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.message.Response; +import java.text.ParseException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -14,8 +23,13 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent implements IMessageHandler{ + private Logger logger = LoggerFactory.getLogger(MessageHandlerAbstract.class); + public Map messageHandlerMap = new ConcurrentHashMap<>(); + @Autowired + private IVideoManagerStorage storage; + public void addHandler(String cmdType, IMessageHandler messageHandler) { messageHandlerMap.put(cmdType, messageHandler); } @@ -23,8 +37,24 @@ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent i @Override public void handForDevice(RequestEvent evt, Device device, Element element) { String cmd = getText(element, "CmdType"); + if (cmd == null) { + try { + responseAck((SIPRequest) evt.getRequest(), Response.OK); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 回复200 OK: {}", e.getMessage()); + } + return; + } IMessageHandler messageHandler = messageHandlerMap.get(cmd); + if (messageHandler != null) { + //两个国标平台互相级联时由于上一步判断导致本该在平台处理的消息 放到了设备的处理逻辑 + //所以对目录查询单独做了校验 + if(messageHandler instanceof CatalogQueryMessageHandler){ + ParentPlatform parentPlatform = storage.queryParentPlatByServerGBId(device.getDeviceId()); + messageHandler.handForPlatform(evt, parentPlatform, element); + return; + } messageHandler.handForDevice(evt, device, element); } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageRequestProcessor.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/ControlMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/ControlMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/NotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/NotifyMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java old mode 100644 new mode 100755 index 6928def26..2fc7ae0e0 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/AlarmNotifyMessageHandler.java @@ -137,6 +137,7 @@ public class AlarmNotifyMessageHandler extends SIPRequestProcessorParent impleme MobilePosition mobilePosition = new MobilePosition(); mobilePosition.setCreateTime(DateUtil.getNow()); mobilePosition.setDeviceId(deviceAlarm.getDeviceId()); + mobilePosition.setChannelId(channelId); mobilePosition.setTime(deviceAlarm.getAlarmTime()); mobilePosition.setLongitude(deviceAlarm.getLongitude()); mobilePosition.setLatitude(deviceAlarm.getLatitude()); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java new file mode 100644 index 000000000..a05847aae --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/BroadcastNotifyMessageHandler.java @@ -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()); + } + + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java old mode 100644 new mode 100755 index 5c577ba68..034e24f96 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/KeepaliveNotifyMessageHandler.java @@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.service.IDeviceService; import com.genersoft.iot.vmp.utils.DateUtil; import gov.nist.javax.sip.message.SIPRequest; +import org.apache.commons.lang3.ObjectUtils; import org.dom4j.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +34,7 @@ import java.text.ParseException; public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { - private Logger logger = LoggerFactory.getLogger(KeepaliveNotifyMessageHandler.class); + private final Logger logger = LoggerFactory.getLogger(KeepaliveNotifyMessageHandler.class); private final static String cmdType = "Keepalive"; @Autowired @@ -59,28 +60,39 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp // 未注册的设备不做处理 return; } - logger.info("[收到心跳], device: {}", device.getDeviceId()); SIPRequest request = (SIPRequest) evt.getRequest(); + logger.info("[收到心跳] device: {}, callId: {}", device.getDeviceId(), request.getCallIdHeader().getCallId()); + // 回复200 OK try { responseAck(request, Response.OK); } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("[命令发送失败] 心跳回复: {}", e.getMessage()); } + if (!ObjectUtils.isEmpty(device.getKeepaliveTime()) && DateUtil.getDifferenceForNow(device.getKeepaliveTime()) <= 3000L) { + logger.info("[收到心跳] 心跳发送过于频繁,已忽略 device: {}, callId: {}", device.getDeviceId(), request.getCallIdHeader().getCallId()); + return; + } RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress()); if (!device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) { - logger.info("[心跳] 设备{}地址变化, 远程地址为: {}:{}", device.getDeviceId(), remoteAddressInfo.getIp(), remoteAddressInfo.getPort()); + logger.info("[收到心跳] 设备{}地址变化, 远程地址为: {}:{}", device.getDeviceId(), remoteAddressInfo.getIp(), remoteAddressInfo.getPort()); device.setPort(remoteAddressInfo.getPort()); device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort()))); device.setIp(remoteAddressInfo.getIp()); + // 设备地址变化会引起目录订阅任务失效,需要重新添加 + if (device.getSubscribeCycleForCatalog() > 0) { + deviceService.removeCatalogSubscribe(device, result->{ + deviceService.addCatalogSubscribe(device); + }); + } } if (device.getKeepaliveTime() == null) { device.setKeepaliveIntervalTime(60); }else { long lastTime = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(device.getKeepaliveTime()); if (System.currentTimeMillis()/1000-lastTime > 10) { - device.setKeepaliveIntervalTime(new Long(System.currentTimeMillis()/1000-lastTime).intValue()); + device.setKeepaliveIntervalTime(Long.valueOf(System.currentTimeMillis()/1000-lastTime).intValue()); } } @@ -104,7 +116,11 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp @Override public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element element) { - // 不会收到上级平台的心跳信息 - + // 个别平台保活不回复200OK会判定离线 + try { + responseAck((SIPRequest) evt.getRequest(), Response.OK); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 心跳回复: {}", e.getMessage()); + } } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java old mode 100644 new mode 100755 index fdcd5e4b3..55094f981 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MediaStatusNotifyMessageHandler.java @@ -102,7 +102,7 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i try { cmder.streamByeCmd(device, ssrcTransaction.getChannelId(), null, callIdHeader.getCallId()); - } catch (InvalidArgumentException | ParseException | SsrcTransactionNotFoundException | SipException e) { + } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { logger.error("[录像流]推送完毕,收到关流通知, 发送BYE失败 {}", e.getMessage()); } // 去除监听流注销自动停止下载的监听 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MobilePositionNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/MobilePositionNotifyMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/QueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/QueryMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/AlarmQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/AlarmQueryMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceInfoQueryMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceStatusQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceStatusQueryMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java old mode 100644 new mode 100755 index f3170a5dd..e2f9cda9e --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/RecordInfoQueryMessageHandler.java @@ -102,6 +102,7 @@ public class RecordInfoQueryMessageHandler extends SIPRequestProcessorParent imp // 接收录像数据 recordEndEventListener.addEndEventHandler(deviceChannel.getDeviceId(), channelId, (recordInfo)->{ try { + logger.info("[国标级联] 录像查询收到数据, 通道: {},准备转发===", channelId); cmderFroPlatform.recordInfo(deviceChannel, parentPlatform, request.getFromTag(), recordInfo); } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("[命令发送失败] 国标级联 回复录像数据: {}", e.getMessage()); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/ResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/ResponseMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/AlarmResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/AlarmResponseMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java old mode 100644 new mode 100755 index 2e0d07166..4b6bbbc25 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/BroadcastResponseMessageHandler.java @@ -1,14 +1,17 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd; -import com.alibaba.fastjson2.JSONObject; +import com.genersoft.iot.vmp.common.VideoManagerConstants; +import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatch; +import com.genersoft.iot.vmp.gb28181.bean.AudioBroadcastCatchStatus; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; -import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.IMessageHandler; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.ResponseMessageHandler; -import com.genersoft.iot.vmp.gb28181.utils.XmlUtil; +import com.genersoft.iot.vmp.service.IPlayService; import gov.nist.javax.sip.message.SIPRequest; import org.dom4j.Element; import org.slf4j.Logger; @@ -35,7 +38,13 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i private ResponseMessageHandler responseMessageHandler; @Autowired - private DeferredResultHolder deferredResultHolder; + private DynamicTask dynamicTask; + + @Autowired + private AudioBroadcastManager audioBroadcastManager; + + @Autowired + private IPlayService playService; @Override public void afterPropertiesSet() throws Exception { @@ -44,23 +53,38 @@ public class BroadcastResponseMessageHandler extends SIPRequestProcessorParent i @Override public void handForDevice(RequestEvent evt, Device device, Element rootElement) { + + SIPRequest request = (SIPRequest) evt.getRequest(); try { String channelId = getText(rootElement, "DeviceID"); - String key = DeferredResultHolder.CALLBACK_CMD_BROADCAST + device.getDeviceId() + channelId; - // 回复200 OK - responseAck((SIPRequest) evt.getRequest(), Response.OK); - // 此处是对本平台发出Broadcast指令的应答 - JSONObject json = new JSONObject(); - XmlUtil.node2Json(rootElement, json); - if (logger.isDebugEnabled()) { - logger.debug(json.toJSONString()); + if (!audioBroadcastManager.exit(device.getDeviceId(), channelId)) { + // 回复410 + responseAck((SIPRequest) evt.getRequest(), Response.GONE); + return; } - RequestMessage msg = new RequestMessage(); - msg.setKey(key); - msg.setData(json); - deferredResultHolder.invokeAllResult(msg); - + String result = getText(rootElement, "Result"); + Element infoElement = rootElement.element("Info"); + String reason = null; + if (infoElement != null) { + reason = getText(infoElement, "Reason"); + } + logger.info("[语音广播]回复:{}, {}/{}", reason == null? result : result + ": " + reason, device.getDeviceId(), channelId ); + // 回复200 OK + responseAck(request, Response.OK); + if (result.equalsIgnoreCase("OK")) { + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(device.getDeviceId(), channelId); + audioBroadcastCatch.setStatus(AudioBroadcastCatchStatus.WaiteInvite); + audioBroadcastManager.update(audioBroadcastCatch); + // 等待invite消息, 超时则结束 + String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + channelId; + dynamicTask.startDelay(key, ()->{ + logger.info("[语音广播]等待invite消息超时:{}/{}", device.getDeviceId(), channelId); + playService.stopAudioBroadcast(device.getDeviceId(), channelId); + }, 2000); + }else { + playService.stopAudioBroadcast(device.getDeviceId(), channelId); + } } catch (ParseException | SipException | InvalidArgumentException e) { logger.error("[命令发送失败] 国标级联 语音喊话: {}", e.getMessage()); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java old mode 100644 new mode 100755 index 8648dc455..19dde718d --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; /** * 目录查询的回复 @@ -38,6 +39,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; public class CatalogResponseMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { private Logger logger = LoggerFactory.getLogger(CatalogResponseMessageHandler.class); + private final String cmdType = "Catalog"; @Autowired @@ -60,6 +62,7 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp @Autowired private SipConfig sipConfig; + private AtomicBoolean processing = new AtomicBoolean(false); @Override public void afterPropertiesSet() throws Exception { @@ -68,7 +71,6 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp @Override public void handForDevice(RequestEvent evt, Device device, Element element) { - boolean isEmpty = taskQueue.isEmpty(); taskQueue.offer(new HandlerCatchData(evt, device, element)); // 回复200 OK try { @@ -76,8 +78,8 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("[命令发送失败] 目录查询回复: {}", e.getMessage()); } - // 如果不为空则说明已经开启消息处理 - if (isEmpty) { + // 已经开启消息处理则跳过 + if (processing.compareAndSet(false, true)) { taskExecutor.execute(() -> { while (!taskQueue.isEmpty()) { // 全局异常捕获,保证下一条可以得到处理 @@ -146,11 +148,12 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp } } - }catch (Exception e) { + } catch (Exception e) { logger.warn("[收到通道] 发现未处理的异常, \r\n{}", evt.getRequest()); logger.error("[收到通道] 异常内容: ", e); } } + processing.set(false); }); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/ConfigDownloadResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/ConfigDownloadResponseMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceConfigResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceConfigResponseMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceControlResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceControlResponseMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceInfoResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceInfoResponseMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/DeviceStatusResponseMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/MobilePositionResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/MobilePositionResponseMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/PresetQueryResponseMessageHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java old mode 100644 new mode 100755 index 36e5df299..fc0fda8d6 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java @@ -78,7 +78,6 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent } taskExecutor.execute(()->{ try { - String sn = getText(rootElement, "SN"); String channelId = getText(rootElement, "DeviceID"); RecordInfo recordInfo = new RecordInfo(); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/ISIPResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/ISIPResponseProcessor.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/SIPResponseProcessorAbstract.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/SIPResponseProcessorAbstract.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/CancelResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/CancelResponseProcessor.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java old mode 100644 new mode 100755 index 436d2a438..d29affcdc --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/InviteResponseProcessor.java @@ -19,6 +19,9 @@ import javax.sdp.SessionDescription; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; import javax.sip.SipException; +import javax.sip.InvalidArgumentException; +import javax.sip.ResponseEvent; +import javax.sip.SipException; import javax.sip.SipFactory; import javax.sip.address.SipURI; import javax.sip.message.Request; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/RegisterResponseProcessor.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/timeout/ITimeoutProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/timeout/ITimeoutProcessor.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/timeout/impl/TimeoutProcessorImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/timeout/impl/TimeoutProcessorImpl.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java index 58fc6012b..c64605883 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java @@ -140,6 +140,26 @@ public class SipUtils { return builder.toString(); } + public static String getNewCallId() { + return (int) Math.floor(Math.random() * 1000000000) + ""; + } + + public static int getTypeCodeFromGbCode(String deviceId) { + if (ObjectUtils.isEmpty(deviceId)) { + return 0; + } + return Integer.parseInt(deviceId.substring(10, 13)); + } + + /** + * 判断是否是前端外围设备 + * @param deviceId + * @return + */ + public static boolean isFrontEnd(String deviceId) { + int typeCodeFromGbCode = getTypeCodeFromGbCode(deviceId); + return typeCodeFromGbCode > 130 && typeCodeFromGbCode < 199; + } /** * 从请求中获取设备ip地址和端口号 * @param request 请求 @@ -261,6 +281,6 @@ public class SipUtils { return null; } } - return localDateTime.format(DateUtil.formatterISO8601); + return localDateTime.format(DateUtil.formatter); } } \ No newline at end of file diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java index 8c96a8e85..41e057079 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/XmlUtil.java @@ -1,647 +1,695 @@ -package com.genersoft.iot.vmp.gb28181.utils; - -import com.alibaba.fastjson2.JSONArray; -import com.alibaba.fastjson2.JSONObject; -import com.genersoft.iot.vmp.common.CivilCodePo; -import com.genersoft.iot.vmp.conf.CivilCodeFileConf; -import com.genersoft.iot.vmp.gb28181.bean.Device; -import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; -import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; -import com.genersoft.iot.vmp.utils.DateUtil; -import org.apache.commons.lang3.math.NumberUtils; -import org.dom4j.Attribute; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.Element; -import org.dom4j.io.SAXReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; - -import javax.sip.RequestEvent; -import javax.sip.message.Request; -import java.io.ByteArrayInputStream; -import java.io.StringReader; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.*; - -/** - * 基于dom4j的工具包 - * - * - */ -public class XmlUtil { - /** - * 日志服务 - */ - private static Logger logger = LoggerFactory.getLogger(XmlUtil.class); - - /** - * 解析XML为Document对象 - * - * @param xml 被解析的XMl - * - * @return Document - */ - public static Element parseXml(String xml) { - Document document = null; - // - StringReader sr = new StringReader(xml); - SAXReader saxReader = new SAXReader(); - try { - document = saxReader.read(sr); - } catch (DocumentException e) { - logger.error("解析失败", e); - } - return null == document ? null : document.getRootElement(); - } - - /** - * 获取element对象的text的值 - * - * @param em 节点的对象 - * @param tag 节点的tag - * @return 节点 - */ - public static String getText(Element em, String tag) { - if (null == em) { - return null; - } - Element e = em.element(tag); - // - return null == e ? null : e.getText().trim(); - } - - /** - * 递归解析xml节点,适用于 多节点数据 - * - * @param node node - * @param nodeName nodeName - * @return List> - */ - public static List> listNodes(Element node, String nodeName) { - if (null == node) { - return null; - } - // 初始化返回 - List> listMap = new ArrayList>(); - // 首先获取当前节点的所有属性节点 - List list = node.attributes(); - - Map map = null; - // 遍历属性节点 - for (Attribute attribute : list) { - if (nodeName.equals(node.getName())) { - if (null == map) { - map = new HashMap(); - listMap.add(map); - } - // 取到的节点属性放到map中 - map.put(attribute.getName(), attribute.getValue()); - } - - } - // 遍历当前节点下的所有节点 ,nodeName 要解析的节点名称 - // 使用递归 - Iterator iterator = node.elementIterator(); - while (iterator.hasNext()) { - Element e = iterator.next(); - listMap.addAll(listNodes(e, nodeName)); - } - return listMap; - } - - /** - * xml转json - * - * @param element - * @param json - */ - public static void node2Json(Element element, JSONObject json) { - // 如果是属性 - for (Object o : element.attributes()) { - Attribute attr = (Attribute) o; - if (!ObjectUtils.isEmpty(attr.getValue())) { - json.put("@" + attr.getName(), attr.getValue()); - } - } - List chdEl = element.elements(); - if (chdEl.isEmpty() && !ObjectUtils.isEmpty(element.getText())) {// 如果没有子元素,只有一个值 - json.put(element.getName(), element.getText()); - } - - for (Element e : chdEl) { // 有子元素 - if (!e.elements().isEmpty()) { // 子元素也有子元素 - JSONObject chdjson = new JSONObject(); - node2Json(e, chdjson); - Object o = json.get(e.getName()); - if (o != null) { - JSONArray jsona = null; - if (o instanceof JSONObject) { // 如果此元素已存在,则转为jsonArray - JSONObject jsono = (JSONObject) o; - json.remove(e.getName()); - jsona = new JSONArray(); - jsona.add(jsono); - jsona.add(chdjson); - } - if (o instanceof JSONArray) { - jsona = (JSONArray) o; - jsona.add(chdjson); - } - json.put(e.getName(), jsona); - } else { - if (!chdjson.isEmpty()) { - json.put(e.getName(), chdjson); - } - } - } else { // 子元素没有子元素 - for (Object o : element.attributes()) { - Attribute attr = (Attribute) o; - if (!ObjectUtils.isEmpty(attr.getValue())) { - json.put("@" + attr.getName(), attr.getValue()); - } - } - if (!e.getText().isEmpty()) { - json.put(e.getName(), e.getText()); - } - } - } - } - public static Element getRootElement(RequestEvent evt) throws DocumentException { - - return getRootElement(evt, "gb2312"); - } - - public static Element getRootElement(RequestEvent evt, String charset) throws DocumentException { - Request request = evt.getRequest(); - return getRootElement(request.getRawContent(), charset); - } - - public static Element getRootElement(byte[] content, String charset) throws DocumentException { - if (charset == null) { - charset = "gb2312"; - } - SAXReader reader = new SAXReader(); - reader.setEncoding(charset); - Document xml = reader.read(new ByteArrayInputStream(content)); - return xml.getRootElement(); - } - - private enum ChannelType{ - CivilCode, BusinessGroup,VirtualOrganization,Other - } - - public static DeviceChannel channelContentHandler(Element itemDevice, Device device, String event, CivilCodeFileConf civilCodeFileConf){ - DeviceChannel deviceChannel = new DeviceChannel(); - deviceChannel.setDeviceId(device.getDeviceId()); - Element channdelIdElement = itemDevice.element("DeviceID"); - if (channdelIdElement == null) { - logger.warn("解析Catalog消息时发现缺少 DeviceID"); - return null; - } - String channelId = channdelIdElement.getTextTrim(); - if (ObjectUtils.isEmpty(channelId)) { - logger.warn("解析Catalog消息时发现缺少 DeviceID"); - return null; - } - deviceChannel.setChannelId(channelId); - if (event != null && !event.equals(CatalogEvent.ADD) && !event.equals(CatalogEvent.UPDATE)) { - // 除了ADD和update情况下需要识别全部内容, - return deviceChannel; - } - Element nameElement = itemDevice.element("Name"); - if (nameElement != null) { - deviceChannel.setName(nameElement.getText()); - } - if(channelId.length() <= 8) { - deviceChannel.setHasAudio(false); - CivilCodePo parentCode = civilCodeFileConf.getParentCode(channelId); - if (parentCode != null) { - deviceChannel.setParentId(parentCode.getCode()); - deviceChannel.setCivilCode(parentCode.getCode()); - }else { - logger.warn("[xml解析] 无法确定行政区划{}的上级行政区划", channelId); - } - deviceChannel.setStatus(true); - return deviceChannel; - }else { - if(channelId.length() != 20) { - logger.warn("[xml解析] 失败,编号不符合国标28181定义: {}", channelId); - return null; - } - - int code = Integer.parseInt(channelId.substring(10, 13)); - if (code == 136 || code == 137 || code == 138) { - deviceChannel.setHasAudio(true); - }else { - deviceChannel.setHasAudio(false); - } - // 设备厂商 - String manufacturer = getText(itemDevice, "Manufacturer"); - // 设备型号 - String model = getText(itemDevice, "Model"); - // 设备归属 - String owner = getText(itemDevice, "Owner"); - // 行政区域 - String civilCode = getText(itemDevice, "CivilCode"); - // 虚拟组织所属的业务分组ID,业务分组根据特定的业务需求制定,一个业务分组包含一组特定的虚拟组织 - String businessGroupID = getText(itemDevice, "BusinessGroupID"); - // 父设备/区域/系统ID - String parentID = getText(itemDevice, "ParentID"); - if (parentID != null && parentID.equalsIgnoreCase("null")) { - parentID = null; - } - // 注册方式(必选)缺省为1;1:符合IETFRFC3261标准的认证注册模式;2:基于口令的双向认证注册模式;3:基于数字证书的双向认证注册模式 - String registerWay = getText(itemDevice, "RegisterWay"); - // 保密属性(必选)缺省为0;0:不涉密,1:涉密 - String secrecy = getText(itemDevice, "Secrecy"); - // 安装地址 - String address = getText(itemDevice, "Address"); - - switch (code){ - case 200: - // 系统目录 - if (!ObjectUtils.isEmpty(manufacturer)) { - deviceChannel.setManufacture(manufacturer); - } - if (!ObjectUtils.isEmpty(model)) { - deviceChannel.setModel(model); - } - if (!ObjectUtils.isEmpty(owner)) { - deviceChannel.setOwner(owner); - } - if (!ObjectUtils.isEmpty(civilCode)) { - deviceChannel.setCivilCode(civilCode); - deviceChannel.setParentId(civilCode); - }else { - if (!ObjectUtils.isEmpty(parentID)) { - deviceChannel.setParentId(parentID); - } - } - if (!ObjectUtils.isEmpty(address)) { - deviceChannel.setAddress(address); - } - deviceChannel.setStatus(true); - if (!ObjectUtils.isEmpty(registerWay)) { - try { - deviceChannel.setRegisterWay(Integer.parseInt(registerWay)); - }catch (NumberFormatException exception) { - logger.warn("[xml解析] 从通道数据获取registerWay失败: {}", registerWay); - } - } - if (!ObjectUtils.isEmpty(secrecy)) { - deviceChannel.setSecrecy(secrecy); - } - return deviceChannel; - case 215: - // 业务分组 - deviceChannel.setStatus(true); - if (!ObjectUtils.isEmpty(parentID)) { - if (!parentID.trim().equalsIgnoreCase(device.getDeviceId())) { - deviceChannel.setParentId(parentID); - } - }else { - logger.warn("[xml解析] 业务分组数据中缺少关键信息->ParentId"); - if (!ObjectUtils.isEmpty(civilCode)) { - deviceChannel.setCivilCode(civilCode); - } - } - break; - case 216: - // 虚拟组织 - deviceChannel.setStatus(true); - if (!ObjectUtils.isEmpty(businessGroupID)) { - deviceChannel.setBusinessGroupId(businessGroupID); - } - - if (!ObjectUtils.isEmpty(parentID)) { - if (parentID.contains("/")) { - String[] parentIdArray = parentID.split("/"); - parentID = parentIdArray[parentIdArray.length - 1]; - } - deviceChannel.setParentId(parentID); - }else { - if (!ObjectUtils.isEmpty(businessGroupID)) { - deviceChannel.setParentId(businessGroupID); - } - } - break; - default: - // 设备目录 - if (!ObjectUtils.isEmpty(manufacturer)) { - deviceChannel.setManufacture(manufacturer); - } - if (!ObjectUtils.isEmpty(model)) { - deviceChannel.setModel(model); - } - if (!ObjectUtils.isEmpty(owner)) { - deviceChannel.setOwner(owner); - } - if (!ObjectUtils.isEmpty(civilCode) - && civilCode.length() <= 8 - && NumberUtils.isParsable(civilCode) - && civilCode.length()%2 == 0 - ) { - deviceChannel.setCivilCode(civilCode); - } - if (!ObjectUtils.isEmpty(businessGroupID)) { - deviceChannel.setBusinessGroupId(businessGroupID); - } - - // 警区 - String block = getText(itemDevice, "Block"); - if (!ObjectUtils.isEmpty(block)) { - deviceChannel.setBlock(block); - } - if (!ObjectUtils.isEmpty(address)) { - deviceChannel.setAddress(address); - } - - if (!ObjectUtils.isEmpty(secrecy)) { - deviceChannel.setSecrecy(secrecy); - } - - // 当为设备时,是否有子设备(必选)1有,0没有 - String parental = getText(itemDevice, "Parental"); - if (!ObjectUtils.isEmpty(parental)) { - try { - // 由于海康会错误的发送65535作为这里的取值,所以这里除非是0否则认为是1 - if (!ObjectUtils.isEmpty(parental) && parental.length() == 1 && Integer.parseInt(parental) == 0) { - deviceChannel.setParental(0); - }else { - deviceChannel.setParental(1); - } - }catch (NumberFormatException e) { - logger.warn("[xml解析] 从通道数据获取 parental失败: {}", parental); - } - } - // 父设备/区域/系统ID - - if (!ObjectUtils.isEmpty(parentID) ) { - if (parentID.contains("/")) { - String[] parentIdArray = parentID.split("/"); - deviceChannel.setParentId(parentIdArray[parentIdArray.length - 1]); - }else { - if (parentID.length()%2 == 0) { - deviceChannel.setParentId(parentID); - }else { - logger.warn("[xml解析] 不规范的parentID:{}, 已舍弃", parentID); - } - } - }else { - if (!ObjectUtils.isEmpty(businessGroupID)) { - deviceChannel.setParentId(businessGroupID); - }else { - if (!ObjectUtils.isEmpty(deviceChannel.getCivilCode())) { - deviceChannel.setParentId(deviceChannel.getCivilCode()); - } - } - } - // 注册方式 - if (!ObjectUtils.isEmpty(registerWay)) { - try { - int registerWayInt = Integer.parseInt(registerWay); - deviceChannel.setRegisterWay(registerWayInt); - }catch (NumberFormatException exception) { - logger.warn("[xml解析] 从通道数据获取registerWay失败: {}", registerWay); - deviceChannel.setRegisterWay(1); - } - }else { - deviceChannel.setRegisterWay(1); - } - - // 信令安全模式(可选)缺省为0; 0:不采用;2:S/MIME 签名方式;3:S/MIME加密签名同时采用方式;4:数字摘要方式 - String safetyWay = getText(itemDevice, "SafetyWay"); - if (!ObjectUtils.isEmpty(safetyWay)) { - try { - deviceChannel.setSafetyWay(Integer.parseInt(safetyWay)); - }catch (NumberFormatException e) { - logger.warn("[xml解析] 从通道数据获取 safetyWay失败: {}", safetyWay); - } - } - - // 证书序列号(有证书的设备必选) - String certNum = getText(itemDevice, "CertNum"); - if (!ObjectUtils.isEmpty(certNum)) { - deviceChannel.setCertNum(certNum); - } - - // 证书有效标识(有证书的设备必选)缺省为0;证书有效标识:0:无效 1:有效 - String certifiable = getText(itemDevice, "Certifiable"); - if (!ObjectUtils.isEmpty(certifiable)) { - try { - deviceChannel.setCertifiable(Integer.parseInt(certifiable)); - }catch (NumberFormatException e) { - logger.warn("[xml解析] 从通道数据获取 Certifiable失败: {}", certifiable); - } - } - - // 无效原因码(有证书且证书无效的设备必选) - String errCode = getText(itemDevice, "ErrCode"); - if (!ObjectUtils.isEmpty(errCode)) { - try { - deviceChannel.setErrCode(Integer.parseInt(errCode)); - }catch (NumberFormatException e) { - logger.warn("[xml解析] 从通道数据获取 ErrCode失败: {}", errCode); - } - } - - // 证书终止有效期(有证书的设备必选) - String endTime = getText(itemDevice, "EndTime"); - if (!ObjectUtils.isEmpty(endTime)) { - deviceChannel.setEndTime(endTime); - } - - - // 设备/区域/系统IP地址 - String ipAddress = getText(itemDevice, "IPAddress"); - if (!ObjectUtils.isEmpty(ipAddress)) { - deviceChannel.setIpAddress(ipAddress); - } - - // 设备/区域/系统端口 - String port = getText(itemDevice, "Port"); - if (!ObjectUtils.isEmpty(port)) { - try { - deviceChannel.setPort(Integer.parseInt(port)); - }catch (NumberFormatException e) { - logger.warn("[xml解析] 从通道数据获取 Port失败: {}", port); - } - } - - // 设备口令 - String password = getText(itemDevice, "Password"); - if (!ObjectUtils.isEmpty(password)) { - deviceChannel.setPassword(password); - } - - - // 设备状态 - String status = getText(itemDevice, "Status"); - if (status != null) { - // ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理 - if (status.equals("ON") || status.equals("On") || status.equals("ONLINE") || status.equals("OK")) { - deviceChannel.setStatus(true); - } - if (status.equals("OFF") || status.equals("Off") || status.equals("OFFLINE")) { - deviceChannel.setStatus(false); - } - }else { - deviceChannel.setStatus(true); - } - - // 经度 - String longitude = getText(itemDevice, "Longitude"); - if (NumericUtil.isDouble(longitude)) { - deviceChannel.setLongitude(Double.parseDouble(longitude)); - } else { - deviceChannel.setLongitude(0.00); - } - - // 纬度 - String latitude = getText(itemDevice, "Latitude"); - if (NumericUtil.isDouble(latitude)) { - deviceChannel.setLatitude(Double.parseDouble(latitude)); - } else { - deviceChannel.setLatitude(0.00); - } - - deviceChannel.setGpsTime(DateUtil.getNow()); - - // -摄像机类型扩展,标识摄像机类型:1-球机;2-半球;3-固定枪机;4-遥控枪机。当目录项为摄像机时可选 - String ptzType = getText(itemDevice, "PTZType"); - if (ObjectUtils.isEmpty(ptzType)) { - //兼容INFO中的信息 - Element info = itemDevice.element("Info"); - String ptzTypeFromInfo = XmlUtil.getText(info, "PTZType"); - if(!ObjectUtils.isEmpty(ptzTypeFromInfo)){ - try { - deviceChannel.setPTZType(Integer.parseInt(ptzTypeFromInfo)); - }catch (NumberFormatException e){ - logger.warn("[xml解析] 从通道数据info中获取PTZType失败: {}", ptzTypeFromInfo); - } - } - } else { - try { - deviceChannel.setPTZType(Integer.parseInt(ptzType)); - }catch (NumberFormatException e){ - logger.warn("[xml解析] 从通道数据中获取PTZType失败: {}", ptzType); - } - } - - // TODO 摄像机位置类型扩展。 - // 1-省际检查站、 - // 2-党政机关、 - // 3-车站码头、 - // 4-中心广场、 - // 5-体育场馆、 - // 6-商业中心、 - // 7-宗教场所、 - // 8-校园周边、 - // 9-治安复杂区域、 - // 10-交通干线。 - // String positionType = getText(itemDevice, "PositionType"); - - // TODO 摄像机安装位置室外、室内属性。1-室外、2-室内。 - // String roomType = getText(itemDevice, "RoomType"); - // TODO 摄像机用途属性 - // String useType = getText(itemDevice, "UseType"); - // TODO 摄像机补光属性。1-无补光、2-红外补光、3-白光补光 - // String supplyLightType = getText(itemDevice, "SupplyLightType"); - // TODO 摄像机监视方位属性。1-东、2-西、3-南、4-北、5-东南、6-东北、7-西南、8-西北。 - // String directionType = getText(itemDevice, "DirectionType"); - // TODO 摄像机支持的分辨率,可有多个分辨率值,各个取值间以“/”分隔。分辨率取值参见附录 F中SDPf字段规定 - // String resolution = getText(itemDevice, "Resolution"); - - // TODO 下载倍速范围(可选),各可选参数以“/”分隔,如设备支持1,2,4倍速下载则应写为“1/2/4 - // String downloadSpeed = getText(itemDevice, "DownloadSpeed"); - // TODO 空域编码能力,取值0:不支持;1:1级增强(1个增强层);2:2级增强(2个增强层);3:3级增强(3个增强层) - // String svcSpaceSupportMode = getText(itemDevice, "SVCSpaceSupportMode"); - // TODO 时域编码能力,取值0:不支持;1:1级增强;2:2级增强;3:3级增强 - // String svcTimeSupportMode = getText(itemDevice, "SVCTimeSupportMode"); - - - deviceChannel.setSecrecy(secrecy); - break; - } - } - - return deviceChannel; - } - - /** - * 新增方法支持内部嵌套 - * - * @param element xmlElement - * @param clazz 结果类 - * @param 泛型 - * @return 结果对象 - * @throws NoSuchMethodException - * @throws InvocationTargetException - * @throws InstantiationException - * @throws IllegalAccessException - */ - public static T loadElement(Element element, Class clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { - Field[] fields = clazz.getDeclaredFields(); - T t = clazz.getDeclaredConstructor().newInstance(); - for (Field field : fields) { - ReflectionUtils.makeAccessible(field); - MessageElement annotation = field.getAnnotation(MessageElement.class); - if (annotation == null) { - continue; - } - String value = annotation.value(); - String subVal = annotation.subVal(); - Element element1 = element.element(value); - if (element1 == null) { - continue; - } - if ("".equals(subVal)) { - // 无下级数据 - Object fieldVal = element1.isTextOnly() ? element1.getText() : loadElement(element1, field.getType()); - Object o = simpleTypeDeal(field.getType(), fieldVal); - ReflectionUtils.setField(field, t, o); - } else { - // 存在下级数据 - ArrayList list = new ArrayList<>(); - Type genericType = field.getGenericType(); - if (!(genericType instanceof ParameterizedType)) { - continue; - } - Class aClass = (Class) ((ParameterizedType) genericType).getActualTypeArguments()[0]; - for (Element element2 : element1.elements(subVal)) { - list.add(loadElement(element2, aClass)); - } - ReflectionUtils.setField(field, t, list); - } - } - return t; - } - - /** - * 简单类型处理 - * - * @param tClass - * @param val - * @return - */ - private static Object simpleTypeDeal(Class tClass, Object val) { - if (tClass.equals(String.class)) { - return val.toString(); - } - if (tClass.equals(Integer.class)) { - return Integer.valueOf(val.toString()); - } - if (tClass.equals(Double.class)) { - return Double.valueOf(val.toString()); - } - if (tClass.equals(Long.class)) { - return Long.valueOf(val.toString()); - } - return val; - } +package com.genersoft.iot.vmp.gb28181.utils; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.genersoft.iot.vmp.common.CivilCodePo; +import com.genersoft.iot.vmp.conf.CivilCodeFileConf; +import com.genersoft.iot.vmp.gb28181.bean.Device; +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; +import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; +import com.genersoft.iot.vmp.utils.DateUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.dom4j.Attribute; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; + +import javax.sip.RequestEvent; +import javax.sip.message.Request; +import java.io.ByteArrayInputStream; +import java.io.StringReader; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.*; + +/** + * 基于dom4j的工具包 + * + * + */ +public class XmlUtil { + /** + * 日志服务 + */ + private static Logger logger = LoggerFactory.getLogger(XmlUtil.class); + + /** + * 解析XML为Document对象 + * + * @param xml 被解析的XMl + * + * @return Document + */ + public static Element parseXml(String xml) { + Document document = null; + // + StringReader sr = new StringReader(xml); + SAXReader saxReader = new SAXReader(); + try { + document = saxReader.read(sr); + } catch (DocumentException e) { + logger.error("解析失败", e); + } + return null == document ? null : document.getRootElement(); + } + + /** + * 获取element对象的text的值 + * + * @param em 节点的对象 + * @param tag 节点的tag + * @return 节点 + */ + public static String getText(Element em, String tag) { + if (null == em) { + return null; + } + Element e = em.element(tag); + // + 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节点,适用于 多节点数据 + * + * @param node node + * @param nodeName nodeName + * @return List> + */ + public static List> listNodes(Element node, String nodeName) { + if (null == node) { + return null; + } + // 初始化返回 + List> listMap = new ArrayList>(); + // 首先获取当前节点的所有属性节点 + List list = node.attributes(); + + Map map = null; + // 遍历属性节点 + for (Attribute attribute : list) { + if (nodeName.equals(node.getName())) { + if (null == map) { + map = new HashMap(); + listMap.add(map); + } + // 取到的节点属性放到map中 + map.put(attribute.getName(), attribute.getValue()); + } + + } + // 遍历当前节点下的所有节点 ,nodeName 要解析的节点名称 + // 使用递归 + Iterator iterator = node.elementIterator(); + while (iterator.hasNext()) { + Element e = iterator.next(); + listMap.addAll(listNodes(e, nodeName)); + } + return listMap; + } + + /** + * xml转json + * + * @param element + * @param json + */ + public static void node2Json(Element element, JSONObject json) { + // 如果是属性 + for (Object o : element.attributes()) { + Attribute attr = (Attribute) o; + if (!ObjectUtils.isEmpty(attr.getValue())) { + json.put("@" + attr.getName(), attr.getValue()); + } + } + List chdEl = element.elements(); + if (chdEl.isEmpty() && !ObjectUtils.isEmpty(element.getText())) {// 如果没有子元素,只有一个值 + json.put(element.getName(), element.getText()); + } + + for (Element e : chdEl) { // 有子元素 + if (!e.elements().isEmpty()) { // 子元素也有子元素 + JSONObject chdjson = new JSONObject(); + node2Json(e, chdjson); + Object o = json.get(e.getName()); + if (o != null) { + JSONArray jsona = null; + if (o instanceof JSONObject) { // 如果此元素已存在,则转为jsonArray + JSONObject jsono = (JSONObject) o; + json.remove(e.getName()); + jsona = new JSONArray(); + jsona.add(jsono); + jsona.add(chdjson); + } + if (o instanceof JSONArray) { + jsona = (JSONArray) o; + jsona.add(chdjson); + } + json.put(e.getName(), jsona); + } else { + if (!chdjson.isEmpty()) { + json.put(e.getName(), chdjson); + } + } + } else { // 子元素没有子元素 + for (Object o : element.attributes()) { + Attribute attr = (Attribute) o; + if (!ObjectUtils.isEmpty(attr.getValue())) { + json.put("@" + attr.getName(), attr.getValue()); + } + } + if (!e.getText().isEmpty()) { + json.put(e.getName(), e.getText()); + } + } + } + } + public static Element getRootElement(RequestEvent evt) throws DocumentException { + + return getRootElement(evt, "gb2312"); + } + + public static Element getRootElement(RequestEvent evt, String charset) throws DocumentException { + Request request = evt.getRequest(); + return getRootElement(request.getRawContent(), charset); + } + + public static Element getRootElement(byte[] content, String charset) throws DocumentException { + if (charset == null) { + charset = "gb2312"; + } + SAXReader reader = new SAXReader(); + reader.setEncoding(charset); + Document xml = reader.read(new ByteArrayInputStream(content)); + return xml.getRootElement(); + } + + private enum ChannelType{ + CivilCode, BusinessGroup,VirtualOrganization,Other + } + + public static DeviceChannel channelContentHandler(Element itemDevice, Device device, String event, CivilCodeFileConf civilCodeFileConf){ + DeviceChannel deviceChannel = new DeviceChannel(); + deviceChannel.setDeviceId(device.getDeviceId()); + Element channdelIdElement = itemDevice.element("DeviceID"); + if (channdelIdElement == null) { + logger.warn("解析Catalog消息时发现缺少 DeviceID"); + return null; + } + String channelId = channdelIdElement.getTextTrim(); + if (ObjectUtils.isEmpty(channelId)) { + logger.warn("解析Catalog消息时发现缺少 DeviceID"); + return null; + } + deviceChannel.setChannelId(channelId); + if (event != null && !event.equals(CatalogEvent.ADD) && !event.equals(CatalogEvent.UPDATE)) { + // 除了ADD和update情况下需要识别全部内容, + return deviceChannel; + } + Element nameElement = itemDevice.element("Name"); + // 当通道名称为空时,设置通道名称为通道编码,避免级联时因通道名称为空导致上级接收通道失败 + if (nameElement != null && StringUtils.isNotBlank(nameElement.getText())) { + deviceChannel.setName(nameElement.getText()); + } else { + deviceChannel.setName(channelId); + } + if(channelId.length() <= 8) { + deviceChannel.setHasAudio(false); + CivilCodePo parentCode = civilCodeFileConf.getParentCode(channelId); + if (parentCode != null) { + deviceChannel.setParentId(parentCode.getCode()); + deviceChannel.setCivilCode(parentCode.getCode()); + }else { + logger.warn("[xml解析] 无法确定行政区划{}的上级行政区划", channelId); + } + deviceChannel.setStatus(true); + return deviceChannel; + }else { + if(channelId.length() != 20) { + logger.warn("[xml解析] 失败,编号不符合国标28181定义: {}", channelId); + return null; + } + + int code = Integer.parseInt(channelId.substring(10, 13)); + if (code == 136 || code == 137 || code == 138) { + deviceChannel.setHasAudio(true); + }else { + deviceChannel.setHasAudio(false); + } + // 设备厂商 + String manufacturer = getText(itemDevice, "Manufacturer"); + // 设备型号 + String model = getText(itemDevice, "Model"); + // 设备归属 + String owner = getText(itemDevice, "Owner"); + // 行政区域 + String civilCode = getText(itemDevice, "CivilCode"); + // 虚拟组织所属的业务分组ID,业务分组根据特定的业务需求制定,一个业务分组包含一组特定的虚拟组织 + String businessGroupID = getText(itemDevice, "BusinessGroupID"); + // 父设备/区域/系统ID + String parentID = getText(itemDevice, "ParentID"); + if (parentID != null && parentID.equalsIgnoreCase("null")) { + parentID = null; + } + // 注册方式(必选)缺省为1;1:符合IETFRFC3261标准的认证注册模式;2:基于口令的双向认证注册模式;3:基于数字证书的双向认证注册模式 + String registerWay = getText(itemDevice, "RegisterWay"); + // 保密属性(必选)缺省为0;0:不涉密,1:涉密 + String secrecy = getText(itemDevice, "Secrecy"); + // 安装地址 + String address = getText(itemDevice, "Address"); + + switch (code){ + case 200: + // 系统目录 + if (!ObjectUtils.isEmpty(manufacturer)) { + deviceChannel.setManufacture(manufacturer); + } + if (!ObjectUtils.isEmpty(model)) { + deviceChannel.setModel(model); + } + if (!ObjectUtils.isEmpty(owner)) { + deviceChannel.setOwner(owner); + } + if (!ObjectUtils.isEmpty(civilCode)) { + deviceChannel.setCivilCode(civilCode); + deviceChannel.setParentId(civilCode); + }else { + if (!ObjectUtils.isEmpty(parentID)) { + deviceChannel.setParentId(parentID); + } + } + if (!ObjectUtils.isEmpty(address)) { + deviceChannel.setAddress(address); + } + deviceChannel.setStatus(true); + if (!ObjectUtils.isEmpty(registerWay)) { + try { + deviceChannel.setRegisterWay(Integer.parseInt(registerWay)); + }catch (NumberFormatException exception) { + logger.warn("[xml解析] 从通道数据获取registerWay失败: {}", registerWay); + } + } + if (!ObjectUtils.isEmpty(secrecy)) { + deviceChannel.setSecrecy(secrecy); + } + return deviceChannel; + case 215: + // 业务分组 + deviceChannel.setStatus(true); + if (!ObjectUtils.isEmpty(parentID)) { + if (!parentID.trim().equalsIgnoreCase(device.getDeviceId())) { + deviceChannel.setParentId(parentID); + } + }else { + logger.warn("[xml解析] 业务分组数据中缺少关键信息->ParentId"); + if (!ObjectUtils.isEmpty(civilCode)) { + deviceChannel.setCivilCode(civilCode); + } + } + break; + case 216: + // 虚拟组织 + deviceChannel.setStatus(true); + if (!ObjectUtils.isEmpty(businessGroupID)) { + deviceChannel.setBusinessGroupId(businessGroupID); + } + + if (!ObjectUtils.isEmpty(parentID)) { + if (parentID.contains("/")) { + String[] parentIdArray = parentID.split("/"); + parentID = parentIdArray[parentIdArray.length - 1]; + } + deviceChannel.setParentId(parentID); + }else { + if (!ObjectUtils.isEmpty(businessGroupID)) { + deviceChannel.setParentId(businessGroupID); + } + } + break; + default: + // 设备目录 + if (!ObjectUtils.isEmpty(manufacturer)) { + deviceChannel.setManufacture(manufacturer); + } + if (!ObjectUtils.isEmpty(model)) { + deviceChannel.setModel(model); + } + if (!ObjectUtils.isEmpty(owner)) { + deviceChannel.setOwner(owner); + } + if (!ObjectUtils.isEmpty(civilCode) + && civilCode.length() <= 8 + && NumberUtils.isParsable(civilCode) + && civilCode.length()%2 == 0 + ) { + deviceChannel.setCivilCode(civilCode); + } + if (!ObjectUtils.isEmpty(businessGroupID)) { + deviceChannel.setBusinessGroupId(businessGroupID); + } + + // 警区 + String block = getText(itemDevice, "Block"); + if (!ObjectUtils.isEmpty(block)) { + deviceChannel.setBlock(block); + } + if (!ObjectUtils.isEmpty(address)) { + deviceChannel.setAddress(address); + } + + if (!ObjectUtils.isEmpty(secrecy)) { + deviceChannel.setSecrecy(secrecy); + } + + // 当为设备时,是否有子设备(必选)1有,0没有 + String parental = getText(itemDevice, "Parental"); + if (!ObjectUtils.isEmpty(parental)) { + try { + // 由于海康会错误的发送65535作为这里的取值,所以这里除非是0否则认为是1 + if (!ObjectUtils.isEmpty(parental) && parental.length() == 1 && Integer.parseInt(parental) == 0) { + deviceChannel.setParental(0); + }else { + deviceChannel.setParental(1); + } + }catch (NumberFormatException e) { + logger.warn("[xml解析] 从通道数据获取 parental失败: {}", parental); + } + } + // 父设备/区域/系统ID + + if (!ObjectUtils.isEmpty(parentID) ) { + if (parentID.contains("/")) { + String[] parentIdArray = parentID.split("/"); + deviceChannel.setParentId(parentIdArray[parentIdArray.length - 1]); + }else { + if (parentID.length()%2 == 0) { + deviceChannel.setParentId(parentID); + }else { + logger.warn("[xml解析] 不规范的parentID:{}, 已舍弃", parentID); + } + } + }else { + if (!ObjectUtils.isEmpty(businessGroupID)) { + deviceChannel.setParentId(businessGroupID); + }else { + if (!ObjectUtils.isEmpty(deviceChannel.getCivilCode())) { + deviceChannel.setParentId(deviceChannel.getCivilCode()); + } + } + } + // 注册方式 + if (!ObjectUtils.isEmpty(registerWay)) { + try { + int registerWayInt = Integer.parseInt(registerWay); + deviceChannel.setRegisterWay(registerWayInt); + }catch (NumberFormatException exception) { + logger.warn("[xml解析] 从通道数据获取registerWay失败: {}", registerWay); + deviceChannel.setRegisterWay(1); + } + }else { + deviceChannel.setRegisterWay(1); + } + + // 信令安全模式(可选)缺省为0; 0:不采用;2:S/MIME 签名方式;3:S/MIME加密签名同时采用方式;4:数字摘要方式 + String safetyWay = getText(itemDevice, "SafetyWay"); + if (!ObjectUtils.isEmpty(safetyWay)) { + try { + deviceChannel.setSafetyWay(Integer.parseInt(safetyWay)); + }catch (NumberFormatException e) { + logger.warn("[xml解析] 从通道数据获取 safetyWay失败: {}", safetyWay); + } + } + + // 证书序列号(有证书的设备必选) + String certNum = getText(itemDevice, "CertNum"); + if (!ObjectUtils.isEmpty(certNum)) { + deviceChannel.setCertNum(certNum); + } + + // 证书有效标识(有证书的设备必选)缺省为0;证书有效标识:0:无效 1:有效 + String certifiable = getText(itemDevice, "Certifiable"); + if (!ObjectUtils.isEmpty(certifiable)) { + try { + deviceChannel.setCertifiable(Integer.parseInt(certifiable)); + }catch (NumberFormatException e) { + logger.warn("[xml解析] 从通道数据获取 Certifiable失败: {}", certifiable); + } + } + + // 无效原因码(有证书且证书无效的设备必选) + String errCode = getText(itemDevice, "ErrCode"); + if (!ObjectUtils.isEmpty(errCode)) { + try { + deviceChannel.setErrCode(Integer.parseInt(errCode)); + }catch (NumberFormatException e) { + logger.warn("[xml解析] 从通道数据获取 ErrCode失败: {}", errCode); + } + } + + // 证书终止有效期(有证书的设备必选) + String endTime = getText(itemDevice, "EndTime"); + if (!ObjectUtils.isEmpty(endTime)) { + deviceChannel.setEndTime(endTime); + } + + + // 设备/区域/系统IP地址 + String ipAddress = getText(itemDevice, "IPAddress"); + if (!ObjectUtils.isEmpty(ipAddress)) { + deviceChannel.setIpAddress(ipAddress); + } + + // 设备/区域/系统端口 + String port = getText(itemDevice, "Port"); + if (!ObjectUtils.isEmpty(port)) { + try { + deviceChannel.setPort(Integer.parseInt(port)); + }catch (NumberFormatException e) { + logger.warn("[xml解析] 从通道数据获取 Port失败: {}", port); + } + } + + // 设备口令 + String password = getText(itemDevice, "Password"); + if (!ObjectUtils.isEmpty(password)) { + deviceChannel.setPassword(password); + } + + + // 设备状态 + String status = getText(itemDevice, "Status"); + if (status != null) { + // ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理 + if (status.equals("ON") || status.equals("On") || status.equals("ONLINE") || status.equals("OK")) { + deviceChannel.setStatus(true); + } + if (status.equals("OFF") || status.equals("Off") || status.equals("OFFLINE")) { + deviceChannel.setStatus(false); + } + }else { + deviceChannel.setStatus(true); + } + + // 经度 + String longitude = getText(itemDevice, "Longitude"); + if (NumericUtil.isDouble(longitude)) { + deviceChannel.setLongitude(Double.parseDouble(longitude)); + } else { + deviceChannel.setLongitude(0.00); + } + + // 纬度 + String latitude = getText(itemDevice, "Latitude"); + if (NumericUtil.isDouble(latitude)) { + deviceChannel.setLatitude(Double.parseDouble(latitude)); + } else { + deviceChannel.setLatitude(0.00); + } + + deviceChannel.setGpsTime(DateUtil.getNow()); + + // -摄像机类型扩展,标识摄像机类型:1-球机;2-半球;3-固定枪机;4-遥控枪机。当目录项为摄像机时可选 + String ptzType = getText(itemDevice, "PTZType"); + if (ObjectUtils.isEmpty(ptzType)) { + //兼容INFO中的信息 + Element info = itemDevice.element("Info"); + String ptzTypeFromInfo = XmlUtil.getText(info, "PTZType"); + if(!ObjectUtils.isEmpty(ptzTypeFromInfo)){ + try { + deviceChannel.setPTZType(Integer.parseInt(ptzTypeFromInfo)); + }catch (NumberFormatException e){ + logger.warn("[xml解析] 从通道数据info中获取PTZType失败: {}", ptzTypeFromInfo); + } + } + } else { + try { + deviceChannel.setPTZType(Integer.parseInt(ptzType)); + }catch (NumberFormatException e){ + logger.warn("[xml解析] 从通道数据中获取PTZType失败: {}", ptzType); + } + } + + // TODO 摄像机位置类型扩展。 + // 1-省际检查站、 + // 2-党政机关、 + // 3-车站码头、 + // 4-中心广场、 + // 5-体育场馆、 + // 6-商业中心、 + // 7-宗教场所、 + // 8-校园周边、 + // 9-治安复杂区域、 + // 10-交通干线。 + // String positionType = getText(itemDevice, "PositionType"); + + // TODO 摄像机安装位置室外、室内属性。1-室外、2-室内。 + // String roomType = getText(itemDevice, "RoomType"); + // TODO 摄像机用途属性 + // String useType = getText(itemDevice, "UseType"); + // TODO 摄像机补光属性。1-无补光、2-红外补光、3-白光补光 + // String supplyLightType = getText(itemDevice, "SupplyLightType"); + // TODO 摄像机监视方位属性。1-东、2-西、3-南、4-北、5-东南、6-东北、7-西南、8-西北。 + // String directionType = getText(itemDevice, "DirectionType"); + // TODO 摄像机支持的分辨率,可有多个分辨率值,各个取值间以“/”分隔。分辨率取值参见附录 F中SDPf字段规定 + // String resolution = getText(itemDevice, "Resolution"); + + // TODO 下载倍速范围(可选),各可选参数以“/”分隔,如设备支持1,2,4倍速下载则应写为“1/2/4 + // String downloadSpeed = getText(itemDevice, "DownloadSpeed"); + // TODO 空域编码能力,取值0:不支持;1:1级增强(1个增强层);2:2级增强(2个增强层);3:3级增强(3个增强层) + // String svcSpaceSupportMode = getText(itemDevice, "SVCSpaceSupportMode"); + // TODO 时域编码能力,取值0:不支持;1:1级增强;2:2级增强;3:3级增强 + // String svcTimeSupportMode = getText(itemDevice, "SVCTimeSupportMode"); + + + deviceChannel.setSecrecy(secrecy); + break; + } + } + + return deviceChannel; + } + + /** + * 新增方法支持内部嵌套 + * + * @param element xmlElement + * @param clazz 结果类 + * @param 泛型 + * @return 结果对象 + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws InstantiationException + * @throws IllegalAccessException + */ + public static T loadElement(Element element, Class clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + Field[] fields = clazz.getDeclaredFields(); + T t = clazz.getDeclaredConstructor().newInstance(); + for (Field field : fields) { + ReflectionUtils.makeAccessible(field); + MessageElement annotation = field.getAnnotation(MessageElement.class); + if (annotation == null) { + continue; + } + String value = annotation.value(); + String subVal = annotation.subVal(); + Element element1 = element.element(value); + if (element1 == null) { + continue; + } + if ("".equals(subVal)) { + // 无下级数据 + Object fieldVal = element1.isTextOnly() ? element1.getText() : loadElement(element1, field.getType()); + Object o = simpleTypeDeal(field.getType(), fieldVal); + ReflectionUtils.setField(field, t, o); + } else { + // 存在下级数据 + ArrayList list = new ArrayList<>(); + Type genericType = field.getGenericType(); + if (!(genericType instanceof ParameterizedType)) { + continue; + } + Class aClass = (Class) ((ParameterizedType) genericType).getActualTypeArguments()[0]; + for (Element element2 : element1.elements(subVal)) { + list.add(loadElement(element2, aClass)); + } + ReflectionUtils.setField(field, t, list); + } + } + return t; + } + + /** + * 简单类型处理 + * + * @param tClass + * @param val + * @return + */ + private static Object simpleTypeDeal(Class tClass, Object val) { + if (tClass.equals(String.class)) { + return val.toString(); + } + if (tClass.equals(Integer.class)) { + return Integer.valueOf(val.toString()); + } + if (tClass.equals(Double.class)) { + return Double.valueOf(val.toString()); + } + if (tClass.equals(Long.class)) { + return Long.valueOf(val.toString()); + } + return val; + } } \ No newline at end of file diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java index 863ff32cb..5735f37fd 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/AssistRESTfulUtils.java @@ -3,39 +3,70 @@ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.utils.SSLSocketClientUtil; import okhttp3.*; import okhttp3.logging.HttpLoggingInterceptor; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; +import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.net.ConnectException; +import java.net.SocketTimeoutException; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; @Component public class AssistRESTfulUtils { private final static Logger logger = LoggerFactory.getLogger(AssistRESTfulUtils.class); + + private OkHttpClient client; + + public interface RequestCallback{ void run(JSONObject response); } private OkHttpClient getClient(){ - OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); - if (logger.isDebugEnabled()) { - HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> { - logger.debug("http请求参数:" + message); - }); - logging.setLevel(HttpLoggingInterceptor.Level.BASIC); - // OkHttp進行添加攔截器loggingInterceptor - httpClientBuilder.addInterceptor(logging); + return getClient(null); + } + + private OkHttpClient getClient(Integer readTimeOut){ + if (client == null) { + if (readTimeOut == null) { + readTimeOut = 10; + } + OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); + // 设置连接超时时间 + httpClientBuilder.connectTimeout(8, TimeUnit.SECONDS); + // 设置读取超时时间 + httpClientBuilder.readTimeout(readTimeOut,TimeUnit.SECONDS); + // 设置连接池 + httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES)); + if (logger.isDebugEnabled()) { + HttpLoggingInterceptor logging = new HttpLoggingInterceptor(message -> { + logger.debug("http请求参数:" + message); + }); + logging.setLevel(HttpLoggingInterceptor.Level.BASIC); + // OkHttp進行添加攔截器loggingInterceptor + httpClientBuilder.addInterceptor(logging); + } + X509TrustManager manager = SSLSocketClientUtil.getX509TrustManager(); + // 设置ssl + httpClientBuilder.sslSocketFactory(SSLSocketClientUtil.getSocketFactory(manager), manager); + httpClientBuilder.hostnameVerifier(SSLSocketClientUtil.getHostnameVerifier());//忽略校验 + client = httpClientBuilder.build(); } - return httpClientBuilder.build(); + return client; + } @@ -49,11 +80,11 @@ public class AssistRESTfulUtils { logger.warn("未启用Assist服务"); return null; } - StringBuffer stringBuffer = new StringBuffer(); - stringBuffer.append(String.format("http://%s:%s/%s", mediaServerItem.getIp(), mediaServerItem.getRecordAssistPort(), api)); + StringBuilder stringBuffer = new StringBuilder(); + stringBuffer.append(api); JSONObject responseJSON = null; - if (param != null && param.keySet().size() > 0) { + if (param != null && !param.keySet().isEmpty()) { stringBuffer.append("?"); int index = 1; for (String key : param.keySet()){ @@ -68,6 +99,7 @@ public class AssistRESTfulUtils { } String url = stringBuffer.toString(); + logger.info("[访问assist]: {}", url); Request request = new Request.Builder() .get() .url(url) @@ -123,13 +155,93 @@ public class AssistRESTfulUtils { return responseJSON; } + public JSONObject sendPost(MediaServerItem mediaServerItem, String url, + JSONObject param, ZLMRESTfulUtils.RequestCallback callback, + Integer readTimeOut) { + OkHttpClient client = getClient(readTimeOut); - public JSONObject fileDuration(MediaServerItem mediaServerItem, String app, String stream, RequestCallback callback){ - Map param = new HashMap<>(); - param.put("app",app); - param.put("stream",stream); - param.put("recordIng",true); - return sendGet(mediaServerItem, "api/record/file/duration",param, callback); + if (mediaServerItem == null) { + return null; + } + logger.info("[访问assist]: {}, 参数: {}", url, param); + JSONObject responseJSON = new JSONObject(); + //-2自定义流媒体 调用错误码 + responseJSON.put("code",-2); + responseJSON.put("msg","ASSIST调用失败"); + + RequestBody requestBodyJson = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), param.toString()); + + Request request = new Request.Builder() + .post(requestBodyJson) + .url(url) + .addHeader("Content-Type", "application/json") + .build(); + if (callback == null) { + try { + Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + ResponseBody responseBody = response.body(); + if (responseBody != null) { + String responseStr = responseBody.string(); + responseJSON = JSON.parseObject(responseStr); + } + }else { + response.close(); + Objects.requireNonNull(response.body()).close(); + } + }catch (IOException e) { + logger.error(String.format("[ %s ]ASSIST请求失败: %s", url, e.getMessage())); + + if(e instanceof SocketTimeoutException){ + //读取超时超时异常 + logger.error(String.format("读取ASSIST数据失败: %s, %s", url, e.getMessage())); + } + if(e instanceof ConnectException){ + //判断连接异常,我这里是报Failed to connect to 10.7.5.144 + logger.error(String.format("连接ASSIST失败: %s, %s", url, e.getMessage())); + } + + }catch (Exception e){ + logger.error(String.format("访问ASSIST失败: %s, %s", url, e.getMessage())); + } + }else { + client.newCall(request).enqueue(new Callback(){ + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response){ + if (response.isSuccessful()) { + try { + String responseStr = Objects.requireNonNull(response.body()).string(); + callback.run(JSON.parseObject(responseStr)); + } catch (IOException e) { + logger.error(String.format("[ %s ]请求失败: %s", url, e.getMessage())); + } + + }else { + response.close(); + Objects.requireNonNull(response.body()).close(); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage())); + + if(e instanceof SocketTimeoutException){ + //读取超时超时异常 + logger.error(String.format("读取ZLM数据失败: %s, %s", call.request().toString(), e.getMessage())); + } + if(e instanceof ConnectException){ + //判断连接异常,我这里是报Failed to connect to 10.7.5.144 + logger.error(String.format("连接ZLM失败: %s, %s", call.request().toString(), e.getMessage())); + } + } + }); + } + + + + return responseJSON; } public JSONObject getInfo(MediaServerItem mediaServerItem, RequestCallback callback){ @@ -137,12 +249,43 @@ public class AssistRESTfulUtils { return sendGet(mediaServerItem, "api/record/info",param, callback); } - public JSONObject addStreamCallInfo(MediaServerItem mediaServerItem, String app, String stream, String callId, RequestCallback callback){ - Map param = new HashMap<>(); - param.put("app",app); - param.put("stream",stream); - param.put("callId",callId); - return sendGet(mediaServerItem, "api/record/addStreamCallInfo",param, callback); + public JSONObject addTask(MediaServerItem mediaServerItem, String app, String stream, String startTime, + String endTime, String callId, List filePathList, String remoteHost) { + + JSONObject videoTaskInfoJSON = new JSONObject(); + videoTaskInfoJSON.put("app", app); + videoTaskInfoJSON.put("stream", stream); + videoTaskInfoJSON.put("startTime", startTime); + videoTaskInfoJSON.put("endTime", endTime); + videoTaskInfoJSON.put("callId", callId); + videoTaskInfoJSON.put("filePathList", filePathList); + if (!ObjectUtils.isEmpty(remoteHost)) { + videoTaskInfoJSON.put("remoteHost", remoteHost); + } + String urlStr = String.format("%s/api/record/file/download/task/add", remoteHost);; + return sendPost(mediaServerItem, urlStr, videoTaskInfoJSON, null, 30); } + public JSONObject queryTaskList(MediaServerItem mediaServerItem, String app, String stream, String callId, + String taskId, Boolean isEnd, String scheme) { + Map param = new HashMap<>(); + if (!ObjectUtils.isEmpty(app)) { + param.put("app", app); + } + if (!ObjectUtils.isEmpty(stream)) { + param.put("stream", stream); + } + if (!ObjectUtils.isEmpty(callId)) { + param.put("callId", callId); + } + if (!ObjectUtils.isEmpty(taskId)) { + param.put("taskId", taskId); + } + if (!ObjectUtils.isEmpty(isEnd)) { + param.put("isEnd", isEnd); + } + String urlStr = String.format("%s://%s:%s/api/record/file/download/task/list", + scheme, mediaServerItem.getIp(), mediaServerItem.getRecordAssistPort());; + return sendGet(mediaServerItem, urlStr, param, null); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/SendRtpPortManager.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/SendRtpPortManager.java old mode 100644 new mode 100755 index f960c7dc8..3f28d02ad --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/SendRtpPortManager.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/SendRtpPortManager.java @@ -30,7 +30,7 @@ public class SendRtpPortManager { private final String KEY = "VM_MEDIA_SEND_RTP_PORT_"; - public int getNextPort(MediaServerItem mediaServer) { + public synchronized int getNextPort(MediaServerItem mediaServer) { if (mediaServer == null) { logger.warn("[发送端口管理] 参数错误,mediaServer为NULL"); return -1; @@ -50,17 +50,15 @@ public class SendRtpPortManager { String sendRtpPortRange = mediaServer.getSendRtpPortRange(); int startPort; int endPort; - if (sendRtpPortRange == null) { - logger.warn("{}未设置发送端口默认值,自动使用40000-50000作为端口范围", mediaServer.getId()); + if (sendRtpPortRange != null) { String[] portArray = sendRtpPortRange.split(","); if (portArray.length != 2 || !NumberUtils.isParsable(portArray[0]) || !NumberUtils.isParsable(portArray[1])) { - logger.warn("{}发送端口配置格式错误,自动使用40000-50000作为端口范围", mediaServer.getId()); + logger.warn("{}发送端口配置格式错误,自动使用50000-60000作为端口范围", mediaServer.getId()); startPort = 50000; endPort = 60000; }else { - if ( Integer.parseInt(portArray[1]) - Integer.parseInt(portArray[0]) < 1) { - logger.warn("{}发送端口配置错误,结束端口至少比开始端口大一,自动使用40000-50000作为端口范围", mediaServer.getId()); + logger.warn("{}发送端口配置错误,结束端口至少比开始端口大一,自动使用50000-60000作为端口范围", mediaServer.getId()); startPort = 50000; endPort = 60000; }else { @@ -69,6 +67,7 @@ public class SendRtpPortManager { } } }else { + logger.warn("{}未设置发送端口默认值,自动使用50000-60000作为端口范围", mediaServer.getId()); startPort = 50000; endPort = 60000; } @@ -76,10 +75,35 @@ public class SendRtpPortManager { logger.warn("{}获取redis连接信息失败", mediaServer.getId()); return -1; } +// RedisAtomicInteger redisAtomicInteger = new RedisAtomicInteger(sendIndexKey , redisTemplate.getConnectionFactory()); +// return redisAtomicInteger.getAndUpdate((current)->{ +// return getPort(current, startPort, endPort, checkPort-> !sendRtpItemMap.containsKey(checkPort)); +// }); + return getSendPort(startPort, endPort, sendIndexKey, sendRtpItemMap); + } + + private synchronized int getSendPort(int startPort, int endPort, String sendIndexKey, Map sendRtpItemMap){ RedisAtomicInteger redisAtomicInteger = new RedisAtomicInteger(sendIndexKey , redisTemplate.getConnectionFactory()); - return redisAtomicInteger.getAndUpdate((current)->{ - return getPort(current, startPort, endPort, checkPort-> !sendRtpItemMap.containsKey(checkPort)); - }); + if (redisAtomicInteger.get() < startPort) { + redisAtomicInteger.set(startPort); + return startPort; + }else { + int port = redisAtomicInteger.getAndIncrement(); + if (port > endPort) { + redisAtomicInteger.set(startPort); + if (sendRtpItemMap.containsKey(startPort)) { + return getSendPort(startPort, endPort, sendIndexKey, sendRtpItemMap); + }else { + return startPort; + } + } + if (sendRtpItemMap.containsKey(port)) { + return getSendPort(startPort, endPort, sendIndexKey, sendRtpItemMap); + }else { + return port; + } + } + } interface CheckPortCallback{ diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java old mode 100644 new mode 100755 index 9b2864fd4..72df8a053 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -10,13 +10,14 @@ import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; import com.genersoft.iot.vmp.media.zlm.dto.HookType; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; @@ -29,6 +30,7 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.genersoft.iot.vmp.vmanager.bean.OtherPsSendInfo; import com.genersoft.iot.vmp.vmanager.bean.OtherRtpSendInfo; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import org.slf4j.Logger; @@ -65,7 +67,13 @@ public class ZLMHttpHookListener { private SIPCommander cmder; @Autowired - private SIPCommanderFroPlatform commanderFroPlatform; + private ISIPCommanderForPlatform commanderFroPlatform; + + @Autowired + private AudioBroadcastManager audioBroadcastManager; + + @Autowired + private ZLMServerFactory zlmServerFactory; @Autowired private IPlayService playService; @@ -109,6 +117,9 @@ public class ZLMHttpHookListener { @Autowired private IUserService userService; + @Autowired + private ICloudRecordService cloudRecordService; + @Autowired private VideoStreamSessionManager sessionManager; @@ -129,7 +140,7 @@ public class ZLMHttpHookListener { * 服务器定时上报时间,上报间隔可配置,默认10s上报一次 */ @ResponseBody - + @PostMapping(value = "/on_server_keepalive", produces = "application/json;charset=UTF-8") public HookResult onServerKeepalive(@RequestBody OnServerKeepaliveHookParam param) { @@ -151,7 +162,7 @@ public class ZLMHttpHookListener { * 播放器鉴权事件,rtsp/rtmp/http-flv/ws-flv/hls的播放都将触发此鉴权事件。 */ @ResponseBody - + @PostMapping(value = "/on_play", produces = "application/json;charset=UTF-8") public HookResult onPlay(@RequestBody OnPlayHookParam param) { if (logger.isDebugEnabled()) { @@ -193,8 +204,18 @@ public class ZLMHttpHookListener { String mediaServerId = json.getString("mediaServerId"); MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); - + if (mediaInfo == null) { + return new HookResultForOnPublish(200, "success"); + } + // 推流鉴权的处理 if (!"rtp".equals(param.getApp())) { + StreamProxyItem stream = streamProxyService.getStreamProxyByAppAndStream(param.getApp(), param.getStream()); + if (stream != null) { + HookResultForOnPublish result = HookResultForOnPublish.SUCCESS(); + result.setEnable_audio(stream.isEnableAudio()); + result.setEnable_mp4(stream.isEnableMp4()); + return result; + } if (userSetting.getPushAuthority()) { // 推流鉴权 if (param.getParams() == null) { @@ -220,12 +241,6 @@ public class ZLMHttpHookListener { streamAuthorityInfo.setSign(sign); // 鉴权通过 redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo); - // 通知assist新的callId - if (mediaInfo != null && mediaInfo.getRecordAssistPort() > 0) { - taskExecutor.execute(() -> { - assistRESTfulUtils.addStreamCallInfo(mediaInfo, param.getApp(), param.getStream(), callId, null); - }); - } } } else { zlmMediaListManager.sendStreamEvent(param.getApp(), param.getStream(), param.getMediaServerId()); @@ -245,47 +260,76 @@ public class ZLMHttpHookListener { } }); + // 是否录像 if ("rtp".equals(param.getApp())) { result.setEnable_mp4(userSetting.getRecordSip()); } else { result.setEnable_mp4(userSetting.isRecordPushLive()); } - List ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream()); - if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) { - String deviceId = ssrcTransactionForAll.get(0).getDeviceId(); - String channelId = ssrcTransactionForAll.get(0).getChannelId(); - DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); - if (deviceChannel != null) { - result.setEnable_audio(deviceChannel.isHasAudio()); - } - // 如果是录像下载就设置视频间隔十秒 - if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) { - result.setMp4_max_second(10); - result.setEnable_mp4(true); - } - } - if (mediaInfo.getRecordAssistPort() > 0 && userSetting.getRecordPath() == null) { - logger.info("推流时发现尚未设置录像路径,从assist服务中读取"); - JSONObject info = assistRESTfulUtils.getInfo(mediaInfo, null); - if (info != null && info.getInteger("code") != null && info.getInteger("code") == 0 ) { - JSONObject dataJson = info.getJSONObject("data"); - if (dataJson != null) { - String recordPath = dataJson.getString("record"); - userSetting.setRecordPath(recordPath); - result.setMp4_save_path(recordPath); - // 修改zlm中的录像路径 - if (mediaInfo.isAutoConfig()) { - taskExecutor.execute(() -> { - mediaServerService.setZLMConfig(mediaInfo, false); - }); - } + // 国标流 + if ("rtp".equals(param.getApp())) { + + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); + + // 单端口模式下修改流 ID + if (!mediaInfo.isRtpEnable() && inviteInfo == null) { + String ssrc = String.format("%010d", Long.parseLong(param.getStream(), 16)); + inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc); + if (inviteInfo != null) { + result.setStream_replace(inviteInfo.getStream()); + logger.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", param.getStream(), inviteInfo.getStream()); } } + + // 设置音频信息及录制信息 + List ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, param.getStream()); + if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) { + + // 为录制国标模拟一个鉴权信息, 方便后续写入录像文件时使用 + StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); + streamAuthorityInfo.setApp(param.getApp()); + streamAuthorityInfo.setStream(ssrcTransactionForAll.get(0).getStream()); + streamAuthorityInfo.setCallId(ssrcTransactionForAll.get(0).getSipTransactionInfo().getCallId()); + + redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), ssrcTransactionForAll.get(0).getStream(), streamAuthorityInfo); + + String deviceId = ssrcTransactionForAll.get(0).getDeviceId(); + String channelId = ssrcTransactionForAll.get(0).getChannelId(); + DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); + if (deviceChannel != null) { + result.setEnable_audio(deviceChannel.isHasAudio()); + } + // 如果是录像下载就设置视频间隔十秒 + if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) { + // 获取录像的总时长,然后设置为这个视频的时长 + InviteInfo inviteInfoForDownload = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, param.getStream()); + if (inviteInfoForDownload != null && inviteInfoForDownload.getStreamInfo() != null) { + String startTime = inviteInfoForDownload.getStreamInfo().getStartTime(); + String endTime = inviteInfoForDownload.getStreamInfo().getEndTime(); + long difference = DateUtil.getDifference(startTime, endTime) / 1000; + result.setMp4_max_second((int) difference); + result.setEnable_mp4(true); + // 设置为2保证得到的mp4的时长是正常的 + result.setModify_stamp(2); + } + } + // 如果是talk对讲,则默认获取声音 + if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.TALK) { + result.setEnable_audio(true); + } + } + } else if (param.getApp().equals("broadcast")) { + result.setEnable_audio(true); + } else if (param.getApp().equals("talk")) { + result.setEnable_audio(true); } if (param.getApp().equalsIgnoreCase("rtp")) { String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + param.getStream(); - OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo)redisTemplate.opsForValue().get(receiveKey); - if (otherRtpSendInfo != null) { + OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo) redisTemplate.opsForValue().get(receiveKey); + + String receiveKeyForPS = VideoManagerConstants.WVP_OTHER_RECEIVE_PS_INFO + userSetting.getServerId() + "_" + param.getStream(); + OtherPsSendInfo otherPsSendInfo = (OtherPsSendInfo) redisTemplate.opsForValue().get(receiveKeyForPS); + if (otherRtpSendInfo != null || otherPsSendInfo != null) { result.setEnable_mp4(true); } } @@ -307,7 +351,6 @@ public class ZLMHttpHookListener { logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); } - JSONObject json = (JSONObject) JSON.toJSON(param); taskExecutor.execute(() -> { ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json); @@ -322,13 +365,11 @@ public class ZLMHttpHookListener { List tracks = param.getTracks(); // TODO 重构此处逻辑 - boolean isPush = false; if (param.isRegist()) { - // 处理流注册的鉴权信息 + // 处理流注册的鉴权信息, 流注销这里不再删除鉴权信息,下次来了新的鉴权信息会对就的进行覆盖 if (param.getOriginType() == OriginType.RTMP_PUSH.ordinal() || param.getOriginType() == OriginType.RTSP_PUSH.ordinal() || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) { - isPush = true; StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); if (streamAuthorityInfo == null) { streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(param); @@ -338,18 +379,15 @@ public class ZLMHttpHookListener { } redisCatchStorage.updateStreamAuthorityInfo(param.getApp(), param.getStream(), streamAuthorityInfo); } - } else { - redisCatchStorage.removeStreamAuthorityInfo(param.getApp(), param.getStream()); } - if ("rtsp".equals(param.getSchema())) { - // 更新流媒体负载信息 + logger.info("流变化:注册->{}, app->{}, stream->{}", param.isRegist(), param.getApp(), param.getStream()); if (param.isRegist()) { mediaServerService.addCount(param.getMediaServerId()); } else { mediaServerService.removeCount(param.getMediaServerId()); } - // 设置拉流代理上线/离线 + int updateStatusResult = streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream()); if (updateStatusResult > 0) { @@ -361,6 +399,63 @@ public class ZLMHttpHookListener { inviteStreamService.removeInviteInfo(inviteInfo); storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); } + } else if ("broadcast".equals(param.getApp())) { + // 语音对讲推流 stream需要满足格式deviceId_channelId + if (param.getStream().indexOf("_") > 0) { + String[] streamArray = param.getStream().split("_"); + if (streamArray.length == 2) { + String deviceId = streamArray[0]; + String channelId = streamArray[1]; + Device device = deviceService.getDevice(deviceId); + if (device != null) { + if (param.isRegist()) { + if (audioBroadcastManager.exit(deviceId, channelId)) { + playService.stopAudioBroadcast(deviceId, channelId); + } + // 开启语音对讲通道 + try { + playService.audioBroadcastCmd(device, channelId, mediaInfo, param.getApp(), param.getStream(), 60, false, (msg) -> { + logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId); + }); + } catch (InvalidArgumentException | ParseException | SipException e) { + logger.error("[命令发送失败] 语音对讲: {}", e.getMessage()); + } + } else { + // 流注销 + playService.stopAudioBroadcast(deviceId, channelId); + } + } else { + logger.info("[语音对讲] 未找到设备:{}", deviceId); + } + } + } + } else if ("talk".equals(param.getApp())) { + // 语音对讲推流 stream需要满足格式deviceId_channelId + if (param.getStream().indexOf("_") > 0) { + String[] streamArray = param.getStream().split("_"); + if (streamArray.length == 2) { + String deviceId = streamArray[0]; + String channelId = streamArray[1]; + Device device = deviceService.getDevice(deviceId); + if (device != null) { + if (param.isRegist()) { + if (audioBroadcastManager.exit(deviceId, channelId)) { + playService.stopAudioBroadcast(deviceId, channelId); + } + // 开启语音对讲通道 + playService.talkCmd(device, channelId, mediaInfo, param.getStream(), (msg) -> { + logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId); + }); + } else { + // 流注销 + playService.stopTalk(device, channelId, param.isRegist()); + } + } else { + logger.info("[语音对讲] 未找到设备:{}", deviceId); + } + } + } + } else { if (!"rtp".equals(param.getApp())) { String type = OriginType.values()[param.getOriginType()].getType(); @@ -380,6 +475,9 @@ public class ZLMHttpHookListener { || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) { param.setSeverId(userSetting.getServerId()); zlmMediaListManager.addPush(param); + + // 冗余数据,自己系统中自用 + redisCatchStorage.addPushListItem(param.getApp(), param.getStream(), param); } } else { // 兼容流注销时类型从redis记录获取 @@ -388,6 +486,10 @@ public class ZLMHttpHookListener { if (onStreamChangedHookParam != null) { type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType(); redisCatchStorage.removeStream(mediaInfo.getId(), type, param.getApp(), param.getStream()); + if ("PUSH".equalsIgnoreCase(type)) { + // 冗余数据,自己系统中自用 + redisCatchStorage.removePushListItem(param.getApp(), param.getStream(), param.getMediaServerId()); + } } GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream()); if (gbStream != null) { @@ -398,7 +500,7 @@ public class ZLMHttpHookListener { GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream()); if (gbStream != null) { if (userSetting.isUsePushingAsStatus()) { - eventPublisher.catalogEventPublishForStream(null, gbStream, param.isRegist()?CatalogEvent.ON:CatalogEvent.OFF); + eventPublisher.catalogEventPublishForStream(null, gbStream, param.isRegist() ? CatalogEvent.ON : CatalogEvent.OFF); } } if (type != null) { @@ -415,7 +517,7 @@ public class ZLMHttpHookListener { } if (!param.isRegist()) { List sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); - if (sendRtpItems.size() > 0) { + if (!sendRtpItems.isEmpty()) { for (SendRtpItem sendRtpItem : sendRtpItems) { if (sendRtpItem != null && sendRtpItem.getApp().equals(param.getApp())) { String platformId = sendRtpItem.getPlatformId(); @@ -426,13 +528,22 @@ public class ZLMHttpHookListener { if (platform != null) { commanderFroPlatform.streamByeCmd(platform, sendRtpItem); redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(), - sendRtpItem.getCallId(), sendRtpItem.getStreamId()); + sendRtpItem.getCallId(), sendRtpItem.getStream()); } else { cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId()); + if (sendRtpItem.getPlayType().equals(InviteStreamType.BROADCAST) + || sendRtpItem.getPlayType().equals(InviteStreamType.TALK)) { + AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); + if (audioBroadcastCatch != null) { + // 来自上级平台的停止对讲 + logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); + audioBroadcastManager.del(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); + } + } } } catch (SipException | InvalidArgumentException | ParseException | SsrcTransactionNotFoundException e) { - logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); + logger.error("[命令发送失败] 发送BYE: {}", e.getMessage()); } } } @@ -440,7 +551,6 @@ public class ZLMHttpHookListener { } } }); - return HookResult.SUCCESS(); } @@ -451,7 +561,7 @@ public class ZLMHttpHookListener { @PostMapping(value = "/on_stream_none_reader", produces = "application/json;charset=UTF-8") public JSONObject onStreamNoneReader(@RequestBody OnStreamNoneReaderHookParam param) { - logger.info("[ZLM HOOK]流无人观看:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), + logger.info("[ZLM HOOK]流无人观看:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); JSONObject ret = new JSONObject(); ret.put("code", 0); @@ -480,10 +590,10 @@ public class ZLMHttpHookListener { logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); } redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), - sendRtpItem.getCallId(), sendRtpItem.getStreamId()); + sendRtpItem.getCallId(), sendRtpItem.getStream()); if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) { MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, - sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(), + sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(), sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId()); messageForPushChannel.setPlatFormIndex(parentPlatform.getId()); redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel); @@ -494,16 +604,21 @@ public class ZLMHttpHookListener { Device device = deviceService.getDevice(inviteInfo.getDeviceId()); if (device != null) { try { + // 多查询一次防止已经被处理了 InviteInfo info = inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()); if (info != null) { cmder.streamByeCmd(device, inviteInfo.getChannelId(), inviteInfo.getStream(), null); + } else { + logger.info("[无人观看] 未找到设备的点播信息: {}, 流:{}", inviteInfo.getDeviceId(), param.getStream()); } } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage()); } + } else { + logger.info("[无人观看] 未找到设备: {},流:{}", inviteInfo.getDeviceId(), param.getStream()); } inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), @@ -511,6 +626,13 @@ public class ZLMHttpHookListener { storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); return ret; } + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, param.getStream(), null); + if (sendRtpItem != null && "talk".equals(sendRtpItem.getApp())) { + ret.put("close", false); + return ret; + } + } else if ("talk".equals(param.getApp()) || "broadcast".equals(param.getApp())) { + ret.put("close", false); } else { // 非国标流 推流/拉流代理 // 拉流代理 @@ -561,14 +683,14 @@ public class ZLMHttpHookListener { if ("rtp".equals(param.getApp())) { String[] s = param.getStream().split("_"); - if (!mediaInfo.isRtpEnable() || (s.length != 2 && s.length != 4)) { + if ((s.length != 2 && s.length != 4)) { defaultResult.setResult(HookResult.SUCCESS()); return defaultResult; } String deviceId = s[0]; String channelId = s[1]; Device device = redisCatchStorage.getDevice(deviceId); - if (device == null) { + if (device == null || !device.isOnLine()) { defaultResult.setResult(new HookResult(ErrorCode.ERROR404.getCode(), ErrorCode.ERROR404.getMsg())); return defaultResult; } @@ -590,9 +712,10 @@ public class ZLMHttpHookListener { result.onTimeout(() -> { logger.info("[ZLM HOOK] 预览流自动点播, 等待超时"); - // 释放rtpserver msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时")); - resultHolder.invokeResult(msg); + resultHolder.invokeAllResult(msg); + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); + storager.stopPlay(deviceId, channelId); }); resultHolder.put(key, uuid, result); @@ -604,7 +727,7 @@ public class ZLMHttpHookListener { }); } return result; - }else if(s.length == 4){ + } else if (s.length == 4) { // 此时为录像回放, 录像回放格式为> 设备ID_通道ID_开始时间_结束时间 String startTimeStr = s[2]; String endTimeStr = s[3]; @@ -638,14 +761,14 @@ public class ZLMHttpHookListener { if (!exist) { SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaInfo, param.getStream(), null, - device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam()); + device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam()); playService.playBack(mediaInfo, ssrcInfo, deviceId, channelId, startTime, endTime, (code, message, data) -> { msg.setData(new HookResult(code, message)); resultHolder.invokeResult(msg); }); } return result; - }else { + } else { defaultResult.setResult(HookResult.SUCCESS()); return defaultResult; } @@ -711,7 +834,7 @@ public class ZLMHttpHookListener { logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); } redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), - sendRtpItem.getCallId(), sendRtpItem.getStreamId()); + sendRtpItem.getCallId(), sendRtpItem.getStream()); } } }); @@ -724,13 +847,14 @@ public class ZLMHttpHookListener { */ @ResponseBody @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8") - public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param) { + public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam + param) { logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc()); taskExecutor.execute(() -> { JSONObject json = (JSONObject) JSON.toJSON(param); List subscribes = this.subscribe.getSubscribes(HookType.on_rtp_server_timeout); - if (subscribes != null && subscribes.size() > 0) { + if (subscribes != null && !subscribes.isEmpty()) { for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { subscribe.response(null, param); } @@ -740,6 +864,28 @@ public class ZLMHttpHookListener { return HookResult.SUCCESS(); } + /** + * 录像完成事件 + */ + @ResponseBody + @PostMapping(value = "/on_record_mp4", produces = "application/json;charset=UTF-8") + public HookResult onRecordMp4(HttpServletRequest request, @RequestBody OnRecordMp4HookParam param) { + logger.info("[ZLM HOOK] 录像完成事件:{}->{}", param.getMediaServerId(), param.getFile_path()); + + taskExecutor.execute(() -> { + List subscribes = this.subscribe.getSubscribes(HookType.on_record_mp4); + if (subscribes != null && !subscribes.isEmpty()) { + for (ZlmHttpHookSubscribe.Event subscribe : subscribes) { + subscribe.response(null, param); + } + } + cloudRecordService.addRecord(param); + + }); + + return HookResult.SUCCESS(); + } + private Map urlParamToMap(String params) { HashMap map = new HashMap<>(); if (ObjectUtils.isEmpty(params)) { diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java old mode 100644 new mode 100755 index e58f2aebb..044f9f30b --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java @@ -25,20 +25,25 @@ public class ZLMRESTfulUtils { private OkHttpClient client; - - public interface RequestCallback{ void run(JSONObject response); } private OkHttpClient getClient(){ + return getClient(null); + } + + private OkHttpClient getClient(Integer readTimeOut){ if (client == null) { + if (readTimeOut == null) { + readTimeOut = 10; + } OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); //todo 暂时写死超时时间 均为5s // 设置连接超时时间 - httpClientBuilder.connectTimeout(5,TimeUnit.SECONDS); + httpClientBuilder.connectTimeout(8,TimeUnit.SECONDS); // 设置读取超时时间 - httpClientBuilder.readTimeout(10,TimeUnit.SECONDS); + httpClientBuilder.readTimeout(readTimeOut,TimeUnit.SECONDS); // 设置连接池 httpClientBuilder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES)); if (logger.isDebugEnabled()) { @@ -55,9 +60,13 @@ public class ZLMRESTfulUtils { } - public JSONObject sendPost(MediaServerItem mediaServerItem, String api, Map param, RequestCallback callback) { - OkHttpClient client = getClient(); + return sendPost(mediaServerItem, api, param, callback, null); + } + + + public JSONObject sendPost(MediaServerItem mediaServerItem, String api, Map param, RequestCallback callback, Integer readTimeOut) { + OkHttpClient client = getClient(readTimeOut); if (mediaServerItem == null) { return null; @@ -87,6 +96,7 @@ public class ZLMRESTfulUtils { if (callback == null) { try { Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { ResponseBody responseBody = response.body(); if (responseBody != null) { @@ -94,6 +104,8 @@ public class ZLMRESTfulUtils { responseJSON = JSON.parseObject(responseStr); } }else { + System.out.println( 2222); + System.out.println( response.code()); response.close(); Objects.requireNonNull(response.body()).close(); } @@ -102,11 +114,11 @@ public class ZLMRESTfulUtils { if(e instanceof SocketTimeoutException){ //读取超时超时异常 - logger.error(String.format("读取ZLM数据失败: %s, %s", url, e.getMessage())); + logger.error(String.format("读取ZLM数据超时失败: %s, %s", url, e.getMessage())); } if(e instanceof ConnectException){ //判断连接异常,我这里是报Failed to connect to 10.7.5.144 - logger.error(String.format("连接ZLM失败: %s, %s", url, e.getMessage())); + logger.error(String.format("连接ZLM连接失败: %s, %s", url, e.getMessage())); } }catch (Exception e){ @@ -204,6 +216,21 @@ public class ZLMRESTfulUtils { } } + public JSONObject isMediaOnline(MediaServerItem mediaServerItem, String app, String stream, String schema){ + Map param = new HashMap<>(); + if (app != null) { + param.put("app",app); + } + if (stream != null) { + param.put("stream",stream); + } + if (schema != null) { + param.put("schema",schema); + } + param.put("vhost","__defaultVhost__"); + return sendPost(mediaServerItem, "isMediaOnline", param, null); + } + public JSONObject getMediaList(MediaServerItem mediaServerItem, String app, String stream, String schema, RequestCallback callback){ Map param = new HashMap<>(); if (app != null) { @@ -261,6 +288,12 @@ public class ZLMRESTfulUtils { return sendPost(mediaServerItem, "delFFmpegSource",param, null); } + public JSONObject delStreamProxy(MediaServerItem mediaServerItem, String key){ + Map param = new HashMap<>(); + param.put("key", key); + return sendPost(mediaServerItem, "delStreamProxy",param, null); + } + public JSONObject getMediaServerConfig(MediaServerItem mediaServerItem){ return sendPost(mediaServerItem, "getServerConfig",null, null); } @@ -293,6 +326,10 @@ public class ZLMRESTfulUtils { return sendPost(mediaServerItem, "startSendRtpPassive",param, null); } + public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Map param, RequestCallback callback) { + return sendPost(mediaServerItem, "startSendRtpPassive",param, callback); + } + public JSONObject stopSendRtp(MediaServerItem mediaServerItem, Map param) { return sendPost(mediaServerItem, "stopSendRtp",param, null); } @@ -310,7 +347,7 @@ public class ZLMRESTfulUtils { param.put("enable_mp4", enable_mp4?1:0); param.put("enable_audio", enable_audio?1:0); param.put("rtp_type", rtp_type); - return sendPost(mediaServerItem, "addStreamProxy",param, null); + return sendPost(mediaServerItem, "addStreamProxy",param, null, 20); } public JSONObject closeStreams(MediaServerItem mediaServerItem, String app, String stream) { @@ -366,4 +403,14 @@ public class ZLMRESTfulUtils { param.put("stream_id", streamId); return sendPost(mediaServerItem, "updateRtpServerSSRC",param, null); } + + public JSONObject deleteRecordDirectory(MediaServerItem mediaServerItem, String app, String stream, String date, String fileName) { + Map param = new HashMap<>(1); + param.put("vhost", "__defaultVhost__"); + param.put("app", app); + param.put("stream", stream); + param.put("period", date); + param.put("name", fileName); + return sendPost(mediaServerItem, "deleteRecordDirectory",param, null); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java old mode 100644 new mode 100755 index 6e5940243..4a781f312 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java @@ -9,6 +9,7 @@ import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForServerStarted; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.service.IMediaServerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerConfig.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerConfig.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java old mode 100644 new mode 100755 index c190de180..24222069d --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMServerFactory.java @@ -42,7 +42,7 @@ public class ZLMServerFactory { * @param tcpMode 0/null udp 模式,1 tcp 被动模式, 2 tcp 主动模式。 * @return */ - public int createRTPServer(MediaServerItem mediaServerItem, String streamId, int ssrc, Integer port, Boolean reUsePort, Integer tcpMode) { + public int createRTPServer(MediaServerItem mediaServerItem, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) { int result = -1; // 查询此rtp server 是否已经存在 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId); @@ -58,7 +58,7 @@ public class ZLMServerFactory { JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerItem, param); if (jsonObject != null ) { if (jsonObject.getInteger("code") == 0) { - return createRTPServer(mediaServerItem, streamId, ssrc, port, reUsePort, tcpMode); + return createRTPServer(mediaServerItem, streamId, ssrc, port,onlyAuto, reUsePort, tcpMode); }else { logger.warn("[开启rtpServer], 重启RtpServer错误"); } @@ -86,6 +86,9 @@ public class ZLMServerFactory { }else { param.put("port", port); } + if (onlyAuto != null) { + param.put("only_audio", onlyAuto?"1":"0"); + } if (ssrc != 0) { param.put("ssrc", ssrc); } @@ -111,9 +114,10 @@ public class ZLMServerFactory { Map param = new HashMap<>(); param.put("stream_id", streamId); JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(serverItem, param); + logger.info("关闭RTP Server " + jsonObject); if (jsonObject != null ) { if (jsonObject.getInteger("code") == 0) { - result = jsonObject.getInteger("hit") == 1; + result = jsonObject.getInteger("hit") >= 1; }else { logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg")); } @@ -164,13 +168,9 @@ public class ZLMServerFactory { public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String deviceId, String channelId, boolean tcp, boolean rtcp){ - // 默认为随机端口 - int localPort = 0; - if (userSetting.getGbSendStreamStrict()) { - localPort = sendRtpPortManager.getNextPort(serverItem); - if (localPort == 0) { - return null; - } + int localPort = sendRtpPortManager.getNextPort(serverItem); + if (localPort == 0) { + return null; } SendRtpItem sendRtpItem = new SendRtpItem(); sendRtpItem.setIp(ip); @@ -200,20 +200,17 @@ public class ZLMServerFactory { */ public SendRtpItem createSendRtpItem(MediaServerItem serverItem, String ip, int port, String ssrc, String platformId, String app, String stream, String channelId, boolean tcp, boolean rtcp){ - // 默认为随机端口 - int localPort = 0; - if (userSetting.getGbSendStreamStrict()) { - localPort = sendRtpPortManager.getNextPort(serverItem); - if (localPort == 0) { - return null; - } + + int localPort = sendRtpPortManager.getNextPort(serverItem); + if (localPort == 0) { + return null; } SendRtpItem sendRtpItem = new SendRtpItem(); sendRtpItem.setIp(ip); sendRtpItem.setPort(port); sendRtpItem.setSsrc(ssrc); sendRtpItem.setApp(app); - sendRtpItem.setStreamId(stream); + sendRtpItem.setStream(stream); sendRtpItem.setPlatformId(platformId); sendRtpItem.setChannelId(channelId); sendRtpItem.setTcp(tcp); @@ -234,10 +231,14 @@ public class ZLMServerFactory { /** * 调用zlm RESTFUL API —— startSendRtpPassive */ - public JSONObject startSendRtpStreamForPassive(MediaServerItem mediaServerItem, Mapparam) { + public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Mapparam) { return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param); } + public JSONObject startSendRtpPassive(MediaServerItem mediaServerItem, Mapparam, ZLMRESTfulUtils.RequestCallback callback) { + return zlmresTfulUtils.startSendRtpPassive(mediaServerItem, param, callback); + } + /** * 查询待转推的流是否就绪 */ @@ -273,11 +274,11 @@ public class ZLMServerFactory { return 0; } Integer code = mediaInfo.getInteger("code"); - if ( code < 0) { + if (code < 0) { logger.warn("查询流({}/{})是否有其它观看者时得到: {}", app, streamId, mediaInfo.getString("msg")); return -1; } - if ( code == 0 && mediaInfo.getBoolean("online") != null && !mediaInfo.getBoolean("online")) { + if ( code == 0 && mediaInfo.getBoolean("online") != null && ! mediaInfo.getBoolean("online")) { logger.warn("查询流({}/{})是否有其它观看者时得到: {}", app, streamId, mediaInfo.getString("msg")); return -1; } @@ -296,13 +297,54 @@ public class ZLMServerFactory { result= true; logger.info("[停止RTP推流] 成功"); } else { - logger.error("[停止RTP推流] 失败: {}, 参数:{}->\r\n{}",jsonObject.getString("msg"), JSON.toJSON(param), jsonObject); + logger.warn("[停止RTP推流] 失败: {}, 参数:{}->\r\n{}",jsonObject.getString("msg"), JSON.toJSON(param), jsonObject); } return result; } - public void closeAllSendRtpStream() { + public JSONObject startSendRtp(MediaServerItem mediaInfo, SendRtpItem sendRtpItem) { + String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; + logger.info("rtp/{}开始推流, 目标={}:{},SSRC={}", sendRtpItem.getStream(), sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc()); + Map 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) { diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java old mode 100644 new mode 100755 index cad119ad8..5e46f55ce --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java @@ -39,6 +39,7 @@ public class ZlmHttpHookSubscribe { hookSubscribe.setExpires(expiresInstant); } allSubscribes.computeIfAbsent(hookSubscribe.getHookType(), k -> new ConcurrentHashMap<>()).put(hookSubscribe, event); + System.out.println(allSubscribes); } public ZlmHttpHookSubscribe.Event sendNotify(HookType type, JSONObject hookResponse) { @@ -49,6 +50,7 @@ public class ZlmHttpHookSubscribe { } for (IHookSubscribe key : eventMap.keySet()) { Boolean result = null; + for (String s : key.getContent().keySet()) { if (result == null) { result = key.getContent().getString(s).equals(hookResponse.getString(s)); diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ChannelOnlineEvent.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java old mode 100644 new mode 100755 index f3f389a7c..f828bdd8b --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeFactory.java @@ -35,10 +35,37 @@ public class HookSubscribeFactory { return hookSubscribe; } + public static HookSubscribeForStreamPush on_publish(String app, String stream, String scheam, String mediaServerId) { + HookSubscribeForStreamPush hookSubscribe = new HookSubscribeForStreamPush(); + JSONObject subscribeKey = new JSONObject(); + subscribeKey.put("app", app); + subscribeKey.put("stream", stream); + if (scheam != null) { + subscribeKey.put("schema", scheam); + } + subscribeKey.put("mediaServerId", mediaServerId); + hookSubscribe.setContent(subscribeKey); + + return hookSubscribe; + } + + public static HookSubscribeForServerStarted on_server_started() { HookSubscribeForServerStarted hookSubscribe = new HookSubscribeForServerStarted(); hookSubscribe.setContent(new JSONObject()); return hookSubscribe; } + + public static HookSubscribeForRecordMp4 on_record_mp4(String mediaServerId, String app, String stream) { + HookSubscribeForRecordMp4 hookSubscribe = new HookSubscribeForRecordMp4(); + JSONObject subscribeKey = new com.alibaba.fastjson2.JSONObject(); + subscribeKey.put("app", app); + subscribeKey.put("stream", stream); + subscribeKey.put("mediaServerId", mediaServerId); + hookSubscribe.setContent(subscribeKey); + + return hookSubscribe; + } + } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForRecordMp4.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForRecordMp4.java new file mode 100755 index 000000000..34c467c54 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForRecordMp4.java @@ -0,0 +1,44 @@ +package com.genersoft.iot.vmp.media.zlm.dto; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.annotation.JSONField; + +import java.time.Instant; + +/** + * hook订阅-录像完成 + * @author lin + */ +public class HookSubscribeForRecordMp4 implements IHookSubscribe{ + + private HookType hookType = HookType.on_record_mp4; + + private JSONObject content; + + @JSONField(format="yyyy-MM-dd HH:mm:ss") + 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; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForRtpServerTimeout.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForRtpServerTimeout.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForServerStarted.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForServerStarted.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForStreamChange.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForStreamChange.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForStreamPush.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForStreamPush.java new file mode 100644 index 000000000..6418134fc --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookSubscribeForStreamPush.java @@ -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; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookType.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/HookType.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/IHookSubscribe.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/IHookSubscribe.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java old mode 100644 new mode 100755 index 066a67762..cebfec3b0 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItem.java @@ -80,9 +80,11 @@ public class MediaServerItem{ @Schema(description = "是否是默认ZLM") private boolean defaultServer; - @Schema(description = "当前使用到的端口") - private int currentPort; + @Schema(description = "录像存储时长") + private int recordDay; + @Schema(description = "录像存储路径") + private String recordPath; public MediaServerItem() { } @@ -269,14 +271,6 @@ public class MediaServerItem{ this.updateTime = updateTime; } - public int getCurrentPort() { - return currentPort; - } - - public void setCurrentPort(int currentPort) { - this.currentPort = currentPort; - } - public boolean isStatus() { return status; } @@ -308,4 +302,20 @@ public class MediaServerItem{ public void setSendRtpPortRange(String sendRtpPortRange) { this.sendRtpPortRange = sendRtpPortRange; } + + public int getRecordDay() { + return recordDay; + } + + public void setRecordDay(int recordDay) { + this.recordDay = recordDay; + } + + public String getRecordPath() { + return recordPath; + } + + public void setRecordPath(String recordPath) { + this.recordPath = recordPath; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItemLite.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItemLite.java new file mode 100644 index 000000000..4e0ffe93b --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaServerItemLite.java @@ -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; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ServerKeepaliveData.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ServerKeepaliveData.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamAuthorityInfo.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamAuthorityInfo.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java old mode 100644 new mode 100755 index dd517e3d3..0486d00d5 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java @@ -41,6 +41,9 @@ public class StreamProxyItem extends GbStream { @Schema(description = "是否 无人观看时自动停用") private boolean enableDisableNoneReader; + @Schema(description = "拉流代理时zlm返回的key,用于停止拉流代理") + private String streamKey; + public String getType() { return type; } @@ -167,5 +170,11 @@ public class StreamProxyItem extends GbStream { this.enableAudio = enable_audio; } + public String getStreamKey() { + return streamKey; + } + public void setStreamKey(String streamKey) { + this.streamKey = streamKey; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamPushItem.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMRunInfo.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/ZLMRunInfo.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookParam.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookParam.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResult.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java old mode 100644 new mode 100755 index 68d969f41..55369e5ba --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/HookResultForOnPublish.java @@ -6,6 +6,8 @@ public class HookResultForOnPublish extends HookResult{ private boolean enable_mp4; private int mp4_max_second; private String mp4_save_path; + private String stream_replace; + private Integer modify_stamp; public HookResultForOnPublish() { } @@ -51,6 +53,22 @@ public class HookResultForOnPublish extends HookResult{ this.mp4_save_path = mp4_save_path; } + public String getStream_replace() { + return stream_replace; + } + + public void setStream_replace(String stream_replace) { + this.stream_replace = stream_replace; + } + + public Integer getModify_stamp() { + return modify_stamp; + } + + public void setModify_stamp(Integer modify_stamp) { + this.modify_stamp = modify_stamp; + } + @Override public String toString() { return "HookResultForOnPublish{" + @@ -58,6 +76,8 @@ public class HookResultForOnPublish extends HookResult{ ", enable_mp4=" + enable_mp4 + ", mp4_max_second=" + mp4_max_second + ", mp4_save_path='" + mp4_save_path + '\'' + + ", stream_replace='" + stream_replace + '\'' + + ", modify_stamp='" + modify_stamp + '\'' + '}'; } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnPlayHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnPlayHookParam.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnPublishHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnPublishHookParam.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnRecordMp4HookParam.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnRecordMp4HookParam.java new file mode 100755 index 000000000..d52165edb --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnRecordMp4HookParam.java @@ -0,0 +1,114 @@ +package com.genersoft.iot.vmp.media.zlm.dto.hook; + +/** + * zlm hook事件中的on_rtp_server_timeout事件的参数 + * @author lin + */ +public class OnRecordMp4HookParam extends HookParam{ + private String app; + private String stream; + private String file_name; + private String file_path; + private long file_size; + private String folder; + private String url; + private String vhost; + private long start_time; + private double time_len; + + 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 String getFile_name() { + return file_name; + } + + public void setFile_name(String file_name) { + this.file_name = file_name; + } + + public String getFile_path() { + return file_path; + } + + public void setFile_path(String file_path) { + this.file_path = file_path; + } + + public long getFile_size() { + return file_size; + } + + public void setFile_size(long file_size) { + this.file_size = file_size; + } + + public String getFolder() { + return folder; + } + + public void setFolder(String folder) { + this.folder = folder; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getVhost() { + return vhost; + } + + public void setVhost(String vhost) { + this.vhost = vhost; + } + + public long getStart_time() { + return start_time; + } + + public void setStart_time(long start_time) { + this.start_time = start_time; + } + + public double getTime_len() { + return time_len; + } + + public void setTime_len(double time_len) { + this.time_len = time_len; + } + + @Override + public String toString() { + return "OnRecordMp4HookParam{" + + "app='" + app + '\'' + + ", stream='" + stream + '\'' + + ", file_name='" + file_name + '\'' + + ", file_path='" + file_path + '\'' + + ", file_size='" + file_size + '\'' + + ", folder='" + folder + '\'' + + ", url='" + url + '\'' + + ", vhost='" + vhost + '\'' + + ", start_time=" + start_time + + ", time_len=" + time_len + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnRtpServerTimeoutHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnRtpServerTimeoutHookParam.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnSendRtpStoppedHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnSendRtpStoppedHookParam.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnServerKeepaliveHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnServerKeepaliveHookParam.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamChangedHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamChangedHookParam.java old mode 100644 new mode 100755 index 4587fb0f8..ffca0d5a7 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamChangedHookParam.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamChangedHookParam.java @@ -120,17 +120,17 @@ public class OnStreamChangedHookParam extends HookParam{ /** * H264 = 0, H265 = 1, AAC = 2, G711A = 3, G711U = 4 */ - private int codecId; + private int codec_id; /** * 编码类型名称 CodecAAC CodecH264 */ - private String codecIdName; + private String codec_id_name; /** * Video = 0, Audio = 1 */ - private int codecType; + private int codec_type; /** * 轨道是否准备就绪 @@ -140,17 +140,17 @@ public class OnStreamChangedHookParam extends HookParam{ /** * 音频采样位数 */ - private int sampleBit; + private int sample_bit; /** * 音频采样率 */ - private int sampleRate; + private int sample_rate; /** * 视频fps */ - private int fps; + private float fps; /** * 视频高 @@ -162,6 +162,31 @@ public class OnStreamChangedHookParam extends HookParam{ */ private int width; + /** + * 帧数 + */ + private int frames; + + /** + * 关键帧数 + */ + private int key_frames; + + /** + * GOP大小 + */ + private int gop_size; + + /** + * GOP间隔时长(ms) + */ + private int gop_interval_ms; + + /** + * 丢帧率 + */ + private float loss; + public int getChannels() { return channels; } @@ -170,28 +195,28 @@ public class OnStreamChangedHookParam extends HookParam{ this.channels = channels; } - public int getCodecId() { - return codecId; + public int getCodec_id() { + return codec_id; } - public void setCodecId(int codecId) { - this.codecId = codecId; + public void setCodec_id(int codec_id) { + this.codec_id = codec_id; } - public String getCodecIdName() { - return codecIdName; + public String getCodec_id_name() { + return codec_id_name; } - public void setCodecIdName(String codecIdName) { - this.codecIdName = codecIdName; + public void setCodec_id_name(String codec_id_name) { + this.codec_id_name = codec_id_name; } - public int getCodecType() { - return codecType; + public int getCodec_type() { + return codec_type; } - public void setCodecType(int codecType) { - this.codecType = codecType; + public void setCodec_type(int codec_type) { + this.codec_type = codec_type; } public boolean isReady() { @@ -202,27 +227,27 @@ public class OnStreamChangedHookParam extends HookParam{ this.ready = ready; } - public int getSampleBit() { - return sampleBit; + public int getSample_bit() { + return sample_bit; } - public void setSampleBit(int sampleBit) { - this.sampleBit = sampleBit; + public void setSample_bit(int sample_bit) { + this.sample_bit = sample_bit; } - public int getSampleRate() { - return sampleRate; + public int getSample_rate() { + return sample_rate; } - public void setSampleRate(int sampleRate) { - this.sampleRate = sampleRate; + public void setSample_rate(int sample_rate) { + this.sample_rate = sample_rate; } - public int getFps() { + public float getFps() { return fps; } - public void setFps(int fps) { + public void setFps(float fps) { this.fps = fps; } @@ -241,6 +266,46 @@ public class OnStreamChangedHookParam extends HookParam{ public void setWidth(int width) { this.width = width; } + + public int getFrames() { + return frames; + } + + public void setFrames(int frames) { + this.frames = frames; + } + + public int getKey_frames() { + return key_frames; + } + + public void setKey_frames(int key_frames) { + this.key_frames = key_frames; + } + + public int getGop_size() { + return gop_size; + } + + public void setGop_size(int gop_size) { + this.gop_size = gop_size; + } + + public int getGop_interval_ms() { + return gop_interval_ms; + } + + public void setGop_interval_ms(int gop_interval_ms) { + this.gop_interval_ms = gop_interval_ms; + } + + public float getLoss() { + return loss; + } + + public void setLoss(float loss) { + this.loss = loss; + } } public static class OriginSock{ diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamNoneReaderHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamNoneReaderHookParam.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamNotFoundHookParam.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnStreamNotFoundHookParam.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OriginType.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OriginType.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMEventAbstract.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMEventAbstract.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMOfflineEvent.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMOfflineEvent.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMOnlineEvent.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMOnlineEvent.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMStatusEventListener.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/event/ZLMStatusEventListener.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/ICloudRecordService.java b/src/main/java/com/genersoft/iot/vmp/service/ICloudRecordService.java new file mode 100755 index 000000000..c10313e8e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/ICloudRecordService.java @@ -0,0 +1,59 @@ +package com.genersoft.iot.vmp.service; + +import com.alibaba.fastjson2.JSONArray; +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam; +import com.genersoft.iot.vmp.service.bean.CloudRecordItem; +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; +import com.github.pagehelper.PageInfo; + +import java.util.List; + +/** + * 云端录像管理 + * @author lin + */ +public interface ICloudRecordService { + + /** + * 分页回去云端录像列表 + */ + PageInfo getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List mediaServerItems); + + /** + * 根据hook消息增加一条记录 + */ + void addRecord(OnRecordMp4HookParam param); + + /** + * 获取所有的日期 + */ + List getDateList(String app, String stream, int year, int month, List mediaServerItems); + + /** + * 添加合并任务 + */ + String addTask(String app, String stream, MediaServerItem mediaServerItem, String startTime, + String endTime, String callId, String remoteHost, boolean filterMediaServer); + + + /** + * 查询合并任务列表 + */ + JSONArray queryTask(String app, String stream, String callId, String taskId, String mediaServerId, Boolean isEnd, String scheme); + + /** + * 收藏视频,收藏的视频过期不会删除 + */ + int changeCollect(boolean result, String app, String stream, String mediaServerId, String startTime, String endTime, String callId); + + /** + * 添加指定录像收藏 + */ + int changeCollectById(Integer recordId, boolean result); + + /** + * 获取播放地址 + */ + DownloadFileInfo getPlayUrlPath(Integer recordId); +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/IDeviceAlarmService.java b/src/main/java/com/genersoft/iot/vmp/service/IDeviceAlarmService.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java b/src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java old mode 100644 new mode 100755 index cd402a109..5a2084157 --- a/src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java @@ -87,4 +87,9 @@ public interface IDeviceChannelService { * 直接批量添加 */ void batchAddChannel(List deviceChannels); + + /** + * 修改通道的码流类型 + */ + void updateChannelStreamIdentification(DeviceChannel channel); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java b/src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java old mode 100644 new mode 100755 index e20c3bf22..afa004421 --- a/src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IDeviceService.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.service; +import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; @@ -39,7 +40,7 @@ public interface IDeviceService { * @param device 设备信息 * @return 布尔 */ - boolean removeCatalogSubscribe(Device device); + boolean removeCatalogSubscribe(Device device, CommonCallback callback); /** * 添加移动位置订阅 @@ -53,7 +54,7 @@ public interface IDeviceService { * @param device 设备信息 * @return 布尔 */ - boolean removeMobilePositionSubscribe(Device device); + boolean removeMobilePositionSubscribe(Device device, CommonCallback callback); /** * 移除移动位置订阅 diff --git a/src/main/java/com/genersoft/iot/vmp/service/IGbStreamService.java b/src/main/java/com/genersoft/iot/vmp/service/IGbStreamService.java old mode 100644 new mode 100755 index 314a38907..43f1a8a67 --- a/src/main/java/com/genersoft/iot/vmp/service/IGbStreamService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IGbStreamService.java @@ -69,4 +69,6 @@ public interface IGbStreamService { * @param catalogId */ void delAllPlatformInfo(String platformId, String catalogId); + + List getGbChannelWithGbid(String gbId); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java b/src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java old mode 100644 new mode 100755 index ae30f26e7..c06400dce --- a/src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java @@ -74,5 +74,13 @@ public interface IInviteStreamService { int getStreamInfoCount(String mediaServerId); + /** + * 获取MediaServer下的流信息 + */ + InviteInfo getInviteInfoBySSRC(String ssrc); + /** + * 更新ssrc + */ + InviteInfo updateInviteInfoForSSRC(InviteInfo inviteInfo, String ssrcInResponse); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/ILogService.java b/src/main/java/com/genersoft/iot/vmp/service/ILogService.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java b/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java old mode 100644 new mode 100755 index 530cd6dfd..2e6151d67 --- a/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java @@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.ServerKeepaliveData; import com.genersoft.iot.vmp.service.bean.MediaServerLoad; import com.genersoft.iot.vmp.service.bean.SSRCInfo; +import com.genersoft.iot.vmp.vmanager.bean.RecordFile; import java.util.List; @@ -44,8 +45,10 @@ public interface IMediaServerService { void updateVmServer(List mediaServerItemList); - SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, - boolean isPlayback, Integer port, Boolean reUsePort, Integer tcpMode); + SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, + boolean isPlayback, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode); + + SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto); void closeRTPServer(MediaServerItem mediaServerItem, String streamId); @@ -86,11 +89,12 @@ public interface IMediaServerService { void updateMediaServerKeepalive(String mediaServerId, ServerKeepaliveData data); - boolean checkRtpServer(MediaServerItem mediaServerItem, String rtp, String stream); - /** * 获取负载信息 * @return */ MediaServerLoad getLoad(MediaServerItem mediaServerItem); + + List getAllWithAssistPort(); + } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IMediaService.java b/src/main/java/com/genersoft/iot/vmp/service/IMediaService.java old mode 100644 new mode 100755 index c05f1977d..82de3bb0f --- a/src/main/java/com/genersoft/iot/vmp/service/IMediaService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IMediaService.java @@ -40,5 +40,5 @@ public interface IMediaService { * @param stream * @return */ - StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId); + StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId, boolean isPlay); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IPlatformChannelService.java b/src/main/java/com/genersoft/iot/vmp/service/IPlatformChannelService.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java b/src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java old mode 100644 new mode 100755 index e9cddff83..f8486a87d --- a/src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IPlatformService.java @@ -1,9 +1,18 @@ package com.genersoft.iot.vmp.service; +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; +import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback; import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo; import com.github.pagehelper.PageInfo; +import javax.sip.InvalidArgumentException; +import javax.sip.SipException; +import java.text.ParseException; + /** * 国标平台的业务类 * @author lin @@ -55,4 +64,22 @@ public interface IPlatformService { * @param platformId 平台 */ void sendNotifyMobilePosition(String platformId); + + /** + * 向上级发送语音喊话的消息 + * @param platform 平台 + * @param channelId 通道 + * @param hookEvent hook事件 + * @param errorEvent 信令错误事件 + * @param timeoutCallback 超时事件 + */ + void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent, + SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException; + + /** + * 语音喊话回复BYE + */ + void stopBroadcast(ParentPlatform platform, DeviceChannel channel, String stream,boolean sendBye, MediaServerItem mediaServerItem); + + void addSimulatedSubscribeInfo(ParentPlatform parentPlatform); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java old mode 100644 new mode 100755 index 1effe96c7..77525e880 --- a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java @@ -1,31 +1,38 @@ package com.genersoft.iot.vmp.service; +import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.exception.ServiceException; import com.genersoft.iot.vmp.gb28181.bean.Device; +import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; +import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.SSRCInfo; +import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; +import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent; +import gov.nist.javax.sip.message.SIPResponse; import javax.sip.InvalidArgumentException; import javax.sip.SipException; +import javax.sip.header.CallIdHeader; import java.text.ParseException; +import java.util.Map; /** * 点播处理 */ public interface IPlayService { - void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, + void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channelId, ErrorCallback callback); SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback callback); - MediaServerItem getNewMediaServerItem(Device device); + StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId); - /** - * 获取包含assist服务的节点 - */ - MediaServerItem getNewMediaServerItemHasAssist(Device device); + MediaServerItem getNewMediaServerItem(Device device); void playBack(String deviceId, String channelId, String startTime, String endTime, ErrorCallback callback); void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, ErrorCallback callback); @@ -38,11 +45,27 @@ public interface IPlayService { void zlmServerOnline(String mediaServerId); + AudioBroadcastResult audioBroadcast(Device device, String channelId, Boolean broadcastMode); + + boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException; + + boolean audioBroadcastInUse(Device device, String channelId); + + void stopAudioBroadcast(String deviceId, String channelId); + void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException; + void startPushStream(SendRtpItem sendRtpItem, SIPResponse sipResponse, ParentPlatform platform, CallIdHeader callIdHeader); + + void startSendRtpStreamHand(SendRtpItem sendRtpItem, Object correlationInfo, + JSONObject jsonObject, Map 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); - } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IRoleService.java b/src/main/java/com/genersoft/iot/vmp/service/IRoleService.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java b/src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java b/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java old mode 100644 new mode 100755 index 8d9200216..333b7b319 --- a/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java @@ -114,4 +114,5 @@ public interface IStreamPushService { * @return */ ResourceBaseInfo getOverview(); + } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IUserService.java b/src/main/java/com/genersoft/iot/vmp/service/IUserService.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/CloudRecordItem.java b/src/main/java/com/genersoft/iot/vmp/service/bean/CloudRecordItem.java new file mode 100644 index 000000000..771e4c811 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/CloudRecordItem.java @@ -0,0 +1,205 @@ +package com.genersoft.iot.vmp.service.bean; + +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam; + +/** + * 云端录像数据 + */ +public class CloudRecordItem { + /** + * 主键 + */ + private int id; + + /** + * 应用名 + */ + private String app; + + /** + * 流 + */ + private String stream; + + /** + * 健全ID + */ + private String callId; + + /** + * 开始时间 + */ + private long startTime; + + /** + * 结束时间 + */ + private long endTime; + + /** + * ZLM Id + */ + private String mediaServerId; + + /** + * 文件名称 + */ + private String fileName; + + /** + * 文件路径 + */ + private String filePath; + + /** + * 文件夹 + */ + private String folder; + + /** + * 收藏,收藏的文件不移除 + */ + private Boolean collect; + + /** + * 保留,收藏的文件不移除 + */ + private Boolean reserve; + + /** + * 文件大小 + */ + private long fileSize; + + /** + * 文件时长 + */ + private long timeLen; + + public static CloudRecordItem getInstance(OnRecordMp4HookParam param) { + CloudRecordItem cloudRecordItem = new CloudRecordItem(); + cloudRecordItem.setApp(param.getApp()); + cloudRecordItem.setStream(param.getStream()); + cloudRecordItem.setStartTime(param.getStart_time()*1000); + cloudRecordItem.setFileName(param.getFile_name()); + cloudRecordItem.setFolder(param.getFolder()); + cloudRecordItem.setFileSize(param.getFile_size()); + cloudRecordItem.setFilePath(param.getFile_path()); + cloudRecordItem.setMediaServerId(param.getMediaServerId()); + cloudRecordItem.setTimeLen((long) param.getTime_len() * 1000); + cloudRecordItem.setEndTime((param.getStart_time() + (long)param.getTime_len()) * 1000); + return cloudRecordItem; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + 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 String getCallId() { + return callId; + } + + public void setCallId(String callId) { + this.callId = callId; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getEndTime() { + return endTime; + } + + public void setEndTime(long endTime) { + this.endTime = endTime; + } + + public String getMediaServerId() { + return mediaServerId; + } + + public void setMediaServerId(String mediaServerId) { + this.mediaServerId = mediaServerId; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public String getFolder() { + return folder; + } + + public void setFolder(String folder) { + this.folder = folder; + } + + public long getFileSize() { + return fileSize; + } + + public void setFileSize(long fileSize) { + this.fileSize = fileSize; + } + + public long getTimeLen() { + return timeLen; + } + + public void setTimeLen(long timeLen) { + this.timeLen = timeLen; + } + + public Boolean getCollect() { + return collect; + } + + public void setCollect(Boolean collect) { + this.collect = collect; + } + + public Boolean getReserve() { + return reserve; + } + + public void setReserve(Boolean reserve) { + this.reserve = reserve; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java b/src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java new file mode 100644 index 000000000..602e18467 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/DownloadFileInfo.java @@ -0,0 +1,41 @@ +package com.genersoft.iot.vmp.service.bean; + +public class DownloadFileInfo { + + private String httpPath; + private String httpsPath; + private String httpDomainPath; + private String httpsDomainPath; + + public String getHttpPath() { + return httpPath; + } + + public void setHttpPath(String httpPath) { + this.httpPath = httpPath; + } + + public String getHttpsPath() { + return httpsPath; + } + + public void setHttpsPath(String httpsPath) { + this.httpsPath = httpsPath; + } + + public String getHttpDomainPath() { + return httpDomainPath; + } + + public void setHttpDomainPath(String httpDomainPath) { + this.httpDomainPath = httpDomainPath; + } + + public String getHttpsDomainPath() { + return httpsDomainPath; + } + + public void setHttpsDomainPath(String httpsDomainPath) { + this.httpsDomainPath = httpsDomainPath; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/ErrorCallback.java b/src/main/java/com/genersoft/iot/vmp/service/bean/ErrorCallback.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/GPSMsgInfo.java b/src/main/java/com/genersoft/iot/vmp/service/bean/GPSMsgInfo.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCode.java b/src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCode.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/InviteTimeOutCallback.java b/src/main/java/com/genersoft/iot/vmp/service/bean/InviteTimeOutCallback.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/MediaServerLoad.java b/src/main/java/com/genersoft/iot/vmp/service/bean/MediaServerLoad.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java b/src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannel.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannelResponse.java b/src/main/java/com/genersoft/iot/vmp/service/bean/MessageForPushChannelResponse.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/PlayBackCallback.java b/src/main/java/com/genersoft/iot/vmp/service/bean/PlayBackCallback.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/PlayBackResult.java b/src/main/java/com/genersoft/iot/vmp/service/bean/PlayBackResult.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/PushStreamStatusChangeFromRedisDto.java b/src/main/java/com/genersoft/iot/vmp/service/bean/PushStreamStatusChangeFromRedisDto.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/RequestPushStreamMsg.java b/src/main/java/com/genersoft/iot/vmp/service/bean/RequestPushStreamMsg.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java b/src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java old mode 100644 new mode 100755 index 0c6502d19..41c16e23f --- a/src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/RequestSendItemMsg.java @@ -72,7 +72,7 @@ public class RequestSendItemMsg { public static RequestSendItemMsg getInstance(String serverId, String mediaServerId, String app, String stream, String ip, int port, - String ssrc, String platformId, String channelId, Boolean isTcp, Boolean rtcp, String platformName) { + String ssrc, String platformId, String channelId, Boolean isTcp, Boolean rtcp, String platformName) { RequestSendItemMsg requestSendItemMsg = new RequestSendItemMsg(); requestSendItemMsg.setServerId(serverId); requestSendItemMsg.setMediaServerId(mediaServerId); diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/ResponseSendItemMsg.java b/src/main/java/com/genersoft/iot/vmp/service/bean/ResponseSendItemMsg.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/SSRCInfo.java b/src/main/java/com/genersoft/iot/vmp/service/bean/SSRCInfo.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/StreamPushItemFromRedis.java b/src/main/java/com/genersoft/iot/vmp/service/bean/StreamPushItemFromRedis.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/ThirdPartyGB.java b/src/main/java/com/genersoft/iot/vmp/service/bean/ThirdPartyGB.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java b/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java old mode 100644 new mode 100755 index 12d79cb23..74890be7e --- a/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsg.java @@ -29,12 +29,12 @@ public class WvpRedisMsg { * 消息的ID */ private String serial; - private Object content; + private String content; private final static String requestTag = "req"; private final static String responseTag = "res"; - public static WvpRedisMsg getRequestInstance(String fromId, String toId, String cmd, String serial, Object content) { + public static WvpRedisMsg getRequestInstance(String fromId, String toId, String cmd, String serial, String content) { WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); wvpRedisMsg.setType(requestTag); wvpRedisMsg.setFromId(fromId); @@ -51,7 +51,7 @@ public class WvpRedisMsg { return wvpRedisMsg; } - public static WvpRedisMsg getResponseInstance(String fromId, String toId, String cmd, String serial, Object content) { + public static WvpRedisMsg getResponseInstance(String fromId, String toId, String cmd, String serial, String content) { WvpRedisMsg wvpRedisMsg = new WvpRedisMsg(); wvpRedisMsg.setType(responseTag); wvpRedisMsg.setFromId(fromId); @@ -106,11 +106,11 @@ public class WvpRedisMsg { this.cmd = cmd; } - public Object getContent() { + public String getContent() { return content; } - public void setContent(Object content) { + public void setContent(String content) { this.content = content; } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsgCmd.java b/src/main/java/com/genersoft/iot/vmp/service/bean/WvpRedisMsgCmd.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/CloudRecordServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/CloudRecordServiceImpl.java new file mode 100644 index 000000000..f160aacc8 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/CloudRecordServiceImpl.java @@ -0,0 +1,235 @@ +package com.genersoft.iot.vmp.service.impl; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.dynamic.datasource.annotation.DS; +import com.genersoft.iot.vmp.conf.exception.ControllerException; +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; +import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; +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.hook.OnRecordMp4HookParam; +import com.genersoft.iot.vmp.service.ICloudRecordService; +import com.genersoft.iot.vmp.service.IMediaServerService; +import com.genersoft.iot.vmp.service.bean.CloudRecordItem; +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; +import com.genersoft.iot.vmp.utils.CloudRecordUtils; +import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.*; +import java.util.*; + +@Service +@DS("share") +public class CloudRecordServiceImpl implements ICloudRecordService { + + private final static Logger logger = LoggerFactory.getLogger(CloudRecordServiceImpl.class); + + @Autowired + private CloudRecordServiceMapper cloudRecordServiceMapper; + + @Autowired + private IMediaServerService mediaServerService; + + @Autowired + private IRedisCatchStorage redisCatchStorage; + + @Autowired + private AssistRESTfulUtils assistRESTfulUtils; + + @Autowired + private VideoStreamSessionManager streamSession; + + @Override + public PageInfo getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List mediaServerItems) { + // 开始时间和结束时间在数据库中都是以秒为单位的 + Long startTimeStamp = null; + Long endTimeStamp = null; + if (startTime != null ) { + if (!DateUtil.verification(startTime, DateUtil.formatter)) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "开始时间格式错误,正确格式为: " + DateUtil.formatter); + } + startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); + + } + if (endTime != null ) { + if (!DateUtil.verification(endTime, DateUtil.formatter)) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "结束时间格式错误,正确格式为: " + DateUtil.formatter); + } + endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); + + } + PageHelper.startPage(page, count); + List all = cloudRecordServiceMapper.getList(query, app, stream, startTimeStamp, endTimeStamp, + null, mediaServerItems); + return new PageInfo<>(all); + } + + @Override + public List getDateList(String app, String stream, int year, int month, List mediaServerItems) { + LocalDate startDate = LocalDate.of(year, month, 1); + LocalDate endDate; + if (month == 12) { + endDate = LocalDate.of(year + 1, 1, 1); + }else { + endDate = LocalDate.of(year, month + 1, 1); + } + long startTimeStamp = startDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).getEpochSecond(); + long endTimeStamp = endDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).getEpochSecond(); + List cloudRecordItemList = cloudRecordServiceMapper.getList(null, app, stream, startTimeStamp, + endTimeStamp, null, mediaServerItems); + if (cloudRecordItemList.isEmpty()) { + return new ArrayList<>(); + } + Set resultSet = new HashSet<>(); + cloudRecordItemList.stream().forEach(cloudRecordItem -> { + String date = DateUtil.timestampTo_yyyy_MM_dd(cloudRecordItem.getStartTime()); + resultSet.add(date); + }); + return new ArrayList<>(resultSet); + } + + @Override + public void addRecord(OnRecordMp4HookParam param) { + CloudRecordItem cloudRecordItem = CloudRecordItem.getInstance(param); + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); + if (streamAuthorityInfo != null) { + cloudRecordItem.setCallId(streamAuthorityInfo.getCallId()); + } + logger.info("[添加录像记录] {}/{} 文件大小:{}, 时长: {}秒", param.getApp(), param.getStream(), param.getFile_size(),param.getTime_len()); + cloudRecordServiceMapper.add(cloudRecordItem); + } + + @Override + public String addTask(String app, String stream, MediaServerItem mediaServerItem, String startTime, String endTime, + String callId, String remoteHost, boolean filterMediaServer) { + // 参数校验 + assert app != null; + assert stream != null; + if (mediaServerItem.getRecordAssistPort() == 0) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "为配置Assist服务"); + } + Long startTimeStamp = null; + Long endTimeStamp = null; + if (startTime != null) { + startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); + } + if (endTime != null) { + endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); + } + + List mediaServers = new ArrayList<>(); + mediaServers.add(mediaServerItem); + // 检索相关的录像文件 + List filePathList = cloudRecordServiceMapper.queryRecordFilePathList(app, stream, startTimeStamp, + endTimeStamp, callId, filterMediaServer ? mediaServers : null); + if (filePathList == null || filePathList.isEmpty()) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未检索到视频文件"); + } + JSONObject result = assistRESTfulUtils.addTask(mediaServerItem, app, stream, startTime, endTime, callId, filePathList, remoteHost); + if (result.getInteger("code") != 0) { + throw new ControllerException(result.getInteger("code"), result.getString("msg")); + } + return result.getString("data"); + } + + @Override + public JSONArray queryTask(String app, String stream, String callId, String taskId, String mediaServerId, + Boolean isEnd, String scheme) { + MediaServerItem mediaServerItem = null; + if (mediaServerId == null) { + mediaServerItem = mediaServerService.getDefaultMediaServer(); + }else { + mediaServerItem = mediaServerService.getOne(mediaServerId); + } + if (mediaServerItem == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的流媒体"); + } + + JSONObject result = assistRESTfulUtils.queryTaskList(mediaServerItem, app, stream, callId, taskId, isEnd, scheme); + if (result == null || result.getInteger("code") != 0) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), result == null ? "查询任务列表失败" : result.getString("msg")); + } + return result.getJSONArray("data"); + } + + @Override + public int changeCollect(boolean result, String app, String stream, String mediaServerId, String startTime, String endTime, String callId) { + // 开始时间和结束时间在数据库中都是以秒为单位的 + Long startTimeStamp = null; + Long endTimeStamp = null; + if (startTime != null ) { + if (!DateUtil.verification(startTime, DateUtil.formatter)) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "开始时间格式错误,正确格式为: " + DateUtil.formatter); + } + startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); + + } + if (endTime != null ) { + if (!DateUtil.verification(endTime, DateUtil.formatter)) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "结束时间格式错误,正确格式为: " + DateUtil.formatter); + } + endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); + + } + + List mediaServerItems; + if (!ObjectUtils.isEmpty(mediaServerId)) { + mediaServerItems = new ArrayList<>(); + MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); + if (mediaServerItem == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId); + } + mediaServerItems.add(mediaServerItem); + } else { + mediaServerItems = null; + } + + List all = cloudRecordServiceMapper.getList(null, app, stream, startTimeStamp, endTimeStamp, + callId, mediaServerItems); + if (all.isEmpty()) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到待收藏的视频"); + } + int limitCount = 50; + int resultCount = 0; + if (all.size() > limitCount) { + for (int i = 0; i < all.size(); i += limitCount) { + int toIndex = i + limitCount; + if (i + limitCount > all.size()) { + toIndex = all.size(); + } + resultCount += cloudRecordServiceMapper.updateCollectList(result, all.subList(i, toIndex)); + + } + }else { + resultCount = cloudRecordServiceMapper.updateCollectList(result, all); + } + return resultCount; + } + + @Override + public int changeCollectById(Integer recordId, boolean result) { + return cloudRecordServiceMapper.changeCollectById(result, recordId); + } + + @Override + public DownloadFileInfo getPlayUrlPath(Integer recordId) { + CloudRecordItem recordItem = cloudRecordServiceMapper.queryOne(recordId); + if (recordItem == null) { + throw new ControllerException(ErrorCode.ERROR400.getCode(), "资源不存在"); + } + String filePath = recordItem.getFilePath(); + MediaServerItem mediaServerItem = mediaServerService.getOne(recordItem.getMediaServerId()); + return CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImpl.java old mode 100644 new mode 100755 index 8c55986ea..e9936c4af --- a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceAlarmServiceImpl.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.service.impl; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.service.IDeviceAlarmService; @@ -12,6 +13,7 @@ import org.springframework.stereotype.Service; import java.util.List; @Service +@DS("master") public class DeviceAlarmServiceImpl implements IDeviceAlarmService { @Autowired diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceChannelServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceChannelServiceImpl.java old mode 100644 new mode 100755 index 76a508727..55fa5e9aa --- a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceChannelServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceChannelServiceImpl.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.service.impl; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.gb28181.bean.Device; @@ -17,6 +18,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; import java.util.ArrayList; import java.util.HashMap; @@ -27,6 +29,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * @author lin */ @Service +@DS("master") public class DeviceChannelServiceImpl implements IDeviceChannelService { private final static Logger logger = LoggerFactory.getLogger(DeviceChannelServiceImpl.class); @@ -141,7 +144,7 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { } } } - int limitCount = 300; + int limitCount = 50; if (addChannels.size() > 0) { if (addChannels.size() > limitCount) { for (int i = 0; i < addChannels.size(); i += limitCount) { @@ -199,7 +202,7 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { deviceChannel.setUpdateTime(now); result.add(updateGps(deviceChannel, device)); }); - int limitCount = 300; + int limitCount = 50; if (result.size() > limitCount) { for (int i = 0; i < result.size(); i += limitCount) { int toIndex = i + limitCount; @@ -243,6 +246,10 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { @Override public void batchUpdateChannel(List channels) { + String now = DateUtil.getNow(); + for (DeviceChannel channel : channels) { + channel.setUpdateTime(now); + } channelMapper.batchUpdate(channels); for (DeviceChannel channel : channels) { if (channel.getParentId() != null) { @@ -261,5 +268,16 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { } } - + @Override + public void updateChannelStreamIdentification(DeviceChannel channel) { + assert !ObjectUtils.isEmpty(channel.getDeviceId()); + assert !ObjectUtils.isEmpty(channel.getStreamIdentification()); + if (ObjectUtils.isEmpty(channel.getStreamIdentification())) { + logger.info("[重置通道码流类型] 设备: {}, 码流: {}", channel.getDeviceId(), channel.getStreamIdentification()); + }else { + logger.info("[更新通道码流类型] 设备: {}, 通道:{}, 码流: {}", channel.getDeviceId(), channel.getChannelId(), + channel.getStreamIdentification()); + } + channelMapper.updateChannelStreamIdentification(channel); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java old mode 100644 new mode 100755 index 6e748f4f3..3da284507 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java @@ -1,10 +1,12 @@ package com.genersoft.iot.vmp.service.impl; +import com.baomidou.dynamic.datasource.annotation.DS; +import com.genersoft.iot.vmp.common.CommonCallback; 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.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.task.ISubscribeTask; import com.genersoft.iot.vmp.gb28181.task.impl.CatalogSubscribeTask; @@ -12,6 +14,8 @@ import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler; +import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.IDeviceChannelService; import com.genersoft.iot.vmp.service.IDeviceService; import com.genersoft.iot.vmp.service.IInviteStreamService; @@ -36,15 +40,14 @@ import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.concurrent.TimeUnit; /** * 设备业务(目录订阅) */ @Service +@DS("master") public class DeviceServiceImpl implements IDeviceService { private final static Logger logger = LoggerFactory.getLogger(DeviceServiceImpl.class); @@ -96,6 +99,12 @@ public class DeviceServiceImpl implements IDeviceService { @Autowired private IMediaServerService mediaServerService; + @Autowired + private AudioBroadcastManager audioBroadcastManager; + + @Autowired + private ZLMRESTfulUtils zlmresTfulUtils; + @Override public void online(Device device, SipTransactionInfo sipTransactionInfo) { logger.info("[设备上线] deviceId:{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort()); @@ -108,6 +117,7 @@ public class DeviceServiceImpl implements IDeviceService { inviteStreamService.clearInviteInfo(device.getDeviceId()); } device.setUpdateTime(now); + device.setKeepaliveTime(now); if (device.getKeepaliveIntervalTime() == 0) { // 默认心跳间隔60 device.setKeepaliveIntervalTime(60); @@ -135,10 +145,6 @@ public class DeviceServiceImpl implements IDeviceService { } sync(device); }else { - - if (deviceInDb != null) { - device.setSwitchPrimarySubStream(deviceInDb.isSwitchPrimarySubStream()); - } if(!device.isOnLine()){ device.setOnLine(true); device.setCreateTime(now); @@ -154,6 +160,19 @@ public class DeviceServiceImpl implements IDeviceService { sync(device); // TODO 如果设备下的通道级联到了其他平台,那么需要发送事件或者notify给上级平台 } + // 上线添加订阅 + if (device.getSubscribeCycleForCatalog() > 0) { + // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅 + addCatalogSubscribe(device); + } + if (device.getSubscribeCycleForMobilePosition() > 0) { + addMobilePositionSubscribe(device); + } + if (userSetting.getDeviceStatusNotify()) { + // 发送redis消息 + redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, true); + } + }else { if (deviceChannelMapper.queryAllChannels(device.getDeviceId()).size() == 0) { logger.info("[设备上线]: {},通道数为0,查询通道信息", device.getDeviceId()); @@ -166,22 +185,21 @@ public class DeviceServiceImpl implements IDeviceService { } - // 上线添加订阅 - if (device.getSubscribeCycleForCatalog() > 0) { - // 查询在线设备那些开启了订阅,为设备开启定时的目录订阅 - addCatalogSubscribe(device); - } - if (device.getSubscribeCycleForMobilePosition() > 0) { - addMobilePositionSubscribe(device); - } // 刷新过期任务 String registerExpireTaskKey = VideoManagerConstants.REGISTER_EXPIRE_TASK_KEY_PREFIX + device.getDeviceId(); // 如果第一次注册那么必须在60 * 3时间内收到一个心跳,否则设备离线 dynamicTask.startDelay(registerExpireTaskKey, ()-> offline(device.getDeviceId(), "首次注册后未能收到心跳"), device.getKeepaliveIntervalTime() * 1000 * 3); - if (userSetting.getDeviceStatusNotify()) { - // 发送redis消息 - redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, true); - } + +// +// try { +// cmder.alarmSubscribe(device, 600, "0", "4", "0", "2023-7-27T00:00:00", "2023-7-28T00:00:00"); +// } catch (InvalidArgumentException e) { +// throw new RuntimeException(e); +// } catch (SipException e) { +// throw new RuntimeException(e); +// } catch (ParseException e) { +// throw new RuntimeException(e); +// } } @@ -194,6 +212,13 @@ public class DeviceServiceImpl implements IDeviceService { } String registerExpireTaskKey = VideoManagerConstants.REGISTER_EXPIRE_TASK_KEY_PREFIX + deviceId; dynamicTask.stop(registerExpireTaskKey); + if (device.isOnLine()) { + if (userSetting.getDeviceStatusNotify()) { + // 发送redis消息 + redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, false); + } + } + device.setOnLine(false); redisCatchStorage.updateDevice(device); deviceMapper.update(device); @@ -205,17 +230,31 @@ public class DeviceServiceImpl implements IDeviceService { for (SsrcTransaction ssrcTransaction : ssrcTransactions) { mediaServerService.releaseSsrc(ssrcTransaction.getMediaServerId(), ssrcTransaction.getSsrc()); mediaServerService.closeRTPServer(ssrcTransaction.getMediaServerId(), ssrcTransaction.getStream()); - streamSession.remove(deviceId, ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); + streamSession.removeByCallId(deviceId, ssrcTransaction.getChannelId(), ssrcTransaction.getCallId()); } } // 移除订阅 - removeCatalogSubscribe(device); - removeMobilePositionSubscribe(device); - if (userSetting.getDeviceStatusNotify()) { - // 发送redis消息 - redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, false); - } + removeCatalogSubscribe(device, null); + removeMobilePositionSubscribe(device, null); + List 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 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 @@ -234,7 +273,7 @@ public class DeviceServiceImpl implements IDeviceService { } @Override - public boolean removeCatalogSubscribe(Device device) { + public boolean removeCatalogSubscribe(Device device, CommonCallback callback) { if (device == null || device.getSubscribeCycleForCatalog() < 0) { return false; } @@ -244,7 +283,7 @@ public class DeviceServiceImpl implements IDeviceService { Runnable runnable = dynamicTask.get(taskKey); if (runnable instanceof ISubscribeTask) { ISubscribeTask subscribeTask = (ISubscribeTask) runnable; - subscribeTask.stop(); + subscribeTask.stop(callback); } } dynamicTask.stop(taskKey); @@ -262,12 +301,12 @@ public class DeviceServiceImpl implements IDeviceService { // 设置最小值为30 int subscribeCycleForCatalog = Math.max(device.getSubscribeCycleForMobilePosition(),30); // 刷新订阅 - dynamicTask.startCron(device.getDeviceId() + "mobile_position" , mobilePositionSubscribeTask, (subscribeCycleForCatalog) * 1000); + dynamicTask.startCron(device.getDeviceId() + "mobile_position" , mobilePositionSubscribeTask, subscribeCycleForCatalog * 1000); return true; } @Override - public boolean removeMobilePositionSubscribe(Device device) { + public boolean removeMobilePositionSubscribe(Device device, CommonCallback callback) { if (device == null || device.getSubscribeCycleForCatalog() < 0) { return false; } @@ -277,7 +316,7 @@ public class DeviceServiceImpl implements IDeviceService { Runnable runnable = dynamicTask.get(taskKey); if (runnable instanceof ISubscribeTask) { ISubscribeTask subscribeTask = (ISubscribeTask) runnable; - subscribeTask.stop(); + subscribeTask.stop(callback); } } dynamicTask.stop(taskKey); @@ -468,21 +507,6 @@ public class DeviceServiceImpl implements IDeviceService { logger.warn("更新设备时未找到设备信息"); return; } - if(deviceInStore.isSwitchPrimarySubStream() != device.isSwitchPrimarySubStream()){ - //当修改设备的主子码流开关时,需要校验是否存在流,如果存在流则直接关闭 - List ssrcTransactionForAll = streamSession.getSsrcTransactionForAll(device.getDeviceId(), null, null, null); - if(ssrcTransactionForAll != null){ - for (SsrcTransaction ssrcTransaction: ssrcTransactionForAll) { - try { - cmder.streamByeCmd(device, ssrcTransaction.getChannelId(), ssrcTransaction.getStream(), null, null); - } catch (InvalidArgumentException | SsrcTransactionNotFoundException | ParseException | SipException e) { - throw new RuntimeException(e); - } - } - } - deviceChannelMapper.clearPlay(device.getDeviceId()); - inviteStreamService.clearInviteInfo(device.getDeviceId()); - } if (!ObjectUtils.isEmpty(device.getName())) { deviceInStore.setName(device.getName()); @@ -499,51 +523,81 @@ public class DeviceServiceImpl implements IDeviceService { if (!ObjectUtils.isEmpty(device.getSdpIp())) { deviceInStore.setSdpIp(device.getSdpIp()); } - + if (!ObjectUtils.isEmpty(device.getPassword())) { + deviceInStore.setPassword(device.getPassword()); + } + if (!ObjectUtils.isEmpty(device.getStreamMode())) { + deviceInStore.setStreamMode(device.getStreamMode()); + } // 目录订阅相关的信息 - if (device.getSubscribeCycleForCatalog() > 0) { - if (deviceInStore.getSubscribeCycleForCatalog() == 0 || deviceInStore.getSubscribeCycleForCatalog() != device.getSubscribeCycleForCatalog()) { - deviceInStore.setSubscribeCycleForCatalog(device.getSubscribeCycleForCatalog()); - // 开启订阅 - addCatalogSubscribe(deviceInStore); - } - }else if (device.getSubscribeCycleForCatalog() == 0) { - if (deviceInStore.getSubscribeCycleForCatalog() != 0) { - deviceInStore.setSubscribeCycleForCatalog(device.getSubscribeCycleForCatalog()); + if (deviceInStore.getSubscribeCycleForCatalog() != device.getSubscribeCycleForCatalog()) { + if (device.getSubscribeCycleForCatalog() > 0) { + // 若已开启订阅,但订阅周期不同,则先取消 + if (deviceInStore.getSubscribeCycleForCatalog() != 0) { + removeCatalogSubscribe(deviceInStore, result->{ + // 开启订阅 + deviceInStore.setSubscribeCycleForCatalog(device.getSubscribeCycleForCatalog()); + addCatalogSubscribe(deviceInStore); + // 因为是异步执行,需要在这里更新下数据 + deviceMapper.updateCustom(deviceInStore); + redisCatchStorage.updateDevice(deviceInStore); + }); + }else { + // 开启订阅 + deviceInStore.setSubscribeCycleForCatalog(device.getSubscribeCycleForCatalog()); + addCatalogSubscribe(deviceInStore); + } + + }else if (device.getSubscribeCycleForCatalog() == 0) { // 取消订阅 - removeCatalogSubscribe(deviceInStore); + deviceInStore.setSubscribeCycleForCatalog(0); + removeCatalogSubscribe(deviceInStore, null); } } - // 移动位置订阅相关的信息 - if (device.getSubscribeCycleForMobilePosition() > 0) { - if (deviceInStore.getSubscribeCycleForMobilePosition() == 0 || deviceInStore.getSubscribeCycleForMobilePosition() != device.getSubscribeCycleForMobilePosition()) { - deviceInStore.setMobilePositionSubmissionInterval(device.getMobilePositionSubmissionInterval()); - deviceInStore.setSubscribeCycleForMobilePosition(device.getSubscribeCycleForMobilePosition()); - // 开启订阅 - addMobilePositionSubscribe(deviceInStore); - } - }else if (device.getSubscribeCycleForMobilePosition() == 0) { - if (deviceInStore.getSubscribeCycleForMobilePosition() != 0) { + if (deviceInStore.getSubscribeCycleForMobilePosition() != device.getSubscribeCycleForMobilePosition()) { + if (device.getSubscribeCycleForMobilePosition() > 0) { + // 若已开启订阅,但订阅周期不同,则先取消 + if (deviceInStore.getSubscribeCycleForMobilePosition() != 0) { + removeMobilePositionSubscribe(deviceInStore, result->{ + // 开启订阅 + deviceInStore.setSubscribeCycleForMobilePosition(device.getSubscribeCycleForMobilePosition()); + addMobilePositionSubscribe(deviceInStore); + // 因为是异步执行,需要在这里更新下数据 + deviceMapper.updateCustom(deviceInStore); + redisCatchStorage.updateDevice(deviceInStore); + }); + }else { + // 开启订阅 + deviceInStore.setSubscribeCycleForMobilePosition(device.getSubscribeCycleForMobilePosition()); + addMobilePositionSubscribe(deviceInStore); + } + + }else if (device.getSubscribeCycleForMobilePosition() == 0) { // 取消订阅 - removeMobilePositionSubscribe(deviceInStore); + deviceInStore.setSubscribeCycleForCatalog(0); + removeCatalogSubscribe(deviceInStore, null); } } if (deviceInStore.getGeoCoordSys() != null) { // 坐标系变化,需要重新计算GCJ02坐标和WGS84坐标 if (!deviceInStore.getGeoCoordSys().equals(device.getGeoCoordSys())) { - updateDeviceChannelGeoCoordSys(device); + deviceInStore.setGeoCoordSys(device.getGeoCoordSys()); + updateDeviceChannelGeoCoordSys(deviceInStore); } }else { - device.setGeoCoordSys("WGS84"); + deviceInStore.setGeoCoordSys("WGS84"); } if (device.getCharset() == null) { - device.setCharset("GB2312"); + deviceInStore.setCharset("GB2312"); } + //SSRC校验 + deviceInStore.setSsrcCheck(device.isSsrcCheck()); + //作为消息通道 + deviceInStore.setAsMessageChannel(device.isAsMessageChannel()); - // 更新redis - redisCatchStorage.updateDevice(device); - deviceMapper.updateCustom(device); + deviceMapper.updateCustom(deviceInStore); + redisCatchStorage.updateDevice(deviceInStore); } @Override diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java old mode 100644 new mode 100755 index b3f4d7db5..9fcbb4071 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/GbStreamServiceImpl.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.service.impl; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; @@ -18,12 +19,14 @@ import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; import java.util.ArrayList; import java.util.List; @Service +@DS("master") public class GbStreamServiceImpl implements IGbStreamService { private final static Logger logger = LoggerFactory.getLogger(GbStreamServiceImpl.class); @@ -76,8 +79,6 @@ public class GbStreamServiceImpl implements IGbStreamService { } try { List deviceChannelList = new ArrayList<>(); - - for (int i = 0; i < gbStreams.size(); i++) { GbStream gbStream = gbStreams.get(i); gbStream.setCatalogId(catalogId); @@ -250,17 +251,21 @@ public class GbStreamServiceImpl implements IGbStreamService { return ; } if (ObjectUtils.isEmpty(catalogId)) { - catalogId = platform.getDeviceGBId(); + catalogId = null; } - if (platformGbStreamMapper.delByPlatformAndCatalogId(platformId, catalogId) > 0) { - List gbStreams = platformGbStreamMapper.queryChannelInParentPlatformAndCatalog(platformId, catalogId); - List deviceChannelList = new ArrayList<>(); - for (GbStream gbStream : gbStreams) { - DeviceChannel deviceChannel = new DeviceChannel(); - deviceChannel.setChannelId(gbStream.getGbId()); - deviceChannelList.add(deviceChannel); - } - eventPublisher.catalogEventPublish(platformId, deviceChannelList, CatalogEvent.DEL); + List gbStreams = platformGbStreamMapper.queryChannelInParentPlatformAndCatalog(platformId, catalogId); + List deviceChannelList = new ArrayList<>(); + for (GbStream gbStream : gbStreams) { + DeviceChannel deviceChannel = new DeviceChannel(); + deviceChannel.setChannelId(gbStream.getGbId()); + deviceChannelList.add(deviceChannel); } + eventPublisher.catalogEventPublish(platformId, deviceChannelList, CatalogEvent.DEL); + platformGbStreamMapper.delByPlatformAndCatalogId(platformId, catalogId); + } + + @Override + public List getGbChannelWithGbid(String gbId) { + return gbStreamMapper.selectByGBId(gbId); } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java old mode 100644 new mode 100755 index f41281633..9dc86f826 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.service.impl; import com.alibaba.fastjson2.JSON; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionStatus; import com.genersoft.iot.vmp.common.InviteSessionType; @@ -20,6 +21,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @Service +@DS("master") public class InviteStreamServiceImpl implements IInviteStreamService { private final Logger logger = LoggerFactory.getLogger(InviteStreamServiceImpl.class); @@ -77,10 +79,11 @@ public class InviteStreamServiceImpl implements IInviteStreamService { } String key = VideoManagerConstants.INVITE_PREFIX + - "_" + inviteInfoForUpdate.getType() + - "_" + inviteInfoForUpdate.getDeviceId() + - "_" + inviteInfoForUpdate.getChannelId() + - "_" + inviteInfoForUpdate.getStream(); + ":" + inviteInfoForUpdate.getType() + + ":" + inviteInfoForUpdate.getDeviceId() + + ":" + inviteInfoForUpdate.getChannelId() + + ":" + inviteInfoForUpdate.getStream()+ + ":" + inviteInfoForUpdate.getSsrcInfo().getSsrc(); redisTemplate.opsForValue().set(key, inviteInfoForUpdate); } @@ -93,11 +96,15 @@ public class InviteStreamServiceImpl implements IInviteStreamService { } removeInviteInfo(inviteInfoInDb); String key = VideoManagerConstants.INVITE_PREFIX + - "_" + inviteInfo.getType() + - "_" + inviteInfo.getDeviceId() + - "_" + inviteInfo.getChannelId() + - "_" + stream; + ":" + inviteInfo.getType() + + ":" + inviteInfo.getDeviceId() + + ":" + inviteInfo.getChannelId() + + ":" + stream + + ":" + inviteInfo.getSsrcInfo().getSsrc(); inviteInfoInDb.setStream(stream); + if (inviteInfoInDb.getSsrcInfo() != null) { + inviteInfoInDb.getSsrcInfo().setStream(stream); + } redisTemplate.opsForValue().set(key, inviteInfoInDb); return inviteInfoInDb; } @@ -105,14 +112,18 @@ public class InviteStreamServiceImpl implements IInviteStreamService { @Override public InviteInfo getInviteInfo(InviteSessionType type, String deviceId, String channelId, String stream) { String key = VideoManagerConstants.INVITE_PREFIX + - "_" + (type != null ? type : "*") + - "_" + (deviceId != null ? deviceId : "*") + - "_" + (channelId != null ? channelId : "*") + - "_" + (stream != null ? stream : "*"); + ":" + (type != null ? type : "*") + + ":" + (deviceId != null ? deviceId : "*") + + ":" + (channelId != null ? channelId : "*") + + ":" + (stream != null ? stream : "*") + + ":*"; List scanResult = RedisUtil.scan(redisTemplate, key); - if (scanResult.size() != 1) { + if (scanResult.isEmpty()) { return null; } + if (scanResult.size() != 1) { + logger.warn("[获取InviteInfo] 发现 key: {}存在多条", key); + } return (InviteInfo) redisTemplate.opsForValue().get(scanResult.get(0)); } @@ -130,10 +141,11 @@ public class InviteStreamServiceImpl implements IInviteStreamService { @Override public void removeInviteInfo(InviteSessionType type, String deviceId, String channelId, String stream) { String scanKey = VideoManagerConstants.INVITE_PREFIX + - "_" + (type != null ? type : "*") + - "_" + (deviceId != null ? deviceId : "*") + - "_" + (channelId != null ? channelId : "*") + - "_" + (stream != null ? stream : "*"); + ":" + (type != null ? type : "*") + + ":" + (deviceId != null ? deviceId : "*") + + ":" + (channelId != null ? channelId : "*") + + ":" + (stream != null ? stream : "*") + + ":*"; List scanResult = RedisUtil.scan(redisTemplate, scanKey); if (scanResult.size() > 0) { for (Object keyObj : scanResult) { @@ -171,10 +183,10 @@ public class InviteStreamServiceImpl implements IInviteStreamService { } private String buildKey(InviteSessionType type, String deviceId, String channelId, String stream) { - String key = type + "_" + deviceId + "_" + channelId; + String key = type + ":" + deviceId + ":" + channelId; // 如果ssrc未null那么可以实现一个通道只能一次操作,ssrc不为null则可以支持一个通道多次invite if (stream != null) { - key += ("_" + stream); + key += (":" + stream); } return key; } @@ -188,7 +200,7 @@ public class InviteStreamServiceImpl implements IInviteStreamService { @Override public int getStreamInfoCount(String mediaServerId) { int count = 0; - String key = VideoManagerConstants.INVITE_PREFIX + "_*_*_*_*"; + String key = VideoManagerConstants.INVITE_PREFIX + ":*:*:*:*:*"; List scanResult = RedisUtil.scan(redisTemplate, key); if (scanResult.size() == 0) { return 0; @@ -219,11 +231,42 @@ public class InviteStreamServiceImpl implements IInviteStreamService { private String buildSubStreamKey(InviteSessionType type, String deviceId, String channelId, String stream) { - String key = type + "_" + "_" + deviceId + "_" + channelId; + String key = type + ":" + ":" + deviceId + ":" + channelId; // 如果ssrc为null那么可以实现一个通道只能一次操作,ssrc不为null则可以支持一个通道多次invite if (stream != null) { - key += ("_" + stream); + key += (":" + stream); } return key; } + + @Override + public InviteInfo getInviteInfoBySSRC(String ssrc) { + String key = VideoManagerConstants.INVITE_PREFIX + ":*:*:*:*:" + ssrc; + List scanResult = RedisUtil.scan(redisTemplate, key); + if (scanResult.size() != 1) { + return null; + } + + return (InviteInfo) redisTemplate.opsForValue().get(scanResult.get(0)); + } + + @Override + public InviteInfo updateInviteInfoForSSRC(InviteInfo inviteInfo, String ssrc) { + InviteInfo inviteInfoInDb = getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()); + if (inviteInfoInDb == null) { + return null; + } + removeInviteInfo(inviteInfoInDb); + String key = VideoManagerConstants.INVITE_PREFIX + + ":" + inviteInfo.getType() + + ":" + inviteInfo.getDeviceId() + + ":" + inviteInfo.getChannelId() + + ":" + inviteInfo.getStream() + + ":" + ssrc; + if (inviteInfoInDb.getSsrcInfo() != null) { + inviteInfoInDb.getSsrcInfo().setSsrc(ssrc); + } + redisTemplate.opsForValue().set(key, inviteInfoInDb); + return inviteInfoInDb; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/LogServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/LogServiceImpl.java old mode 100644 new mode 100755 index 92fa5960d..c72d6c0ab --- a/src/main/java/com/genersoft/iot/vmp/service/impl/LogServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/LogServiceImpl.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.service.impl; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import com.genersoft.iot.vmp.service.ILogService; import com.genersoft.iot.vmp.storager.dao.LogMapper; @@ -12,6 +13,7 @@ import org.springframework.stereotype.Service; import java.util.List; @Service +@DS("master") public class LogServiceImpl implements ILogService { @Autowired diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java old mode 100644 new mode 100755 index 142b81006..190d665b6 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServerServiceImpl.java @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.DynamicTask; @@ -24,28 +25,36 @@ import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.utils.JsonUtil; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.genersoft.iot.vmp.vmanager.bean.RecordFile; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import java.io.File; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; /** * 媒体服务器节点管理 */ @Service +@DS("master") public class MediaServerServiceImpl implements IMediaServerService { private final static Logger logger = LoggerFactory.getLogger(MediaServerServiceImpl.class); @@ -104,6 +113,13 @@ public class MediaServerServiceImpl implements IMediaServerService { @Autowired private RedisTemplate redisTemplate; + @Qualifier("taskExecutor") + @Autowired + private ThreadPoolTaskExecutor taskExecutor; + + + + /** * 初始化 @@ -116,7 +132,7 @@ public class MediaServerServiceImpl implements IMediaServerService { continue; } // 更新 - if (ssrcFactory.hasMediaServerSSRC(mediaServerItem.getId())) { + if (!ssrcFactory.hasMediaServerSSRC(mediaServerItem.getId())) { ssrcFactory.initMediaServerSSRC(mediaServerItem.getId(), null); } // 查询redis是否存在此mediaServer @@ -131,7 +147,7 @@ public class MediaServerServiceImpl implements IMediaServerService { @Override public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, - boolean isPlayback, Integer port, Boolean reUsePort, Integer tcpMode) { + boolean isPlayback, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) { if (mediaServerItem == null || mediaServerItem.getId() == null) { logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null"); return null; @@ -149,17 +165,27 @@ public class MediaServerServiceImpl implements IMediaServerService { } if (streamId == null) { - streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase(); + streamId = String.format("%08x", Long.parseLong(ssrc)).toUpperCase(); + } + if (ssrcCheck && tcpMode > 0) { + // 目前zlm不支持 tcp模式更新ssrc,暂时关闭ssrc校验 + logger.warn("[openRTPServer] 平台对接时下级可能自定义ssrc,但是tcp模式zlm收流目前无法更新ssrc,可能收流超时,此时请使用udp收流或者关闭ssrc校验"); } int rtpServerPort; if (mediaServerItem.isRtpEnable()) { - rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(ssrc):0, port, reUsePort, tcpMode); + rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck ? Long.parseLong(ssrc) : 0, port, onlyAuto, reUsePort, tcpMode); } else { rtpServerPort = mediaServerItem.getRtpProxyPort(); } return new SSRCInfo(rtpServerPort, ssrc, streamId); } + @Override + public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto) { + return openRTPServer(mediaServerItem, streamId, ssrc, ssrcCheck, isPlayback, port, onlyAuto, null, 0); + } + + @Override public void closeRTPServer(MediaServerItem mediaServerItem, String streamId) { if (mediaServerItem == null) { @@ -180,7 +206,10 @@ public class MediaServerServiceImpl implements IMediaServerService { @Override public void closeRTPServer(String mediaServerId, String streamId) { MediaServerItem mediaServerItem = this.getOne(mediaServerId); - closeRTPServer(mediaServerItem, streamId); + if (mediaServerItem != null && mediaServerItem.isRtpEnable()) { + closeRTPServer(mediaServerItem, streamId); + } + zlmresTfulUtils.closeStreams(mediaServerItem, "rtp", streamId); } @Override @@ -212,7 +241,7 @@ public class MediaServerServiceImpl implements IMediaServerService { mediaServerMapper.update(mediaSerItem); MediaServerItem mediaServerItemInRedis = getOne(mediaSerItem.getId()); MediaServerItem mediaServerItemInDataBase = mediaServerMapper.queryOne(mediaSerItem.getId()); - if (mediaServerItemInRedis == null || ssrcFactory.hasMediaServerSSRC(mediaSerItem.getId())) { + if (mediaServerItemInRedis == null || !ssrcFactory.hasMediaServerSSRC(mediaSerItem.getId())) { ssrcFactory.initMediaServerSSRC(mediaServerItemInDataBase.getId(),null); } String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItemInDataBase.getId(); @@ -285,9 +314,9 @@ public class MediaServerServiceImpl implements IMediaServerService { return JsonUtil.redisJsonToObject(redisTemplate, key, MediaServerItem.class); } + @Override public MediaServerItem getDefaultMediaServer() { - return mediaServerMapper.queryDefault(); } @@ -394,7 +423,7 @@ public class MediaServerServiceImpl implements IMediaServerService { } mediaServerMapper.update(serverItem); String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + zlmServerConfig.getGeneralMediaServerId(); - if (ssrcFactory.hasMediaServerSSRC(serverItem.getId())) { + if (!ssrcFactory.hasMediaServerSSRC(serverItem.getId())) { ssrcFactory.initMediaServerSSRC(zlmServerConfig.getGeneralMediaServerId(), null); } redisTemplate.opsForValue().set(key, serverItem); @@ -402,17 +431,6 @@ public class MediaServerServiceImpl implements IMediaServerService { if (serverItem.isAutoConfig()) { - // 查看assist服务的录像路径配置 - if (serverItem.getRecordAssistPort() > 0 && userSetting.getRecordPath() == null) { - JSONObject info = assistRESTfulUtils.getInfo(serverItem, null); - if (info != null && info.getInteger("code") != null && info.getInteger("code") == 0 ) { - JSONObject dataJson = info.getJSONObject("data"); - if (dataJson != null) { - String recordPath = dataJson.getString("record"); - userSetting.setRecordPath(recordPath); - } - } - } setZLMConfig(serverItem, "0".equals(zlmServerConfig.getHookEnable())); } final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + serverItem.getId(); @@ -547,34 +565,30 @@ public class MediaServerServiceImpl implements IMediaServerService { logger.info("[ZLM] 正在设置 :{} -> {}:{}", mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort()); String protocol = sslEnabled ? "https" : "http"; - String hookPrex = String.format("%s://%s:%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort); + String hookPrefix = String.format("%s://%s:%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort); Map param = new HashMap<>(); param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline if (mediaServerItem.getRtspPort() != 0) { - param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -t 0.001 %s"); + param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -frames:v 1 %s"); } param.put("hook.enable","1"); param.put("hook.on_flow_report",""); - param.put("hook.on_play",String.format("%s/on_play", hookPrex)); + param.put("hook.on_play",String.format("%s/on_play", hookPrefix)); param.put("hook.on_http_access",""); - param.put("hook.on_publish", String.format("%s/on_publish", hookPrex)); + param.put("hook.on_publish", String.format("%s/on_publish", hookPrefix)); param.put("hook.on_record_ts",""); param.put("hook.on_rtsp_auth",""); param.put("hook.on_rtsp_realm",""); - param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrex)); + param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrefix)); param.put("hook.on_shell_login",""); - param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrex)); - param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex)); - param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex)); - param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrex)); - param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrex)); - param.put("hook.on_rtp_server_timeout",String.format("%s/on_rtp_server_timeout", hookPrex)); - if (mediaServerItem.getRecordAssistPort() > 0) { - param.put("hook.on_record_mp4",String.format("http://127.0.0.1:%s/api/record/on_record_mp4", mediaServerItem.getRecordAssistPort())); - }else { - param.put("hook.on_record_mp4",""); - } + param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrefix)); + param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrefix)); + param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrefix)); + param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrefix)); + param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrefix)); + param.put("hook.on_rtp_server_timeout",String.format("%s/on_rtp_server_timeout", hookPrefix)); + param.put("hook.on_record_mp4",String.format("%s/on_record_mp4", hookPrefix)); param.put("hook.timeoutSec","20"); // 推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。 // 置0关闭此特性(推流断开会导致立即断开播放器) @@ -583,15 +597,14 @@ public class MediaServerServiceImpl implements IMediaServerService { param.put("protocol.continue_push_ms", "3000" ); // 最多等待未初始化的Track时间,单位毫秒,超时之后会忽略未初始化的Track, 设置此选项优化那些音频错误的不规范流, // 等zlm支持给每个rtpServer设置关闭音频的时候可以不设置此选项 -// param.put("general.wait_track_ready_ms", "3000" ); if (mediaServerItem.isRtpEnable() && !ObjectUtils.isEmpty(mediaServerItem.getRtpPortRange())) { param.put("rtp_proxy.port_range", mediaServerItem.getRtpPortRange().replace(",", "-")); } - if (userSetting.getRecordPath() != null) { - File recordPathFile = new File(userSetting.getRecordPath()); - File mp4SavePathFile = recordPathFile.getParentFile().getAbsoluteFile(); - param.put("protocol.mp4_save_path", mp4SavePathFile.getAbsoluteFile()); + if (!ObjectUtils.isEmpty(mediaServerItem.getRecordPath())) { + File recordPathFile = new File(mediaServerItem.getRecordPath()); + param.put("protocol.mp4_save_path", recordPathFile.getParentFile().getPath()); + param.put("protocol.downloadRoot", recordPathFile.getParentFile().getPath()); param.put("record.appName", recordPathFile.getName()); } @@ -696,6 +709,7 @@ public class MediaServerServiceImpl implements IMediaServerService { ssrcFactory.initMediaServerSSRC(mediaServerItem.getId(), null); String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItem.getId(); redisTemplate.opsForValue().set(key, mediaServerItem); + resetOnlineServerItem(mediaServerItem); clearRTPServer(mediaServerItem); } final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + mediaServerItem.getId(); @@ -723,15 +737,6 @@ public class MediaServerServiceImpl implements IMediaServerService { } } - @Override - public boolean checkRtpServer(MediaServerItem mediaServerItem, String app, String stream) { - JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, stream); - if(rtpInfo.getInteger("code") == 0){ - return rtpInfo.getBoolean("exist"); - } - return false; - } - @Override public MediaServerLoad getLoad(MediaServerItem mediaServerItem) { MediaServerLoad result = new MediaServerLoad(); @@ -744,4 +749,8 @@ public class MediaServerServiceImpl implements IMediaServerService { return result; } + @Override + public List getAllWithAssistPort() { + return mediaServerMapper.queryAllWithAssistPort(); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java old mode 100644 new mode 100755 index b8241d7a1..784f43f3d --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java @@ -4,21 +4,18 @@ import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.StreamInfo; -import com.genersoft.iot.vmp.common.StreamURL; import com.genersoft.iot.vmp.conf.MediaConfig; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.service.IMediaServerService; +import com.genersoft.iot.vmp.service.IMediaService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; -import com.genersoft.iot.vmp.service.IMediaService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; -import java.net.URL; - @Service public class MediaServiceImpl implements IMediaService { @@ -42,7 +39,7 @@ public class MediaServiceImpl implements IMediaService { @Override public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String callId) { - return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null, callId); + return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null, callId, true); } @Override @@ -67,12 +64,12 @@ public class MediaServiceImpl implements IMediaService { if (data == null) { return null; } - JSONObject mediaJSON = JSON.parseObject(JSON.toJSONString(data.get(0)), JSONObject.class); + JSONObject mediaJSON = data.getJSONObject(0); JSONArray tracks = mediaJSON.getJSONArray("tracks"); if (authority) { - streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld); + streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld, true); }else { - streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null); + streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null, true); } } } @@ -87,7 +84,7 @@ public class MediaServiceImpl implements IMediaService { } @Override - public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId) { + public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId, boolean isPlay) { StreamInfo streamInfoResult = new StreamInfo(); streamInfoResult.setStream(stream); streamInfoResult.setApp(app); @@ -104,10 +101,9 @@ public class MediaServiceImpl implements IMediaService { streamInfoResult.setFmp4(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam); streamInfoResult.setHls(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam); streamInfoResult.setTs(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam); - streamInfoResult.setRtc(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam); + streamInfoResult.setRtc(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam, isPlay); streamInfoResult.setTracks(tracks); return streamInfoResult; } - } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java old mode 100644 new mode 100755 index 517cb0465..35028dff4 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformChannelServiceImpl.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.service.impl; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; @@ -27,6 +28,7 @@ import java.util.Map; * @author lin */ @Service +@DS("master") public class PlatformChannelServiceImpl implements IPlatformChannelService { private final static Logger logger = LoggerFactory.getLogger(PlatformChannelServiceImpl.class); @@ -82,7 +84,7 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService { int allCount = 0; boolean result = false; TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition); - int limitCount = 300; + int limitCount = 50; if (channelReducesToAdd.size() > 0) { if (channelReducesToAdd.size() > limitCount) { for (int i = 0; i < channelReducesToAdd.size(); i += limitCount) { @@ -134,7 +136,7 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService { deviceChannelList.add(deviceChannel); } return deviceChannelList; - } else if (catalog == null || !catalogId.equals(platform.getDeviceGBId())) { + } else if (catalog == null && !catalogId.equals(platform.getDeviceGBId())) { logger.warn("未查询到目录{}的信息", catalogId); return null; } @@ -162,13 +164,12 @@ public class PlatformChannelServiceImpl implements IPlatformChannelService { return 0; } if (ObjectUtils.isEmpty(catalogId)) { - catalogId = platform.getDeviceGBId(); + catalogId = null; } - if ((result = platformChannelMapper.delChannelForGBByCatalogId(platformId, catalogId)) > 0) { - List deviceChannels = platformChannelMapper.queryAllChannelInCatalog(platformId, catalogId); - eventPublisher.catalogEventPublish(platformId, deviceChannels, CatalogEvent.DEL); - } - return result; + List deviceChannels = platformChannelMapper.queryAllChannelInCatalog(platformId, catalogId); + eventPublisher.catalogEventPublish(platformId, deviceChannels, CatalogEvent.DEL); + + return platformChannelMapper.delChannelForGBByCatalogId(platformId, catalogId); } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java old mode 100644 new mode 100755 index b75b47710..06c621e58 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/PlatformServiceImpl.java @@ -1,37 +1,64 @@ package com.genersoft.iot.vmp.service.impl; +import com.alibaba.fastjson2.JSONObject; +import com.genersoft.iot.vmp.common.InviteInfo; +import com.genersoft.iot.vmp.common.InviteSessionStatus; +import com.genersoft.iot.vmp.common.InviteSessionType; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; +import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; +import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; +import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; +import com.genersoft.iot.vmp.service.IInviteStreamService; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IPlatformService; -import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; +import com.genersoft.iot.vmp.service.IPlayService; +import com.genersoft.iot.vmp.service.bean.*; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.*; import com.genersoft.iot.vmp.utils.DateUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; +import gov.nist.javax.sip.message.SIPRequest; +import gov.nist.javax.sip.message.SIPResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import javax.sdp.*; import javax.sip.InvalidArgumentException; +import javax.sip.ResponseEvent; +import javax.sip.PeerUnavailableException; import javax.sip.SipException; import java.text.ParseException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.*; /** * @author lin */ @Service +@DS("master") public class PlatformServiceImpl implements IPlatformService { private final static String REGISTER_KEY_PREFIX = "platform_register_"; @@ -44,15 +71,6 @@ public class PlatformServiceImpl implements IPlatformService { @Autowired private ParentPlatformMapper platformMapper; - @Autowired - private PlatformCatalogMapper catalogMapper; - - @Autowired - private PlatformChannelMapper platformChannelMapper; - - @Autowired - private PlatformGbStreamMapper platformGbStreamMapper; - @Autowired private IRedisCatchStorage redisCatchStorage; @@ -80,6 +98,21 @@ public class PlatformServiceImpl implements IPlatformService { @Autowired private UserSetting userSetting; + @Autowired + private ZlmHttpHookSubscribe subscribe; + + @Autowired + private VideoStreamSessionManager streamSession; + + + @Autowired + private IPlayService playService; + + @Autowired + private IInviteStreamService inviteStreamService; + + @Autowired + private ZLMRESTfulUtils zlmresTfulUtils; @Override @@ -143,7 +176,7 @@ public class PlatformServiceImpl implements IPlatformService { dynamicTask.stop(registerTaskKey); // 注销旧的 try { - if (parentPlatformOld.isStatus()) { + if (parentPlatformOld.isStatus() && parentPlatformCatchOld != null) { logger.info("保存平台{}时发现旧平台在线,发送注销命令", parentPlatformOld.getServerGBId()); commanderForPlatform.unregister(parentPlatformOld, parentPlatformCatchOld.getSipTransactionInfo(), null, eventResult -> { logger.info("[国标级联] 注销成功, 平台:{}", parentPlatformOld.getServerGBId()); @@ -182,6 +215,7 @@ public class PlatformServiceImpl implements IPlatformService { } } + return false; } @@ -226,20 +260,20 @@ public class PlatformServiceImpl implements IPlatformService { try { commanderForPlatform.keepalive(parentPlatform, eventResult -> { // 心跳失败 - if (eventResult.type == SipSubscribe.EventResultType.timeout) { - // 心跳超时 - ParentPlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId()); - // 此时是第三次心跳超时, 平台离线 - if (platformCatch.getKeepAliveReply() == 2) { - // 设置平台离线,并重新注册 - logger.info("[国标级联] 三次心跳超时, 平台{}({})离线", parentPlatform.getName(), parentPlatform.getServerGBId()); - offline(parentPlatform, false); - - } - - }else { + if (eventResult.type != SipSubscribe.EventResultType.timeout) { logger.warn("[国标级联]发送心跳收到错误,code: {}, msg: {}", eventResult.statusCode, eventResult.msg); } + // 心跳失败 + ParentPlatformCatch platformCatch = redisCatchStorage.queryPlatformCatchInfo(parentPlatform.getServerGBId()); + // 此时是第三次心跳超时, 平台离线 + if (platformCatch.getKeepAliveReply() == 2) { + // 设置平台离线,并重新注册 + logger.info("[国标级联] 三次心跳失败, 平台{}({})离线", parentPlatform.getName(), parentPlatform.getServerGBId()); + offline(parentPlatform, false); + }else { + platformCatch.setKeepAliveReply(platformCatch.getKeepAliveReply() + 1); + redisCatchStorage.updatePlatformCatchInfo(platformCatch); + } }, eventResult -> { // 心跳成功 @@ -249,6 +283,7 @@ public class PlatformServiceImpl implements IPlatformService { platformCatch.setKeepAliveReply(0); redisCatchStorage.updatePlatformCatchInfo(platformCatch); } + logger.info("[发送心跳] 国标级联 发送心跳, code: {}, msg: {}", eventResult.statusCode, eventResult.msg); }); } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("[命令发送失败] 国标级联 发送心跳: {}", e.getMessage()); @@ -256,6 +291,32 @@ public class PlatformServiceImpl implements IPlatformService { }, (parentPlatform.getKeepTimeout())*1000); } + if (parentPlatform.isAutoPushChannel()) { + if (subscribeHolder.getCatalogSubscribe(parentPlatform.getServerGBId()) == null) { + logger.info("[国标级联]:{}, 添加自动通道推送模拟订阅信息", parentPlatform.getServerGBId()); + addSimulatedSubscribeInfo(parentPlatform); + } + }else { + SubscribeInfo catalogSubscribe = subscribeHolder.getCatalogSubscribe(parentPlatform.getServerGBId()); + if (catalogSubscribe != null && catalogSubscribe.getExpires() == -1) { + subscribeHolder.removeCatalogSubscribe(parentPlatform.getServerGBId()); + } + } + } + + @Override + public void addSimulatedSubscribeInfo(ParentPlatform parentPlatform) { + // 自动添加一条模拟的订阅信息 + SubscribeInfo subscribeInfo = new SubscribeInfo(); + subscribeInfo.setId(parentPlatform.getServerGBId()); + subscribeInfo.setExpires(-1); + subscribeInfo.setEventType("Catalog"); + int random = (int) Math.floor(Math.random() * 10000); + subscribeInfo.setEventId(random + ""); + subscribeInfo.setSimulatedCallId(UUID.randomUUID().toString().replace("-", "") + "@" + parentPlatform.getServerIP()); + subscribeInfo.setSimulatedFromTag(UUID.randomUUID().toString().replace("-", "")); + subscribeInfo.setSimulatedToTag(UUID.randomUUID().toString().replace("-", "")); + subscribeHolder.putCatalogSubscribe(parentPlatform.getServerGBId(), subscribeInfo); } private void registerTask(ParentPlatform parentPlatform, SipTransactionInfo sipTransactionInfo){ @@ -276,7 +337,7 @@ public class PlatformServiceImpl implements IPlatformService { eventResult.statusCode, eventResult.msg); offline(parentPlatform, false); }, null); - } catch (InvalidArgumentException | ParseException | SipException e) { + } catch (Exception e) { logger.error("[命令发送失败] 国标级联定时注册: {}", e.getMessage()); } } @@ -310,9 +371,16 @@ public class PlatformServiceImpl implements IPlatformService { // 清除心跳任务 dynamicTask.stop(keepaliveTaskKey); } - // 停止目录订阅回复 - logger.info("[平台离线] {}, 停止订阅回复", parentPlatform.getServerGBId()); - subscribeHolder.removeAllSubscribe(parentPlatform.getServerGBId()); + // 停止订阅回复 + SubscribeInfo catalogSubscribe = subscribeHolder.getCatalogSubscribe(parentPlatform.getServerGBId()); + if (catalogSubscribe != null) { + if (catalogSubscribe.getExpires() > 0) { + logger.info("[平台离线] {}, 停止目录订阅回复", parentPlatform.getServerGBId()); + subscribeHolder.removeCatalogSubscribe(parentPlatform.getServerGBId()); + } + } + logger.info("[平台离线] {}, 停止移动位置订阅回复", parentPlatform.getServerGBId()); + subscribeHolder.removeMobilePositionSubscribe(parentPlatform.getServerGBId()); // 发起定时自动重新注册 if (!stopRegister) { // 设置为60秒自动尝试重新注册 @@ -323,7 +391,6 @@ public class PlatformServiceImpl implements IPlatformService { ()-> registerTask(platform, null), userSetting.getRegisterAgainAfterTime() * 1000); } - } } @@ -337,7 +404,7 @@ public class PlatformServiceImpl implements IPlatformService { Map param = new HashMap<>(3); param.put("vhost", "__defaultVhost__"); param.put("app", sendRtpItem.getApp()); - param.put("stream", sendRtpItem.getStreamId()); + param.put("stream", sendRtpItem.getStream()); zlmServerFactory.stopSendRtpStream(mediaInfo, param); } } @@ -394,4 +461,319 @@ public class PlatformServiceImpl implements IPlatformService { } } } + + @Override + public void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent, + SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException { + + if (mediaServerItem == null) { + logger.info("[国标级联] 语音喊话未找到可用的zlm. platform: {}", platform.getServerGBId()); + return; + } + InviteInfo inviteInfoForOld = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, platform.getServerGBId(), channelId); + + if (inviteInfoForOld != null && inviteInfoForOld.getStreamInfo() != null) { + // 如果zlm不存在这个流,则删除数据即可 + MediaServerItem mediaServerItemForStreamInfo = mediaServerService.getOne(inviteInfoForOld.getStreamInfo().getMediaServerId()); + if (mediaServerItemForStreamInfo != null) { + Boolean ready = zlmServerFactory.isStreamReady(mediaServerItemForStreamInfo, inviteInfoForOld.getStreamInfo().getApp(), inviteInfoForOld.getStreamInfo().getStream()); + if (!ready) { + // 错误存在于redis中的数据 + inviteStreamService.removeInviteInfo(inviteInfoForOld); + }else { + // 流确实尚在推流,直接回调结果 + OnStreamChangedHookParam hookParam = new OnStreamChangedHookParam(); + hookParam.setApp(inviteInfoForOld.getStreamInfo().getApp()); + hookParam.setStream(inviteInfoForOld.getStreamInfo().getStream()); + + hookEvent.response(mediaServerItemForStreamInfo, hookParam); + return; + } + } + } + + String streamId = null; + if (mediaServerItem.isRtpEnable()) { + streamId = String.format("%s_%s", platform.getServerGBId(), channelId); + } + // 默认不进行SSRC校验, TODO 后续可改为配置 + boolean ssrcCheck = false; + int tcpMode; + if (userSetting.getBroadcastForPlatform().equalsIgnoreCase("TCP-PASSIVE")) { + tcpMode = 1; + }else if (userSetting.getBroadcastForPlatform().equalsIgnoreCase("TCP-ACTIVE")) { + tcpMode = 2; + } else { + tcpMode = 0; + } + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, ssrcCheck, false, null, true, false, tcpMode); + if (ssrcInfo == null || ssrcInfo.getPort() < 0) { + logger.info("[国标级联] 发起语音喊话 开启端口监听失败, platform: {}, channel: {}", platform.getServerGBId(), channelId); + SipSubscribe.EventResult 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 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 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); + } + } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java old mode 100644 new mode 100755 index 0c287a9af..fc9318b3b --- a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java @@ -1,43 +1,70 @@ package com.genersoft.iot.vmp.service.impl; +import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.common.InviteInfo; import com.genersoft.iot.vmp.common.InviteSessionStatus; import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.exception.ServiceException; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; +import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager; import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; -import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; +import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; -import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; +import com.genersoft.iot.vmp.gb28181.utils.SipUtils; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; +import com.genersoft.iot.vmp.media.zlm.dto.*; +import com.genersoft.iot.vmp.media.zlm.*; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRecordMp4; +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; +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.OnRecordMp4HookParam; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; import com.genersoft.iot.vmp.service.*; +import com.genersoft.iot.vmp.service.bean.*; +import com.genersoft.iot.vmp.service.bean.ErrorCallback; +import com.genersoft.iot.vmp.service.bean.InviteErrorCode; +import com.genersoft.iot.vmp.service.bean.RequestPushStreamMsg; +import com.genersoft.iot.vmp.service.bean.SSRCInfo; +import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener; +import com.genersoft.iot.vmp.service.bean.DownloadFileInfo; import com.genersoft.iot.vmp.service.bean.ErrorCallback; import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; +import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper; +import com.genersoft.iot.vmp.utils.CloudRecordUtils; import com.genersoft.iot.vmp.utils.DateUtil; +import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.genersoft.iot.vmp.vmanager.bean.StreamContent; +import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent; +import gov.nist.javax.sip.message.SIPResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; @@ -45,16 +72,16 @@ import javax.sdp.*; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; import javax.sip.SipException; +import javax.sip.header.CallIdHeader; import java.io.File; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.ParseException; -import java.util.List; -import java.util.UUID; -import java.util.Vector; +import java.util.*; @SuppressWarnings(value = {"rawtypes", "unchecked"}) @Service +@DS("master") public class PlayServiceImpl implements IPlayService { private final static Logger logger = LoggerFactory.getLogger(PlayServiceImpl.class); @@ -63,28 +90,34 @@ public class PlayServiceImpl implements IPlayService { private IVideoManagerStorage storager; @Autowired - private SIPCommander cmder; + private ISIPCommander cmder; @Autowired - private SIPCommanderFroPlatform sipCommanderFroPlatform; + private AudioBroadcastManager audioBroadcastManager; + + @Autowired + private IDeviceService deviceService; + + @Autowired + private ISIPCommanderForPlatform sipCommanderFroPlatform; @Autowired private IRedisCatchStorage redisCatchStorage; - @Autowired - private IInviteStreamService inviteStreamService; - - @Autowired - private DeferredResultHolder resultHolder; - - @Autowired - private ZLMRESTfulUtils zlmresTfulUtils; - @Autowired private ZLMServerFactory zlmServerFactory; @Autowired - private AssistRESTfulUtils assistRESTfulUtils; + private IInviteStreamService inviteStreamService; + + @Autowired + private ZlmHttpHookSubscribe subscribe; + + @Autowired + private SendRtpPortManager sendRtpPortManager; + + @Autowired + private ZLMRESTfulUtils zlmresTfulUtils; @Autowired private IMediaService mediaService; @@ -95,18 +128,34 @@ public class PlayServiceImpl implements IPlayService { @Autowired private VideoStreamSessionManager streamSession; - - @Autowired - private IDeviceService deviceService; - @Autowired private UserSetting userSetting; + @Autowired + private IDeviceChannelService channelService; + + @Autowired + private SipConfig sipConfig; + @Autowired private DynamicTask dynamicTask; @Autowired - private ZlmHttpHookSubscribe subscribe; + 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; @@ -118,15 +167,26 @@ public class PlayServiceImpl implements IPlayService { @Override public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback callback) { if (mediaServerItem == null) { + logger.warn("[点播] 未找到可用的zlm deviceId: {},channelId:{}", deviceId, channelId); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm"); } Device device = redisCatchStorage.getDevice(deviceId); + if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE") && !mediaServerItem.isRtpEnable()) { + logger.warn("[点播] 单端口收流时不支持TCP主动方式收流 deviceId: {},channelId:{}", deviceId, channelId); + throw new ControllerException(ErrorCode.ERROR100.getCode(), "单端口收流时不支持TCP主动方式收流"); + } + DeviceChannel channel = channelService.getOne(deviceId, channelId); + if (channel == null) { + logger.warn("[点播] 未找到通道 deviceId: {},channelId:{}", deviceId, channelId); + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到通道"); + } InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); if (inviteInfo != null ) { if (inviteInfo.getStreamInfo() == null) { // 点播发起了但是尚未成功, 仅注册回调等待结果即可 inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback); + logger.info("[点播开始] 已经请求中,等待结果, deviceId: {}, channelId: {}", device.getDeviceId(), channelId); return inviteInfo.getSsrcInfo(); }else { StreamInfo streamInfo = inviteInfo.getStreamInfo(); @@ -149,6 +209,7 @@ public class PlayServiceImpl implements IPlayService { InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); + logger.info("[点播已存在] 直接返回, deviceId: {}, channelId: {}", device.getDeviceId(), channelId); return inviteInfo.getSsrcInfo(); }else { // 点播发起了但是尚未成功, 仅注册回调等待结果即可 @@ -158,11 +219,8 @@ public class PlayServiceImpl implements IPlayService { } } } - String streamId = null; - if (mediaServerItem.isRtpEnable()) { - streamId = String.format("%s_%s", device.getDeviceId(), channelId); - } - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(), false, 0, false, device.getStreamModeForParam()); + String streamId = String.format("%s_%s", device.getDeviceId(), channelId);; + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(), false, 0, false, false, device.getStreamModeForParam()); if (ssrcInfo == null) { callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null); inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, @@ -171,14 +229,154 @@ public class PlayServiceImpl implements IPlayService { null); return null; } - // TODO 记录点播的状态 - play(mediaServerItem, ssrcInfo, device, channelId, callback); + play(mediaServerItem, ssrcInfo, device, channel, callback); 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 param = new HashMap<>(12); + param.put("vhost","__defaultVhost__"); + param.put("app", sendRtpItem.getApp()); + param.put("stream", sendRtpItem.getStream()); + param.put("ssrc", sendRtpItem.getSsrc()); + param.put("src_port", sendRtpItem.getLocalPort()); + param.put("pt", sendRtpItem.getPt()); + param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0"); + param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0"); + param.put("is_udp", sendRtpItem.isTcp() ? "0" : "1"); + param.put("recv_stream_id", sendRtpItem.getReceiveStream()); + param.put("close_delay_ms", userSetting.getPlayTimeout() * 1000); + + zlmServerFactory.startSendRtpPassive(mediaServerItem, param, jsonObject -> { + if (jsonObject == null || jsonObject.getInteger("code") != 0 ) { + mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc()); + logger.info("[语音对讲]失败 deviceId: {}, channelId: {}", device.getDeviceId(), channelId); + audioEvent.call("失败, " + jsonObject.getString("msg")); + // 查看是否已经建立了通道,存在则发送bye + stopTalk(device, channelId); + } + }); + + + // 查看设备是否已经在推流 + try { + cmder.talkStreamCmd(mediaServerItem, sendRtpItem, device, channelId, callId, (mediaServerItemInuse, hookParam) -> { + logger.info("[语音对讲] 流已生成, 开始推流: " + hookParam); + dynamicTask.stop(timeOutTaskKey); + // TODO 暂不做处理 + }, (mediaServerItemInuse, hookParam) -> { + logger.info("[语音对讲] 设备开始推流: " + hookParam); + dynamicTask.stop(timeOutTaskKey); + + }, (event) -> { + dynamicTask.stop(timeOutTaskKey); + + if (event.event instanceof ResponseEvent) { + ResponseEvent responseEvent = (ResponseEvent) event.event; + if (responseEvent.getResponse() instanceof SIPResponse) { + SIPResponse response = (SIPResponse) responseEvent.getResponse(); + sendRtpItem.setFromTag(response.getFromTag()); + sendRtpItem.setToTag(response.getToTag()); + sendRtpItem.setCallId(response.getCallIdHeader().getCallId()); + redisCatchStorage.updateSendRTPSever(sendRtpItem); + + streamSession.put(device.getDeviceId(), channelId, "talk", + sendRtpItem.getStream(), sendRtpItem.getSsrc(), sendRtpItem.getMediaServerId(), + response, InviteSessionType.TALK); + } else { + logger.error("[语音对讲]收到的消息错误,response不是SIPResponse"); + } + } else { + logger.error("[语音对讲]收到的消息错误,event不是ResponseEvent"); + } + + }, (event) -> { + dynamicTask.stop(timeOutTaskKey); + mediaServerService.closeRTPServer(mediaServerItem, sendRtpItem.getStream()); + // 释放ssrc + mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc()); + streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream()); + errorEvent.response(event); + }); + } catch (InvalidArgumentException | SipException | ParseException e) { + + logger.error("[命令发送失败] 对讲消息: {}", e.getMessage()); + dynamicTask.stop(timeOutTaskKey); + mediaServerService.closeRTPServer(mediaServerItem, sendRtpItem.getStream()); + // 释放ssrc + mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc()); + + streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream()); + SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(); + eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent; + eventResult.statusCode = -1; + eventResult.msg = "命令发送失败"; + errorEvent.response(eventResult); + } +// } + + } + + @Override - public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, + public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel, ErrorCallback callback) { if (mediaServerItem == null || ssrcInfo == null) { @@ -187,228 +385,109 @@ public class PlayServiceImpl implements IPlayService { null); return; } - logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{}, 收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", - device.getDeviceId(), channelId, device.isSwitchPrimarySubStream() ? "辅码流" : "主码流", ssrcInfo.getPort(), + logger.info("[点播开始] deviceId: {}, channelId: {},码流类型:{}, 收流端口: {}, 码流:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", + device.getDeviceId(), channel.getChannelId(), channel.getStreamIdentification(), ssrcInfo.getPort(), ssrcInfo.getStream(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); //端口获取失败的ssrcInfo 没有必要发送点播指令 if (ssrcInfo.getPort() <= 0) { - logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo); + logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channel.getChannelId(), ssrcInfo); // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream()); callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null); - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId(), null, InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null); return; } // 初始化redis中的invite消息状态 - InviteInfo inviteInfo = InviteInfo.getInviteInfo(device.getDeviceId(), channelId, ssrcInfo.getStream(), ssrcInfo, + InviteInfo inviteInfo = InviteInfo.getInviteInfo(device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream(), ssrcInfo, mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAY, InviteSessionStatus.ready); - inviteInfo.setSubStream(device.isSwitchPrimarySubStream()); inviteStreamService.updateInviteInfo(inviteInfo); // 超时处理 String timeOutTaskKey = UUID.randomUUID().toString(); dynamicTask.startDelay(timeOutTaskKey, () -> { // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况 - InviteInfo inviteInfoForTimeOut = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); + InviteInfo inviteInfoForTimeOut = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId()); if (inviteInfoForTimeOut == null || inviteInfoForTimeOut.getStreamInfo() == null) { - logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},码流类型:{},端口:{}, SSRC: {}", - device.getDeviceId(), channelId, device.isSwitchPrimarySubStream() ? "辅码流" : "主码流", + logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},码流:{},端口:{}, SSRC: {}", + device.getDeviceId(), channel.getChannelId(), channel.getStreamIdentification(), ssrcInfo.getPort(), ssrcInfo.getSsrc()); - // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 -// InviteInfo inviteInfoForTimeout = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.play, device.getDeviceId(), channelId); -// if (inviteInfoForTimeout == null) { -// return; -// } -// if (InviteSessionStatus.ok == inviteInfoForTimeout.getStatus() ) { -// // TODO 发送bye -// }else { -// // TODO 发送cancel -// } callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null); - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId(), null, InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null); - inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId()); try { - cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null); + cmder.streamByeCmd(device, channel.getChannelId(), ssrcInfo.getStream(), null); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { logger.error("[点播超时], 发送BYE失败 {}", e.getMessage()); } finally { mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream()); mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); // 取消订阅消息监听 HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId()); subscribe.removeSubscribe(hookSubscribe); } + }else { + logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},码流:{},端口:{}, SSRC: {}", + device.getDeviceId(), channel.getChannelId(), channel.getStreamIdentification(), + ssrcInfo.getPort(), ssrcInfo.getSsrc()); + + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); + + mediaServerService.closeRTPServer(mediaServerItem.getId(), ssrcInfo.getStream()); + streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream()); } }, userSetting.getPlayTimeout()); try { - cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (mediaServerItemInuse, hookParam ) -> { + cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channel, (mediaServerItemInuse, hookParam ) -> { logger.info("收到订阅消息: " + hookParam); dynamicTask.stop(timeOutTaskKey); // hook响应 - StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, hookParam, device.getDeviceId(), channelId); + StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, hookParam, device.getDeviceId(), channel.getChannelId()); if (streamInfo == null){ callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId(), null, InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); return; } callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId(), null, InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); - logger.info("[点播成功] deviceId: {}, channelId:{}, 码流类型:{}", device.getDeviceId(), channelId, - device.isSwitchPrimarySubStream() ? "辅码流" : "主码流"); - snapOnPlay(mediaServerItemInuse, device.getDeviceId(), channelId, ssrcInfo.getStream()); - }, (event) -> { - inviteInfo.setStatus(InviteSessionStatus.ok); - - 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).trim(); - // 查询到ssrc不一致且开启了ssrc校验则需要针对处理 - if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { - if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { - String substring = contentString.substring(0, contentString.indexOf("y=")); - 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("96")) { - port = media.getMediaPort(); - break; - } - } - logger.info("[点播-TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); - JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream()); - logger.info("[点播-TCP主动连接对方] 结果: {}", jsonObject); - } catch (SdpException e) { - logger.error("[点播-TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e); - 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.PLAY, device.getDeviceId(), channelId, null, - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); - } - } - return; - } - logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); - if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { - 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); - String stream = String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase(); - hookSubscribe.getContent().put("stream", stream); - inviteStreamService.updateInviteInfoForStream(inviteInfo, stream); - subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> { - logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + hookParam); - dynamicTask.stop(timeOutTaskKey); - // hook响应 - StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, hookParam, device.getDeviceId(), channelId); - if (streamInfo == null){ - callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), - InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); - return; - } - callback.run(InviteErrorCode.SUCCESS.getCode(), - InviteErrorCode.SUCCESS.getMsg(), streamInfo); - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, - InviteErrorCode.SUCCESS.getCode(), - InviteErrorCode.SUCCESS.getMsg(), - streamInfo); - snapOnPlay(mediaServerItemInUse, device.getDeviceId(), channelId, stream); - }); - return; - } - - // 更新ssrc - Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse); - if (!result) { - try { - logger.warn("[点播] 更新ssrc失败,停止点播 {}/{}", device.getDeviceId(), channelId); - cmder.streamByeCmd(device, 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(device.getDeviceId(), channelId, ssrcInfo.getStream()); - - callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), - "下级自定义了ssrc,重新设置收流信息失败", null); - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, - InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), - "下级自定义了ssrc,重新设置收流信息失败", null); - - }else { - if (ssrcInfo.getStream()!= null && !ssrcInfo.getStream().equals(inviteInfo.getStream())) { - inviteStreamService.removeInviteInfo(inviteInfo); - } - ssrcInfo.setSsrc(ssrcInResponse); - inviteInfo.setSsrcInfo(ssrcInfo); - inviteInfo.setStream(ssrcInfo.getStream()); - } - }else { - logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正"); - } - } - inviteStreamService.updateInviteInfo(inviteInfo); + logger.info("[点播成功] deviceId: {}, channelId:{}, 码流类型:{}", device.getDeviceId(), channel.getChannelId(), + channel.getStreamIdentification()); + snapOnPlay(mediaServerItemInuse, device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream()); + }, (eventResult) -> { + // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题 + InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channel.getChannelId(), + timeOutTaskKey, callback, inviteInfo, InviteSessionType.PLAY); }, (event) -> { + logger.info("[点播失败] deviceId: {}, channelId:{}, {}: {}", device.getDeviceId(), channel.getChannelId(), event.statusCode, event.msg); dynamicTask.stop(timeOutTaskKey); mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream()); callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_ERROR.getCode(), String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null); - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId(), null, InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null); - inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId()); }); } catch (InvalidArgumentException | SipException | ParseException e) { @@ -418,15 +497,77 @@ public class PlayServiceImpl implements IPlayService { // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream()); callback.run(InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(), InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); - inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId(), null, InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(), InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); - inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId()); + } + } + + private void tcpActiveHandler(Device device, String channelId, String contentString, + MediaServerItem mediaServerItem, + String timeOutTaskKey, SSRCInfo ssrcInfo, ErrorCallback callback){ + if (!device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { + 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("96")) { + port = media.getMediaPort(); + break; + } + } + logger.info("[TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); + JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream()); + logger.info("[TCP主动连接对方] 结果: {}" , jsonObject); + if (jsonObject.getInteger("code") != 0) { + // 主动连接失败,结束流程, 清理数据 + dynamicTask.stop(timeOutTaskKey); + mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); + // 释放ssrc + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); + + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + + callback.run(InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), + InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); + inviteStreamService.call(InviteSessionType.BROADCAST, device.getDeviceId(), channelId, null, + InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), + InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); + } + } catch (SdpException e) { + logger.error("[TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e); + dynamicTask.stop(timeOutTaskKey); + 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); } } @@ -452,7 +593,7 @@ public class PlayServiceImpl implements IPlayService { zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName); } - private StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId) { + public StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId) { StreamInfo streamInfo = null; Device device = redisCatchStorage.getDevice(deviceId); OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam)hookParam; @@ -485,7 +626,7 @@ public class PlayServiceImpl implements IPlayService { deviceChannel.setStreamId(streamInfo.getStream()); storager.startPlay(deviceId, channelId, streamInfo.getStream()); } - InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAYBACK, deviceId, channelId); + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, ((OnStreamChangedHookParam) param).getStream()); if (inviteInfo != null) { inviteInfo.setStatus(InviteSessionStatus.ok); @@ -514,51 +655,35 @@ public class PlayServiceImpl implements IPlayService { return mediaServerItem; } - @Override - public MediaServerItem getNewMediaServerItemHasAssist(Device device) { - if (device == null) { - return null; - } - MediaServerItem mediaServerItem; - if (ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) { - mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(true); - } else { - mediaServerItem = mediaServerService.getOne(device.getMediaServerId()); - } - if (mediaServerItem == null) { - logger.warn("[获取可用的ZLM节点]未找到可使用的ZLM..."); - } - return mediaServerItem; - } - @Override public void playBack(String deviceId, String channelId, String startTime, - String endTime, ErrorCallback callback) { + String endTime, ErrorCallback callback) { Device device = storager.queryVideoDevice(deviceId); if (device == null) { - return; + logger.warn("[录像回放] 未找到设备 deviceId: {},channelId:{}", deviceId, channelId); + throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备:" + deviceId); } + MediaServerItem newMediaServerItem = getNewMediaServerItem(device); - String stream = null; - if (newMediaServerItem.isRtpEnable()) { - String startTimeStr = startTime.replace("-", "") - .replace(":", "") - .replace(" ", ""); - System.out.println(startTimeStr); - String endTimeTimeStr = endTime.replace("-", "") - .replace(":", "") - .replace(" ", ""); - System.out.println(endTimeTimeStr); - stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr; + if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE") && ! newMediaServerItem.isRtpEnable()) { + logger.warn("[录像回放] 单端口收流时不支持TCP主动方式收流 deviceId: {},channelId:{}", deviceId, channelId); + throw new ControllerException(ErrorCode.ERROR100.getCode(), "单端口收流时不支持TCP主动方式收流"); } - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, device.getStreamModeForParam()); + String startTimeStr = startTime.replace("-", "") + .replace(":", "") + .replace(" ", ""); + String endTimeTimeStr = endTime.replace("-", "") + .replace(":", "") + .replace(" ", ""); + String stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr; + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam()); playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, callback); } @Override public void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, - String deviceId, String channelId, String startTime, - String endTime, ErrorCallback callback) { + String deviceId, String channelId, String startTime, + String endTime, ErrorCallback callback) { if (mediaServerItem == null || ssrcInfo == null) { callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), @@ -624,113 +749,12 @@ public class PlayServiceImpl implements IPlayService { try { cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, hookEvent, eventResult -> { - inviteInfo.setStatus(InviteSessionStatus.ok); - ResponseEvent responseEvent = (ResponseEvent) eventResult.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)) { - if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { - String substring = contentString.substring(0, contentString.indexOf("y=")); - 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("96")) { - port = media.getMediaPort(); - break; - } - } - logger.info("[录像回放-TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); - JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream()); - logger.info("[录像回放-TCP主动连接对方] 结果: {}", jsonObject); - } catch (SdpException e) { - logger.error("[录像回放-TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e); - dynamicTask.stop(playBackTimeOutTaskKey); - 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.PLAY, device.getDeviceId(), channelId, null, - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); - } - } - return; - } - logger.info("[录像回放] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); - if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { - 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); - String stream = String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase(); - hookSubscribe.getContent().put("stream", stream); - inviteStreamService.updateInviteInfoForStream(inviteInfo, stream); - subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> { - logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + hookParam); - dynamicTask.stop(playBackTimeOutTaskKey); - // hook响应 - hookEvent.response(mediaServerItemInUse, hookParam); - }); - } - // 更新ssrc - Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse); - if (!result) { - try { - logger.warn("[录像回放] 更新ssrc失败,停止录像回放 {}/{}", device.getDeviceId(), channelId); - cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null, null); - } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) { - logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage()); - - } - - dynamicTask.stop(playBackTimeOutTaskKey); - // 释放ssrc - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); - - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); - - callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), - "下级自定义了ssrc,重新设置收流信息失败", null); - - }else { - if (ssrcInfo.getStream()!= null && !ssrcInfo.getStream().equals(inviteInfo.getStream())) { - inviteStreamService.removeInviteInfo(inviteInfo); - } - - ssrcInfo.setSsrc(ssrcInResponse); - inviteInfo.setSsrcInfo(ssrcInfo); - inviteInfo.setStream(ssrcInfo.getStream()); - } - }else { - logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正"); - } - } - inviteStreamService.updateInviteInfo(inviteInfo); + // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题 + InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId, + playBackTimeOutTaskKey, callback, inviteInfo, InviteSessionType.PLAYBACK); }, errorEvent); } catch (InvalidArgumentException | SipException | ParseException e) { - logger.error("[命令发送失败] 回放: {}", e.getMessage()); + logger.error("[命令发送失败] 录像回放: {}", e.getMessage()); SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(); eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent; @@ -741,6 +765,95 @@ public class PlayServiceImpl implements IPlayService { } + private void InviteOKHandler(SipSubscribe.EventResult eventResult, SSRCInfo ssrcInfo, MediaServerItem mediaServerItem, + Device device, String channelId, String timeOutTaskKey, ErrorCallback callback, + InviteInfo inviteInfo, InviteSessionType inviteSessionType){ + inviteInfo.setStatus(InviteSessionStatus.ok); + ResponseEvent responseEvent = (ResponseEvent) eventResult.event; + String contentString = new String(responseEvent.getResponse().getRawContent()); + String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString); + // 兼容回复的消息中缺少ssrc(y字段)的情况 + if (ssrcInResponse == null) { + ssrcInResponse = ssrcInfo.getSsrc(); + } + if (ssrcInfo.getSsrc().equals(ssrcInResponse)) { + // ssrc 一致 + if (mediaServerItem.isRtpEnable()) { + // 多端口 + if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { + tcpActiveHandler(device, channelId, contentString, mediaServerItem, timeOutTaskKey, ssrcInfo, callback); + } + }else { + // 单端口 + if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { + logger.warn("[Invite 200OK] 单端口收流模式不支持tcp主动模式收流"); + } + + } + }else { + logger.info("[Invite 200OK] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); + // ssrc 不一致 + if (mediaServerItem.isRtpEnable()) { + // 多端口 + if (device.isSsrcCheck()) { + // 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失败,停止点播 {}/{}", device.getDeviceId(), channelId); + cmder.streamByeCmd(device, 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(device.getDeviceId(), channelId, ssrcInfo.getStream()); + + callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), + "下级自定义了ssrc,重新设置收流信息失败", null); + inviteStreamService.call(inviteSessionType, device.getDeviceId(), channelId, null, + InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), + "下级自定义了ssrc,重新设置收流信息失败", null); + + }else { + ssrcInfo.setSsrc(ssrcInResponse); + inviteInfo.setSsrcInfo(ssrcInfo); + inviteInfo.setStream(ssrcInfo.getStream()); + if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { + if (mediaServerItem.isRtpEnable()) { + tcpActiveHandler(device, channelId, contentString, mediaServerItem, 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(device.getDeviceId(), channelId, ssrcTransaction.getCallId(), + inviteInfo.getStream(), ssrcInResponse, mediaServerItem.getId(), (SIPResponse) responseEvent.getResponse(), inviteSessionType); + } + } + } + } + + + @Override public void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback callback) { @@ -748,14 +861,15 @@ public class PlayServiceImpl implements IPlayService { if (device == null) { return; } - MediaServerItem newMediaServerItem = getNewMediaServerItemHasAssist(device); + MediaServerItem newMediaServerItem = this.getNewMediaServerItem(device); if (newMediaServerItem == null) { callback.run(InviteErrorCode.ERROR_FOR_ASSIST_NOT_READY.getCode(), InviteErrorCode.ERROR_FOR_ASSIST_NOT_READY.getMsg(), null); 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); } @@ -823,108 +937,31 @@ public class PlayServiceImpl implements IPlayService { try { cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, hookEvent, errorEvent, eventResult ->{ - inviteInfo.setStatus(InviteSessionStatus.ok); - ResponseEvent responseEvent = (ResponseEvent) eventResult.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)) { - if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) { - String substring = contentString.substring(0, contentString.indexOf("y=")); - 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(); + // 处理收到200ok后的TCP主动连接以及SSRC不一致的问题 + InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channelId, + downLoadTimeOutTaskKey, callback, inviteInfo, InviteSessionType.DOWNLOAD); - Vector mediaFormats = media.getMediaFormats(false); - if (mediaFormats.contains("96")) { - port = media.getMediaPort(); - break; - } - } - logger.info("[录像下载-TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); - JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream()); - logger.info("[录像下载-TCP主动连接对方] 结果: {}", jsonObject); - } catch (SdpException e) { - logger.error("[录像下载-TCP主动连接对方] deviceId: {}, channelId: {}, 解析200OK的SDP信息失败", device.getDeviceId(), channelId, e); - dynamicTask.stop(downLoadTimeOutTaskKey); - mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); - // 释放ssrc - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); + // 注册录像回调事件,录像下载结束后写入下载地址 + ZlmHttpHookSubscribe.Event hookEventForRecord = (mediaServerItemInuse, hookParam) -> { + logger.info("[录像下载] 收到录像写入磁盘消息: , {}/{}-{}", + inviteInfo.getDeviceId(), inviteInfo.getChannelId(), ssrcInfo.getStream()); + logger.info("[录像下载] 收到录像写入磁盘消息内容: " + hookParam); + OnRecordMp4HookParam recordMp4HookParam = (OnRecordMp4HookParam)hookParam; + String filePath = recordMp4HookParam.getFile_path(); + DownloadFileInfo downloadFileInfo = CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath); + InviteInfo inviteInfoForNew = inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId() + , inviteInfo.getChannelId(), inviteInfo.getStream()); + inviteInfoForNew.getStreamInfo().setDownLoadFilePath(downloadFileInfo); + inviteStreamService.updateInviteInfo(inviteInfoForNew); + }; + HookSubscribeForRecordMp4 hookSubscribe = HookSubscribeFactory.on_record_mp4( + mediaServerItem.getId(), "rtp", ssrcInfo.getStream()); - 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.PLAY, device.getDeviceId(), channelId, null, - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getCode(), - InviteErrorCode.ERROR_FOR_SDP_PARSING_EXCEPTIONS.getMsg(), null); - } - } - return; - } - logger.info("[录像下载] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); - if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { - 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); - String stream = String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase(); - hookSubscribe.getContent().put("stream", stream); - inviteStreamService.updateInviteInfoForStream(inviteInfo, stream); - subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam) -> { - logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + hookParam); - dynamicTask.stop(downLoadTimeOutTaskKey); - hookEvent.response(mediaServerItemInUse, hookParam); - }); - } - - // 更新ssrc - Boolean result = mediaServerService.updateRtpServerSSRC(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse); - if (!result) { - try { - logger.warn("[录像下载] 更新ssrc失败,停止录像回放 {}/{}", device.getDeviceId(), channelId); - cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null, null); - } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) { - logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage()); - } - - dynamicTask.stop(downLoadTimeOutTaskKey); - // 释放ssrc - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); - - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); - - callback.run(InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), - "下级自定义了ssrc,重新设置收流信息失败", null); - - }else { - if (ssrcInfo.getStream()!= null && !ssrcInfo.getStream().equals(inviteInfo.getStream())) { - inviteStreamService.removeInviteInfo(inviteInfo); - } - ssrcInfo.setSsrc(ssrcInResponse); - inviteInfo.setSsrcInfo(ssrcInfo); - inviteInfo.setStream(ssrcInfo.getStream()); - } - }else { - logger.info("[录像下载] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正"); - } - } - inviteStreamService.updateInviteInfo(inviteInfo); + // 设置过期时间,下载失败时自动处理订阅数据 +// long difference = DateUtil.getDifference(startTime, endTime)/1000; +// Instant expiresInstant = Instant.now().plusSeconds(TimeUnit.MINUTES.toSeconds(difference * 2)); +// hookSubscribe.setExpires(expiresInstant); + subscribe.addSubscribe(hookSubscribe, hookEventForRecord); }); } catch (InvalidArgumentException | SipException | ParseException e) { logger.error("[命令发送失败] 录像下载: {}", e.getMessage()); @@ -940,47 +977,71 @@ public class PlayServiceImpl implements IPlayService { @Override public StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream) { InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, stream); + if (inviteInfo == null || inviteInfo.getStreamInfo() == null) { + logger.warn("[获取下载进度] 未查询到录像下载的信息"); + return null; + } - if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { - if (inviteInfo.getStreamInfo().getProgress() == 1) { - return inviteInfo.getStreamInfo(); - } - - // 获取当前已下载时长 - String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId(); - MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); - if (mediaServerItem == null) { - logger.warn("查询录像信息时发现节点已离线"); - return null; - } - if (mediaServerItem.getRecordAssistPort() > 0) { - JSONObject jsonObject = assistRESTfulUtils.fileDuration(mediaServerItem, inviteInfo.getStreamInfo().getApp(), inviteInfo.getStreamInfo().getStream(), null); - if (jsonObject == null) { - throw new ControllerException(ErrorCode.ERROR100.getCode(), "连接Assist服务失败"); - } - if (jsonObject.getInteger("code") == 0) { - long duration = jsonObject.getLong("data"); - - if (duration == 0) { - inviteInfo.getStreamInfo().setProgress(0); - } else { - String startTime = inviteInfo.getStreamInfo().getStartTime(); - String endTime = inviteInfo.getStreamInfo().getEndTime(); - long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); - long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); - - BigDecimal currentCount = new BigDecimal(duration / 1000); - BigDecimal totalCount = new BigDecimal(end - start); - BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP); - double process = divide.doubleValue(); - inviteInfo.getStreamInfo().setProgress(process); - } - inviteStreamService.updateInviteInfo(inviteInfo); - } - } + if (inviteInfo.getStreamInfo().getProgress() == 1) { return inviteInfo.getStreamInfo(); } - return null; + + // 获取当前已下载时长 + String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId(); + MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); + if (mediaServerItem == null) { + logger.warn("[获取下载进度] 查询录像信息时发现节点不存在"); + return null; + } + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(deviceId, channelId, null, stream); + + if (ssrcTransaction == null) { + logger.warn("[获取下载进度] 下载已结束"); + return null; + } + + JSONObject mediaListJson= zlmresTfulUtils.getMediaList(mediaServerItem, "rtp", stream); + if (mediaListJson == null) { + logger.warn("[获取下载进度] 从zlm查询进度失败"); + return null; + } + if (mediaListJson.getInteger("code") != 0) { + logger.warn("[获取下载进度] 从zlm查询进度出现错误: {}", mediaListJson.getString("msg")); + return null; + } + JSONArray data = mediaListJson.getJSONArray("data"); + if (data == null) { + logger.warn("[获取下载进度] 从zlm查询进度时未返回数据"); + return null; + } + JSONObject mediaJSON = data.getJSONObject(0); + JSONArray tracks = mediaJSON.getJSONArray("tracks"); + if (tracks.isEmpty()) { + logger.warn("[获取下载进度] 从zlm查询进度时未返回数据"); + return null; + } + JSONObject jsonObject = tracks.getJSONObject(0); + long duration = jsonObject.getLongValue("duration"); + if (duration == 0) { + inviteInfo.getStreamInfo().setProgress(0); + } else { + String startTime = inviteInfo.getStreamInfo().getStartTime(); + String endTime = inviteInfo.getStreamInfo().getEndTime(); + // 此时start和end单位是秒 + long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime); + long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime); + + BigDecimal currentCount = new BigDecimal(duration); + BigDecimal totalCount = new BigDecimal((end - start) * 1000); + BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP); + double process = divide.doubleValue(); + if (process > 0.999) { + process = 1.0; + } + inviteInfo.getStreamInfo().setProgress(process); + } + inviteStreamService.updateInviteInfo(inviteInfo); + return inviteInfo.getStreamInfo(); } private StreamInfo onPublishHandlerForDownload(MediaServerItem mediaServerItemInuse, HookParam hookParam, String deviceId, String channelId, String startTime, String endTime) { @@ -1039,7 +1100,7 @@ public class PlayServiceImpl implements IPlayService { cmder.streamByeCmd(device, ssrcTransaction.getChannelId(), ssrcTransaction.getStream(), null); } catch (InvalidArgumentException | ParseException | SipException | - SsrcTransactionNotFoundException e) { + SsrcTransactionNotFoundException e) { logger.error("[zlm离线]为正在使用此zlm的设备, 发送BYE失败 {}", e.getMessage()); } } @@ -1047,6 +1108,142 @@ public class PlayServiceImpl implements IPlayService { } } + @Override + public AudioBroadcastResult audioBroadcast(Device device, String channelId, Boolean broadcastMode) { + // TODO 必须多端口模式才支持语音喊话鹤语音对讲 + if (device == null || channelId == null) { + return null; + } + logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId); + DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId); + if (deviceChannel == null) { + logger.warn("开启语音广播的时候未找到通道: {}", channelId); + return null; + } + MediaServerItem mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null); + if (broadcastMode == null) { + broadcastMode = true; + } + String app = broadcastMode?"broadcast":"talk"; + String stream = device.getDeviceId() + "_" + channelId; + AudioBroadcastResult audioBroadcastResult = new AudioBroadcastResult(); + audioBroadcastResult.setApp(app); + audioBroadcastResult.setStream(stream); + audioBroadcastResult.setStreamInfo(new StreamContent(mediaService.getStreamInfoByAppAndStream(mediaServerItem, app, stream, null, null, null, false))); + audioBroadcastResult.setCodec("G.711"); + return audioBroadcastResult; + } + + @Override + public boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException { + if (device == null || channelId == null) { + return false; + } + logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId); + DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId); + if (deviceChannel == null) { + logger.warn("开启语音广播的时候未找到通道: {}", channelId); + event.call("开启语音广播的时候未找到通道"); + return false; + } + // 查询通道使用状态 + if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) { + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null); + if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) { + // 查询流是否存在,不存在则认为是异常状态 + Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStream()); + if (streamReady) { + logger.warn("语音广播已经开启: {}", channelId); + event.call("语音广播已经开启"); + return false; + } else { + stopAudioBroadcast(device.getDeviceId(), channelId); + } + } + } +// SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null); +// if (sendRtpItem != null) { +// MediaServerItem mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId()); +// Boolean streamReady = zlmServerFactory.isStreamReady(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream()); +// if (streamReady) { +// logger.warn("[语音对讲] 进行中: {}", channelId); +// event.call("语音对讲进行中"); +// return false; +// } else { +// stopTalk(device, channelId); +// } +// } + + // 发送通知 + cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> { + // 发送成功 + AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, mediaServerItem, app, stream, event, AudioBroadcastCatchStatus.Ready, isFromPlatform); + audioBroadcastManager.update(audioBroadcastCatch); + }, eventResultForError -> { + // 发送失败 + logger.error("语音广播发送失败: {}:{}", channelId, eventResultForError.msg); + event.call("语音广播发送失败"); + stopAudioBroadcast(device.getDeviceId(), channelId); + }); + return true; + } + + @Override + public boolean audioBroadcastInUse(Device device, String channelId) { + if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) { + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null); + if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) { + // 查询流是否存在,不存在则认为是异常状态 + MediaServerItem mediaServerServiceOne = mediaServerService.getOne(sendRtpItem.getMediaServerId()); + Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerServiceOne, sendRtpItem.getApp(), sendRtpItem.getStream()); + if (streamReady) { + logger.warn("语音广播通道使用中: {}", channelId); + return true; + } + } + } + return false; + } + + + @Override + public void stopAudioBroadcast(String deviceId, String channelId) { + logger.info("[停止对讲] 设备:{}, 通道:{}", deviceId, channelId); + List 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 param = new HashMap<>(); + param.put("vhost", "__defaultVhost__"); + param.put("app", sendRtpItem.getApp()); + param.put("stream", sendRtpItem.getStream()); + zlmresTfulUtils.stopSendRtp(mediaInfo, param); + try { + cmder.streamByeCmdForDeviceInvite(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null); + } catch (InvalidArgumentException | ParseException | SipException | + SsrcTransactionNotFoundException e) { + logger.error("[消息发送失败] 发送语音喊话BYE失败"); + } + } + + audioBroadcastManager.del(deviceId, channelId); + } + } + } + + @Override public void zlmServerOnline(String mediaServerId) { // TODO 查找之前的点播,流如果不存在则给下级发送bye @@ -1114,7 +1311,12 @@ public class PlayServiceImpl implements IPlayService { throw new ServiceException("mediaServer不存在"); } // zlm 暂停RTP超时检查 - JSONObject jsonObject = zlmresTfulUtils.pauseRtpCheck(mediaServerItem, streamId); + // 使用zlm中的流ID + String streamKey = inviteInfo.getStream(); + if (!mediaServerItem.isRtpEnable()) { + streamKey = Long.toHexString(Long.parseLong(inviteInfo.getSsrcInfo().getSsrc())).toUpperCase(); + } + JSONObject jsonObject = zlmresTfulUtils.pauseRtpCheck(mediaServerItem, streamKey); if (jsonObject == null || jsonObject.getInteger("code") != 0) { throw new ServiceException("暂停RTP接收失败"); } @@ -1137,7 +1339,12 @@ public class PlayServiceImpl implements IPlayService { throw new ServiceException("mediaServer不存在"); } // zlm 暂停RTP超时检查 - JSONObject jsonObject = zlmresTfulUtils.resumeRtpCheck(mediaServerItem, streamId); + // 使用zlm中的流ID + String streamKey = inviteInfo.getStream(); + if (!mediaServerItem.isRtpEnable()) { + streamKey = Long.toHexString(Long.parseLong(inviteInfo.getSsrcInfo().getSsrc())).toUpperCase(); + } + JSONObject jsonObject = zlmresTfulUtils.resumeRtpCheck(mediaServerItem, streamKey); if (jsonObject == null || jsonObject.getInteger("code") != 0) { throw new ServiceException("继续RTP接收失败"); } @@ -1145,6 +1352,199 @@ public class PlayServiceImpl implements IPlayService { cmder.playResumeCmd(device, inviteInfo.getStreamInfo()); } + @Override + public void startPushStream(SendRtpItem sendRtpItem, SIPResponse sipResponse, ParentPlatform platform, CallIdHeader callIdHeader) { + // 开始发流 + String is_Udp = sendRtpItem.isTcp() ? "0" : "1"; + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); + logger.info("[开始推流] rtp/{}, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStream(), + sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp()); + Map 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 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 param = new HashMap<>(); + param.put("vhost", "__defaultVhost__"); + param.put("app", sendRtpItem.getApp()); + param.put("stream", sendRtpItem.getStream()); + param.put("ssrc", sendRtpItem.getSsrc()); + zlmServerFactory.stopSendRtpStream(mediaServer, param); + } + + ssrcFactory.releaseSsrc(mediaServerId, sendRtpItem.getSsrc()); + + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, null, sendRtpItem.getStream()); + if (ssrcTransaction != null) { + try { + cmder.streamByeCmd(device, channelId, sendRtpItem.getStream(), null); + } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { + logger.info("[语音对讲] 停止消息发送失败,可能已经停止"); + } + } + redisCatchStorage.deleteSendRTPServer(device.getDeviceId(), channelId,null, null); + } + @Override public void getSnap(String deviceId, String channelId, String fileName, ErrorCallback errorCallback) { Device device = deviceService.getDevice(deviceId); @@ -1179,16 +1579,16 @@ public class PlayServiceImpl implements IPlayService { MediaServerItem newMediaServerItem = getNewMediaServerItem(device); play(newMediaServerItem, deviceId, channelId, null, (code, msg, data)->{ - if (code == InviteErrorCode.SUCCESS.getCode()) { - InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); - if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) { - getSnap(deviceId, channelId, fileName, errorCallback); - }else { - errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); - } - }else { - errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); - } + if (code == InviteErrorCode.SUCCESS.getCode()) { + InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); + if (inviteInfoForPlay != null && inviteInfoForPlay.getStreamInfo() != null) { + getSnap(deviceId, channelId, fileName, errorCallback); + }else { + errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); + } + }else { + errorCallback.run(InviteErrorCode.FAIL.getCode(), InviteErrorCode.FAIL.getMsg(), null); + } }); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/RoleServerImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/RoleServerImpl.java old mode 100644 new mode 100755 index d31bbcefb..f12c3cb8f --- a/src/main/java/com/genersoft/iot/vmp/service/impl/RoleServerImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/RoleServerImpl.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.service.impl; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.service.IRoleService; import com.genersoft.iot.vmp.storager.dao.RoleMapper; import com.genersoft.iot.vmp.storager.dao.dto.Role; @@ -9,6 +10,7 @@ import org.springframework.stereotype.Service; import java.util.List; @Service +@DS("master") public class RoleServerImpl implements IRoleService { @Autowired diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java old mode 100644 new mode 100755 index d699bbcd6..59c5acef2 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.service.impl; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.common.GeneralCallback; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.DynamicTask; @@ -10,7 +11,10 @@ import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; +import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory; import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; +import com.genersoft.iot.vmp.media.zlm.dto.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.StreamProxyItem; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; @@ -32,20 +36,25 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; /** * 视频代理业务 */ @Service +@DS("master") public class StreamProxyServiceImpl implements IStreamProxyService { private final static Logger logger = LoggerFactory.getLogger(StreamProxyServiceImpl.class); @@ -59,6 +68,9 @@ public class StreamProxyServiceImpl implements IStreamProxyService { @Autowired private ZLMRESTfulUtils zlmresTfulUtils; + @Autowired + private ZLMServerFactory zlmServerFactory; + @Autowired private StreamProxyMapper streamProxyMapper; @@ -77,12 +89,6 @@ public class StreamProxyServiceImpl implements IStreamProxyService { @Autowired private PlatformGbStreamMapper platformGbStreamMapper; - @Autowired - private EventPublisher eventPublisher; - - @Autowired - private ParentPlatformMapper parentPlatformMapper; - @Autowired private IGbStreamService gbStreamService; @@ -122,7 +128,13 @@ public class StreamProxyServiceImpl implements IStreamProxyService { } JSONArray dataArray = jsonObject.getJSONArray("data"); JSONObject mediaServerConfig = dataArray.getJSONObject(0); + if (ObjectUtils.isEmpty(param.getFfmpegCmdKey())) { + param.setFfmpegCmdKey("ffmpeg.cmd"); + } String ffmpegCmd = mediaServerConfig.getString(param.getFfmpegCmdKey()); + if (ffmpegCmd == null) { + throw new ControllerException(ErrorCode.ERROR100.getCode(), "ffmpeg拉流代理无法获取ffmpeg cmd"); + } String schema = getSchemaFromFFmpegCmd(ffmpegCmd); if (schema == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "ffmpeg拉流代理无法从ffmpeg cmd中获取到输出格式"); @@ -143,7 +155,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService { dstUrl = String.format("%s://%s:%s/%s/%s", schemaForUri, "127.0.0.1", port, param.getApp(), param.getStream()); }else { - dstUrl = String.format("rtmp://%s:%s/%s/%s", "127.0.0.1", mediaInfo.getRtmpPort(), param.getApp(), + dstUrl = String.format("rtsp://%s:%s/%s/%s", "127.0.0.1", mediaInfo.getRtspPort(), param.getApp(), param.getStream()); } param.setDstUrl(dstUrl); @@ -160,15 +172,14 @@ public class StreamProxyServiceImpl implements IStreamProxyService { callback.run(ErrorCode.ERROR100.getCode(), "保存失败", null); return; } - + HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed(param.getApp(), param.getStream(), true, "rtsp", mediaInfo.getId()); + hookSubscribe.addSubscribe(hookSubscribeForStreamChange, (mediaServerItem, response) -> { + StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream( + mediaInfo, param.getApp(), param.getStream(), null, null); + callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); + }); if (param.isEnable()) { String talkKey = UUID.randomUUID().toString(); - dynamicTask.startCron(talkKey, ()->{ - StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStreamWithCheck(param.getApp(), param.getStream(), mediaInfo.getId(), false); - if (streamInfo != null) { - callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo); - } - }, 1000); String delayTalkKey = UUID.randomUUID().toString(); dynamicTask.startDelay(delayTalkKey, ()->{ StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStreamWithCheck(param.getApp(), param.getStream(), mediaInfo.getId(), false); @@ -178,9 +189,10 @@ public class StreamProxyServiceImpl implements IStreamProxyService { dynamicTask.stop(talkKey); callback.run(ErrorCode.ERROR100.getCode(), "超时", null); } - }, 5000); + }, 7000); JSONObject jsonObject = addStreamProxyToZlm(param); if (jsonObject != null && jsonObject.getInteger("code") == 0) { + hookSubscribe.removeSubscribe(hookSubscribeForStreamChange); dynamicTask.stop(talkKey); StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream( mediaInfo, param.getApp(), param.getStream(), null, null); @@ -310,13 +322,32 @@ public class StreamProxyServiceImpl implements IStreamProxyService { if (mediaServerItem == null) { return null; } - if ("default".equals(param.getType())){ - result = zlmresTfulUtils.addStreamProxy(mediaServerItem, param.getApp(), param.getStream(), param.getUrl().trim(), - param.isEnableAudio(), param.isEnableMp4(), param.getRtpType()); - }else if ("ffmpeg".equals(param.getType())) { + if (zlmServerFactory.isStreamReady(mediaServerItem, param.getApp(), param.getStream())) { + zlmresTfulUtils.closeStreams(mediaServerItem, param.getApp(), param.getStream()); + } + if ("ffmpeg".equalsIgnoreCase(param.getType())){ result = zlmresTfulUtils.addFFmpegSource(mediaServerItem, param.getSrcUrl().trim(), param.getDstUrl(), param.getTimeoutMs() + "", param.isEnableAudio(), param.isEnableMp4(), param.getFfmpegCmdKey()); + }else { + result = zlmresTfulUtils.addStreamProxy(mediaServerItem, param.getApp(), param.getStream(), param.getUrl().trim(), + param.isEnableAudio(), param.isEnableMp4(), param.getRtpType()); + } + System.out.println("addStreamProxyToZlm===="); + System.out.println(result); + if (result != null && result.getInteger("code") == 0) { + JSONObject data = result.getJSONObject("data"); + if (data == null) { + logger.warn("[获取拉流代理的结果数据Data] 失败: {}", result ); + return result; + } + String key = data.getString("key"); + if (key == null) { + logger.warn("[获取拉流代理的结果数据Data中的KEY] 失败: {}", result ); + return result; + } + param.setStreamKey(key); + streamProxyMapper.update(param); } return result; } @@ -327,7 +358,12 @@ public class StreamProxyServiceImpl implements IStreamProxyService { return null; } MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); - JSONObject result = zlmresTfulUtils.closeStreams(mediaServerItem, param.getApp(), param.getStream()); + JSONObject result = null; + if ("ffmpeg".equalsIgnoreCase(param.getType())){ + result = zlmresTfulUtils.delFFmpegSource(mediaServerItem, param.getStreamKey()); + }else { + result = zlmresTfulUtils.delStreamProxy(mediaServerItem, param.getStreamKey()); + } return result; } @@ -341,18 +377,19 @@ public class StreamProxyServiceImpl implements IStreamProxyService { StreamProxyItem streamProxyItem = videoManagerStorager.queryStreamProxy(app, stream); if (streamProxyItem != null) { gbStreamService.sendCatalogMsg(streamProxyItem, CatalogEvent.DEL); + + // 如果关联了国标那么移除关联 + platformGbStreamMapper.delByAppAndStream(app, stream); + gbStreamMapper.del(app, stream); videoManagerStorager.deleteStreamProxy(app, stream); + redisCatchStorage.removeStream(streamProxyItem.getMediaServerId(), "PULL", app, stream); JSONObject jsonObject = removeStreamProxyFromZlm(streamProxyItem); if (jsonObject != null && jsonObject.getInteger("code") == 0) { - // 如果关联了国标那么移除关联 - gbStreamMapper.del(app, stream); - platformGbStreamMapper.delByAppAndStream(app, stream); - // TODO 如果关联的推流, 那么状态设置为离线 + logger.info("[移除代理]: 代理: {}/{}, 从zlm移除成功", app, stream); + }else { + logger.info("[移除代理]: 代理: {}/{}, 从zlm移除失败", app, stream); } - redisCatchStorage.removeStream(streamProxyItem.getMediaServerId(), "PULL", app, stream); } - - } @Override @@ -372,6 +409,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService { logger.info("启用代理失败: {}/{}->{}({})", app, stream, jsonObject.getString("msg"), streamProxy.getSrcUrl() == null? streamProxy.getUrl():streamProxy.getSrcUrl()); } + } else if (streamProxy != null && streamProxy.isEnable()) { + return true ; } return result; } @@ -423,7 +462,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService { streamProxyMapper.deleteAutoRemoveItemByMediaServerId(mediaServerId); // 移除拉流代理生成的流信息 -// syncPullStream(mediaServerId); + syncPullStream(mediaServerId); // 恢复流代理, 只查找这个这个流媒体 List streamProxyListForEnable = storager.getStreamProxyListForEnableInMediaServer( @@ -529,4 +568,43 @@ public class StreamProxyServiceImpl implements IStreamProxyService { return new ResourceBaseInfo(total, online); } + + + @Scheduled(cron = "* 0/10 * * * ?") + public void asyncCheckStreamProxyStatus() { + + List all = mediaServerService.getAllOnline(); + + if (CollectionUtils.isEmpty(all)){ + return; + } + + Map serverItemMap = all.stream().collect(Collectors.toMap(MediaServerItem::getId, Function.identity(), (m1, m2) -> m1)); + + List list = videoManagerStorager.getStreamProxyListForEnable(true); + + if (CollectionUtils.isEmpty(list)){ + return; + } + + for (StreamProxyItem streamProxyItem : list) { + + MediaServerItem mediaServerItem = serverItemMap.get(streamProxyItem.getMediaServerId()); + + // TODO 支持其他 schema + JSONObject mediaInfo = zlmresTfulUtils.isMediaOnline(mediaServerItem, streamProxyItem.getApp(), streamProxyItem.getStream(), "rtsp"); + + if (mediaInfo == null){ + streamProxyItem.setStatus(false); + } else { + if (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online")) { + streamProxyItem.setStatus(true); + } else { + streamProxyItem.setStatus(false); + } + } + + updateStreamProxy(streamProxyItem); + } + } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java old mode 100644 new mode 100755 index dcaab9e35..13c452c1f --- a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java @@ -4,6 +4,7 @@ import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.TypeReference; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.conf.MediaConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.*; @@ -36,6 +37,7 @@ import java.util.*; import java.util.stream.Collectors; @Service +@DS("master") public class StreamPushServiceImpl implements IStreamPushService { private final static Logger logger = LoggerFactory.getLogger(StreamPushServiceImpl.class); @@ -282,6 +284,8 @@ public class StreamPushServiceImpl implements IStreamPushService { redisCatchStorage.sendStreamChangeMsg(type, jsonObject); // 移除redis内流的信息 redisCatchStorage.removeStream(mediaServerItem.getId(), "PUSH", offlineOnStreamChangedHookParam.getApp(), offlineOnStreamChangedHookParam.getStream()); + // 冗余数据,自己系统中自用 + redisCatchStorage.removePushListItem(offlineOnStreamChangedHookParam.getApp(), offlineOnStreamChangedHookParam.getStream(), mediaServerItem.getId()); } } @@ -319,6 +323,9 @@ public class StreamPushServiceImpl implements IStreamPushService { jsonObject.put("register", false); jsonObject.put("mediaServerId", mediaServerId); redisCatchStorage.sendStreamChangeMsg(type, jsonObject); + + // 冗余数据,自己系统中自用 + redisCatchStorage.removePushListItem(onStreamChangedHookParam.getApp(), onStreamChangedHookParam.getStream(), mediaServerId); } } } @@ -440,7 +447,7 @@ public class StreamPushServiceImpl implements IStreamPushService { } } - if (streamPushItemListFroPlatform.size() > 0) { + if (!streamPushItemListFroPlatform.isEmpty()) { platformGbStreamMapper.batchAdd(streamPushItemListFroPlatform); // 发送通知 for (String platformId : platformForEvent.keySet()) { @@ -506,6 +513,9 @@ public class StreamPushServiceImpl implements IStreamPushService { stream.setUpdateTime(DateUtil.getNow()); stream.setCreateTime(DateUtil.getNow()); stream.setServerId(userSetting.getServerId()); + stream.setMediaServerId(mediaConfig.getId()); + stream.setSelf(true); + stream.setPushIng(true); // 放在事务内执行 boolean result = false; diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushUploadFileHandler.java b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushUploadFileHandler.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java old mode 100644 new mode 100755 index 30d9c1304..400b19bc6 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.service.impl; +import com.baomidou.dynamic.datasource.annotation.DS; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.dao.UserMapper; import com.genersoft.iot.vmp.storager.dao.dto.User; @@ -12,6 +13,7 @@ import org.springframework.util.DigestUtils; import java.util.List; @Service +@DS("master") public class UserServiceImpl implements IUserService { @Autowired diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisAlarmMsgListener.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisCloseStreamMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisCloseStreamMsgListener.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java old mode 100644 new mode 100755 index 2b0f366bd..eb261e345 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java @@ -13,6 +13,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.*; +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.utils.redis.RedisUtil; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import org.slf4j.Logger; @@ -77,6 +78,9 @@ public class RedisGbPlayMsgListener implements MessageListener { @Autowired private IMediaServerService mediaServerService; + @Autowired + private IRedisCatchStorage redisCatchStorage; + @Autowired private DynamicTask dynamicTask; @@ -113,8 +117,8 @@ public class RedisGbPlayMsgListener implements MessageListener { while (!taskQueue.isEmpty()) { Message msg = taskQueue.poll(); try { - JSONObject msgJSON = JSON.parseObject(msg.getBody(), JSONObject.class); - WvpRedisMsg wvpRedisMsg = JSON.to(WvpRedisMsg.class, msgJSON); + WvpRedisMsg wvpRedisMsg = JSON.parseObject(msg.getBody(), WvpRedisMsg.class); + logger.info("[收到REDIS通知] 消息: {}", JSON.toJSONString(wvpRedisMsg)); if (!userSetting.getServerId().equals(wvpRedisMsg.getToId())) { continue; } @@ -123,7 +127,7 @@ public class RedisGbPlayMsgListener implements MessageListener { switch (wvpRedisMsg.getCmd()){ case WvpRedisMsgCmd.GET_SEND_ITEM: - RequestSendItemMsg content = JSON.to(RequestSendItemMsg.class, wvpRedisMsg.getContent()); + RequestSendItemMsg content = JSON.parseObject(wvpRedisMsg.getContent(), RequestSendItemMsg.class); requestSendItemMsgHand(content, wvpRedisMsg.getFromId(), wvpRedisMsg.getSerial()); break; case WvpRedisMsgCmd.REQUEST_PUSH_STREAM: @@ -242,7 +246,7 @@ public class RedisGbPlayMsgListener implements MessageListener { result.setData(content); WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), toId, - WvpRedisMsgCmd.REQUEST_PUSH_STREAM, serial, result); + WvpRedisMsgCmd.REQUEST_PUSH_STREAM, serial, JSON.toJSONString(result)); JSONObject jsonObject = (JSONObject)JSON.toJSON(response); redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); } @@ -260,7 +264,7 @@ public class RedisGbPlayMsgListener implements MessageListener { result.setMsg("流媒体不存在"); WvpRedisMsg response = WvpRedisMsg.getResponseInstance(userSetting.getServerId(), toId, - WvpRedisMsgCmd.GET_SEND_ITEM, serial, result); + WvpRedisMsgCmd.GET_SEND_ITEM, serial, JSON.toJSONString(result)); JSONObject jsonObject = (JSONObject)JSON.toJSON(response); redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); @@ -283,7 +287,7 @@ public class RedisGbPlayMsgListener implements MessageListener { WVPResult result = new WVPResult<>(); result.setCode(ERROR_CODE_TIMEOUT); WvpRedisMsg response = WvpRedisMsg.getResponseInstance( - userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, result + userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, JSON.toJSONString(result) ); JSONObject jsonObject = (JSONObject)JSON.toJSON(response); redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); @@ -322,9 +326,10 @@ public class RedisGbPlayMsgListener implements MessageListener { responseSendItemMsg.setSendRtpItem(sendRtpItem); responseSendItemMsg.setMediaServerItem(mediaServerItem); result.setData(responseSendItemMsg); + redisCatchStorage.updateSendRTPSever(sendRtpItem); WvpRedisMsg response = WvpRedisMsg.getResponseInstance( - userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, result + userSetting.getServerId(), toId, WvpRedisMsgCmd.GET_SEND_ITEM, serial, JSON.toJSONString(result) ); JSONObject jsonObject = (JSONObject)JSON.toJSON(response); redisTemplate.convertAndSend(WVP_PUSH_STREAM_KEY, jsonObject); @@ -350,7 +355,7 @@ public class RedisGbPlayMsgListener implements MessageListener { requestSendItemMsg.setServerId(serverId); String key = UUID.randomUUID().toString(); WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId, WvpRedisMsgCmd.GET_SEND_ITEM, - key, requestSendItemMsg); + key, JSON.toJSONString(requestSendItemMsg)); JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg); logger.info("[请求推流SendItem] {}: {}", serverId, jsonObject); @@ -375,7 +380,7 @@ public class RedisGbPlayMsgListener implements MessageListener { public void sendMsgForStartSendRtpStream(String serverId, RequestPushStreamMsg param, PlayMsgCallbackForStartSendRtpStream callback) { String key = UUID.randomUUID().toString(); WvpRedisMsg redisMsg = WvpRedisMsg.getRequestInstance(userSetting.getServerId(), serverId, - WvpRedisMsgCmd.REQUEST_PUSH_STREAM, key, param); + WvpRedisMsgCmd.REQUEST_PUSH_STREAM, key, JSON.toJSONString(param)); JSONObject jsonObject = (JSONObject)JSON.toJSON(redisMsg); logger.info("[REDIS 请求其他平台推流] {}: {}", serverId, jsonObject); diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGpsMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGpsMsgListener.java old mode 100644 new mode 100755 index 4e81a591d..cea39b4c0 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGpsMsgListener.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGpsMsgListener.java @@ -50,11 +50,12 @@ public class RedisGpsMsgListener implements MessageListener { Message msg = taskQueue.poll(); try { GPSMsgInfo gpsMsgInfo = JSON.parseObject(msg.getBody(), GPSMsgInfo.class); + logger.info("[REDIS的位置变化通知], {}", JSON.toJSONString(gpsMsgInfo)); // 只是放入redis缓存起来 redisCatchStorage.updateGpsMsgInfo(gpsMsgInfo); }catch (Exception e) { - logger.warn("[REDIS的ALARM通知] 发现未处理的异常, \r\n{}", JSON.toJSONString(message)); - logger.error("[REDIS的ALARM通知] 异常内容: ", e); + logger.warn("[REDIS的位置变化通知] 发现未处理的异常, \r\n{}", JSON.toJSONString(message)); + logger.error("[REDIS的位置变化通知] 异常内容: ", e); } } }); diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamCloseResponseListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamCloseResponseListener.java old mode 100644 new mode 100755 index ad00baf7e..1d7c2fd50 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamCloseResponseListener.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamCloseResponseListener.java @@ -73,14 +73,22 @@ public class RedisPushStreamCloseResponseListener implements MessageListener { MessageForPushChannel pushChannel = JSON.parseObject(message.getBody(), MessageForPushChannel.class); StreamPushItem push = streamPushService.getPush(pushChannel.getApp(), pushChannel.getStream()); if (push != null) { - if (redisCatchStorage.isChannelSendingRTP(push.getGbId())) { - List sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId( - push.getGbId()); - if (sendRtpItems.size() > 0) { - for (SendRtpItem sendRtpItem : sendRtpItems) { - ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); + List sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId( + push.getGbId()); + if (!sendRtpItems.isEmpty()) { + for (SendRtpItem sendRtpItem : sendRtpItems) { + ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); + if (parentPlatform != null) { + redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStream()); + try { + commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); + } + } + if (push.isSelf()) { // 停止向上级推流 - String streamId = sendRtpItem.getStreamId(); + String streamId = sendRtpItem.getStream(); Map param = new HashMap<>(); param.put("vhost","__defaultVhost__"); param.put("app",sendRtpItem.getApp()); @@ -88,17 +96,11 @@ public class RedisPushStreamCloseResponseListener implements MessageListener { param.put("ssrc",sendRtpItem.getSsrc()); logger.info("[REDIS消息-推流结束] 停止向上级推流:{}", streamId); MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); - redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStreamId()); + redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStream()); zlmServerFactory.stopSendRtpStream(mediaInfo, param); - - try { - commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); - } if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) { MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, - sendRtpItem.getApp(), sendRtpItem.getStreamId(), sendRtpItem.getChannelId(), + sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(), sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId()); messageForPushChannel.setPlatFormIndex(parentPlatform.getId()); redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel); diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamResponseListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamResponseListener.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusListMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusListMsgListener.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusMsgListener.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisStreamMsgListener.java b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisStreamMsgListener.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java old mode 100644 new mode 100755 index a97e454c4..b663c5c66 --- a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java @@ -208,4 +208,8 @@ public interface IRedisCatchStorage { void sendPlatformStartPlayMsg(MessageForPushChannel messageForPushChannel); void sendPlatformStopPlayMsg(MessageForPushChannel messageForPushChannel); + + void addPushListItem(String app, String stream, OnStreamChangedHookParam param); + + void removePushListItem(String app, String stream, String mediaServerId); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorage.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java new file mode 100644 index 000000000..08f67ba94 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java @@ -0,0 +1,122 @@ +package com.genersoft.iot.vmp.storager.dao; + +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.service.bean.CloudRecordItem; +import org.apache.ibatis.annotations.*; + +import java.util.List; + +@Mapper +public interface CloudRecordServiceMapper { + + @Insert(" ") + int add(CloudRecordItem cloudRecordItem); + + @Select(" ") + List getList(@Param("query") String query, @Param("app") String app, @Param("stream") String stream, + @Param("startTimeStamp")Long startTimeStamp, @Param("endTimeStamp")Long endTimeStamp, + @Param("callId")String callId, List mediaServerItemList); + + + @Select(" ") + List queryRecordFilePathList(@Param("app") String app, @Param("stream") String stream, + @Param("startTimeStamp")Long startTimeStamp, @Param("endTimeStamp")Long endTimeStamp, + @Param("callId")String callId, List mediaServerItemList); + + @Update(" ") + int updateCollectList(@Param("collect") boolean collect, List cloudRecordItemList); + + @Delete(" ") + void deleteByFileList(List filePathList, @Param("mediaServerId") String mediaServerId); + + + @Select(" ") + List queryRecordListForDelete(@Param("endTimeStamp")Long endTimeStamp, String mediaServerId); + + @Update(" ") + int changeCollectById(@Param("collect") boolean collect, @Param("recordId") Integer recordId); + + @Delete(" ") + int deleteList(List cloudRecordItemIdList); + + @Select(" ") + List getListByCallId(@Param("callId") String callId); + + @Select(" ") + CloudRecordItem queryOne(@Param("id") Integer id); +} diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceAlarmMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceAlarmMapper.java old mode 100644 new mode 100755 index 28a2e913f..589e6f2ac --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceAlarmMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceAlarmMapper.java @@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.gb28181.bean.DeviceAlarm; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Repository; @@ -32,8 +33,8 @@ public interface DeviceAlarmMapper { " AND alarm_time <= #{endTime} " + " ORDER BY alarm_time ASC " + " "}) - List query(String deviceId, String alarmPriority, String alarmMethod, - String alarmType, String startTime, String endTime); + List query(@Param("deviceId") String deviceId, @Param("alarmPriority") String alarmPriority, @Param("alarmMethod") String alarmMethod, + @Param("alarmType") String alarmType, @Param("startTime") String startTime, @Param("endTime") String endTime); @Delete(" " ) - int clearAlarmBeforeTime(Integer id, List deviceIdList, String time); + int clearAlarmBeforeTime(@Param("id") Integer id, @Param("deviceIdList") List deviceIdList, @Param("time") String time); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java old mode 100644 new mode 100755 index bdc45bf20..e823c6e00 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java @@ -20,17 +20,17 @@ public interface DeviceChannelMapper { @Insert("INSERT INTO wvp_device_channel (channel_id, device_id, name, manufacture, model, owner, civil_code, block, " + "address, parental, parent_id, safety_way, register_way, cert_num, certifiable, err_code, secrecy, " + "ip_address, port, password, ptz_type, status, stream_id, longitude, latitude, longitude_gcj02, latitude_gcj02, " + - "longitude_wgs84, latitude_wgs84, has_audio, create_time, update_time, business_group_id, gps_time) " + + "longitude_wgs84, latitude_wgs84, has_audio, create_time, update_time, business_group_id, gps_time, stream_identification) " + "VALUES (#{channelId}, #{deviceId}, #{name}, #{manufacture}, #{model}, #{owner}, #{civilCode}, #{block}," + "#{address}, #{parental}, #{parentId}, #{safetyWay}, #{registerWay}, #{certNum}, #{certifiable}, #{errCode}, #{secrecy}, " + "#{ipAddress}, #{port}, #{password}, #{PTZType}, #{status}, #{streamId}, #{longitude}, #{latitude}, #{longitudeGcj02}, " + - "#{latitudeGcj02}, #{longitudeWgs84}, #{latitudeWgs84}, #{hasAudio}, #{createTime}, #{updateTime}, #{businessGroupId}, #{gpsTime})") + "#{latitudeGcj02}, #{longitudeWgs84}, #{latitudeWgs84}, #{hasAudio}, #{createTime}, #{updateTime}, #{businessGroupId}, #{gpsTime}, #{streamIdentification})") int add(DeviceChannel channel); @Update(value = {" "}) int update(DeviceChannel channel); @Select(value = {" "}) - List queryChannels(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online, List channelIds); + List queryChannels(@Param("deviceId") String deviceId, @Param("parentChannelId") String parentChannelId, @Param("query") String query, @Param("hasSubChannel") Boolean hasSubChannel, @Param("online") Boolean online, @Param("channelIds") List channelIds); @Select(value = {" "}) - List queryChannelsWithDeviceInfo(String deviceId, String parentChannelId, String query, Boolean hasSubChannel, Boolean online, List channelIds); + List queryChannelsWithDeviceInfo(@Param("deviceId") String deviceId, @Param("parentChannelId") String parentChannelId, @Param("query") String query, @Param("hasSubChannel") Boolean hasSubChannel, @Param("online") Boolean online, @Param("channelIds") List channelIds); @Select(value = {" "}) - List queryChannelsByDeviceIdWithStartAndLimit(String deviceId,List channelIds, String parentChannelId, String query, - Boolean hasSubChannel, Boolean online, int start, int limit); + List queryChannelsByDeviceIdWithStartAndLimit(@Param("deviceId") String deviceId, @Param("channelIds") List channelIds, @Param("parentChannelId") String parentChannelId, @Param("query") String query, @Param("hasSubChannel") Boolean hasSubChannel, @Param("online") Boolean online, @Param("start") int start, @Param("limit") int limit); @Select("SELECT * FROM wvp_device_channel WHERE device_id=#{deviceId} AND channel_id=#{channelId}") - DeviceChannel queryChannel(String deviceId, String channelId); + DeviceChannel queryChannel(@Param("deviceId") String deviceId,@Param("channelId") String channelId); @Delete("DELETE FROM wvp_device_channel WHERE device_id=#{deviceId}") - int cleanChannelsByDeviceId(String deviceId); + int cleanChannelsByDeviceId(@Param("deviceId") String deviceId); @Delete("DELETE FROM wvp_device_channel WHERE device_id=#{deviceId} AND channel_id=#{channelId}") - int del(String deviceId, String channelId); + int del(@Param("deviceId") String deviceId, @Param("channelId") String channelId); @Update(value = {"UPDATE wvp_device_channel SET stream_id=null WHERE device_id=#{deviceId} AND channel_id=#{channelId}"}) - void stopPlay(String deviceId, String channelId); + void stopPlay(@Param("deviceId") String deviceId, @Param("channelId") String channelId); @Update(value = {"UPDATE wvp_device_channel SET stream_id=#{streamId} WHERE device_id=#{deviceId} AND channel_id=#{channelId}"}) - void startPlay(String deviceId, String channelId, String streamId); + void startPlay(@Param("deviceId") String deviceId, @Param("channelId") String channelId, @Param("streamId") String streamId); + @Select(value = {" "}) - List queryChannelListInAll(String query, Boolean online, Boolean hasSubChannel, String platformId, String catalogId); + List queryChannelListInAll(@Param("query") String query, @Param("online") Boolean online, @Param("hasSubChannel") Boolean hasSubChannel, @Param("platformId") String platformId, @Param("catalogId") String catalogId); @Select(value = {" ") - int batchAdd(List addChannels); + int batchAdd(@Param("addChannels") List addChannels); @Insert(""}) - int cleanChannelsNotInList(String deviceId, List channels); + int cleanChannelsNotInList(@Param("deviceId") String deviceId, @Param("channels") List channels); @Update(" update wvp_device_channel" + " set sub_count = (select *" + @@ -337,7 +380,7 @@ public interface DeviceChannelMapper { " where device_id = #{deviceId} and parent_id = #{channelId}) as temp)" + " where device_id = #{deviceId} " + " and channel_id = #{channelId}") - int updateChannelSubCount(String deviceId, String channelId); + int updateChannelSubCount(@Param("deviceId") String deviceId, @Param("channelId") String channelId); @Update(value = {" "}) - List getChannelsWithCivilCodeAndLength(String deviceId, String parentId, Integer length); + List getChannelsWithCivilCodeAndLength(@Param("deviceId") String deviceId, @Param("parentId") String parentId, @Param("length") Integer length); @Select(value = {" "}) - List getChannelsByCivilCode(String deviceId, String parentId); + List getChannelsByCivilCode(@Param("deviceId") String deviceId, @Param("parentId") String parentId); @Select("select min(length(channel_id)) as minLength " + "from wvp_device_channel " + @@ -389,12 +432,12 @@ public interface DeviceChannelMapper { List getChannelWithoutCivilCode(String deviceId); @Select("select * from wvp_device_channel where device_id=#{deviceId} and SUBSTRING(channel_id, 11, 3)=#{typeCode}") - List getBusinessGroups(String deviceId, String typeCode); + List getBusinessGroups(@Param("deviceId") String deviceId, @Param("typeCode") String typeCode); - @Select("select dc.id, dc.channel_id, dc.device_id, dc.name, dc.manufacture,dc.model,dc.owner, pc.civil_code,dc.block, " + + @Select("select dc.id, dc.channel_id, dc.device_id, COALESCE(dc.custom_name, dc.name) AS name, dc.manufacture,dc.model,dc.owner, pc.civil_code,dc.block, " + " dc.address, '0' as parental,'0' as channel_type, pc.id as parent_id, dc.safety_way, dc.register_way,dc.cert_num, dc.certifiable, " + - " dc.err_code,dc.end_time, dc.secrecy, dc.ip_address, dc.port, dc.ptz_type, dc.password, dc.status, " + - " dc.longitude_wgs84 as longitude, dc.latitude_wgs84 as latitude, pc.business_group_id " + + " dc.err_code,dc.end_time, dc.secrecy, dc.ip_address, dc.port, COALESCE(dc.custom_ptz_type, dc.ptz_type) AS ptz_type, dc.password, dc.status, " + + " COALESCE(dc.custom_longitude, dc.longitude) AS longitude, COALESCE(dc.custom_latitude, dc.latitude) AS latitude, pc.business_group_id " + " from wvp_device_channel dc" + " LEFT JOIN wvp_platform_gb_channel pgc on dc.id = pgc.device_channel_id" + " LEFT JOIN wvp_platform_catalog pc on pgc.catalog_id = pc.id and pgc.platform_id = pc.platform_id" + @@ -428,14 +471,14 @@ public interface DeviceChannelMapper { "DELETE FROM wvp_device_channel WHERE device_id=#{item.deviceId} AND channel_id=#{item.channelId}" + "" + ""}) - int batchDel(List deleteChannelList); + int batchDel(@Param("deleteChannelList") List deleteChannelList); @Update({""}) - int batchOnline(List channels); + int batchOnline(@Param("channels") List channels); @Update({""}) - List getSubChannelsByDeviceId(String deviceId, String parentId, boolean onlyCatalog); + List getSubChannelsByDeviceId(@Param("deviceId") String deviceId, @Param("parentId") String parentId, @Param("onlyCatalog") boolean onlyCatalog); + @Update("") + void updateChannelStreamIdentification(DeviceChannel channel); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java old mode 100644 new mode 100755 index 96773fe95..42d2b7aed --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMapper.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.gb28181.bean.Device; import org.apache.ibatis.annotations.*; +import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -42,7 +43,7 @@ public interface DeviceMapper { "geo_coord_sys," + "on_line," + "media_server_id," + - "switch_primary_sub_stream," + + "broadcast_push_after_ack," + "(SELECT count(0) FROM wvp_device_channel WHERE device_id=wvp_device.device_id) as channel_count "+ " FROM wvp_device WHERE device_id = #{deviceId}") Device getDeviceByDeviceId(String deviceId); @@ -73,6 +74,7 @@ public interface DeviceMapper { "subscribe_cycle_for_alarm,"+ "ssrc_check,"+ "as_message_channel,"+ + "broadcast_push_after_ack,"+ "geo_coord_sys,"+ "on_line"+ ") VALUES (" + @@ -101,6 +103,7 @@ public interface DeviceMapper { "#{subscribeCycleForAlarm}," + "#{ssrcCheck}," + "#{asMessageChannel}," + + "#{broadcastPushAfterAck}," + "#{geoCoordSys}," + "#{onLine}" + ")") @@ -155,10 +158,10 @@ public interface DeviceMapper { "subscribe_cycle_for_alarm,"+ "ssrc_check,"+ "as_message_channel,"+ + "broadcast_push_after_ack,"+ "geo_coord_sys,"+ "on_line,"+ "media_server_id,"+ - "switch_primary_sub_stream switchPrimarySubStream,"+ "(SELECT count(0) FROM wvp_device_channel WHERE device_id=de.device_id) as channel_count " + "FROM wvp_device de" + " where on_line=${onLine}"+ @@ -196,6 +199,7 @@ public interface DeviceMapper { "subscribe_cycle_for_alarm,"+ "ssrc_check,"+ "as_message_channel,"+ + "broadcast_push_after_ack,"+ "geo_coord_sys,"+ "on_line"+ " FROM wvp_device WHERE on_line = true") @@ -226,10 +230,11 @@ public interface DeviceMapper { "subscribe_cycle_for_alarm,"+ "ssrc_check,"+ "as_message_channel,"+ + "broadcast_push_after_ack,"+ "geo_coord_sys,"+ "on_line"+ " FROM wvp_device WHERE ip = #{host} AND port=#{port}") - Device getDeviceByHostAndPort(String host, int port); + Device getDeviceByHostAndPort(@Param("host") String host, @Param("port") int port); @Update(value = {" "}) @@ -264,10 +269,10 @@ public interface DeviceMapper { "charset,"+ "ssrc_check,"+ "as_message_channel,"+ + "broadcastPushAfterAck,"+ "geo_coord_sys,"+ "on_line,"+ - "media_server_id,"+ - "switch_primary_sub_stream"+ + "media_server_id"+ ") VALUES (" + "#{deviceId}," + "#{name}," + @@ -278,10 +283,10 @@ public interface DeviceMapper { "#{charset}," + "#{ssrcCheck}," + "#{asMessageChannel}," + + "#{broadcastPushAfterAck}," + "#{geoCoordSys}," + "#{onLine}," + - "#{mediaServerId}," + - "#{switchPrimarySubStream}" + + "#{mediaServerId}" + ")") void addCustomDevice(Device device); diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMobilePositionMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMobilePositionMapper.java old mode 100644 new mode 100755 index dbc9a1677..7bf243cae --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMobilePositionMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceMobilePositionMapper.java @@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.gb28181.bean.MobilePosition; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; @@ -23,7 +24,7 @@ public interface DeviceMobilePositionMapper { " AND time<=#{endTime}" + " ORDER BY time ASC" + " "}) - List queryPositionByDeviceIdAndTime(String deviceId, String channelId, String startTime, String endTime); + List queryPositionByDeviceIdAndTime(@Param("deviceId") String deviceId, @Param("channelId") String channelId, @Param("startTime") String startTime, @Param("endTime") String endTime); @Select("SELECT * FROM wvp_device_mobile_position WHERE device_id = #{deviceId}" + " ORDER BY time DESC LIMIT 1") diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java old mode 100644 new mode 100755 index e313acea5..6591e3f9d --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java @@ -6,6 +6,7 @@ import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import org.apache.ibatis.annotations.*; +import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -14,12 +15,12 @@ import java.util.List; @Repository public interface GbStreamMapper { - @Insert("REPLACE INTO wvp_gb_stream (app, stream, gb_id, name, " + + @Insert("INSERT INTO wvp_gb_stream (app, stream, gb_id, name, " + "longitude, latitude, stream_type,media_server_id,create_time) VALUES" + "(#{app}, #{stream}, #{gbId}, #{name}, " + "#{longitude}, #{latitude}, #{streamType}, " + "#{mediaServerId}, #{createTime})") - @Options(useGeneratedKeys = true, keyProperty = "gbStreamId", keyColumn = "gbStreamId") + @Options(useGeneratedKeys = true, keyProperty = "gbStreamId", keyColumn = "gb_stream_id") int add(GbStream gbStream); @Update("UPDATE wvp_gb_stream " + @@ -47,7 +48,7 @@ public interface GbStreamMapper { int update(GbStream gbStream); @Delete("DELETE FROM wvp_gb_stream WHERE app=#{app} AND stream=#{stream}") - int del(String app, String stream); + int del(@Param("app") String app, @Param("stream") String stream); @Select("") - List selectAll(String platformId, String catalogId, String query, String mediaServerId); + List selectAll(@Param("platformId") String platformId, @Param("catalogId") String catalogId, @Param("query") String query, @Param("mediaServerId") String mediaServerId); @Select("SELECT * FROM wvp_gb_stream WHERE app=#{app} AND stream=#{stream}") - GbStream selectOne(String app, String stream); + GbStream selectOne(@Param("app") String app, @Param("stream") String stream); @Select("SELECT * FROM wvp_gb_stream WHERE gb_id=#{gbId}") List selectByGBId(String gbId); @@ -72,7 +73,7 @@ public interface GbStreamMapper { @Select("SELECT gs.*, pgs.platform_id as platform_id, pgs.catalog_id as catalog_id FROM wvp_gb_stream gs " + "LEFT JOIN wvp_platform_gb_stream pgs ON gs.gb_stream_id = pgs.gb_stream_id " + "WHERE gs.gb_id = #{gbId} AND pgs.platform_id = #{platformId}") - GbStream queryStreamInPlatform(String platformId, String gbId); + GbStream queryStreamInPlatform(@Param("platformId") String platformId, @Param("gbId") String gbId); @Select("") - List queryGbStreamListInPlatform(String platformId, boolean usPushingAsStatus); + List queryGbStreamListInPlatform(String platformId, @Param("usPushingAsStatus") boolean usPushingAsStatus); @Select("SELECT gs.* FROM wvp_gb_stream gs left join wvp_platform_gb_stream pgs " + @@ -98,7 +99,7 @@ public interface GbStreamMapper { List queryStreamNotInPlatform(); @Delete("DELETE FROM wvp_gb_stream WHERE stream_type=#{type} AND gb_id=NULL AND media_server_id=#{mediaServerId}") - void deleteWithoutGBId(String type, String mediaServerId); + void deleteWithoutGBId(@Param("type") String type, @Param("mediaServerId") String mediaServerId); @Delete("") @Options(useGeneratedKeys = true, keyProperty = "gbStreamId", keyColumn = "gb_stream_id") - void batchAdd(List subList); + void batchAdd(@Param("subList") List subList); @Update({"") int updateGbIdOrName(List streamPushItemForUpdate); @Select("SELECT status FROM wvp_stream_proxy WHERE app=#{app} AND stream=#{stream}") - Boolean selectStatusForProxy(String app, String stream); + Boolean selectStatusForProxy(@Param("app") String app, @Param("stream") String stream); @Select("SELECT status FROM wvp_stream_push WHERE app=#{app} AND stream=#{stream}") - Boolean selectStatusForPush(String app, String stream); + Boolean selectStatusForPush(@Param("app") String app, @Param("stream") String stream); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/LogMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/LogMapper.java old mode 100644 new mode 100755 index ae8471b2a..f1c45850f --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/LogMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/LogMapper.java @@ -4,6 +4,7 @@ import com.genersoft.iot.vmp.storager.dao.dto.LogDto; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Repository; @@ -29,7 +30,7 @@ public interface LogMapper { " AND create_time <= #{endTime} " + " ORDER BY create_time DESC " + " "}) - List query(String query, String type, String startTime, String endTime); + List query(@Param("query") String query, @Param("type") String type, @Param("startTime") String startTime, @Param("endTime") String endTime); @Delete("DELETE FROM wvp_log") int clear(); diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java old mode 100644 new mode 100755 index cdc303dde..46785911a --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import org.apache.ibatis.annotations.*; +import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -30,6 +31,8 @@ public interface MediaServerMapper { "rtp_port_range,"+ "send_rtp_port_range,"+ "record_assist_port,"+ + "record_day,"+ + "record_path,"+ "default_server,"+ "create_time,"+ "update_time,"+ @@ -54,6 +57,8 @@ public interface MediaServerMapper { "#{rtpPortRange}, " + "#{sendRtpPortRange}, " + "#{recordAssistPort}, " + + "#{recordDay}, " + + "#{recordPath}, " + "#{defaultServer}, " + "#{createTime}, " + "#{updateTime}, " + @@ -81,6 +86,8 @@ public interface MediaServerMapper { ", secret=#{secret}" + ", record_assist_port=#{recordAssistPort}" + ", hook_alive_interval=#{hookAliveInterval}" + + ", record_day=#{recordDay}" + + ", record_path=#{recordPath}" + "WHERE id=#{id}"+ " "}) int update(MediaServerItem mediaServerItem); @@ -104,6 +111,8 @@ public interface MediaServerMapper { ", send_rtp_port_range=#{sendRtpPortRange}" + ", secret=#{secret}" + ", record_assist_port=#{recordAssistPort}" + + ", record_day=#{recordDay}" + + ", record_path=#{recordPath}" + ", hook_alive_interval=#{hookAliveInterval}" + "WHERE ip=#{ip} and http_port=#{httpPort}"+ " "}) @@ -119,14 +128,18 @@ public interface MediaServerMapper { void delOne(String id); @Select("DELETE FROM wvp_media_server WHERE ip=#{host} and http_port=#{port}") - void delOneByIPAndPort(String host, int port); + void delOneByIPAndPort(@Param("host") String host, @Param("port") int port); @Delete("DELETE FROM wvp_media_server WHERE default_server=true") int delDefault(); @Select("SELECT * FROM wvp_media_server WHERE ip=#{host} and http_port=#{port}") - MediaServerItem queryOneByHostAndPort(String host, int port); + MediaServerItem queryOneByHostAndPort(@Param("host") String host, @Param("port") int port); @Select("SELECT * FROM wvp_media_server WHERE default_server=true") MediaServerItem queryDefault(); + + @Select("SELECT * FROM wvp_media_server WHERE record_assist_port > 0") + List queryAllWithAssistPort(); + } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/ParentPlatformMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/ParentPlatformMapper.java old mode 100644 new mode 100755 index 386d7dc0e..9dc05034b --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/ParentPlatformMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/ParentPlatformMapper.java @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; import com.genersoft.iot.vmp.storager.dao.dto.ChannelSourceInfo; import org.apache.ibatis.annotations.*; +import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -15,10 +16,10 @@ import java.util.List; public interface ParentPlatformMapper { @Insert("INSERT INTO wvp_platform (enable, name, server_gb_id, server_gb_domain, server_ip, server_port,device_gb_id,device_ip,"+ - "device_port,username,password,expires,keep_timeout,transport,character_set,ptz,rtcp,as_message_channel,"+ + "device_port,username,password,expires,keep_timeout,transport,character_set,ptz,rtcp,as_message_channel,auto_push_channel,"+ "status,start_offline_push,catalog_id,administrative_division,catalog_group,create_time,update_time) " + " VALUES (#{enable}, #{name}, #{serverGBId}, #{serverGBDomain}, #{serverIP}, #{serverPort}, #{deviceGBId}, #{deviceIp}, " + - " #{devicePort}, #{username}, #{password}, #{expires}, #{keepTimeout}, #{transport}, #{characterSet}, #{ptz}, #{rtcp}, #{asMessageChannel}, " + + " #{devicePort}, #{username}, #{password}, #{expires}, #{keepTimeout}, #{transport}, #{characterSet}, #{ptz}, #{rtcp}, #{asMessageChannel}, #{autoPushChannel}, " + " #{status}, #{startOfflinePush}, #{catalogId}, #{administrativeDivision}, #{catalogGroup}, #{createTime}, #{updateTime})") int addParentPlatform(ParentPlatform parentPlatform); @@ -41,6 +42,7 @@ public interface ParentPlatformMapper { "ptz=#{ptz}, " + "rtcp=#{rtcp}, " + "as_message_channel=#{asMessageChannel}, " + + "auto_push_channel=#{autoPushChannel}, " + "status=#{status}, " + "start_offline_push=#{startOfflinePush}, " + "catalog_group=#{catalogGroup}, " + @@ -84,17 +86,17 @@ public interface ParentPlatformMapper { int outlineForAllParentPlatform(); @Update("UPDATE wvp_platform SET status=#{online} WHERE server_gb_id=#{platformGbID}" ) - int updateParentPlatformStatus(String platformGbID, boolean online); + int updateParentPlatformStatus(@Param("platformGbID") String platformGbID, @Param("online") boolean online); @Update(value = {" "}) - int setDefaultCatalog(String platformId, String catalogId, String updateTime); + int setDefaultCatalog(@Param("platformId") String platformId, @Param("catalogId") String catalogId, @Param("updateTime") String updateTime); @Select("select 'channel' as name, count(pgc.platform_id) count from wvp_platform_gb_channel pgc left join wvp_device_channel dc on dc.id = pgc.device_channel_id where pgc.platform_id=#{platform_id} and dc.channel_id =#{gbId} " + "union " + "select 'stream' as name, count(pgs.platform_id) count from wvp_platform_gb_stream pgs left join wvp_gb_stream gs on pgs.gb_stream_id = gs.gb_stream_id where pgs.platform_id=#{platform_id} and gs.gb_id =#{gbId}") - List getChannelSource(String platform_id, String gbId); + List getChannelSource(@Param("platform_id") String platform_id, @Param("gbId") String gbId); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformCatalogMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformCatalogMapper.java old mode 100644 new mode 100755 index 3cb4776b9..62d610190 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformCatalogMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformCatalogMapper.java @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.storager.dao; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.bean.PlatformCatalog; import org.apache.ibatis.annotations.*; +import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -17,37 +18,49 @@ public interface PlatformCatalogMapper { int add(PlatformCatalog platformCatalog); @Delete("DELETE from wvp_platform_catalog WHERE platform_id=#{platformId} and id=#{id}") - int del(String platformId, String id); + int del(@Param("platformId") String platformId, @Param("id") String id); @Delete("DELETE from wvp_platform_catalog WHERE platform_id=#{platformId}") - int delByPlatformId(String platformId); + int delByPlatformId(@Param("platformId") String platformId); @Select("SELECT pc.*, count(pc2.id) as children_count from wvp_platform_catalog pc " + "left join wvp_platform_catalog pc2 on pc.id = pc2.parent_id " + "WHERE pc.parent_id=#{parentId} AND pc.platform_id=#{platformId} " + "group by pc.id, pc.name, pc.platform_id, pc.business_group_id, pc.civil_code, pc.parent_id") - List selectByParentId(String platformId, String parentId); + List selectByParentId(@Param("platformId") String platformId, @Param("parentId") String parentId); @Update(value = {" "}) - int update(PlatformCatalog platformCatalog); + int update(@Param("platformCatalog") PlatformCatalog platformCatalog); @Select("SELECT *, (SELECT COUNT(1) from wvp_platform_catalog where parent_id = pc.id) as children_count from wvp_platform_catalog pc WHERE pc.platform_id=#{platformId}") - List selectByPlatForm(String platformId); + List selectByPlatForm(@Param("platformId") String platformId); @Select("SELECT pc.* FROM wvp_platform_catalog pc WHERE pc.id = (SELECT pp.catalog_id from wvp_platform pp WHERE pp.server_gb_id=#{platformId})") - PlatformCatalog selectDefaultByPlatFormId(String platformId); + PlatformCatalog selectDefaultByPlatFormId(@Param("platformId") String platformId); @Select("SELECT pc.id as channel_id, pc.name, pc.civil_code, pc.business_group_id,'1' as parental, pc.parent_id " + " from wvp_platform_catalog pc WHERE pc.platform_id=#{platformId}") - List queryCatalogInPlatform(String platformId); + List queryCatalogInPlatform(@Param("platformId") String platformId); @Select("SELECT *, " + "(SELECT COUNT(1) from wvp_platform_catalog where parent_id = pc.id) as children_count " + " from wvp_platform_catalog pc " + " WHERE pc.id=#{id} and pc.platform_id=#{platformId}") - PlatformCatalog selectByPlatFormAndCatalogId(String platformId, String id); + PlatformCatalog selectByPlatFormAndCatalogId(@Param("platformId") String platformId, @Param("id") String id); + + + @Delete("") + int deleteAll(String platformId, List ids); + + @Select("SELECT id from wvp_platform_catalog WHERE platform_id=#{platformId} and parent_id = #{id}") + List queryCatalogFromParent(@Param("id") String id, @Param("platformId") String platformId); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformChannelMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformChannelMapper.java old mode 100644 new mode 100755 index 7a749cbda..f5ce3f0a0 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformChannelMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformChannelMapper.java @@ -8,6 +8,7 @@ import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Repository; @@ -24,7 +25,7 @@ public interface PlatformChannelMapper { "SELECT device_channel_id from wvp_platform_gb_channel WHERE platform_id=#{platformId} AND device_channel_id in" + " #{item.id}" + "") - List findChannelRelatedPlatform(String platformId, List channelReduces); + List findChannelRelatedPlatform(@Param("platformId") String platformId, @Param("channelReduces") List channelReduces); @Insert("") - int addChannels(String platformId, List channelReducesToAdd); + int addChannels(@Param("platformId") String platformId, @Param("channelReducesToAdd") List channelReducesToAdd); @Delete("") - int delChannelForGB(String platformId, List channelReducesToDel); + int delChannelForGB(@Param("platformId") String platformId, @Param("channelReducesToDel") List channelReducesToDel); @Delete("") + List queryAllChannelInCatalog(@Param("platformId") String platformId, @Param("catalogId") String catalogId); @Select(" select dc.channel_id as id, dc.name as name, pgc.platform_id as platform_id, pgc.catalog_id as parent_id, 0 as children_count, 1 as type " + " from wvp_device_channel dc left join wvp_platform_gb_channel pgc on dc.id = pgc.device_channel_id " + " where pgc.platform_id=#{platformId} and pgc.catalog_id=#{catalogId}") - List queryChannelInParentPlatformAndCatalog(String platformId, String catalogId); + List queryChannelInParentPlatformAndCatalog(@Param("platformId") String platformId, @Param("catalogId") String catalogId); @Select("select d.*\n" + "from wvp_platform_gb_channel pgc\n" + " left join wvp_device_channel dc on dc.id = pgc.device_channel_id\n" + " left join wvp_device d on dc.device_id = d.device_id\n" + "where dc.channel_id = #{channelId} and pgc.platform_id=#{platformId}") - List queryVideoDeviceByPlatformIdAndChannelId(String platformId, String channelId); + List queryVideoDeviceByPlatformIdAndChannelId(@Param("platformId") String platformId, @Param("channelId") String channelId); @Delete("") - int delByCatalogId(String platformId, String id); + int delByCatalogId(@Param("platformId") String platformId, @Param("id") String id); @Delete(" ") - List queryPlatFormListForGBWithGBId(String channelId, List platforms); + List queryPlatFormListForGBWithGBId(@Param("channelId") String channelId, @Param("platforms") List platforms); @Delete("") - int delChannelForGBByCatalogId(String platformId, String catalogId); + int delChannelForGBByCatalogId(@Param("platformId") String platformId, @Param("catalogId") String catalogId); @Select("select dc.channel_id dc.device_id,dc.name,d.manufacturer,d.model,d.firmware\n" + "from wvp_platform_gb_channel pgc\n" + " left join wvp_device_channel dc on dc.id = pgc.device_channel_id\n" + " left join wvp_device d on dc.device_id = d.device_id\n" + "where dc.channel_id = #{channelId} and pgc.platform_id=#{platformId}") - List queryDeviceInfoByPlatformIdAndChannelId(String platformId, String channelId); + List queryDeviceInfoByPlatformIdAndChannelId(@Param("platformId") String platformId, @Param("channelId") String channelId); - @Select("SELECT pgc.platform_id from wvp_platform_gb_channel pgc left join wvp_device_channel dc on dc.id = pgc.device_channel_id WHERE dc.channel_id='${channelId}'") - List queryParentPlatformByChannelId(String channelId); + @Select("SELECT pgc.platform_id from wvp_platform_gb_channel pgc left join wvp_device_channel dc on dc.id = pgc.device_channel_id WHERE dc.channel_id=#{channelId}") + List queryParentPlatformByChannelId(@Param("channelId") String channelId); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformGbStreamMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformGbStreamMapper.java old mode 100644 new mode 100755 index 40640db7f..e4fc3568a --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformGbStreamMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/PlatformGbStreamMapper.java @@ -16,7 +16,7 @@ import java.util.List; @Repository public interface PlatformGbStreamMapper { - @Insert("REPLACE INTO wvp_platform_gb_stream (gb_stream_id, platform_id, catalog_id) VALUES" + + @Insert("INSERT INTO wvp_platform_gb_stream (gb_stream_id, platform_id, catalog_id) VALUES" + "( #{gbStreamId}, #{platformId}, #{catalogId})") int add(PlatformGbStream platformGbStream); @@ -32,7 +32,7 @@ public interface PlatformGbStreamMapper { int batchAdd(List streamPushItems); @Delete("DELETE from wvp_platform_gb_stream WHERE gb_stream_id = (select gb_stream_id from wvp_gb_stream where app=#{app} AND stream=#{stream})") - int delByAppAndStream(String app, String stream); + int delByAppAndStream(@Param("app") String app, @Param("stream") String stream); @Delete("DELETE from wvp_platform_gb_stream WHERE platform_id=#{platformId}") int delByPlatformId(String platformId); @@ -46,26 +46,29 @@ public interface PlatformGbStreamMapper { "WHERE " + "gs.app =#{app} " + "AND gs.stream =#{stream} ") - List selectByAppAndStream(String app, String stream); + List selectByAppAndStream(@Param("app") String app, @Param("stream") String stream); @Select("SELECT pgs.*, gs.gb_id from wvp_platform_gb_stream pgs " + "LEFT join wvp_gb_stream gs ON pgs.gb_stream_id = gs.gb_stream_id " + "WHERE gs.app=#{app} AND gs.stream=#{stream} AND pgs.platform_id=#{platformId}") - StreamProxyItem selectOne(String app, String stream, String platformId); + StreamProxyItem selectOne(@Param("app") String app, @Param("stream") String stream, @Param("platformId") String platformId); - @Select("select gs.* \n" + - "from wvp_gb_stream gs\n" + + @Select("") + List queryChannelInParentPlatformAndCatalog(@Param("platformId") String platformId, @Param("catalogId") String catalogId); @Select("select gs.gb_id as id, gs.name as name, pgs.platform_id as platform_id, pgs.catalog_id as catalog_id , 0 as children_count, 2 as type\n" + "from wvp_gb_stream gs\n" + " left join wvp_platform_gb_stream pgs\n" + " on gs.gb_stream_id = pgs.gb_stream_id\n" + "where pgs.platform_id=#{platformId} and pgs.catalog_id=#{catalogId}") - List queryChannelInParentPlatformAndCatalogForCatalog(String platformId, String catalogId); + List queryChannelInParentPlatformAndCatalogForCatalog(@Param("platformId") String platformId, @Param("catalogId") String catalogId); @Select(" ") - List queryPlatFormListForGBWithGBId(String app, String stream, List platforms); + List queryPlatFormListForGBWithGBId(@Param("app") String app, @Param("stream") String stream, @Param("platforms") List platforms); @Delete("DELETE from wvp_platform_gb_stream WHERE gb_stream_id = (select id from wvp_gb_stream where app=#{app} AND stream=#{stream}) AND platform_id=#{platformId}") int delByAppAndStreamAndPlatform(String app, String stream, String platformId); @@ -101,8 +104,11 @@ public interface PlatformGbStreamMapper { "#{item.gbStreamId} " + "" + "") - void delByAppAndStreamsByPlatformId(List gbStreams, String platformId); + void delByAppAndStreamsByPlatformId(@Param("gbStreams") List gbStreams, @Param("platformId") String platformId); - @Delete("DELETE from wvp_platform_gb_stream WHERE platform_id=#{platformId} and catalog_id=#{catalogId}") - int delByPlatformAndCatalogId(String platformId, String catalogId); + @Delete("") + int delByPlatformAndCatalogId(@Param("platformId") String platformId, @Param("catalogId") String catalogId); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/RoleMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/RoleMapper.java old mode 100644 new mode 100755 diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java old mode 100644 new mode 100755 index a5a80c475..6ad36cef6 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java @@ -12,9 +12,9 @@ import java.util.List; public interface StreamProxyMapper { @Insert("INSERT INTO wvp_stream_proxy (type, name, app, stream,media_server_id, url, src_url, dst_url, " + - "timeout_ms, ffmpeg_cmd_key, rtp_type, enable_audio, enable_mp4, enable, status, enable_remove_none_reader, enable_disable_none_reader, create_time) VALUES" + + "timeout_ms, ffmpeg_cmd_key, rtp_type, enable_audio, enable_mp4, enable, status, stream_key, enable_remove_none_reader, enable_disable_none_reader, create_time) VALUES" + "(#{type}, #{name}, #{app}, #{stream}, #{mediaServerId}, #{url}, #{srcUrl}, #{dstUrl}, " + - "#{timeoutMs}, #{ffmpegCmdKey}, #{rtpType}, #{enableAudio}, #{enableMp4}, #{enable}, #{status}, " + + "#{timeoutMs}, #{ffmpegCmdKey}, #{rtpType}, #{enableAudio}, #{enableMp4}, #{enable}, #{status}, #{streamKey}, " + "#{enableRemoveNoneReader}, #{enableDisableNoneReader}, #{createTime} )") int add(StreamProxyItem streamProxyDto); @@ -33,6 +33,7 @@ public interface StreamProxyMapper { "enable_audio=#{enableAudio}, " + "enable=#{enable}, " + "status=#{status}, " + + "stream_key=#{streamKey}, " + "enable_remove_none_reader=#{enableRemoveNoneReader}, " + "enable_disable_none_reader=#{enableDisableNoneReader}, " + "enable_mp4=#{enableMp4} " + @@ -45,16 +46,16 @@ public interface StreamProxyMapper { @Select("SELECT st.*, pgs.gb_id, pgs.name, pgs.longitude, pgs.latitude FROM wvp_stream_proxy st LEFT join wvp_gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream order by st.create_time desc") List selectAll(); - @Select("SELECT st.*, pgs.gb_id, pgs.name, pgs.longitude, pgs.latitude FROM wvp_stream_proxy st LEFT join wvp_gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream WHERE st.enable=#{enable} order by st.create_time desc") + @Select("SELECT st.*, pgs.gb_id, pgs.name, pgs.longitude, pgs.latitude, 'proxy' as streamType FROM wvp_stream_proxy st LEFT join wvp_gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream WHERE st.enable=#{enable} order by st.create_time desc") List selectForEnable(boolean enable); @Select("SELECT st.*, pgs.gb_id, pgs.name, pgs.longitude, pgs.latitude FROM wvp_stream_proxy st LEFT join wvp_gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream WHERE st.app=#{app} AND st.stream=#{stream} order by st.create_time desc") - StreamProxyItem selectOne(String app, String stream); + StreamProxyItem selectOne(@Param("app") String app, @Param("stream") String stream); @Select("SELECT st.*, pgs.gb_id, pgs.name, pgs.longitude, pgs.latitude FROM wvp_stream_proxy st " + "LEFT join wvp_gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream " + "WHERE st.enable=#{enable} and st.media_server_id= #{id} order by st.create_time desc") - List selectForEnableInMediaServer(String id, boolean enable); + List selectForEnableInMediaServer( @Param("id") String id, @Param("enable") boolean enable); @Select("SELECT st.*, pgs.gb_id, pgs.name, pgs.longitude, pgs.latitude FROM wvp_stream_proxy st " + "LEFT join wvp_gb_stream pgs on st.app = pgs.app AND st.stream = pgs.stream " + @@ -64,12 +65,12 @@ public interface StreamProxyMapper { @Update("UPDATE wvp_stream_proxy " + "SET status=#{status} " + "WHERE media_server_id=#{mediaServerId}") - void updateStatusByMediaServerId(String mediaServerId, boolean status); + void updateStatusByMediaServerId(@Param("mediaServerId") String mediaServerId, @Param("status") boolean status); @Update("UPDATE wvp_stream_proxy " + "SET status=#{status} " + "WHERE app=#{app} AND stream=#{stream}") - int updateStatus(String app, String stream, boolean status); + int updateStatus(@Param("app") String app, @Param("stream") String stream, @Param("status") boolean status); @Delete("DELETE FROM wvp_stream_proxy WHERE enable_remove_none_reader=true AND media_server_id=#{mediaServerId}") void deleteAutoRemoveItemByMediaServerId(String mediaServerId); diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java old mode 100644 new mode 100755 index 4efc058d0..682f07c6d --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamPushMapper.java @@ -13,9 +13,9 @@ import java.util.List; public interface StreamPushMapper { @Insert("INSERT INTO wvp_stream_push (app, stream, total_reader_count, origin_type, origin_type_str, " + - "push_time, alive_second, media_server_id, update_time, create_time, push_ing, self) VALUES" + + "push_time, alive_second, media_server_id, server_id, update_time, create_time, push_ing, self) VALUES" + "(#{app}, #{stream}, #{totalReaderCount}, #{originType}, #{originTypeStr}, " + - "#{pushTime}, #{aliveSecond}, #{mediaServerId} , #{updateTime} , #{createTime}, " + + "#{pushTime}, #{aliveSecond}, #{mediaServerId} , #{serverId} , #{updateTime} , #{createTime}, " + "#{pushIng}, #{self} )") int add(StreamPushItem streamPushItem); @@ -24,6 +24,7 @@ public interface StreamPushMapper { "UPDATE wvp_stream_push " + "SET update_time=#{updateTime}" + ", media_server_id=#{mediaServerId}" + + ", server_id=#{serverId}" + ", total_reader_count=#{totalReaderCount}" + ", origin_type=#{originType}" + ", origin_type_str=#{originTypeStr}" + @@ -79,20 +80,20 @@ public interface StreamPushMapper { " AND st.media_server_id=#{mediaServerId} " + "order by st.create_time desc" + " "}) - List selectAllForList(String query, Boolean pushing, String mediaServerId); + List selectAllForList(@Param("query") String query, @Param("pushing") Boolean pushing, @Param("mediaServerId") String mediaServerId); @Select("SELECT st.*, gs.gb_id, gs.name, gs.longitude, gs.latitude FROM wvp_stream_push st LEFT join wvp_gb_stream gs on st.app = gs.app AND st.stream = gs.stream order by st.create_time desc") List selectAll(); @Select("SELECT st.*, gs.gb_id, gs.name, gs.longitude, gs.latitude FROM wvp_stream_push st LEFT join wvp_gb_stream gs on st.app = gs.app AND st.stream = gs.stream WHERE st.app=#{app} AND st.stream=#{stream}") - StreamPushItem selectOne(String app, String stream); + StreamPushItem selectOne(@Param("app") String app, @Param("stream") String stream); @Insert("") @@ -122,17 +123,17 @@ public interface StreamPushMapper { @Update("UPDATE wvp_stream_push " + "SET status=#{status} " + "WHERE app=#{app} AND stream=#{stream}") - int updateStatus(String app, String stream, boolean status); + int updateStatus(@Param("app") String app, @Param("stream") String stream, @Param("status") boolean status); @Update("UPDATE wvp_stream_push " + "SET push_ing=#{pushIng} " + "WHERE app=#{app} AND stream=#{stream}") - int updatePushStatus(String app, String stream, boolean pushIng); + int updatePushStatus(@Param("app") String app, @Param("stream") String stream, @Param("pushIng") boolean pushIng); @Update("UPDATE wvp_stream_push " + "SET status=#{status} " + "WHERE media_server_id=#{mediaServerId}") - void updateStatusByMediaServerId(String mediaServerId, boolean status); + void updateStatusByMediaServerId(@Param("mediaServerId") String mediaServerId, @Param("status") boolean status); @Select(" diff --git a/web_src/src/components/dialog/getCatalog.vue b/web_src/src/components/dialog/getCatalog.vue old mode 100644 new mode 100755 diff --git a/web_src/src/components/dialog/importChannel.vue b/web_src/src/components/dialog/importChannel.vue old mode 100644 new mode 100755 diff --git a/web_src/src/components/dialog/importChannelShowErrorData.vue b/web_src/src/components/dialog/importChannelShowErrorData.vue old mode 100644 new mode 100755 diff --git a/web_src/src/components/dialog/onvifEdit.vue b/web_src/src/components/dialog/onvifEdit.vue old mode 100644 new mode 100755 diff --git a/web_src/src/components/dialog/platformEdit.vue b/web_src/src/components/dialog/platformEdit.vue old mode 100644 new mode 100755 index 0158cf742..f3f4255b9 --- a/web_src/src/components/dialog/platformEdit.vue +++ b/web_src/src/components/dialog/platformEdit.vue @@ -91,9 +91,10 @@ - + - + + {{ @@ -141,6 +142,7 @@ export default { ptz: true, rtcp: false, asMessageChannel: false, + autoPushChannel: false, name: null, serverGBId: null, serverGBDomain: null, @@ -208,6 +210,7 @@ export default { this.platform.ptz = platform.ptz; this.platform.rtcp = platform.rtcp; this.platform.asMessageChannel = platform.asMessageChannel; + this.platform.autoPushChannel = platform.autoPushChannel; this.platform.name = platform.name; this.platform.serverGBId = platform.serverGBId; this.platform.serverGBDomain = platform.serverGBDomain; @@ -284,6 +287,7 @@ export default { ptz: true, rtcp: false, asMessageChannel: false, + autoPushChannel: false, name: null, serverGBId: null, administrativeDivision: null, diff --git a/web_src/src/components/dialog/pushStreamEdit.vue b/web_src/src/components/dialog/pushStreamEdit.vue old mode 100644 new mode 100755 diff --git a/web_src/src/components/dialog/queryTrace.vue b/web_src/src/components/dialog/queryTrace.vue old mode 100644 new mode 100755 diff --git a/web_src/src/components/dialog/recordDownload.vue b/web_src/src/components/dialog/recordDownload.vue old mode 100644 new mode 100755 index 7a945400d..c78cd1ce3 --- a/web_src/src/components/dialog/recordDownload.vue +++ b/web_src/src/components/dialog/recordDownload.vue @@ -6,8 +6,7 @@ - 停止缓存并下载 - 点击下载 + 下载 @@ -27,7 +26,7 @@ export default { }, data() { return { - title: "四倍速下载中...", + title: "下载中...", deviceId: "", channelId: "", app: "", @@ -39,7 +38,6 @@ export default { streamInfo: null, taskId: null, getProgressRun: false, - getProgressForFileRun: false, timer: null, downloadFile: null, @@ -62,7 +60,7 @@ export default { return; } if (this.percentage == 100 ) { - this.getFileDownload(); + return; } setTimeout( ()=>{ @@ -75,7 +73,6 @@ export default { method: 'get', url: `/api/gb_record/download/progress/${this.deviceId}/${this.channelId}/${this.stream}` }).then((res)=> { - console.log(res) if (res.data.code === 0) { this.streamInfo = res.data.data; if (parseFloat(res.data.progress) == 1) { @@ -83,6 +80,15 @@ export default { }else { this.percentage = (parseFloat(res.data.data.progress)*100).toFixed(1); } + if (this.streamInfo.downLoadFilePath) { + if (location.protocol === "https:") { + this.downloadFile = this.streamInfo.downLoadFilePath.httpsPath; + }else { + this.downloadFile = this.streamInfo.downLoadFilePath.httpPath; + } + this.getProgressRun = false; + this.downloadFileClientEvent() + } if (callback)callback(); }else { this.$message({ @@ -108,24 +114,11 @@ export default { } this.showDialog=false; this.getProgressRun = false; - this.getProgressForFileRun = false; }, gbScale: function (scale){ this.scale = scale; }, - download: function (){ - this.getProgressRun = false; - if (this.streamInfo != null ) { - if (this.streamInfo.progress < 1) { - // 发送停止缓存 - this.stopDownloadRecord((res)=>{ - this.getFileDownload() - }) - }else { - this.getFileDownload() - } - } - }, + stopDownloadRecord: function (callback) { this.$axios({ method: 'get', @@ -134,74 +127,20 @@ export default { if (callback) callback(res) }); }, - getFileDownload: function (){ - this.$axios({ - method: 'get', - url:`/record_proxy/${this.mediaServerId}/api/record/file/download/task/add`, - params: { - app: this.app, - stream: this.stream, - startTime: null, - endTime: null, - } - }).then((res) =>{ - if (res.data.code === 0 ) { - // 查询进度 - this.title = "录像文件处理中..." - this.taskId = res.data.data; - this.percentage = 0.0; - this.getProgressForFileRun = true; - this.getProgressForFileTimer(); - } - }).catch(function (error) { - console.log(error); - }); - }, - getProgressForFileTimer: function (){ - if (!this.getProgressForFileRun || this.percentage == 100) { - return; - } - setTimeout( ()=>{ - if (!this.showDialog) return; - this.getProgressForFile(this.getProgressForFileTimer) - }, 1000) - }, - getProgressForFile: function (callback){ - this.$axios({ - method: 'get', - url:`/record_proxy/${this.mediaServerId}/api/record/file/download/task/list`, - params: { - app: this.app, - stream: this.stream, - taskId: this.taskId, - isEnd: true, - } - }).then((res) => { - console.log(res) - if (res.data.code === 0) { - if (res.data.data.length === 0){ - this.percentage = 0 - // 往往在多次请求后(实验五分钟的视频是三次请求),才会返回数据,第一次请求通常是返回空数组 - if (callback)callback() - return - } - // res.data.data应是数组类型 - this.percentage = parseFloat(res.data.data[0].percentage)*100 - if (res.data.data[0].percentage === '1') { - this.getProgressForFileRun = false; - this.downloadFile = res.data.data[0].downloadFile - this.title = "文件处理完成,点击按扭下载" - // window.open(res.data.data[0].downloadFile) - }else { - if (callback)callback() - } - } - }).catch(function (error) { - console.log(error); - }); - }, downloadFileClientEvent: function (){ - window.open(this.downloadFile ) + // window.open(this.downloadFile ) + + let x = new XMLHttpRequest(); + x.open("GET", this.downloadFile, true); + x.responseType = 'blob'; + x.onload=(e)=> { + let url = window.URL.createObjectURL(x.response) + let a = document.createElement('a'); + a.href = url + a.download = this.deviceId + "-" + this.channelId + ".mp4"; + a.click() + } + x.send(); } }, destroyed() { diff --git a/web_src/src/components/dialog/rtcPlayer.vue b/web_src/src/components/dialog/rtcPlayer.vue old mode 100644 new mode 100755 diff --git a/web_src/src/components/live.vue b/web_src/src/components/live.vue old mode 100644 new mode 100755 diff --git a/web_src/src/components/map.vue b/web_src/src/components/map.vue old mode 100644 new mode 100755 diff --git a/web_src/src/components/service/DeviceService.js b/web_src/src/components/service/DeviceService.js old mode 100644 new mode 100755 diff --git a/web_src/src/components/service/MediaServer.js b/web_src/src/components/service/MediaServer.js old mode 100644 new mode 100755 diff --git a/web_src/src/components/service/UserService.js b/web_src/src/components/service/UserService.js old mode 100644 new mode 100755 diff --git a/web_src/src/components/setting/Media.vue b/web_src/src/components/setting/Media.vue old mode 100644 new mode 100755 diff --git a/web_src/src/components/setting/Sip.vue b/web_src/src/components/setting/Sip.vue old mode 100644 new mode 100755 diff --git a/web_src/src/components/setting/Web.vue b/web_src/src/components/setting/Web.vue old mode 100644 new mode 100755 diff --git a/web_src/src/layout/UiHeader.vue b/web_src/src/layout/UiHeader.vue old mode 100644 new mode 100755 index 2cdca02c0..fdfcb9f91 --- a/web_src/src/layout/UiHeader.vue +++ b/web_src/src/layout/UiHeader.vue @@ -37,7 +37,6 @@