diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 7e61cd7c8..a104248be 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -14,9 +14,10 @@ assignees: '' - 3. 端口开放情况 - 4. 是否是公网部署 - 5. 是否使用https - - 6. 方便的话提供下使用的设备品牌或平台 + - 6. 接入设备/平台品牌 - 7. 你做过哪些尝试 - 8. 代码更新时间 + - 9. 是否是4G设备接入 **描述错误** 描述下您遇到的问题 diff --git a/doc/_content/ability/device.md b/doc/_content/ability/device.md index ceed545ea..134f7add0 100644 --- a/doc/_content/ability/device.md +++ b/doc/_content/ability/device.md @@ -3,9 +3,6 @@ 设备接入主要是需要在设备上配置28181上级也就是WVP-PRO的信息,只有信息一致的情况才可以注册成功。设备注册成功后打开WVP->国标设备,可以看到新增加的设备;[设备使用](./_content/ability/device_use.md), 主要有以下字段需要配置: -- sip->ip -本机IP,不要使用127.0.0.1/0.0.0.0, 除非你对项目及其熟悉 - - sip->port 28181服务监听的端口 diff --git a/doc/_content/introduction/compile.md b/doc/_content/introduction/compile.md index 66693bd9c..15f2a9e68 100644 --- a/doc/_content/introduction/compile.md +++ b/doc/_content/introduction/compile.md @@ -2,12 +2,8 @@ # 编译 WVP-PRO不只是实现了国标28181的协议,本身也是一个完整的视频平台。所以对于新手来说,你可能需要一些耐心来完成。遇到问题不要焦躁,你可以 1. 百度 -2. 如果身边有熟悉java的朋友,可以咨询下朋友; -3. 来群里(901799015)咨询群友; -4. 向作者发送邮件648540858@qq.com; -5. 作者远程支持(有偿)。 - 如果这些仍不能解决你的问题,那么你可能需要与作者我一起合作完成这个项目,解决你遇到的问题。 - +2. 加入星球体提问;[知识星球](https://t.zsxq.com/0d8VAD3Dm) +3. 向作者发送邮件648540858@qq.com,寻求技术支持(有偿); WVP-PRO使用Spring boot开发,maven管理依赖。对于熟悉spring开发的朋友是很容易进行编译部署以及运行的。 下面将提供一种通用方法方便大家运行项目。 @@ -44,7 +40,7 @@ window环境,以windows10为例: 这里依然是参考网上教程,自行安装吧。 ## 4 编译ZLMediaKit -参考ZLMediaKit[WIKI](https://github.com/ZLMediaKit/ZLMediaKit/wiki),截取一下关键步骤: +参考ZLMediaKit[WIKI](https://github.com/ZLMediaKit/ZLMediaKit/wiki),如果需要使用语音对讲功能,请参考[zlm启用webrtc编译指南](https://github.com/ZLMediaKit/ZLMediaKit/wiki/zlm%E5%90%AF%E7%94%A8webrtc%E7%BC%96%E8%AF%91%E6%8C%87%E5%8D%97),开启zlm的webrtc功能。截取一下关键步骤: ```bash # 国内用户推荐从同步镜像网站gitee下载 git clone --depth 1 https://gitee.com/xia-chu/ZLMediaKit diff --git a/doc/_content/introduction/config.md b/doc/_content/introduction/config.md index 29f00a4b8..359aac9ef 100644 --- a/doc/_content/introduction/config.md +++ b/doc/_content/introduction/config.md @@ -14,20 +14,11 @@ WVP-PRO通过调用ZLMediaKit的RESTful接口实现对ZLMediaKit行为的控制; 基于spring boot的开发方式,配置文件的加载是很灵活的。默认在src/main/resources/application.yml,部分配置项是可选,你不需要全部配置在配置文件中, 完全的配置说明可以参看all-application.yml。 ### 1.1 默认加载配置文件方式 -使用maven打包后的jar包里,已经存在了配置文件,但是每次打开jar包修改配置文件或者修改后再打包都是比较麻烦的,所以大家可通过指定配置文件路径来加载指定位置的配置文件。 +使用maven打包后的target里,已经存在了配置文件,默认加载配置文件为application.yml,查看内容发现其中spring.profiles.active配置的内容,将入配置的值为dev,那么具体加载的配置文件就是application-dev.yml,如果配置的值找不到对应的配置文件,修改值为dev。 ```shell cd wvp-GB28181-pro/target -java -jar wvp-pro-*.jar --spring.config.location=../src/main/resources/application.yml +java -jar wvp-pro-*.jar ``` -### 1.2 迁移配置文件以方便启动 -由于配置文件的命令比较长,所以为了启动方便通常我会把配置文件放到jar包的同级目录,类似这样, -移除jar包内/BOOT-INF/classes/下所有以application开头的文件,使用解压缩工具打开jar即可,不需要解压出来。 -```shell -cd wvp-GB28181-pro/target -mv ../src/main/resources/application-dev.yml application.yml -java -jar wvp-pro-*.jar -``` -这也是我自己最常用的方式。 ## 2 配置WVP-PRO wvp支持多种数据库,包括Mysql,Postgresql,金仓等,配置任选一种即可。 ### 2.1 数据库配置 @@ -37,27 +28,28 @@ wvp支持多种数据库,包括Mysql,Postgresql,金仓等,配置任选 数据库名称以wvp为例 ```yaml spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true - username: root - password: 12345678 - -mybatis: - configuration: - map-underscore-to-camel-case: true + dynamic: + primary: master + datasource: + master: + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true + username: root + password: root123 ``` #### 2.1.3 Postgresql数据库配置 数据库名称以wvp为例 ```yaml spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - driver-class-name: org.postgresql.Driver - url: jdbc:postgresql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true - username: root - password: 12345678 + dynamic: + primary: master + datasource: + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true + username: root + password: 12345678 pagehelper: helper-dialect: postgresql @@ -66,12 +58,14 @@ pagehelper: 数据库名称以wvp为例 ```yaml spring: - datasource: - type: com.zaxxer.hikari.HikariDataSource - driver-class-name: com.kingbase8.Driver - url: jdbc:kingbase8://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=utf8 - username: root - password: 12345678 + dynamic: + primary: master + datasource: + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: com.kingbase8.Driver + url: jdbc:kingbase8://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=utf8 + username: root + password: 12345678 pagehelper: helper-dialect: postgresql @@ -88,8 +82,6 @@ server: ```yaml # 作为28181服务器的配置 sip: - # [必须修改] 本机的IP - ip: 192.168.1.3 # [可选] 28181服务监听的端口 port: 5060 # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) @@ -106,58 +98,42 @@ sip: ```yaml #zlm 默认服务器配置 media: - # ZLMediaKit的服务ID,必须配置 - id: FQ3TF8yT83wh5Wvz - # [必须修改] zlm服务器的内网IP,sdp-ip与stream-ip使用默认值的情况下,这里不要使用127.0.0.1/0.0.0.0 - ip: 192.168.1.3 - # [必须修改] zlm服务器的http.port - http-port: 6080 - # [可选] zlm服务器的hook.admin_params=secret - secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc - # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 - rtp: - # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 - enable: true - # [可选] 在此范围内选择端口用于媒体流传输, - port-range: 30000,30500 # 端口范围 - # [可选] 国标级联在此范围内选择端口发送媒体流, - send-port-range: 30000,30500 # 端口范围 - # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用 - record-assist-port: 18081 + id: zlmediakit-local + # [必须修改] zlm服务器的内网IP + ip: 172.19.128.50 + # [可选] 有公网IP就配置公网IP, 不可用域名 + wan_ip: + # [必须修改] zlm服务器的http.port + http-port: 9092 + # [可选] zlm服务器访问WVP所使用的IP, 默认使用127.0.0.1,zlm和wvp没有部署在同一台服务器时必须配置 + hook-ip: 172.19.128.50 + # [必选选] zlm服务器的hook.admin_params=secret + secret: TWSYFgYJOQWB4ftgeYut8DW4wbs7pQnj + # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 + rtp: + # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 + enable: true + # [可选] 在此范围内选择端口用于媒体流传输, 必须提前在zlm上配置该属性,不然自动配置此属性可能不成功 + port-range: 30000,35000 # 端口范围 + # [可选] 国标级联在此范围内选择端口发送媒体流, + send-port-range: 40000,40300 # 端口范围 ``` ### 2.4 个性化定制信息配置 ```yaml # [根据业务需求配置] user-settings: - # [可选] 服务ID,不写则为000000 - server-id: - # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true - auto-apply-play: false - # [可选] 部分设备需要扩展SDP,需要打开此设置 - senior-sdp: false - # 保存移动位置历史轨迹:true:保留历史数据,false:仅保留最后的位置(默认) - save-position-history: false - # 点播等待超时时间,单位:毫秒 - play-timeout: 3000 - # 等待音视频编码信息再返回, true: 可以根据编码选择合适的播放器,false: 可以更快点播 - wait-track: false - # 是否开启接口鉴权 - interface-authentication: true - # 自动配置redis 可以过期事件 - redis-config: true - # 接口鉴权例外的接口, 即不进行接口鉴权的接口,尽量详细书写,尽量不用/**,至少两级目录 - interface-authentication-excludes: - - /api/v1/** - # 推流直播是否录制 - record-push-live: true - # 国标是否录制 - record-sip: true - # 是否将日志存储进数据库 - logInDatabase: true - # 第三方匹配,用于从stream钟获取有效信息 - thirdPartyGBIdReg: [\s\S]* + # 点播/录像回放 等待超时时间,单位:毫秒 + play-timeout: 180000 + # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true + auto-apply-play: true + # 推流直播是否录制 + record-push-live: true + # 国标是否录制 + record-sip: true + # 国标点播 按需拉流, true:有人观看拉流,无人观看释放, false:拉起后不自动释放 + stream-on-demand: true ``` - +更多完整的配置信息参考all-application.yml文件,需要那个配置项,复制到正在使用的配置文件中对应的文件即可。 如果配置信息无误,你可以启动zlm,再启动wvp来测试了,启动成功的话,你可以在wvp的日志下看到zlm已连接的提示。 接下来[部署到服务器](./_content/introduction/deployment.md), 如果你只是本地运行直接在本地运行即可。 diff --git a/doc/_content/introduction/deployment.md b/doc/_content/introduction/deployment.md index 3883842a7..f11fe7f26 100644 --- a/doc/_content/introduction/deployment.md +++ b/doc/_content/introduction/deployment.md @@ -19,7 +19,7 @@ 3. 测试环境部署建议所有服务部署在一台主机,关闭防火墙,减少因网络出现问题的可能; 4. 生产环境按需开放端口,但是建议修改默认端口,尤其是5060端口,易受到攻击; -5. zlm使用docker部署的情况,要求端口映射一致,比如映射5060,应将外部端口也映射为5060端口; +5. zlm使用docker部署的情况,请使用host模式,或者端口映射一致,比如映射5060,应将外部端口也映射为5060端口; 6. zlm与wvp会保持高频率的通信,所以不要去将wvp与zlm分属在两个网络,比如wvp在内网,zlm却在公网的情况。 7. 启动服务,以linux为例 **启动WVP-PRO** @@ -36,19 +36,9 @@ nohup ./MediaServer -d -m 3 & ### 前后端分离部署 前后端部署目前在最新的版本已经支持,请使用3月15日之后的版本部署 前端编译后的文件在`src/main/resources/static`中,将此目录下的文件部署。 -前后端分离部署最大的问题是跨域的解决,之前版本使用cookie完成登录流程,而cookie是不可以在复杂跨域中使用的。所以当前版本使用JWT生成的TOKEN作为认证凭据, -部署前端后需要在wvp中配置前端访问的地址以完成跨域流程。 +WVP默认开启全部接口支持跨域。部署前端文件到WEB容器,并将访问的地址设置为WVP的地址即可。 **配置前端服务器** -1. 假如你的服务有公网域名为xxx.com,公网IP为11.11.11.11, 那么你可以在wvp中这样配置: -```yaml -user-settings: - # 跨域配置,配置你访问前端页面的地址即可, 可以配置多个 - allowed-origins: - - http://xxx.com:8008 - - http://11.11.11.11:8008 -``` -配置不是必须的,你使用哪个ip/域名访问就配置哪个即可。修改配置后重启wvp以使配置生效。 -2. 在`src/main/resources/static/static/js/config.js`下配置服务器的地址,也就是wvp服务的地址 +1. 在`src/main/resources/static/static/js/config.js`下配置服务器的地址,也就是wvp服务的地址 ```javascript window.baseUrl = "http://xxx.com:18080" ``` diff --git a/doc/_sidebar.md b/doc/_sidebar.md index bdf595527..09a675df0 100644 --- a/doc/_sidebar.md +++ b/doc/_sidebar.md @@ -1,7 +1,6 @@ * **编译与部署** - * [测试](_content/introduction/test.md) * [编译](_content/introduction/compile.md) * [配置](_content/introduction/config.md) * [部署](_content/introduction/deployment.md) 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 f4774393f..96a987c94 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java @@ -87,6 +87,9 @@ public class StreamInfo implements Serializable, Cloneable{ @Schema(description = "产生源类型,包括 unknown = 0,rtmp_push=1,rtsp_push=2,rtp_push=3,pull=4,ffmpeg_pull=5,mp4_vod=6,device_chn=7") private int originType; + @Schema(description = "转码后的视频流") + private StreamInfo transcodeStream; + public void setFlv(StreamURL flv) { this.flv = flv; } @@ -533,6 +536,14 @@ public class StreamInfo implements Serializable, Cloneable{ this.transactionInfo = transactionInfo; } + public StreamInfo getTranscodeStream() { + return transcodeStream; + } + + public void setTranscodeStream(StreamInfo transcodeStream) { + this.transcodeStream = transcodeStream; + } + @Override public StreamInfo clone() { StreamInfo instance = null; 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 eac7c1138..a9ea67f74 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/MediaConfig.java @@ -27,19 +27,19 @@ public class MediaConfig{ @Value("${media.ip}") private String ip; - @Value("${media.hook-ip:}") - private String hookIp; + @Value("${media.wan_ip:}") + private String wanIp; - @Value("${sip.ip}") - private String sipIp; + @Value("${media.hook-ip:127.0.0.1}") + private String hookIp; @Value("${sip.domain}") private String sipDomain; - @Value("${media.sdp-ip:${media.ip}}") + @Value("${media.sdp-ip:${media.wan_ip:}}") private String sdpIp; - @Value("${media.stream-ip:${media.ip}}") + @Value("${media.stream-ip:${media.wan_ip:}}") private String streamIp; @Value("${media.http-port:0}") @@ -111,20 +111,7 @@ public class MediaConfig{ } public String getHookIp() { - if (ObjectUtils.isEmpty(hookIp)){ - return sipIp; - }else { - return hookIp; - } - - } - - public String getSipIp() { - if (sipIp == null) { - return this.ip; - }else { - return sipIp; - } + return hookIp; } public int getHttpPort() { @@ -296,4 +283,12 @@ public class MediaConfig{ } return false; } + + public String getWanIp() { + return wanIp; + } + + public void setWanIp(String wanIp) { + this.wanIp = wanIp; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java index 6a6c54f90..4b85e0da8 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/SipConfig.java @@ -98,9 +98,6 @@ public class SipConfig { } public String getShowIp() { - if (this.showIp == null) { - return this.ip; - } return showIp; } 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 cce914594..18114c5a4 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java @@ -21,7 +21,7 @@ public class UserSetting { private Boolean seniorSdp = Boolean.FALSE; - private Integer playTimeout = 18000; + private Integer playTimeout = 10000; private int platformPlayTimeout = 20000; @@ -74,7 +74,7 @@ public class UserSetting { private boolean registerKeepIntDialog = false; - private int gbDeviceOnline = 0; + private int gbDeviceOnline = 1; public Boolean getSavePositionHistory() { return savePositionHistory; diff --git a/src/main/java/com/genersoft/iot/vmp/conf/WVPTimerTask.java b/src/main/java/com/genersoft/iot/vmp/conf/WVPTimerTask.java index c58625501..229aeabb7 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/WVPTimerTask.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/WVPTimerTask.java @@ -22,7 +22,7 @@ public class WVPTimerTask { @Scheduled(fixedRate = 2 * 1000) //每3秒执行一次 public void execute(){ JSONObject jsonObject = new JSONObject(); - jsonObject.put("ip", sipConfig.getIp()); + jsonObject.put("ip", sipConfig.getShowIp()); jsonObject.put("port", serverPort); redisCatchStorage.updateWVPInfo(jsonObject, 3); } 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 56663f18d..d52fbc519 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java @@ -16,6 +16,9 @@ import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import javax.sip.*; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -36,19 +39,50 @@ public class SipLayer implements CommandLineRunner { private final Map tcpSipProviderMap = new ConcurrentHashMap<>(); private final Map udpSipProviderMap = new ConcurrentHashMap<>(); + private final List monitorIps = new ArrayList<>(); @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)); + if (ObjectUtils.isEmpty(sipConfig.getIp())) { + try { + // 获得本机的所有网络接口 + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = nifs.nextElement(); + // 获得与该网络接口绑定的 IP 地址,一般只有一个 + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (addr instanceof Inet4Address) { + if (addr.getHostAddress().equals("127.0.0.1")){ + continue; + } + if (nif.getName().startsWith("docker")) { + continue; + } + logger.info("[自动配置SIP监听网卡] 网卡接口地址: {}", addr.getHostAddress());// 只关心 IPv4 地址 + monitorIps.add(addr.getHostAddress()); + } + } + } + }catch (Exception e) { + logger.error("[读取网卡信息失败]", e); + } + if (monitorIps.isEmpty()) { + logger.error("[自动配置SIP监听网卡信息失败], 请手动配置SIP.IP后重新启动"); + System.exit(1); + } }else { - monitorIps.add(sipConfig.getIp()); + // 使用逗号分割多个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()); + } } - + sipConfig.setShowIp(String.join(",", monitorIps)); SipFactory.getInstance().setPathName("gov.nist"); if (monitorIps.size() > 0) { for (String monitorIp : monitorIps) { @@ -105,6 +139,9 @@ public class SipLayer implements CommandLineRunner { } public SipProviderImpl getUdpSipProvider(String ip) { + if (udpSipProviderMap.size() == 1) { + return udpSipProviderMap.values().stream().findFirst().get(); + } if (ObjectUtils.isEmpty(ip)) { return null; } @@ -126,6 +163,9 @@ public class SipLayer implements CommandLineRunner { } public SipProviderImpl getTcpSipProvider(String ip) { + if (tcpSipProviderMap.size() == 1) { + return tcpSipProviderMap.values().stream().findFirst().get(); + } if (ObjectUtils.isEmpty(ip)) { return null; } @@ -133,6 +173,9 @@ public class SipLayer implements CommandLineRunner { } public String getLocalIp(String deviceLocalIp) { + if (monitorIps.size() == 1) { + return monitorIps.get(0); + } if (!ObjectUtils.isEmpty(deviceLocalIp)) { return deviceLocalIp; } 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 index dd30fea53..8787ed1fd 100755 --- 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 @@ -351,38 +351,6 @@ 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/event/request/impl/InviteRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/InviteRequestProcessor.java index de1a92907..411c40fd2 100755 --- 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 @@ -27,6 +27,7 @@ import com.genersoft.iot.vmp.media.event.hook.HookType; import com.genersoft.iot.vmp.media.service.IMediaServerService; import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; import com.genersoft.iot.vmp.service.IPlayService; import com.genersoft.iot.vmp.service.IStreamProxyService; import com.genersoft.iot.vmp.service.IStreamPushService; @@ -593,12 +594,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements sendRtpItem.setPlayType(InviteStreamType.PUSH); if (streamPushItem != null) { // 从redis查询是否正在接收这个推流 - StreamPushItem pushListItem = redisCatchStorage.getPushListItem(gbStream.getApp(), gbStream.getStream()); + OnStreamChangedHookParam pushListItem = redisCatchStorage.getPushListItem(gbStream.getApp(), gbStream.getStream()); if (pushListItem != null) { - sendRtpItem.setServerId(pushListItem.getServerId()); + sendRtpItem.setServerId(pushListItem.getSeverId()); sendRtpItem.setMediaServerId(pushListItem.getMediaServerId()); - pushListItem.setSelf(userSetting.getServerId().equals(pushListItem.getServerId())); redisCatchStorage.updateSendRTPSever(sendRtpItem); // 开始推流 sendPushStream(sendRtpItem, mediaServerItem, platform, request); @@ -618,12 +618,14 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } } else if ("proxy".equals(gbStream.getStreamType())) { if (null != proxyByAppAndStream) { + sendRtpItem.setServerId(userSetting.getServerId()); if (sendRtpItem.getSsrc() == null) { // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 String ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId()); sendRtpItem.setSsrc(ssrc); } - if (proxyByAppAndStream.isStatus()) { + MediaInfo mediaInfo = redisCatchStorage.getProxyStream(gbStream.getApp(), gbStream.getStream()); + if (mediaInfo != null) { sendProxyStream(sendRtpItem, mediaServerItem, platform, request); } else { //开启代理拉流 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 index 6185cda4a..155d5f73d 100755 --- 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 @@ -315,8 +315,8 @@ public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent } } - @Scheduled(fixedRate = 10000) //每1秒执行一次 - public void execute(){ - logger.info("[待处理Notify-目录订阅消息数量]: {}", taskQueue.size()); - } +// @Scheduled(fixedRate = 10000) //每1秒执行一次 +// public void execute(){ +// logger.info("[待处理Notify-目录订阅消息数量]: {}", taskQueue.size()); +// } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForMobilePositionProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForMobilePositionProcessor.java index 52fc7a3fc..9c414a855 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForMobilePositionProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForMobilePositionProcessor.java @@ -188,8 +188,8 @@ public class NotifyRequestForMobilePositionProcessor extends SIPRequestProcessor } taskQueue.clear(); } - @Scheduled(fixedRate = 10000) - public void execute(){ - logger.info("[待处理Notify-移动位置订阅消息数量]: {}", taskQueue.size()); - } +// @Scheduled(fixedRate = 10000) +// public void execute(){ +// logger.debug("[待处理Notify-移动位置订阅消息数量]: {}", taskQueue.size()); +// } } 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 index 69dc45514..1f0ba5393 100755 --- 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 @@ -114,6 +114,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen 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 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 index bfc42f5ad..85f2da747 100755 --- 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 @@ -78,11 +78,12 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp } RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress()); - if (!device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort()) { + if (!device.getIp().equalsIgnoreCase(remoteAddressInfo.getIp()) || device.getPort() != remoteAddressInfo.getPort() || !request.getRemoteAddress().getHostAddress().equals(device.getLocalIp())) { 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()); + device.setLocalIp(request.getRemoteAddress().getHostAddress()); // 设备地址变化会引起目录订阅任务失效,需要重新添加 if (device.getSubscribeCycleForCatalog() > 0) { deviceService.removeCatalogSubscribe(device, result->{ diff --git a/src/main/java/com/genersoft/iot/vmp/media/MediaServerConfig.java b/src/main/java/com/genersoft/iot/vmp/media/MediaServerConfig.java index fb9de3f89..ccd2828e1 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/MediaServerConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/media/MediaServerConfig.java @@ -1,9 +1,9 @@ package com.genersoft.iot.vmp.media; import com.genersoft.iot.vmp.conf.MediaConfig; +import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.mediaServer.MediaServerChangeEvent; import com.genersoft.iot.vmp.media.service.IMediaServerService; -import com.genersoft.iot.vmp.media.bean.MediaServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/com/genersoft/iot/vmp/media/bean/MediaServer.java b/src/main/java/com/genersoft/iot/vmp/media/bean/MediaServer.java index c951e41f6..e6faac364 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/bean/MediaServer.java +++ b/src/main/java/com/genersoft/iot/vmp/media/bean/MediaServer.java @@ -15,7 +15,7 @@ public class MediaServer { private String ip; @Schema(description = "hook使用的IP(zlm访问WVP使用的IP)") - private String hookIp; + private String hookIp = "127.0.0.1"; @Schema(description = "SDP IP") private String sdpIp; diff --git a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaArrivalEvent.java b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaArrivalEvent.java index 2379321a4..2f51a3e21 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaArrivalEvent.java +++ b/src/main/java/com/genersoft/iot/vmp/media/event/media/MediaArrivalEvent.java @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.media.event.media; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; +import com.genersoft.iot.vmp.vmanager.bean.StreamContent; /** * 流到来事件 @@ -19,7 +20,8 @@ public class MediaArrivalEvent extends MediaEvent { mediaArrivalEvent.setStream(hookParam.getStream()); mediaArrivalEvent.setMediaServer(mediaServer); mediaArrivalEvent.setSchema(hookParam.getSchema()); - mediaArrivalEvent.setCallId(hookParam.getCallId()); + mediaArrivalEvent.setSchema(hookParam.getSchema()); + mediaArrivalEvent.setHookParam(hookParam); return mediaArrivalEvent; } @@ -27,6 +29,10 @@ public class MediaArrivalEvent extends MediaEvent { private String callId; + private OnStreamChangedHookParam hookParam; + + private StreamContent streamInfo; + public MediaInfo getMediaInfo() { return mediaInfo; } @@ -43,4 +49,20 @@ public class MediaArrivalEvent extends MediaEvent { public void setCallId(String callId) { this.callId = callId; } + + public OnStreamChangedHookParam getHookParam() { + return hookParam; + } + + public void setHookParam(OnStreamChangedHookParam hookParam) { + this.hookParam = hookParam; + } + + public StreamContent getStreamInfo() { + return streamInfo; + } + + public void setStreamInfo(StreamContent streamInfo) { + this.streamInfo = streamInfo; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java index 24eb59055..2e69f2334 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/media/service/impl/MediaServerServiceImpl.java @@ -809,6 +809,13 @@ public class MediaServerServiceImpl implements IMediaServerService { streamInfoResult.setRtc(addr, mediaServer.getHttpPort(),mediaServer.getHttpSSlPort(), app, stream, callIdParam, isPlay); streamInfoResult.setMediaInfo(mediaInfo); + + if (!"broadcast".equalsIgnoreCase(app) && !ObjectUtils.isEmpty(mediaServer.getTranscodeSuffix()) && !"null".equalsIgnoreCase(mediaServer.getTranscodeSuffix())) { + String newStream = stream + "_" + mediaServer.getTranscodeSuffix(); + mediaServer.setTranscodeSuffix(null); + StreamInfo transcodeStreamInfo = getStreamInfoByAppAndStream(mediaServer, app, newStream, null, addr, callId, isPlay); + streamInfoResult.setTranscodeStream(transcodeStreamInfo); + } return streamInfoResult; } 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 index 738d3f80d..636b4bf1c 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -190,7 +190,15 @@ public class ZLMHttpHookListener { } } - + /** + * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 + */ +// @ResponseBody +// @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") +// public HookResult onStreamChanged(@RequestBody JSONObject param) { +// System.out.println(11); +// return HookResult.SUCCESS(); +// } /** * rtsp/rtmp流注册或注销时触发此事件;此事件对回复不敏感。 */ diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java index f4e521213..26e27677b 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaNodeServerService.java @@ -8,15 +8,14 @@ import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; import com.genersoft.iot.vmp.media.bean.MediaInfo; -import com.genersoft.iot.vmp.media.service.IMediaNodeServerService; import com.genersoft.iot.vmp.media.bean.MediaServer; +import com.genersoft.iot.vmp.media.service.IMediaNodeServerService; import com.genersoft.iot.vmp.media.zlm.dto.ZLMServerConfig; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; @@ -36,9 +35,6 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { @Autowired private ZLMServerFactory zlmServerFactory; - @Value("${sip.ip}") - private String sipIp; - @Override public int createRTPServer(MediaServer mediaServer, String streamId, long ssrc, Integer port, Boolean onlyAuto, Boolean disableAudio, Boolean reUsePort, Integer tcpMode) { return zlmServerFactory.createRTPServer(mediaServer, streamId, ssrc, port, onlyAuto, disableAudio, reUsePort, tcpMode); @@ -120,7 +116,7 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService { mediaServer.setRtpProxyPort(zlmServerConfig.getRtpProxyPort()); mediaServer.setStreamIp(ip); - mediaServer.setHookIp(sipIp.split(",")[0]); + mediaServer.setHookIp("127.0.0.1"); mediaServer.setSdpIp(ip); mediaServer.setType("zlm"); return mediaServer; 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 index 285b9e6b8..5d5f1c317 100755 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java @@ -96,7 +96,6 @@ public class ZLMRESTfulUtils { if (callback == null) { try { Response response = client.newCall(request).execute(); - if (response.isSuccessful()) { ResponseBody responseBody = response.body(); if (responseBody != null) { diff --git a/src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java b/src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java index c06400dce..e54fa92a3 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java @@ -14,6 +14,8 @@ public interface IInviteStreamService { */ void updateInviteInfo(InviteInfo inviteInfo); + void updateInviteInfo(InviteInfo inviteInfo, Long time); + InviteInfo updateInviteInfoForStream(InviteInfo inviteInfo, String stream); /** diff --git a/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java b/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java index c71868177..e315d927a 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java @@ -43,9 +43,9 @@ public interface IStreamPushService { /** * 停止一路推流 * @param app 应用名 - * @param streamId 流ID + * @param stream 流ID */ - boolean stop(String app, String streamId); + boolean stop(String app, String stream); /** * 新的节点加入 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 index 6e0096099..e5847f69a 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; @Service @DS("master") @@ -64,9 +65,13 @@ public class InviteStreamServiceImpl implements IInviteStreamService { } } } - @Override public void updateInviteInfo(InviteInfo inviteInfo) { + updateInviteInfo(inviteInfo, null); + } + + @Override + public void updateInviteInfo(InviteInfo inviteInfo, Long time) { if (inviteInfo == null || (inviteInfo.getDeviceId() == null || inviteInfo.getChannelId() == null)) { logger.warn("[更新Invite信息],参数不全: {}", JSON.toJSON(inviteInfo)); return; @@ -118,7 +123,11 @@ public class InviteStreamServiceImpl implements IInviteStreamService { ":" + inviteInfoForUpdate.getChannelId() + ":" + inviteInfoForUpdate.getStream()+ ":" + inviteInfoForUpdate.getSsrcInfo().getSsrc(); - redisTemplate.opsForValue().set(key, inviteInfoForUpdate); + if (time != null && time > 0) { + redisTemplate.opsForValue().set(key, inviteInfoForUpdate, time, TimeUnit.SECONDS); + }else { + redisTemplate.opsForValue().set(key, inviteInfoForUpdate); + } } @Override @@ -243,6 +252,9 @@ public class InviteStreamServiceImpl implements IInviteStreamService { String keyStr = (String) keyObj; InviteInfo inviteInfo = (InviteInfo) redisTemplate.opsForValue().get(keyStr); if (inviteInfo != null && inviteInfo.getStreamInfo() != null && inviteInfo.getStreamInfo().getMediaServerId().equals(mediaServerId)) { + if (inviteInfo.getType().equals(InviteSessionType.DOWNLOAD) && inviteInfo.getStreamInfo().getProgress() == 1) { + continue; + } count++; } } 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 index bc85ecf94..39c67c264 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/PlayServiceImpl.java @@ -1038,7 +1038,8 @@ public class PlayServiceImpl implements IPlayService { InviteInfo inviteInfoForNew = inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId() , inviteInfo.getChannelId(), inviteInfo.getStream()); inviteInfoForNew.getStreamInfo().setDownLoadFilePath(downloadFileInfo); - inviteStreamService.updateInviteInfo(inviteInfoForNew); + // 不可以马上移除会导致后续接口拿不到下载地址 + inviteStreamService.updateInviteInfo(inviteInfoForNew, 60*15L); }; Hook hook = Hook.getInstance(HookType.on_record_mp4, "rtp", ssrcInfo.getStream(), mediaServerItem.getId()); // 设置过期时间,下载失败时自动处理订阅数据 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 index a64b71a87..18de78a03 100755 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java @@ -26,6 +26,7 @@ import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.*; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo; +import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.slf4j.Logger; @@ -126,18 +127,13 @@ public class StreamPushServiceImpl implements IStreamPushService { streamPushMapper.update(transform); gbStreamMapper.updateMediaServer(event.getApp(), event.getStream(), event.getMediaServer().getId()); } - // TODO 相关的事件自行管理,不需要写入ZLMMediaListManager -// ChannelOnlineEvent channelOnlineEventLister = getChannelOnlineEventLister(transform.getApp(), transform.getStream()); -// if ( channelOnlineEventLister != null) { -// try { -// channelOnlineEventLister.run(transform.getApp(), transform.getStream(), transform.getServerId());; -// } catch (ParseException e) { -// logger.error("addPush: ", e); -// } -// removedChannelOnlineEventLister(transform.getApp(), transform.getStream()); -// } // 冗余数据,自己系统中自用 - redisCatchStorage.addPushListItem(event.getApp(), event.getStream(), event); + if (!"broadcast".equals(event.getApp()) && !"talk".equals(event.getApp())) { + StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream( + event.getMediaServer(), event.getApp(), event.getStream(), event.getMediaInfo(), event.getCallId()); + event.getHookParam().setStreamInfo(new StreamContent(streamInfo)); + redisCatchStorage.addPushListItem(event.getApp(), event.getStream(), event); + } // 发送流变化redis消息 JSONObject jsonObject = new JSONObject(); @@ -274,19 +270,19 @@ public class StreamPushServiceImpl implements IStreamPushService { } @Override - public boolean stop(String app, String streamId) { - logger.info("[推流 ] 停止流: {}/{}", app, streamId); - StreamPushItem streamPushItem = streamPushMapper.selectOne(app, streamId); + public boolean stop(String app, String stream) { + logger.info("[推流 ] 停止流: {}/{}", app, stream); + StreamPushItem streamPushItem = streamPushMapper.selectOne(app, stream); if (streamPushItem != null) { gbStreamService.sendCatalogMsg(streamPushItem, CatalogEvent.DEL); } - platformGbStreamMapper.delByAppAndStream(app, streamId); - gbStreamMapper.del(app, streamId); - int delStream = streamPushMapper.del(app, streamId); + platformGbStreamMapper.delByAppAndStream(app, stream); + gbStreamMapper.del(app, stream); + int delStream = streamPushMapper.del(app, stream); if (delStream > 0) { MediaServer mediaServerItem = mediaServerService.getOne(streamPushItem.getMediaServerId()); - mediaServerService.closeStreams(mediaServerItem,app, streamId); + mediaServerService.closeStreams(mediaServerItem,app, stream); } return true; } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java index 2347f5025..869d39bf9 100755 --- a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java @@ -7,7 +7,7 @@ import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; -import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; @@ -135,6 +135,8 @@ public interface IRedisCatchStorage { MediaInfo getStreamInfo(String app, String streamId, String mediaServerId); + MediaInfo getProxyStream(String app, String streamId); + void addCpuInfo(double cpuInfo); void addMemInfo(double memInfo); @@ -214,7 +216,7 @@ public interface IRedisCatchStorage { void addPushListItem(String app, String stream, MediaArrivalEvent param); - StreamPushItem getPushListItem(String app, String stream); + OnStreamChangedHookParam getPushListItem(String app, String stream); void removePushListItem(String app, String stream, String mediaServerId); 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 index a654ea06b..af1b42426 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java @@ -53,7 +53,7 @@ public interface CloudRecordServiceMapper { " and id in " + " #{item}" + " " + - " order by start_time DESC" + + " order by start_time ASC" + " ") List getList(@Param("query") String query, @Param("app") String app, @Param("stream") String stream, @Param("startTimeStamp")Long startTimeStamp, @Param("endTimeStamp")Long endTimeStamp, diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java index b743cfc62..e70e195c7 100755 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java @@ -5,16 +5,13 @@ import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.SystemAllInfo; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.conf.UserSetting; -import com.genersoft.iot.vmp.gb28181.bean.AlarmChannelMessage; -import com.genersoft.iot.vmp.gb28181.bean.Device; -import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch; -import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; +import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.media.bean.MediaInfo; import com.genersoft.iot.vmp.media.bean.MediaServer; import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent; -import com.genersoft.iot.vmp.gb28181.bean.*; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; +import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; import com.genersoft.iot.vmp.service.bean.GPSMsgInfo; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; @@ -496,6 +493,20 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { return result; } + @Override + public MediaInfo getProxyStream(String app, String streamId) { + String scanKey = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetting.getServerId() + "_PULL_" + app + "_" + streamId + "_*"; + + MediaInfo result = null; + List keys = RedisUtil.scan(redisTemplate, scanKey); + if (keys.size() > 0) { + String key = (String) keys.get(0); + result = JsonUtil.redisJsonToObject(redisTemplate, key, MediaInfo.class); + } + + return result; + } + @Override public void addCpuInfo(double cpuInfo) { String key = VideoManagerConstants.SYSTEM_INFO_CPU_PREFIX + userSetting.getServerId(); @@ -684,14 +695,13 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Override public void addPushListItem(String app, String stream, MediaArrivalEvent event) { String key = VideoManagerConstants.PUSH_STREAM_LIST + app + "_" + stream; - StreamPushItem streamPushItem = StreamPushItem.getInstance(event, userSetting.getServerId()); - redisTemplate.opsForValue().set(key, streamPushItem); + redisTemplate.opsForValue().set(key, event.getHookParam()); } @Override - public StreamPushItem getPushListItem(String app, String stream) { + public OnStreamChangedHookParam getPushListItem(String app, String stream) { String key = VideoManagerConstants.PUSH_STREAM_LIST + app + "_" + stream; - return (StreamPushItem)redisTemplate.opsForValue().get(key); + return (OnStreamChangedHookParam)redisTemplate.opsForValue().get(key); } @Override diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java index 193c95d4a..ce7425127 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/bean/StreamContent.java @@ -98,6 +98,9 @@ public class StreamContent { @Schema(description = "文件下载地址(录像下载使用)") private DownloadFileInfo downLoadFilePath; + @Schema(description = "转码后的视频流") + private StreamContent transcodeStream; + private double progress; public StreamContent(StreamInfo streamInfo) { @@ -179,6 +182,17 @@ public class StreamContent { if (streamInfo.getDownLoadFilePath() != null) { this.downLoadFilePath = streamInfo.getDownLoadFilePath(); } + if (streamInfo.getTranscodeStream() != null) { + this.transcodeStream = new StreamContent(streamInfo.getTranscodeStream()); + } + } + + public StreamContent getTranscodeStream() { + return transcodeStream; + } + + public void setTranscodeStream(StreamContent transcodeStream) { + this.transcodeStream = transcodeStream; } public String getApp() { diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java index 97803f987..debc3b2f0 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/platform/PlatformController.java @@ -89,7 +89,7 @@ public class PlatformController { @GetMapping("/server_config") public JSONObject serverConfig() { JSONObject result = new JSONObject(); - result.put("deviceIp", sipConfig.getIp()); + result.put("deviceIp", sipConfig.getShowIp()); result.put("devicePort", sipConfig.getPort()); result.put("username", sipConfig.getId()); result.put("password", sipConfig.getPassword()); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/streamPush/StreamPushController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/streamPush/StreamPushController.java index 2ea4b8730..3717e69f4 100755 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/streamPush/StreamPushController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/streamPush/StreamPushController.java @@ -113,8 +113,8 @@ public class StreamPushController { @Operation(summary = "中止一个推流", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Parameter(name = "app", description = "应用名", required = true) @Parameter(name = "stream", description = "流id", required = true) - public void stop(String app, String streamId){ - if (!streamPushService.stop(app, streamId)){ + public void stop(String app, String stream){ + if (!streamPushService.stop(app, stream)){ throw new ControllerException(ErrorCode.ERROR100); } } diff --git a/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiController.java b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiController.java index 3457cf993..e0973b961 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiController.java @@ -40,7 +40,7 @@ public class ApiController { result.put("Server",""); result.put("SIPSerial", sipConfig.getId()); result.put("SIPRealm", sipConfig.getDomain()); - result.put("SIPHost", sipConfig.getIp()); + result.put("SIPHost", sipConfig.getShowIp()); result.put("SIPPort", sipConfig.getPort()); result.put("ChannelCount","1000"); result.put("VersionType",""); diff --git a/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java index be740c193..8937fd86e 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java @@ -126,13 +126,32 @@ public class ApiStreamController { resultJjson.put("ChannelID", code); resultJjson.put("ChannelName", deviceChannel.getName()); resultJjson.put("ChannelCustomName", ""); - resultJjson.put("FLV", streamInfo.getFlv().getUrl()); - if(streamInfo.getHttps_flv() != null) { - resultJjson.put("HTTPS_FLV", streamInfo.getHttps_flv().getUrl()); + if (streamInfo.getTranscodeStream() != null) { + resultJjson.put("FLV", streamInfo.getTranscodeStream().getFlv().getUrl()); + }else { + resultJjson.put("FLV", streamInfo.getFlv().getUrl()); + } - resultJjson.put("WS_FLV", streamInfo.getWs_flv().getUrl()); + if(streamInfo.getHttps_flv() != null) { + if (streamInfo.getTranscodeStream() != null) { + resultJjson.put("HTTPS_FLV", streamInfo.getTranscodeStream().getHttps_flv().getUrl()); + }else { + resultJjson.put("HTTPS_FLV", streamInfo.getHttps_flv().getUrl()); + } + } + + if (streamInfo.getTranscodeStream() != null) { + resultJjson.put("WS_FLV", streamInfo.getTranscodeStream().getWs_flv().getUrl()); + }else { + resultJjson.put("WS_FLV", streamInfo.getWs_flv().getUrl()); + } + if(streamInfo.getWss_flv() != null) { - resultJjson.put("WSS_FLV", streamInfo.getWss_flv().getUrl()); + if (streamInfo.getTranscodeStream() != null) { + resultJjson.put("WSS_FLV", streamInfo.getTranscodeStream().getWss_flv().getUrl()); + }else { + resultJjson.put("WSS_FLV", streamInfo.getWss_flv().getUrl()); + } } resultJjson.put("RTMP", streamInfo.getRtmp().getUrl()); if (streamInfo.getRtmps() != null) { diff --git a/src/main/resources/all-application.yml b/src/main/resources/all-application.yml index 5c46d5202..31db6807e 100644 --- a/src/main/resources/all-application.yml +++ b/src/main/resources/all-application.yml @@ -139,12 +139,14 @@ media: id: # [必须修改] zlm服务器的内网IP ip: 192.168.0.100 + # [可选] 有公网IP就配置公网IP, 不可用域名 + wan_ip: # [可选] 返回流地址时的ip,置空使用 media.ip stream-ip: # [可选] wvp在国标信令中使用的ip,此ip为摄像机可以访问到的ip, 置空使用 media.ip sdp-ip: - # [可选] zlm服务器的hook所使用的IP, 默认使用sip.ip - hook-ip: + # [可选] zlm服务器访问WVP所使用的IP, 默认使用127.0.0.1,zlm和wvp没有部署在同一台服务器时必须配置 + hook-ip: 172.19.128.50 # [必须修改] zlm服务器的http.port http-port: 80 # [可选] zlm服务器的http.sslport, 置空使用zlm配置文件配置 @@ -249,7 +251,7 @@ user-settings: - http://192.168.1.3:8008 # 国标设备离线后的上线策略, # 0: 国标标准实现,设备离线后不回复心跳,直到设备重新注册上线, - # 1: 对于离线设备,收到心跳就把设备设置为上线,并更新注册时间为上次这次心跳的时间。防止过期时间判断异常 + # 1(默认): 对于离线设备,收到心跳就把设备设置为上线,并更新注册时间为上次这次心跳的时间。防止过期时间判断异常 gb-device-online: 0 # 关闭在线文档(生产环境建议关闭) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index e61ac0b39..93c8af3b0 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -58,11 +58,6 @@ server: # 作为28181服务器的配置 sip: - # [必须修改] 本机的IP,对应你的网卡,监听什么ip就是使用什么网卡, - # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4 - # 如果不明白,就使用0.0.0.0,大部分情况都是可以的 - # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。 - ip: 172.19.128.50 # [可选] 28181服务监听的端口 port: 8116 # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) @@ -82,18 +77,14 @@ media: id: zlmediakit-local # [必须修改] zlm服务器的内网IP ip: 172.19.128.50 + # [可选] 有公网IP就配置公网IP, 不可用域名 + wan_ip: # [必须修改] zlm服务器的http.port http-port: 9092 - # [可选] 返回流地址时的ip,置空使用 media.ip - stream-ip: 172.19.128.50 - # [可选] wvp在国标信令中使用的ip,此ip为摄像机可以访问到的ip, 置空使用 media.ip - sdp-ip: 172.19.128.50 - # [可选] zlm服务器的hook所使用的IP, 默认使用sip.ip + # [可选] zlm服务器访问WVP所使用的IP, 默认使用127.0.0.1,zlm和wvp没有部署在同一台服务器时必须配置 hook-ip: 172.19.128.50 - # [可选] zlm服务器的http.sslport, 置空使用zlm配置文件配置 - http-ssl-port: 1443 - # [可选] zlm服务器的hook.admin_params=secret - secret: 10000 + # [必选选] zlm服务器的hook.admin_params=secret + secret: TWSYFgYJOQWB4ftgeYut8DW4wbs7pQnj # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 rtp: # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 @@ -102,14 +93,16 @@ media: port-range: 50000,50300 # 端口范围 # [可选] 国标级联在此范围内选择端口发送媒体流, send-port-range: 50000,50300 # 端口范围 - # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用 - record-assist-port: 18081 # [根据业务需求配置] user-settings: # 点播/录像回放 等待超时时间,单位:毫秒 play-timeout: 180000 # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true auto-apply-play: true - # 设备/通道状态变化时发送消息 - device-status-notify: true + # 推流直播是否录制 + record-push-live: true + # 国标是否录制 + record-sip: true + # 国标点播 按需拉流, true:有人观看拉流,无人观看释放, false:拉起后不自动释放 + stream-on-demand: true diff --git a/web_src/src/components/channelList.vue b/web_src/src/components/channelList.vue index ef4920503..ff1d78420 100755 --- a/web_src/src/components/channelList.vue +++ b/web_src/src/components/channelList.vue @@ -604,12 +604,13 @@ export default { if (row.location) { const segements = row.location.split(","); if (segements.length !== 2) { + console.log(1) this.$message.warning("位置信息格式有误,例:117.234,36.378"); return; } else { row.customLongitude = parseFloat(segements[0]); - row.custom_latitude = parseFloat(segements[1]); - if (!(row.longitude && row.latitude)) { + row.customLatitude = parseFloat(segements[1]); + if (!(row.customLongitude && row.customLatitude)) { this.$message.warning("位置信息格式有误,例:117.234,36.378"); return; } diff --git a/web_src/src/components/dialog/devicePlayer.vue b/web_src/src/components/dialog/devicePlayer.vue index 1c9705bc5..e0a1d01da 100755 --- a/web_src/src/components/dialog/devicePlayer.vue +++ b/web_src/src/components/dialog/devicePlayer.vue @@ -473,10 +473,14 @@ export default { }, getUrlByStreamInfo() { console.log(this.streamInfo) + let streamInfo = this.streamInfo + if (this.streamInfo.transcodeStream) { + streamInfo = this.streamInfo.transcodeStream; + } if (location.protocol === "https:") { - this.videoUrl = this.streamInfo[this.player[this.activePlayer][1]] + this.videoUrl = streamInfo[this.player[this.activePlayer][1]] } else { - this.videoUrl = this.streamInfo[this.player[this.activePlayer][0]] + this.videoUrl = streamInfo[this.player[this.activePlayer][0]] } return this.videoUrl;