diff --git a/.github/ISSUE_TEMPLATE/-------.md b/.github/ISSUE_TEMPLATE/-------.md deleted file mode 100644 index 461ce76a6..000000000 --- a/.github/ISSUE_TEMPLATE/-------.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: "[ 新功能 ]" -about: 新功能 -title: '' -labels: '' -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/--bug---.md b/.github/ISSUE_TEMPLATE/bug.md similarity index 62% rename from .github/ISSUE_TEMPLATE/--bug---.md rename to .github/ISSUE_TEMPLATE/bug.md index ff09d5cf2..7e61cd7c8 100644 --- a/.github/ISSUE_TEMPLATE/--bug---.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -1,29 +1,36 @@ --- name: "[ BUG ] " -about: Create a report to help us improve -title: '' -labels: '' +about: 关于wvp的bug,与zlm有关的建议直接在zlm的issue中提问 +title: 'BUG' +labels: 'wvp的bug' assignees: '' --- +**环境信息:** + + - 1. 部署方式 wvp-pro docker / zlm(docker) + 编译wvp-pro/ wvp-prp + zlm都是编译部署/ + - 2. 部署环境 windows / ubuntu/ centos ... + - 3. 端口开放情况 + - 4. 是否是公网部署 + - 5. 是否使用https + - 6. 方便的话提供下使用的设备品牌或平台 + - 7. 你做过哪些尝试 + - 8. 代码更新时间 + **描述错误** 描述下您遇到的问题 **如何复现** 有明确复现步骤的问题会很容易被解决 -**预期行为** -清晰简洁的描述您期望发生的事情 +**截图** -**截图** +**抓包文件** + +**日志** +``` +日志内容放这里, 文件的话请直接上传 +``` -**环境信息:** - - 1. 部署方式 wvp-pro docker / zlm(docker) + 编译wvp-pro/ wvp-prp + zlm都是编译部署/ - - 2. 部署环境 windows / ubuntu/ centos ... - - 3. 端口开放情况 - - 4. 是否是公网部署 - - 5. 是否使用https - - 6. 方便的话提供下使用的设备品牌或平台 - - 7. 你做过哪些尝试 diff --git a/.github/ISSUE_TEMPLATE/new.md b/.github/ISSUE_TEMPLATE/new.md new file mode 100644 index 000000000..796142189 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new.md @@ -0,0 +1,13 @@ +--- +name: "[ 新功能 ]" +about: 新功能 +title: '希望wVP实现的新功能,此功能应与你的具体业务无关' +labels: '' +assignees: '' + +--- + +**项目的详细需求** + +**这样的实现什么作用** + diff --git a/.github/ISSUE_TEMPLATE/solve.md b/.github/ISSUE_TEMPLATE/solve.md new file mode 100644 index 000000000..75a0eed76 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/solve.md @@ -0,0 +1,31 @@ +--- +name: "[ 技术咨询 ] " +about: 对于使用中遇到问题 +title: '技术咨询' +labels: '技术咨询' +assignees: '' + +--- + +**环境信息:** + + - 1. 部署方式 wvp-pro docker / zlm(docker) + 编译wvp-pro/ wvp-prp + zlm都是编译部署/ + - 2. 部署环境 windows / ubuntu/ centos ... + - 3. 端口开放情况 + - 4. 是否是公网部署 + - 5. 是否使用https + - 6. 方便的话提供下使用的设备品牌或平台 + - 7. 你做过哪些尝试 + - 8. 代码更新时间 + + +**内容描述:** + +**截图** + +**抓包文件** + +**日志** +``` +日志内容放这里, 文件的话请直接上传 +``` diff --git a/README.md b/README.md index d9e44ab9d..55766ec0f 100644 --- a/README.md +++ b/README.md @@ -15,17 +15,21 @@ WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网 前端页面基于@Kyle MediaServerUI [https://gitee.com/kkkkk5G/MediaServerUI](https://gitee.com/kkkkk5G/MediaServerUI) 进行修改. # 应用场景: -支持浏览器无插件播放摄像头视频。 -支持摄像机、平台、NVR等设备接入。 +支持浏览器无插件播放摄像头视频。 +支持国标设备(摄像机、平台、NVR等)设备接入 +支持非国标(onvif, rtsp, rtmp,直播设备等等)设备接入,充分利旧。 支持国标级联。多平台级联。跨网视频预览。 -支持rtsp/rtmp等视频流转发到国标平台。 -支持rtsp/rtmp等推流转发到国标平台。 +支持跨网网闸平台互联。 -# 项目目标 -旨在打造一个易配置,易使用,便于维护的28181国标信令系统, 依托优秀的开源流媒体服务框架ZLMediaKit, 实现一个完整易用GB28181平台. -# 部署文档 -[doc.wvp-pro.cn](https://doc.wvp-pro.cn) +# 文档 +wvp使用文档 [https://doc.wvp-pro.cn](https://doc.wvp-pro.cn) +ZLM使用文档 [https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit) +> wvp文档由gitee提供服务,如果遇到打不开请多刷新几次。 + +# 社群地址 +[![社群](doc/_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm) +> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。 # gitee同步仓库 https://gitee.com/pan648540858/wvp-GB28181-pro.git @@ -100,23 +104,17 @@ https://gitee.com/pan648540858/wvp-GB28181-pro.git - [X] 云端录像,推流/代理/国标视频均可以录制在云端服务器,支持预览和下载 - [X] 支持打包可执行jar和war - [X] 支持跨域请求,支持前后端分离部署 - - -# 遇到问题如何解决 -国标最麻烦的地方在于设备的兼容性,所以需要大量的设备来测试,目前作者手里的设备有限,再加上作者水平有限,所以遇到问题在所难免; -1. 查看文档网站,仔细的阅读可以帮你避免几乎所有的问题 -2. 搜索issues,这里有大部分的答案 -3. 加QQ群(901799015),这里有大量热心的小伙伴,但是前提新希望你已经仔细阅读了wiki和搜索了issues。 -4. 你可以请作者为你解答,但是我不是免费的。 -5. 你可以把遇到问题的设备寄给我,可以更容易的兼容设备和解决问题。 - -# 使用帮助 -QQ群: 901799015, ZLM使用文档[https://github.com/ZLMediaKit/ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit) -QQ私信一般不回, 精力有限.欢迎大家在群里讨论.觉得项目对你有帮助,欢迎star和提交pr。 # 授权协议 本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; 由于使用本项目而产生的商业纠纷或侵权行为一概与本项目及开发者无关,请自行承担法律风险。 在使用本项目代码时,也应该在授权协议中同时表明本项目依赖的第三方库的协议 +# 技术支持 + +[知识星球](https://t.zsxq.com/0d8VAD3Dm)专栏列表: +- [使用入门系列一:WVP-PRO能做什么](https://t.zsxq.com/0dLguVoSp) + +有偿技术支持,请发送邮件到648540858@qq.com + # 致谢 感谢作者[夏楚](https://github.com/xia-chu) 提供这么棒的开源流媒体服务框架,并在开发过程中给予支持与帮助。 感谢作者[dexter langhuihui](https://github.com/langhuihui) 开源这么好用的WEB播放器。 @@ -128,7 +126,6 @@ QQ私信一般不回, 精力有限.欢迎大家在群里讨论.觉得项目对 [ydpd](https://github.com/ydpd) [szy833](https://github.com/szy833) [ydwxb](https://github.com/ydwxb) [Albertzhu666](https://github.com/Albertzhu666) [mk1990](https://github.com/mk1990) [SaltFish001](https://github.com/SaltFish001) -ps: 刚增加了这个名单,肯定遗漏了一些大佬,欢迎大佬联系我添加。 ffmpeg -re -i 123.mp3 -acodec pcm_alaw -ar 8000 -ac 1 -f rtsp rtsp://192.168.1.3:30554/broadcast/34020000001320000101_34020000001310000001 diff --git a/doc/README.md b/doc/README.md index 2bbc18441..a898229d7 100644 --- a/doc/README.md +++ b/doc/README.md @@ -14,6 +14,10 @@ - 完全开源,且使用MIT许可协议。保留版权的情况下可以用于商业项目。 - 支持多流媒体节点负载均衡。 +# 社群 +[![社群](_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm) +> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。 + # 我们实现了哪些国标功能 **作为上级平台** - [X] 注册 diff --git a/doc/_content/ability/_media/img_16.png b/doc/_content/ability/_media/img_16.png index f7ce9e7aa..b09e8cd39 100644 Binary files a/doc/_content/ability/_media/img_16.png and b/doc/_content/ability/_media/img_16.png differ diff --git a/doc/_content/qa/bug.md b/doc/_content/qa/bug.md index f452161a4..81267ffe2 100644 --- a/doc/_content/qa/bug.md +++ b/doc/_content/qa/bug.md @@ -2,18 +2,11 @@ # 反馈bug 代码是在不断的完善的,不断修改会修复旧的问题也有可能引入新的问题,所以遇到BUG是很正常的一件事。所以遇到问题不要烦燥,咱们就事论事就好了。 ## 如何反馈 -1. 更新代码,很可能你遇到问题别人已经更早的遇到了,或者是作者自己发现了,已经解决了,所以你可以更新代码再次进行测试; -2. 可以在github提ISSUE,我几乎每天都会去看issue,你的问题我会尽快给予答复; -3. 你可以来我的QQ群里,询问群友看看是否遇到了同样的问题; -4. 你可以私聊我的QQ,如果我有时间我会给你答复,但是除非你有明确的复现步骤或者修复方案,否则你可能等不到我的答复。 - -## 如何快速解决BUG -目前解决BUG有三种方式: -1. 作者验证以及修复; -2. 热心开发者提来的PR; -3. 使用运维手段屏蔽BUG的影响。 - -- 对于第一种:详细的复现步骤,完整的抓包文件,有条理的错误分析都可以帮助作者复现问题,进而解决问题。解决问题往往不是最难的,复现才是。 -- 对于第二种:如果你是开发者,你已经发现了造成BUG的原因以及知道如何正确的修复,那么我很希望你PR,SRS的大佬经常说的,开源不是一个人的事。所以你的参与就是最大的鼓励。 -- 对于第三种:如果你有一个有经验的运维伙伴,那么部分问题是可以通过运维的手段暂时屏蔽的,在等待修复的这段时间了以保证项目的运行。 +1. 在知识星球提问。 +2. 更新代码,很可能你遇到问题别人已经更早的遇到了,或者是作者自己发现了,已经解决了,所以你可以更新代码再次进行测试; +3. 可以在github提ISSUE,我几乎每天都会去看issue,你的问题我会尽快给予答复; +> 有偿支持可以给我发邮件, 648540858@qq.com +## 社群 +[![社群](../../_media/shequ.png "shequ")](https://t.zsxq.com/0d8VAD3Dm) +> 收费是为了提供更好的服务,也是对作者更大的激励。加入星球的用户三天后可以私信我留下微信号,我会拉大家入群。加入三天内不满意可以直接退款,大家不需要有顾虑,来白嫖三天也不是不可以。 \ No newline at end of file diff --git a/doc/_media/shequ.png b/doc/_media/shequ.png new file mode 100644 index 000000000..c5aec9807 Binary files /dev/null and b/doc/_media/shequ.png differ diff --git a/src/main/java/com/genersoft/iot/vmp/common/CommonCallback.java b/src/main/java/com/genersoft/iot/vmp/common/CommonCallback.java new file mode 100644 index 000000000..819fe0dd5 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/common/CommonCallback.java @@ -0,0 +1,5 @@ +package com.genersoft.iot.vmp.common; + +public interface CommonCallback{ + public void run(T t); +} diff --git a/src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java b/src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java new file mode 100644 index 000000000..9fe43f742 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/common/InviteInfo.java @@ -0,0 +1,126 @@ +package com.genersoft.iot.vmp.common; + +import com.genersoft.iot.vmp.service.bean.SSRCInfo; + +/** + * 记录每次发送invite消息的状态 + */ +public class InviteInfo { + + private String deviceId; + + private String channelId; + + private String stream; + + private SSRCInfo ssrcInfo; + + private String receiveIp; + + private Integer receivePort; + + private String streamMode; + + private InviteSessionType type; + + private InviteSessionStatus status; + + private StreamInfo streamInfo; + + + public static InviteInfo getinviteInfo(String deviceId, String channelId, String stream, SSRCInfo ssrcInfo, + String receiveIp, Integer receivePort, String streamMode, + InviteSessionType type, InviteSessionStatus status) { + InviteInfo inviteInfo = new InviteInfo(); + inviteInfo.setDeviceId(deviceId); + inviteInfo.setChannelId(channelId); + inviteInfo.setStream(stream); + inviteInfo.setSsrcInfo(ssrcInfo); + inviteInfo.setReceiveIp(receiveIp); + inviteInfo.setReceivePort(receivePort); + inviteInfo.setStreamMode(streamMode); + inviteInfo.setType(type); + inviteInfo.setStatus(status); + return inviteInfo; + } + + 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 InviteSessionType getType() { + return type; + } + + public void setType(InviteSessionType type) { + this.type = type; + } + + public InviteSessionStatus getStatus() { + return status; + } + + public void setStatus(InviteSessionStatus status) { + this.status = status; + } + + public StreamInfo getStreamInfo() { + return streamInfo; + } + + public void setStreamInfo(StreamInfo streamInfo) { + this.streamInfo = streamInfo; + } + + public String getStream() { + return stream; + } + + public void setStream(String stream) { + this.stream = stream; + } + + public SSRCInfo getSsrcInfo() { + return ssrcInfo; + } + + public void setSsrcInfo(SSRCInfo ssrcInfo) { + this.ssrcInfo = ssrcInfo; + } + + public String getReceiveIp() { + return receiveIp; + } + + public void setReceiveIp(String receiveIp) { + this.receiveIp = receiveIp; + } + + public Integer getReceivePort() { + return receivePort; + } + + public void setReceivePort(Integer receivePort) { + this.receivePort = receivePort; + } + + public String getStreamMode() { + return streamMode; + } + + public void setStreamMode(String streamMode) { + this.streamMode = streamMode; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/common/InviteSessionStatus.java b/src/main/java/com/genersoft/iot/vmp/common/InviteSessionStatus.java new file mode 100644 index 000000000..04cc7c918 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/common/InviteSessionStatus.java @@ -0,0 +1,11 @@ +package com.genersoft.iot.vmp.common; + +/** + * 标识invite消息发出后的各个状态, + * 收到ok钱停止invite发送cancel, + * 收到200ok后发送BYE停止invite + */ +public enum InviteSessionStatus { + ready, + ok, +} diff --git a/src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java b/src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java new file mode 100644 index 000000000..924130577 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/common/InviteSessionType.java @@ -0,0 +1,9 @@ +package com.genersoft.iot.vmp.common; + +public enum InviteSessionType { + PLAY, + PLAYBACK, + DOWNLOAD, + BROADCAST, + TALK +} 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 99fc074e6..cd8ed189c 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java +++ b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java @@ -16,8 +16,6 @@ public class VideoManagerConstants { public static final String MEDIA_SERVERS_ONLINE_PREFIX = "VMP_MEDIA_ONLINE_SERVERS_"; - public static final String MEDIA_STREAM_PREFIX = "VMP_MEDIA_STREAM"; - public static final String DEVICE_PREFIX = "VMP_DEVICE_"; // 设备同步完成 @@ -28,9 +26,10 @@ public class VideoManagerConstants { public static final String KEEPLIVEKEY_PREFIX = "VMP_KEEPALIVE_"; // TODO 此处多了一个_,暂不修改 - public static final String PLAYER_PREFIX = "VMP_PLAYER_"; - public static final String PLAY_BLACK_PREFIX = "VMP_PLAYBACK_"; - public static final String DOWNLOAD_PREFIX = "VMP_DOWNLOAD_"; + 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_"; @@ -123,6 +122,7 @@ public class VideoManagerConstants { */ public static final String VM_MSG_SUBSCRIBE_ALARM = "alarm"; + /** * 报警通知的发送 (收到redis发出的通知,转发给其他平台) */ 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 6cc3b415b..569b5e199 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java @@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.conf; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.IMediaServerService; -import org.apache.catalina.connector.ClientAbortException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; @@ -194,11 +193,11 @@ public class ProxyServletConfig { } catch (IOException ioException) { if (ioException instanceof ConnectException) { logger.error("录像服务 连接失败"); - }else if (ioException instanceof ClientAbortException) { - /** - * TODO 使用这个代理库实现代理在遇到代理视频文件时,如果是206结果,会遇到报错蛋市目前功能正常, - * TODO 暂时去除异常处理。后续使用其他代理框架修改测试 - */ +// }else if (ioException instanceof ClientAbortException) { +// /** +// * TODO 使用这个代理库实现代理在遇到代理视频文件时,如果是206结果,会遇到报错蛋市目前功能正常, +// * TODO 暂时去除异常处理。后续使用其他代理框架修改测试 +// */ }else { logger.error("录像服务 代理失败: ", e); diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ScheduleConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/ScheduleConfig.java new file mode 100644 index 000000000..432fafbb0 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ScheduleConfig.java @@ -0,0 +1,30 @@ +package com.genersoft.iot.vmp.conf; + +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; + +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * "@Scheduled"是Spring框架提供的一种定时任务执行机制,默认情况下它是单线程的,在同时执行多个定时任务时可能会出现阻塞和性能问题。 + * 为了解决这种单线程瓶颈问题,可以将定时任务的执行机制改为支持多线程 + */ +@Configuration +public class ScheduleConfig implements SchedulingConfigurer { + + public static final int cpuNum = Runtime.getRuntime().availableProcessors(); + + private static final int corePoolSize = cpuNum; + + private static final String threadNamePrefix = "scheduled-task-pool-%d"; + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(corePoolSize, + new BasicThreadFactory.Builder().namingPattern(threadNamePrefix).daemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy())); + } +} 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 0a8405b16..55363efbf 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/SipPlatformRunner.java @@ -48,10 +48,13 @@ public class SipPlatformRunner implements CommandLineRunner { parentPlatformCatch.setParentPlatform(parentPlatform); parentPlatformCatch.setId(parentPlatform.getServerGBId()); redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch); - // 取消订阅 - sipCommanderForPlatform.unregister(parentPlatform, parentPlatformCatchOld.getSipTransactionInfo(), null, (eventResult)->{ - platformService.login(parentPlatform); - }); + if (parentPlatformCatchOld != null) { + // 取消订阅 + sipCommanderForPlatform.unregister(parentPlatform, parentPlatformCatchOld.getSipTransactionInfo(), null, (eventResult)->{ + platformService.login(parentPlatform); + }); + } + // 设置所有平台离线 platformService.offline(parentPlatform, true); } 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 242efb4a3..64cbd57d7 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/UserSetting.java @@ -54,6 +54,9 @@ public class UserSetting { private Boolean refuseChannelStatusChannelFormNotify = Boolean.FALSE; + private Boolean deviceStatusNotify = Boolean.FALSE; + private Boolean useCustomSsrcForParentInvite = Boolean.TRUE; + private String serverId = "000000"; private String recordPath = null; @@ -66,6 +69,8 @@ public class UserSetting { private List allowedOrigins = new ArrayList<>(); + private int maxNotifyCountQueue = 10000; + public Boolean getSavePositionHistory() { return savePositionHistory; } @@ -277,4 +282,28 @@ public class UserSetting { public void setRecordPath(String recordPath) { this.recordPath = recordPath; } + + public int getMaxNotifyCountQueue() { + return maxNotifyCountQueue; + } + + public void setMaxNotifyCountQueue(int maxNotifyCountQueue) { + this.maxNotifyCountQueue = maxNotifyCountQueue; + } + + public Boolean getDeviceStatusNotify() { + return deviceStatusNotify; + } + + public void setDeviceStatusNotify(Boolean deviceStatusNotify) { + this.deviceStatusNotify = deviceStatusNotify; + } + + public Boolean getUseCustomSsrcForParentInvite() { + return useCustomSsrcForParentInvite; + } + + public void setUseCustomSsrcForParentInvite(Boolean useCustomSsrcForParentInvite) { + this.useCustomSsrcForParentInvite = useCustomSsrcForParentInvite; + } } 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 index 836da67d1..e69de29bb 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/redis/RedisConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/redis/RedisConfig.java @@ -1,45 +0,0 @@ -package com.genersoft.iot.vmp.conf.redis; - - -import com.genersoft.iot.vmp.common.VideoManagerConstants; -import com.genersoft.iot.vmp.service.redisMsg.*; -import com.genersoft.iot.vmp.utils.redis.FastJsonRedisSerializer; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.annotation.CachingConfigurerSupport; -import com.alibaba.fastjson2.support.spring.data.redis.GenericFastJsonRedisSerializer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.StringRedisSerializer; - - -/** - * Redis中间件配置类,使用spring-data-redis集成,自动从application.yml中加载redis配置 - * swwheihei - * 2019年5月30日 上午10:58:25 - * - */ -@Configuration -@Order(value=1) -public class RedisConfig { - - - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { - - RedisTemplate redisTemplate = new RedisTemplate<>(); - // 使用fastJson序列化 - GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer(); - // value值的序列化采用fastJsonRedisSerializer - redisTemplate.setValueSerializer(fastJsonRedisSerializer); - redisTemplate.setHashValueSerializer(fastJsonRedisSerializer); - - // key的序列化采用StringRedisSerializer - redisTemplate.setKeySerializer(new StringRedisSerializer()); - redisTemplate.setHashKeySerializer(new StringRedisSerializer()); - redisTemplate.setConnectionFactory(redisConnectionFactory); - return redisTemplate; - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/redis/RedisTemplateConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/redis/RedisTemplateConfig.java new file mode 100644 index 000000000..df3345eef --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/redis/RedisTemplateConfig.java @@ -0,0 +1,28 @@ +package com.genersoft.iot.vmp.conf.redis; + +import com.alibaba.fastjson2.support.spring.data.redis.GenericFastJsonRedisSerializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisTemplateConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + // 使用fastJson序列化 + GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer(); + // value值的序列化采用fastJsonRedisSerializer + redisTemplate.setValueSerializer(fastJsonRedisSerializer); + redisTemplate.setHashValueSerializer(fastJsonRedisSerializer); + + // key的序列化采用StringRedisSerializer + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + redisTemplate.setConnectionFactory(redisConnectionFactory); + return redisTemplate; + } +} 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 e50a8b0ec..f35b5bd86 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 @@ -2,6 +2,8 @@ package com.genersoft.iot.vmp.conf.security; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.security.dto.JwtUser; +import com.genersoft.iot.vmp.storager.dao.dto.Role; +import com.genersoft.iot.vmp.storager.dao.dto.User; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -38,7 +40,6 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { return; } if (!userSetting.isInterfaceAuthentication()) { - // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录 UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(null, null, new ArrayList<>() ); SecurityContextHolder.getContext().setAuthentication(token); chain.doFilter(request, response); @@ -76,7 +77,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { } // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录 - UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, jwtUser.getPassword(), new ArrayList<>() ); + User user = new User(); + user.setUsername(jwtUser.getUserName()); + user.setPassword(jwtUser.getPassword()); + Role role = new Role(); + role.setId(jwtUser.getRoleId()); + user.setRole(role); + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, jwtUser.getPassword(), new ArrayList<>() ); SecurityContextHolder.getContext().setAuthentication(token); chain.doFilter(request, response); } 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 57911b045..c9c7b680f 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 @@ -37,7 +37,7 @@ public class JwtUtils { */ public static final long expirationTime = 30; - public static String createToken(String username, String password) { + public static String createToken(String username, String password, Integer roleId) { try { /** * “iss” (issuer) 发行人 @@ -64,6 +64,7 @@ public class JwtUtils { //添加自定义参数,必须是字符串类型 claims.setClaim("username", username); claims.setClaim("password", password); + claims.setClaim("roleId", roleId); //jws JsonWebSignature jws = new JsonWebSignature(); @@ -118,8 +119,10 @@ public class JwtUtils { String username = (String) claims.getClaimValue("username"); String password = (String) claims.getClaimValue("password"); + Long roleId = (Long) claims.getClaimValue("roleId"); jwtUser.setUserName(username); jwtUser.setPassword(password); + jwtUser.setRoleId(roleId.intValue()); return jwtUser; } catch (InvalidJwtException e) { diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/LoginFailureHandler.java b/src/main/java/com/genersoft/iot/vmp/conf/security/LoginFailureHandler.java deleted file mode 100644 index 9bbf2e7d8..000000000 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/LoginFailureHandler.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.genersoft.iot.vmp.conf.security; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.*; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.stereotype.Component; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -@Component -public class LoginFailureHandler implements AuthenticationFailureHandler { - - private final static Logger logger = LoggerFactory.getLogger(LoginFailureHandler.class); - - @Autowired - private ObjectMapper objectMapper; - - @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { - - String username = request.getParameter("username"); - if (e instanceof AccountExpiredException) { - // 账号过期 - logger.info("[登录失败] - 用户[{}]账号过期", username); - - } else if (e instanceof BadCredentialsException) { - // 密码错误 - logger.info("[登录失败] - 用户[{}]密码/SIP服务器ID 错误", username); - - } else if (e instanceof CredentialsExpiredException) { - // 密码过期 - logger.info("[登录失败] - 用户[{}]密码过期", username); - - } else if (e instanceof DisabledException) { - // 用户被禁用 - logger.info("[登录失败] - 用户[{}]被禁用", username); - - } else if (e instanceof LockedException) { - // 用户被锁定 - logger.info("[登录失败] - 用户[{}]被锁定", username); - - } else if (e instanceof InternalAuthenticationServiceException) { - // 内部错误 - logger.error(String.format("[登录失败] - [%s]内部错误", username), e); - - } else { - // 其他错误 - logger.error(String.format("[登录失败] - [%s]其他错误", username), e); - } - Map map = new HashMap<>(); - map.put("code","0"); - map.put("msg","登录失败"); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write(objectMapper.writeValueAsString(map)); - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java b/src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java deleted file mode 100644 index d26342ef5..000000000 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/LoginSuccessHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.genersoft.iot.vmp.conf.security; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.stereotype.Component; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * @author lin - */ -@Component -public class LoginSuccessHandler implements AuthenticationSuccessHandler { - - private final static Logger logger = LoggerFactory.getLogger(LoginSuccessHandler.class); - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { -// String username = request.getParameter("username"); -// httpServletResponse.setContentType("application/json;charset=UTF-8"); -// // 生成JWT,并放置到请求头中 -// String jwt = JwtUtils.createToken(authentication.getName(), ); -// httpServletResponse.setHeader(JwtUtils.getHeader(), jwt); -// ServletOutputStream outputStream = httpServletResponse.getOutputStream(); -// outputStream.write(JSON.toJSONString(ErrorCode.SUCCESS).getBytes(StandardCharsets.UTF_8)); -// outputStream.flush(); -// outputStream.close(); - -// logger.info("[登录成功] - [{}]", username); - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java b/src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java index a8d35681e..f012f7efb 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/security/SecurityUtils.java @@ -53,14 +53,10 @@ public class SecurityUtils { Authentication authentication = getAuthentication(); if(authentication!=null){ Object principal = authentication.getPrincipal(); - if(principal!=null && !"anonymousUser".equals(principal)){ -// LoginUser user = (LoginUser) authentication.getPrincipal(); + if(principal!=null && !"anonymousUser".equals(principal.toString())){ - String username = (String) principal; - User user = new User(); - user.setUsername(username); - LoginUser loginUser = new LoginUser(user, LocalDateTime.now()); - return loginUser; + User user = (User) principal; + return new LoginUser(user, LocalDateTime.now()); } } return null; 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 96ae6b91c..1fbe3a4eb 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 @@ -47,16 +47,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { * 登出成功的处理 */ @Autowired - private LoginFailureHandler loginFailureHandler; - /** - * 登录成功的处理 - */ - @Autowired - private LoginSuccessHandler loginSuccessHandler; - /** - * 登出成功的处理 - */ - @Autowired private LogoutHandler logoutHandler; /** * 未登录的处理 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 1639d1fc2..8921a3083 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 @@ -25,6 +25,8 @@ public class JwtUser { private String password; + private int roleId; + private TokenStatus status; public String getUserName() { @@ -50,4 +52,12 @@ public class JwtUser { public void setPassword(String password) { this.password = password; } + + public int getRoleId() { + return roleId; + } + + public void setRoleId(int roleId) { + this.roleId = roleId; + } } 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 d452771ec..782384623 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java @@ -36,8 +36,6 @@ public class SipLayer implements CommandLineRunner { private final Map tcpSipProviderMap = new ConcurrentHashMap<>(); private final Map udpSipProviderMap = new ConcurrentHashMap<>(); - private SipFactory sipFactory; - @Override public void run(String... args) { List monitorIps = new ArrayList<>(); @@ -50,8 +48,7 @@ public class SipLayer implements CommandLineRunner { monitorIps.add(sipConfig.getIp()); } - sipFactory = SipFactory.getInstance(); - sipFactory.setPathName("gov.nist"); + SipFactory.getInstance().setPathName("gov.nist"); if (monitorIps.size() > 0) { for (String monitorIp : monitorIps) { addListeningPoint(monitorIp, sipConfig.getPort()); @@ -65,7 +62,7 @@ public class SipLayer implements CommandLineRunner { private void addListeningPoint(String monitorIp, int port){ SipStackImpl sipStack; try { - sipStack = (SipStackImpl)sipFactory.createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog())); + sipStack = (SipStackImpl)SipFactory.getInstance().createSipStack(DefaultProperties.getProperties(monitorIp, userSetting.getSipLog())); } catch (PeerUnavailableException e) { logger.error("[Sip Server] SIP服务启动失败, 监听地址{}失败,请检查ip是否正确", monitorIp); return; @@ -106,10 +103,6 @@ public class SipLayer implements CommandLineRunner { } } - public SipFactory getSipFactory() { - return sipFactory; - } - public SipProviderImpl getUdpSipProvider(String ip) { if (ObjectUtils.isEmpty(ip)) { return null; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CmdSendFailEvent.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CmdSendFailEvent.java deleted file mode 100644 index 0cd4086e3..000000000 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/CmdSendFailEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.genersoft.iot.vmp.gb28181.bean; - -import javax.sip.Dialog; -import java.util.EventObject; - -public class CmdSendFailEvent extends EventObject { - - private String callId; - - /** - * Constructs a prototypical Event. - * - * @param dialog - * @throws IllegalArgumentException if source is null. - */ - public CmdSendFailEvent(Dialog dialog) { - super(dialog); - } - - public String getCallId() { - return callId; - } - - public void setCallId(String callId) { - this.callId = callId; - } -} 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 9e9d338fc..bb3391e34 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 @@ -247,6 +247,17 @@ public class Device { return streamMode; } + public Integer getStreamModeForParam() { + if (streamMode.equalsIgnoreCase("UDP")) { + return 0; + }else if (streamMode.equalsIgnoreCase("TCP-PASSIVE")) { + return 1; + }else if (streamMode.equalsIgnoreCase("TCP-ACTIVE")) { + return 2; + } + return 0; + } + public void setStreamMode(String streamMode) { this.streamMode = streamMode; } 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 index dde7639ee..4f62c6687 100644 --- 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,PUSH,PROXY,CLOUD_RECORD_PUSH,CLOUD_RECORD_PROXY,BROADCAST,TALK + 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/SsrcTransaction.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java index d27ce2628..6ed8d1441 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/SsrcTransaction.java @@ -1,6 +1,6 @@ package com.genersoft.iot.vmp.gb28181.bean; -import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; +import com.genersoft.iot.vmp.common.InviteSessionType; public class SsrcTransaction { @@ -13,7 +13,7 @@ public class SsrcTransaction { private SipTransactionInfo sipTransactionInfo; - private VideoStreamSessionManager.SessionType type; + private InviteSessionType type; public String getDeviceId() { return deviceId; @@ -63,11 +63,11 @@ public class SsrcTransaction { this.ssrc = ssrc; } - public VideoStreamSessionManager.SessionType getType() { + public InviteSessionType getType() { return type; } - public void setType(VideoStreamSessionManager.SessionType type) { + public void setType(InviteSessionType type) { this.type = type; } 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 index 3fc1d374e..19e1906c3 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/conf/ServerLoggerImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/conf/ServerLoggerImpl.java @@ -27,7 +27,7 @@ public class ServerLoggerImpl implements ServerLogger { return; } StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(!sender? "发送:目标--->" + from:"接收:来自--->" + to) + stringBuilder.append(sender? "发送:目标--->" + from:"接收:来自--->" + to) .append("\r\n") .append(message); this.stackLogger.logInfo(stringBuilder.toString()); @@ -40,7 +40,7 @@ public class ServerLoggerImpl implements ServerLogger { return; } StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(!sender? "发送: 目标->" + from :"接收:来自->" + to) + stringBuilder.append(sender? "发送: 目标->" + from :"接收:来自->" + to) .append("\r\n") .append(message); this.stackLogger.logInfo(stringBuilder.toString()); @@ -52,7 +52,7 @@ public class ServerLoggerImpl implements ServerLogger { return; } StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(!sender? "发送: 目标->" + from :"接收:来自->" + to) + stringBuilder.append(sender? "发送: 目标->" + from :"接收:来自->" + to) .append("\r\n") .append(message); this.stackLogger.logInfo(stringBuilder.toString()); @@ -87,6 +87,4 @@ public class ServerLoggerImpl implements ServerLogger { this.stackLogger = this.sipStack.getStackLogger(); } } - - } 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 index 5cc9cb929..36e571448 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/SipSubscribe.java @@ -76,7 +76,11 @@ public class SipSubscribe { // 会话已结束 dialogTerminated, // 设备未找到 - deviceNotFoundEvent + deviceNotFoundEvent, + // 消息发送失败 + cmdSendFailEvent, + // 消息发送失败 + failedToGetPort } public static class EventResult{ @@ -86,9 +90,7 @@ public class SipSubscribe { public String callId; public EventObject event; - public EventResult(int statusCode, String msg) { - this.statusCode = statusCode; - this.msg = msg; + public EventResult() { } public EventResult(EventObject event) { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java index be73ebd98..89ecb1867 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/subscribe/catalog/CatalogEventLister.java @@ -1,14 +1,9 @@ package com.genersoft.iot.vmp.gb28181.event.subscribe.catalog; -import com.genersoft.iot.vmp.common.VideoManagerConstants; -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.transmit.cmd.impl.SIPCommanderFroPlatform; -import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; import com.genersoft.iot.vmp.service.IGbStreamService; -import com.genersoft.iot.vmp.service.IMediaServerService; -import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,12 +11,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * catalog事件 @@ -43,6 +40,9 @@ public class CatalogEventLister implements ApplicationListener { @Autowired private SubscribeHolder subscribeHolder; + @Autowired + private UserSetting userSetting; + @Override public void onApplicationEvent(CatalogEvent event) { SubscribeInfo subscribe = null; @@ -93,6 +93,9 @@ public class CatalogEventLister implements ApplicationListener { } if (event.getGbStreams() != null && event.getGbStreams().size() > 0){ for (GbStream gbStream : event.getGbStreams()) { + if (gbStream.getStreamType().equals("push") && !userSetting.isUsePushingAsStatus()) { + continue; + } DeviceChannel deviceChannelByStream = gbStreamService.getDeviceChannelListByStream(gbStream, gbStream.getCatalogId(), parentPlatform); deviceChannelList.add(deviceChannelByStream); } 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 new file mode 100644 index 000000000..ec8e0ba6a --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/session/SSRCFactory.java @@ -0,0 +1,132 @@ +package com.genersoft.iot.vmp.gb28181.session; + +import com.genersoft.iot.vmp.conf.SipConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * ssrc使用 + */ +@Component +public class SSRCFactory { + + /** + * 播流最大并发个数 + */ + private static final Integer MAX_STREAM_COUNT = 10000; + + /** + * 播流最大并发个数 + */ + private static final String SSRC_INFO_KEY = "VMP_SSRC_INFO_"; + + @Autowired + private StringRedisTemplate redisTemplate; + + @Autowired + private SipConfig sipConfig; + + + public void initMediaServerSSRC(String mediaServerId, Set usedSet) { + String ssrcPrefix = sipConfig.getDomain().substring(3, 8); + String redisKey = SSRC_INFO_KEY + mediaServerId; + List ssrcList = new ArrayList<>(); + for (int i = 1; i < MAX_STREAM_COUNT; i++) { + String ssrc = String.format("%s%04d", ssrcPrefix, i); + + if (null == usedSet || !usedSet.contains(ssrc)) { + ssrcList.add(ssrc); + + } + } + if (redisTemplate.opsForSet().size(redisKey) != null) { + redisTemplate.delete(redisKey); + } + redisTemplate.opsForSet().add(redisKey, ssrcList.toArray(new String[0])); + } + + + /** + * 获取视频预览的SSRC值,第一位固定为0 + * + * @return ssrc + */ + public String getPlaySsrc(String mediaServerId) { + return "0" + getSN(mediaServerId); + } + + /** + * 获取录像回放的SSRC值,第一位固定为1 + */ + public String getPlayBackSsrc(String mediaServerId) { + return "1" + getSN(mediaServerId); + } + + /** + * 释放ssrc,主要用完的ssrc一定要释放,否则会耗尽 + * + * @param ssrc 需要重置的ssrc + */ + public void releaseSsrc(String mediaServerId, String ssrc) { + if (ssrc == null) { + return; + } + String sn = ssrc.substring(1); + String redisKey = SSRC_INFO_KEY + mediaServerId; + redisTemplate.opsForSet().add(redisKey, sn); + } + + /** + * 获取后四位数SN,随机数 + */ + private String getSN(String mediaServerId) { + String sn = null; + String redisKey = SSRC_INFO_KEY + mediaServerId; + Long size = redisTemplate.opsForSet().size(redisKey); + if (size == null || size == 0) { + throw new RuntimeException("ssrc已经用完"); + } else { + // 在集合中移除并返回一个随机成员。 + sn = (String) redisTemplate.opsForSet().pop(redisKey); + redisTemplate.opsForSet().remove(redisKey, sn); + } + return sn; + } + + /** + * 重置一个流媒体服务的所有ssrc + * + * @param mediaServerId 流媒体服务ID + */ + public void reset(String mediaServerId) { + this.initMediaServerSSRC(mediaServerId, null); + } + + /** + * 是否已经存在了某个MediaServer的SSRC信息 + * + * @param mediaServerId 流媒体服务ID + */ + public boolean hasMediaServerSSRC(String mediaServerId) { + String redisKey = SSRC_INFO_KEY + mediaServerId; + return redisTemplate.opsForSet().members(redisKey) != null; + } + + /** + * 查询ssrc是否可用 + * + * @param mediaServerId + * @param ssrc + * @return + */ + public boolean checkSsrc(String mediaServerId, String ssrc) { + String sn = ssrc.substring(1); + String redisKey = SSRC_INFO_KEY + mediaServerId; + return redisTemplate.opsForSet().isMember(redisKey, sn) != null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcConfig.java b/src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcConfig.java deleted file mode 100644 index cc303c8bd..000000000 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/session/SsrcConfig.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.genersoft.iot.vmp.gb28181.session; - -import com.genersoft.iot.vmp.utils.ConfigConst; -import io.swagger.v3.oas.annotations.media.Schema; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.Set; - -@Schema(description = "ssrc信息") -public class SsrcConfig { - - /** - * zlm流媒体服务器Id - */ - @Schema(description = "流媒体服务器Id") - private String mediaServerId; - - @Schema(description = "SSRC前缀") - private String ssrcPrefix; - - /** - * zlm流媒体服务器已用会话句柄 - */ - @Schema(description = "zlm流媒体服务器已用会话句柄") - private List isUsed; - - /** - * zlm流媒体服务器可用会话句柄 - */ - @Schema(description = "zlm流媒体服务器可用会话句柄") - private List notUsed; - - public SsrcConfig() { - } - - public SsrcConfig(String mediaServerId, Set usedSet, String sipDomain) { - this.mediaServerId = mediaServerId; - this.isUsed = new ArrayList<>(); - this.ssrcPrefix = sipDomain.substring(3, 8); - this.notUsed = new ArrayList<>(); - for (int i = 1; i < ConfigConst.MAX_STRTEAM_COUNT; i++) { - String ssrc; - if (i < 10) { - ssrc = "000" + i; - } else if (i < 100) { - ssrc = "00" + i; - } else if (i < 1000) { - ssrc = "0" + i; - } else { - ssrc = String.valueOf(i); - } - if (null == usedSet || !usedSet.contains(ssrc)) { - this.notUsed.add(ssrc); - } else { - this.isUsed.add(ssrc); - } - } - } - - - /** - * 获取视频预览的SSRC值,第一位固定为0 - * @return ssrc - */ - public String getPlaySsrc() { - return "0" + getSsrcPrefix() + getSN(); - } - - /** - * 获取录像回放的SSRC值,第一位固定为1 - * - */ - public String getPlayBackSsrc() { - return "1" + getSsrcPrefix() + getSN(); - } - - /** - * 释放ssrc,主要用完的ssrc一定要释放,否则会耗尽 - * @param ssrc 需要重置的ssrc - */ - public void releaseSsrc(String ssrc) { - if (ssrc == null) { - return; - } - String sn = ssrc.substring(6); - try { - isUsed.remove(sn); - notUsed.add(sn); - }catch (NullPointerException e){ - } - } - - /** - * 获取后四位数SN,随机数 - * - */ - private String getSN() { - String sn = null; - int index = 0; - if (notUsed.size() == 0) { - throw new RuntimeException("ssrc已经用完"); - } else if (notUsed.size() == 1) { - sn = notUsed.get(0); - } else { - index = new Random().nextInt(notUsed.size() - 1); - sn = notUsed.get(index); - } - notUsed.remove(index); - isUsed.add(sn); - return sn; - } - - public String getSsrcPrefix() { - return ssrcPrefix; - } - - public String getMediaServerId() { - return mediaServerId; - } - - public void setMediaServerId(String mediaServerId) { - this.mediaServerId = mediaServerId; - } - - public void setSsrcPrefix(String ssrcPrefix) { - this.ssrcPrefix = ssrcPrefix; - } - - public List getIsUsed() { - return isUsed; - } - - public void setIsUsed(List isUsed) { - this.isUsed = isUsed; - } - - public List getNotUsed() { - return notUsed; - } - - public void setNotUsed(List notUsed) { - this.notUsed = notUsed; - } - - public boolean checkSsrc(String ssrcInResponse) { - return !isUsed.contains(ssrcInResponse); - } -} 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 index e856faf66..a5da0186b 100644 --- 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,5 +1,6 @@ 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; @@ -27,14 +28,6 @@ public class VideoStreamSessionManager { @Autowired private RedisTemplate redisTemplate; - public enum SessionType { - play, - playback, - download, - broadcast, - talk - } - /** * 添加一个点播/回放的事务信息 * 后续可以通过流Id/callID @@ -45,7 +38,7 @@ public class VideoStreamSessionManager { * @param mediaServerId 所使用的流媒体ID * @param response 回复 */ - public void put(String deviceId, String channelId, String callId, String stream, String ssrc, String mediaServerId, SIPResponse response, SessionType type){ + 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); 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 index d1e64c691..4b9244368 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/task/SipRunner.java @@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.conf.UserSetting; 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.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; @@ -37,6 +38,9 @@ public class SipRunner implements CommandLineRunner { @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private SSRCFactory ssrcFactory; + @Autowired private UserSetting userSetting; @@ -96,6 +100,7 @@ public class SipRunner implements CommandLineRunner { MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId()); 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()); 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 03ce619c9..e8066b755 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 @@ -46,8 +46,7 @@ public class SIPSender { transmitRequest(ip, message, errorEvent, null); } - public void transmitRequest(String ip, Message message, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException, ParseException { - try { + public void transmitRequest(String ip, Message message, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent) throws SipException { ViaHeader viaHeader = (ViaHeader)message.getHeader(ViaHeader.NAME); String transport = "UDP"; if (viaHeader == null) { @@ -57,7 +56,7 @@ public class SIPSender { } if (message.getHeader(UserAgentHeader.NAME) == null) { try { - message.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + message.addHeader(SipUtils.createUserAgentHeader(gitUtil)); } catch (ParseException e) { logger.error("添加UserAgentHeader失败", e); } @@ -104,9 +103,6 @@ public class SIPSender { sipProvider.sendResponse((Response)message); } } - } finally { -// logger.info("[SEND]:SUCCESS:{}", message); - } } public CallIdHeader getNewCallIdHeader(String ip, String transport){ 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 index e6fd3ed2c..d7dbe94f6 100644 --- 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 @@ -3,6 +3,8 @@ 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.event.SipSubscribe; import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; @@ -107,7 +109,7 @@ public interface ISIPCommander { * @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,InviteStreamCallback inviteStreamCallback, InviteStreamCallback event, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException; + 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; /** * 请求历史媒体下载 @@ -119,7 +121,7 @@ public interface ISIPCommander { * @param downloadSpeed 下载倍速参数 */ void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, - String startTime, String endTime, int downloadSpeed, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent, + String startTime, String endTime, int downloadSpeed, ZlmHttpHookSubscribe.Event hookEvent, 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 index 315ddecdf..0b097ffa4 100644 --- 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 @@ -17,6 +17,7 @@ import org.springframework.util.DigestUtils; import javax.sip.InvalidArgumentException; import javax.sip.PeerUnavailableException; +import javax.sip.SipFactory; import javax.sip.address.Address; import javax.sip.address.SipURI; import javax.sip.header.*; @@ -50,39 +51,39 @@ public class SIPRequestHeaderPlarformProvider { Request request = null; String sipAddress = parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort(); //请求行 - SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), + SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort()); //via ArrayList viaHeaders = new ArrayList(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getServerIP(), + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getServerIP(), parentPlatform.getServerPort(), parentPlatform.getTransport(), SipUtils.getNewViaTag()); viaHeader.setRPort(); viaHeaders.add(viaHeader); //from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain()); + Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); //to - SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain()); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,toTag); + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain()); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,toTag); //Forwards - MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(CSeq, Request.REGISTER); - request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.REGISTER, callIdHeader, + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(CSeq, Request.REGISTER); + request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.REGISTER, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); - Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory() + Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory() .createSipURI(parentPlatform.getDeviceGBId(), sipAddress)); - request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); + request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); - ExpiresHeader expiresHeader = sipLayer.getSipFactory().createHeaderFactory().createExpiresHeader(expires); + ExpiresHeader expiresHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(expires); request.addHeader(expiresHeader); - request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); return request; } @@ -92,9 +93,9 @@ public class SIPRequestHeaderPlarformProvider { Request registerRequest = createRegisterRequest(parentPlatform, redisCatchStorage.getCSEQ(), fromTag, toTag, callIdHeader, expires); - SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort()); + SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP() + ":" + parentPlatform.getServerPort()); if (www == null) { - AuthorizationHeader authorizationHeader = sipLayer.getSipFactory().createHeaderFactory().createAuthorizationHeader("Digest"); + AuthorizationHeader authorizationHeader = SipFactory.getInstance().createHeaderFactory().createAuthorizationHeader("Digest"); String username = parentPlatform.getUsername(); if ( username == null || username == "" ) { @@ -147,7 +148,7 @@ public class SIPRequestHeaderPlarformProvider { String RESPONSE = DigestUtils.md5DigestAsHex(reStr.toString().getBytes()); - AuthorizationHeader authorizationHeader = sipLayer.getSipFactory().createHeaderFactory().createAuthorizationHeader(scheme); + AuthorizationHeader authorizationHeader = SipFactory.getInstance().createHeaderFactory().createAuthorizationHeader(scheme); authorizationHeader.setUsername(parentPlatform.getDeviceGBId()); authorizationHeader.setRealm(realm); authorizationHeader.setNonce(nonce); @@ -165,7 +166,7 @@ public class SIPRequestHeaderPlarformProvider { } public Request createMessageRequest(ParentPlatform parentPlatform, String content, SendRtpItem sendRtpItem) throws PeerUnavailableException, ParseException, InvalidArgumentException { - CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId()); + CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId()); return createMessageRequest(parentPlatform, content, sendRtpItem.getToTag(), SipUtils.getNewViaTag(), sendRtpItem.getFromTag(), callIdHeader); } @@ -178,36 +179,36 @@ public class SIPRequestHeaderPlarformProvider { Request request = null; String serverAddress = parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort(); // sipuri - SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); + SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); // via ArrayList viaHeaders = new ArrayList(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(), + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(), parentPlatform.getTransport(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); // from - // SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), parentPlatform.getDeviceIp() + ":" + parentPlatform.getDeviceIp()); - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); + // SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), parentPlatform.getDeviceIp() + ":" + parentPlatform.getDeviceIp()); + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), sipConfig.getDomain()); + Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, fromTag); // to - SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, toTag); + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), serverAddress); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag); // Forwards - MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); // ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE); - MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory(); + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE); + MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory(); // 设置编码, 防止中文乱码 messageFactory.setDefaultContentEncodingCharset(parentPlatform.getCharacterSet()); request = messageFactory.createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); - request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); - ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); + ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); request.setContent(content, contentTypeHeader); return request; } @@ -215,54 +216,54 @@ public class SIPRequestHeaderPlarformProvider { public SIPRequest createNotifyRequest(ParentPlatform parentPlatform, String content, SubscribeInfo subscribeInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException { SIPRequest request = null; // sipuri - SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort()); + SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerIP()+ ":" + parentPlatform.getServerPort()); // via ArrayList viaHeaders = new ArrayList<>(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(), + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(parentPlatform.getDeviceIp(), parentPlatform.getDevicePort(), parentPlatform.getTransport(), SipUtils.getNewViaTag()); viaHeader.setRPort(); viaHeaders.add(viaHeader); // from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(parentPlatform.getDeviceGBId(), parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getResponse().getToTag()); + Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, subscribeInfo.getResponse().getToTag()); // to - SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(parentPlatform.getServerGBId(), parentPlatform.getServerGBDomain()); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, subscribeInfo.getRequest().getFromTag()); + 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()); // Forwards - MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); // ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.NOTIFY); - MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory(); + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.NOTIFY); + MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory(); // 设置编码, 防止中文乱码 messageFactory.setDefaultContentEncodingCharset("gb2312"); - CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(subscribeInfo.getRequest().getCallIdHeader().getCallId()); + CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(subscribeInfo.getRequest().getCallIdHeader().getCallId()); request = (SIPRequest) messageFactory.createRequest(requestURI, Request.NOTIFY, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); - request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); - EventHeader event = sipLayer.getSipFactory().createHeaderFactory().createEventHeader(subscribeInfo.getEventType()); + EventHeader event = SipFactory.getInstance().createHeaderFactory().createEventHeader(subscribeInfo.getEventType()); if (subscribeInfo.getEventId() != null) { event.setEventId(subscribeInfo.getEventId()); } request.addHeader(event); - SubscriptionStateHeader active = sipLayer.getSipFactory().createHeaderFactory().createSubscriptionStateHeader("active"); + SubscriptionStateHeader active = SipFactory.getInstance().createHeaderFactory().createSubscriptionStateHeader("active"); request.setHeader(active); String sipAddress = parentPlatform.getDeviceIp() + ":" + parentPlatform.getDevicePort(); - Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory() + Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory() .createSipURI(parentPlatform.getDeviceGBId(), sipAddress)); - request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); + request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); - ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); + ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); request.setContent(content, contentTypeHeader); return request; } @@ -275,42 +276,42 @@ public class SIPRequestHeaderPlarformProvider { SIPRequest request = null; // sipuri - SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort()); + SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerIP()+ ":" + platform.getServerPort()); // via ArrayList viaHeaders = new ArrayList<>(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(), + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(platform.getDeviceIp(), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag()); viaHeader.setRPort(); viaHeaders.add(viaHeader); // from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getDeviceGBId(), + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getDeviceGBId(), platform.getDeviceIp() + ":" + platform.getDevicePort()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, sendRtpItem.getToTag()); + Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, sendRtpItem.getToTag()); // to - SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerGBDomain()); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, sendRtpItem.getFromTag()); + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(platform.getServerGBId(), platform.getServerGBDomain()); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, sendRtpItem.getFromTag()); // Forwards - MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); // ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE); - MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory(); + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE); + MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory(); // 设置编码, 防止中文乱码 messageFactory.setDefaultContentEncodingCharset("gb2312"); - CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId()); + CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(sendRtpItem.getCallId()); request = (SIPRequest) messageFactory.createRequest(requestURI, Request.BYE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); - request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); String sipAddress = platform.getDeviceIp() + ":" + platform.getDevicePort(); - Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory() + Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory() .createSipURI(platform.getDeviceGBId(), sipAddress)); - request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); + request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); return request; } @@ -320,37 +321,37 @@ public class SIPRequestHeaderPlarformProvider { //请求行 String platformHostAddress = platform.getServerIP() + ":" + platform.getServerPort(); String localHostAddress = sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort(); - SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, platformHostAddress); + SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, platformHostAddress); //via ArrayList viaHeaders = new ArrayList(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), viaTag); + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); //from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(platform.getDeviceGBId(), sipConfig.getDomain()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack + 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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, platformHostAddress); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null); + 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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE); - request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); + 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(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); - Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),localHostAddress)); - request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); + Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(),localHostAddress)); + request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); // Subject - SubjectHeader subjectHeader = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0)); + SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0)); request.addHeader(subjectHeader); - ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); + ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); request.setContent(content, contentTypeHeader); return request; } @@ -358,35 +359,35 @@ public class SIPRequestHeaderPlarformProvider { public Request createByteRequest(ParentPlatform platform, String channelId, SipTransactionInfo transactionInfo) throws PeerUnavailableException, ParseException, InvalidArgumentException { String deviceHostAddress = platform.getDeviceIp() + ":" + platform.getDevicePort(); Request request = null; - SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress); + SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, deviceHostAddress); // via ArrayList viaHeaders = new ArrayList(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag()); + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(platform.getDeviceIp()), platform.getDevicePort(), platform.getTransport(), SipUtils.getNewViaTag()); viaHeaders.add(viaHeader); //from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.isAsSender()?transactionInfo.getFromTag():transactionInfo.getToTag()); + 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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, deviceHostAddress); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,transactionInfo.isAsSender()?transactionInfo.getToTag():transactionInfo.getFromTag()); + 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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE); - CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId()); - request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); + 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(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); - Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(platform.getDeviceIp())+":"+ platform.getDevicePort())); - request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); + 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(sipLayer.getSipFactory(), gitUtil)); + 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 index 62abe5ed6..ca1b1dbc3 100644 --- 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 @@ -16,6 +16,7 @@ import org.springframework.stereotype.Component; import javax.sip.InvalidArgumentException; import javax.sip.PeerUnavailableException; import javax.sip.SipException; +import javax.sip.SipFactory; import javax.sip.address.Address; import javax.sip.address.SipURI; import javax.sip.header.*; @@ -49,32 +50,32 @@ public class SIPRequestHeaderProvider { public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; // sipuri - SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); + SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag); + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); // from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); + 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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, toTag); + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, toTag); // Forwards - MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); // ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE); + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE); - request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader, + request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); - request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); - ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); + ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); request.setContent(content, contentTypeHeader); return request; } @@ -82,39 +83,39 @@ public class SIPRequestHeaderProvider { public Request createInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, String ssrc, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; //请求行 - SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress()); + SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); //via ArrayList viaHeaders = new ArrayList(); - HeaderFactory headerFactory = sipLayer.getSipFactory().createHeaderFactory(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag); + HeaderFactory headerFactory = SipFactory.getInstance().createHeaderFactory(); + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); //from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack + 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); //必须要有标记,否则无法创建会话,无法回应ack //to - SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress()); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null); + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null); //Forwards - MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE); - request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); + 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(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); - Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort())); - // Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort())); - request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); + Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort())); + // Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort())); + request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); // Subject - SubjectHeader subjectHeader = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0)); + SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0)); request.addHeader(subjectHeader); - ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); + ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); request.setContent(content, contentTypeHeader); return request; } @@ -122,69 +123,74 @@ public class SIPRequestHeaderProvider { public Request createPlaybackInviteRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader, String ssrc) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; //请求行 - SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress()); + SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag); + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); //from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); //必须要有标记,否则无法创建会话,无法回应ack + 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); //必须要有标记,否则无法创建会话,无法回应ack //to - SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress()); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress,null); + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress,null); //Forwards - MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE); - request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INVITE); + request = SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INVITE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); - Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort())); - // Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort())); - request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); + Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort())); + // Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), device.getHost().getIp()+":"+device.getHost().getPort())); + request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); - request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); // Subject - SubjectHeader subjectHeader = sipLayer.getSipFactory().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0)); + SubjectHeader subjectHeader = SipFactory.getInstance().createHeaderFactory().createSubjectHeader(String.format("%s:%s,%s:%s", channelId, ssrc, sipConfig.getId(), 0)); request.addHeader(subjectHeader); - ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); + ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP"); request.setContent(content, contentTypeHeader); return request; } public Request createByteRequest(Device device, String channelId, SipTransactionInfo transactionInfo) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; - SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress()); - + //请求行 + SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag()); + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag()); viaHeaders.add(viaHeader); //from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, !transactionInfo.isAsSender()? transactionInfo.getToTag():transactionInfo.getFromTag()); + 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.getFromTag()); //to - SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId,device.getHostAddress()); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, !transactionInfo.isAsSender()? transactionInfo.getToTag(): transactionInfo.getFromTag()); + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress()); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, transactionInfo.getToTag()); //Forwards - MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.BYE); - CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId()); - request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.BYE, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); + 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(sipLayer.getSipFactory(), gitUtil)); + 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; } @@ -192,50 +198,50 @@ public class SIPRequestHeaderProvider { public Request createSubscribeRequest(Device device, String content, SIPRequest requestOld, Integer expires, String event, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; // sipuri - SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); + SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag()); viaHeader.setRPort(); viaHeaders.add(viaHeader); // from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag()); + SipURI fromSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); + Address fromAddress = SipFactory.getInstance().createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = SipFactory.getInstance().createHeaderFactory().createFromHeader(fromAddress, requestOld == null ? SipUtils.getNewFromTag() :requestOld.getFromTag()); // to - SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, requestOld == null ? null :requestOld.getToTag()); + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(device.getDeviceId(), device.getHostAddress()); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, requestOld == null ? null :requestOld.getToTag()); // Forwards - MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); // ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.SUBSCRIBE); + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.SUBSCRIBE); - request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader, + request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.SUBSCRIBE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); - Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort())); - request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); + 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)); // Expires - ExpiresHeader expireHeader = sipLayer.getSipFactory().createHeaderFactory().createExpiresHeader(expires); + ExpiresHeader expireHeader = SipFactory.getInstance().createHeaderFactory().createExpiresHeader(expires); request.addHeader(expireHeader); // Event - EventHeader eventHeader = sipLayer.getSipFactory().createHeaderFactory().createEventHeader(event); + EventHeader eventHeader = SipFactory.getInstance().createHeaderFactory().createEventHeader(event); int random = (int) Math.floor(Math.random() * 10000); eventHeader.setEventId(random + ""); request.addHeader(eventHeader); - ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); + ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); request.setContent(content, contentTypeHeader); - request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); return request; } @@ -247,37 +253,37 @@ public class SIPRequestHeaderProvider { } SIPRequest request = null; //请求行 - SipURI requestLine = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress()); + SipURI requestLine = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag()); + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipLayer.getLocalIp(device.getLocalIp()), sipConfig.getPort(), device.getTransport(), SipUtils.getNewViaTag()); viaHeaders.add(viaHeader); //from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(),sipConfig.getDomain()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, transactionInfo.getFromTag()); + 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.getFromTag()); //to - SipURI toSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId,device.getHostAddress()); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, transactionInfo.getToTag()); + SipURI toSipURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId,device.getHostAddress()); + Address toAddress = SipFactory.getInstance().createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = SipFactory.getInstance().createHeaderFactory().createToHeader(toAddress, transactionInfo.getToTag()); //Forwards - MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INFO); - CallIdHeader callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId()); - request = (SIPRequest)sipLayer.getSipFactory().createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.INFO); + CallIdHeader callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(transactionInfo.getCallId()); + request = (SIPRequest)SipFactory.getInstance().createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader,fromHeader, toHeader, viaHeaders, maxForwards); - request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); - Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipLayer.getLocalIp(device.getLocalIp())+":"+sipConfig.getPort())); - request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); + 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(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); if (content != null) { - ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", + ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSRTSP"); request.setContent(content, contentTypeHeader); } @@ -289,56 +295,55 @@ public class SIPRequestHeaderProvider { // via ArrayList viaHeaders = new ArrayList(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(localIp, sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag()); + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(localIp, sipConfig.getPort(), sipResponse.getTopmostViaHeader().getTransport(), SipUtils.getNewViaTag()); viaHeaders.add(viaHeader); //Forwards - MaxForwardsHeader maxForwards = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); //ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK); + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(sipResponse.getCSeqHeader().getSeqNumber(), Request.ACK); - Request request = sipLayer.getSipFactory().createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards); + Request request = SipFactory.getInstance().createMessageFactory().createRequest(sipURI, Request.ACK, sipResponse.getCallIdHeader(), cSeqHeader, sipResponse.getFromHeader(), sipResponse.getToHeader(), viaHeaders, maxForwards); - request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); - Address concatAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), localIp + ":"+sipConfig.getPort())); - request.addHeader(sipLayer.getSipFactory().createHeaderFactory().createContactHeader(concatAddress)); + Address concatAddress = SipFactory.getInstance().createAddressFactory().createAddress(SipFactory.getInstance().createAddressFactory().createSipURI(sipConfig.getId(), localIp + ":"+sipConfig.getPort())); + request.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress)); - request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + request.addHeader(SipUtils.createUserAgentHeader(gitUtil)); return request; } - public Request createBroadcastMessageRequest(Device device, String channelId, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; // sipuri - SipURI requestURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress()); + SipURI requestURI = SipFactory.getInstance().createAddressFactory().createSipURI(channelId, device.getHostAddress()); // via ArrayList viaHeaders = new ArrayList(); - ViaHeader viaHeader = sipLayer.getSipFactory().createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag); + ViaHeader viaHeader = SipFactory.getInstance().createHeaderFactory().createViaHeader(sipConfig.getIp(), sipConfig.getPort(), device.getTransport(), viaTag); viaHeader.setRPort(); viaHeaders.add(viaHeader); // from - SipURI fromSipURI = sipLayer.getSipFactory().createAddressFactory().createSipURI(sipConfig.getId(), sipConfig.getDomain()); - Address fromAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(fromSipURI); - FromHeader fromHeader = sipLayer.getSipFactory().createHeaderFactory().createFromHeader(fromAddress, fromTag); + 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 = sipLayer.getSipFactory().createAddressFactory().createSipURI(channelId, device.getHostAddress()); - Address toAddress = sipLayer.getSipFactory().createAddressFactory().createAddress(toSipURI); - ToHeader toHeader = sipLayer.getSipFactory().createHeaderFactory().createToHeader(toAddress, toTag); + 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 = sipLayer.getSipFactory().createHeaderFactory().createMaxForwardsHeader(70); + MaxForwardsHeader maxForwards = SipFactory.getInstance().createHeaderFactory().createMaxForwardsHeader(70); // ceq - CSeqHeader cSeqHeader = sipLayer.getSipFactory().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE); + CSeqHeader cSeqHeader = SipFactory.getInstance().createHeaderFactory().createCSeqHeader(redisCatchStorage.getCSEQ(), Request.MESSAGE); - ContentTypeHeader contentTypeHeader = sipLayer.getSipFactory().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); + ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("Application", "MANSCDP+xml"); - request = sipLayer.getSipFactory().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader, + request = SipFactory.getInstance().createMessageFactory().createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards, contentTypeHeader, content); - request.addHeader(SipUtils.createUserAgentHeader(sipLayer.getSipFactory(), gitUtil)); + 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 index 0e2d04e59..8de56034c 100644 --- 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 @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; import com.alibaba.fastjson2.JSONObject; +import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; @@ -36,6 +37,7 @@ 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; import javax.sip.message.Request; import java.text.ParseException; @@ -358,7 +360,7 @@ public class SIPCommander implements ISIPCommander { // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 ResponseEvent responseEvent = (ResponseEvent) e.event; SIPResponse response = (SIPResponse) responseEvent.getResponse(); - streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.play); + streamSession.put(device.getDeviceId(), channelId, "play", stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAY); okEvent.response(e); }); } @@ -373,11 +375,11 @@ public class SIPCommander implements ISIPCommander { */ @Override public void playbackStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, - String startTime, String endTime, InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent, + String startTime, String endTime, ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event okEvent, SipSubscribe.Event errorEvent) throws InvalidArgumentException, SipException, ParseException { - logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getSdpIp(), mediaServerItem.getIp(), ssrcInfo.getPort()); + logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort()); String sdpIp; if (!ObjectUtils.isEmpty(device.getSdpIp())) { sdpIp = device.getSdpIp(); @@ -450,8 +452,7 @@ public class SIPCommander implements ISIPCommander { // 添加订阅 subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> { if (hookEvent != null) { - InviteStreamInfo inviteStreamInfo = new InviteStreamInfo(mediaServerItemInUse, json,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream()); - hookEvent.call(inviteStreamInfo); + hookEvent.response(mediaServerItemInUse, json); } subscribe.removeSubscribe(hookSubscribe); }); @@ -460,12 +461,9 @@ public class SIPCommander implements ISIPCommander { sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> { ResponseEvent responseEvent = (ResponseEvent) event.event; SIPResponse response = (SIPResponse) responseEvent.getResponse(); - streamSession.put(device.getDeviceId(), channelId,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.playback); + streamSession.put(device.getDeviceId(), channelId,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), ssrcInfo.getStream(), ssrcInfo.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.PLAYBACK); okEvent.response(event); }); - if (inviteStreamCallback != null) { - inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()).getCallId(), "rtp", ssrcInfo.getStream())); - } } /** @@ -480,10 +478,10 @@ public class SIPCommander implements ISIPCommander { @Override public void downloadStreamCmd(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, String startTime, String endTime, int downloadSpeed, - InviteStreamCallback inviteStreamCallback, InviteStreamCallback hookEvent, + ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,SipSubscribe.Event okEvent) throws InvalidArgumentException, SipException, ParseException { - logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getSdpIp(), mediaServerItem.getIp(), ssrcInfo.getPort()); + logger.info("{} 分配的ZLM为: {} [{}:{}]", ssrcInfo.getStream(), mediaServerItem.getId(), mediaServerItem.getSdpIp(), ssrcInfo.getPort()); String sdpIp; if (!ObjectUtils.isEmpty(device.getSdpIp())) { sdpIp = device.getSdpIp(); @@ -551,13 +549,13 @@ public class SIPCommander implements ISIPCommander { content.append("y=" + ssrcInfo.getSsrc() + "\r\n");//ssrc logger.debug("此时请求下载信令的ssrc===>{}",ssrcInfo.getSsrc()); - HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, null, mediaServerItem.getId()); + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId()); // 添加订阅 CallIdHeader newCallIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()), device.getTransport()); - String callId=newCallIdHeader.getCallId(); + String callId= newCallIdHeader.getCallId(); subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> { logger.debug("sipc 添加订阅===callId {}",callId); - hookEvent.call(new InviteStreamInfo(mediaServerItem, json,callId, "rtp", ssrcInfo.getStream())); + hookEvent.response(mediaServerItemInUse, json); subscribe.removeSubscribe(hookSubscribe); hookSubscribe.getContent().put("regist", false); hookSubscribe.getContent().put("schema", "rtsp"); @@ -566,7 +564,7 @@ public class SIPCommander implements ISIPCommander { (MediaServerItem mediaServerItemForEnd, JSONObject jsonForEnd) -> { logger.info("[录像]下载结束, 发送BYE"); try { - streamByeCmd(device, channelId, ssrcInfo.getStream(),callId); + streamByeCmd(device, channelId, ssrcInfo.getStream(), callId); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { logger.error("[录像]下载结束, 发送BYE失败 {}", e.getMessage()); @@ -575,9 +573,6 @@ public class SIPCommander implements ISIPCommander { }); Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, SipUtils.getNewFromTag(), null,newCallIdHeader, ssrcInfo.getSsrc()); - if (inviteStreamCallback != null) { - inviteStreamCallback.call(new InviteStreamInfo(mediaServerItem, null,callId, "rtp", ssrcInfo.getStream())); - } sipSender.transmitRequest(sipLayer.getLocalIp(device.getLocalIp()), request, errorEvent, event -> { ResponseEvent responseEvent = (ResponseEvent) event.event; @@ -588,9 +583,7 @@ public class SIPCommander implements ISIPCommander { if (ssrcIndex >= 0) { ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); } - logger.debug("接收到的下载响应ssrc====>{}",ssrcInfo.getSsrc()); - logger.debug("接收到的下载响应ssrc====>{}",ssrc); - streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.download); + streamSession.put(device.getDeviceId(), channelId, response.getCallIdHeader().getCallId(), ssrcInfo.getStream(), ssrc, mediaServerItem.getId(), response, InviteSessionType.DOWNLOAD); okEvent.response(event); }); } @@ -654,7 +647,7 @@ public class SIPCommander implements ISIPCommander { // 这里为例避免一个通道的点播只有一个callID这个参数使用一个固定值 ResponseEvent responseEvent = (ResponseEvent) e.event; SIPResponse response = (SIPResponse) responseEvent.getResponse(); - streamSession.put(device.getDeviceId(), channelId, "talk", stream, sendRtpItem.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.talk); + streamSession.put(device.getDeviceId(), channelId, "talk", stream, sendRtpItem.getSsrc(), mediaServerItem.getId(), response, InviteSessionType.TALK); okEvent.response(e); }); } @@ -1247,7 +1240,7 @@ public class SIPCommander implements ISIPCommander { CallIdHeader callIdHeader; if (requestOld != null) { - callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId()); + callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId()); } else { callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()); } @@ -1322,7 +1315,7 @@ public class SIPCommander implements ISIPCommander { CallIdHeader callIdHeader; if (requestOld != null) { - callIdHeader = sipLayer.getSipFactory().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId()); + callIdHeader = SipFactory.getInstance().createHeaderFactory().createCallIdHeader(requestOld.getCallIdHeader().getCallId()); } else { callIdHeader = sipSender.getNewCallIdHeader(sipLayer.getLocalIp(device.getLocalIp()),device.getTransport()); } 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 index 630d7daeb..ad015d953 100644 --- 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 @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; +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; @@ -39,6 +40,7 @@ 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; import javax.sip.header.WWWAuthenticateHeader; import javax.sip.message.Request; @@ -518,7 +520,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { private void sendNotify(ParentPlatform parentPlatform, String catalogXmlContent, SubscribeInfo subscribeInfo, SipSubscribe.Event errorEvent, SipSubscribe.Event okEvent ) throws SipException, ParseException, InvalidArgumentException { - MessageFactoryImpl messageFactory = (MessageFactoryImpl) sipLayer.getSipFactory().createMessageFactory(); + MessageFactoryImpl messageFactory = (MessageFactoryImpl) SipFactory.getInstance().createMessageFactory(); String characterSet = parentPlatform.getCharacterSet(); // 设置编码, 防止中文乱码 messageFactory.setDefaultContentEncodingCharset(characterSet); @@ -854,7 +856,7 @@ public class SIPCommanderFroPlatform implements ISIPCommanderForPlatform { }), e -> { ResponseEvent responseEvent = (ResponseEvent) e.event; SIPResponse response = (SIPResponse) responseEvent.getResponse(); - streamSession.put(platform.getServerGBId(), channelId, callIdHeader.getCallId(), stream, ssrcInfo.getSsrc(), mediaServerItem.getId(), response, VideoStreamSessionManager.SessionType.broadcast); + 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/impl/ByeRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/ByeRequestProcessor.java index 202d1c66b..cbe2be16d 100644 --- 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 @@ -1,9 +1,11 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; -import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.common.InviteInfo; +import com.genersoft.iot.vmp.common.InviteSessionType; 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; @@ -13,6 +15,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorP import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.IDeviceService; +import com.genersoft.iot.vmp.service.IInviteStreamService; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IPlayService; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; @@ -29,6 +32,7 @@ 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; @@ -56,6 +60,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private IInviteStreamService inviteStreamService; + @Autowired private IDeviceService deviceService; @@ -68,6 +75,9 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In @Autowired private ZLMRTPServerFactory zlmrtpServerFactory; + @Autowired + private SSRCFactory ssrcFactory; + @Autowired private IMediaServerService mediaServerService; @@ -100,92 +110,89 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In } catch (SipException | InvalidArgumentException | ParseException e) { logger.error("[回复BYE信息失败],{}", e.getMessage()); } - - SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, request.getCallIdHeader().getCallId()); - - if (sendRtpItem != null){ - logger.info("[收到bye] {}/{}", sendRtpItem.getPlatformId(), sendRtpItem.getChannelId()); - String streamId = sendRtpItem.getStream(); - MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId()); - if (mediaServerItem == null) { - return; - } - - Boolean ready = zlmrtpServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), streamId); - if (!ready) { - logger.info("[收到bye] 发现流{}/{}已经结束,不需处理", sendRtpItem.getApp(), sendRtpItem.getStream()); - return; - } - 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); - MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); - redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), request.getCallIdHeader().getCallId(), null); - zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); - - int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId); - if (totalReaderCount <= 0) { - logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId); - if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) { - Device device = deviceService.getDevice(sendRtpItem.getDeviceId()); - if (device == null) { - logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId); + CallIdHeader callIdHeader = (CallIdHeader)evt.getRequest().getHeader(CallIdHeader.NAME); + String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser(); + String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser(); + SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(platformGbId, channelId, null, callIdHeader.getCallId()); + logger.info("[收到bye] {}/{}", platformGbId, channelId); + if (sendRtpItem != null){ + 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); + MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); + redisCatchStorage.deleteSendRTPServer(platformGbId, channelId, callIdHeader.getCallId(), null); + ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); + zlmrtpServerFactory.stopSendRtpStream(mediaInfo, param); + int totalReaderCount = zlmrtpServerFactory.totalReaderCount(mediaInfo, sendRtpItem.getApp(), streamId); + if (totalReaderCount <= 0) { + logger.info("[收到bye] {} 无其它观看者,通知设备停止推流", streamId); + if (sendRtpItem.getPlayType().equals(InviteStreamType.PLAY)) { + Device device = deviceService.getDevice(sendRtpItem.getDeviceId()); + if (device == null) { + logger.info("[收到bye] {} 通知设备停止推流时未找到设备信息", streamId); + } + try { + logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), channelId); + cmder.streamByeCmd(device, channelId, streamId, null); + } catch (InvalidArgumentException | ParseException | SipException | + SsrcTransactionNotFoundException e) { + logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage()); + } } - try { - logger.warn("[停止点播] {}/{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); - cmder.streamByeCmd(device, sendRtpItem.getChannelId(), streamId, null); - } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { - logger.error("[收到bye] {} 无其它观看者,通知设备停止推流, 发送BYE失败 {}",streamId, e.getMessage()); + if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) { + MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, + sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(), + sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId()); + redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); } } + } + // 可能是设备主动停止 + Device device = storager.queryVideoDeviceByChannelId(platformGbId); + if (device != null) { + storager.stopPlay(device.getDeviceId(), channelId); + SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null); + if (ssrcTransactionForPlay != null){ + if (ssrcTransactionForPlay.getCallId().equals(callIdHeader.getCallId())){ + // 释放ssrc + MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlay.getMediaServerId()); + if (mediaServerItem != null) { + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlay.getSsrc()); + } + streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream()); + } + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); - if (sendRtpItem.getPlayType().equals(InviteStreamType.PUSH)) { - MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, - sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(), - sendRtpItem.getPlatformId(), null, null, sendRtpItem.getMediaServerId()); - redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); + if (inviteInfo != null) { + inviteStreamService.removeInviteInfo(inviteInfo); + if (inviteInfo.getStreamInfo() != null) { + mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream()); + } + } } - } - - playService.stopAudioBroadcast(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId()); - } - - String platformGbId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(FromHeader.NAME)).getAddress().getURI()).getUser(); - String channelId = ((SipURI) ((HeaderAddress) evt.getRequest().getHeader(ToHeader.NAME)).getAddress().getURI()).getUser(); - - // 可能是设备主动停止 - Device device = storager.queryVideoDeviceByChannelId(platformGbId); - if (device != null) { - storager.stopPlay(device.getDeviceId(), channelId); - StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(device.getDeviceId(), channelId); - if (streamInfo != null) { - redisCatchStorage.stopPlay(streamInfo); - mediaServerService.closeRTPServer(streamInfo.getMediaServerId(), streamInfo.getStream()); - } - SsrcTransaction ssrcTransactionForPlay = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, "play", null); - if (ssrcTransactionForPlay != null){ - if (ssrcTransactionForPlay.getCallId().equals(request.getCallIdHeader().getCallId())){ + SsrcTransaction ssrcTransactionForPlayBack = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, callIdHeader.getCallId(), null); + if (ssrcTransactionForPlayBack != null) { // 释放ssrc - MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlay.getMediaServerId()); + MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlayBack.getMediaServerId()); if (mediaServerItem != null) { - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlay.getSsrc()); + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlayBack.getSsrc()); + } + streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlayBack.getStream()); + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAYBACK, device.getDeviceId(), channelId); + + if (inviteInfo != null) { + inviteStreamService.removeInviteInfo(inviteInfo); + if (inviteInfo.getStreamInfo() != null) { + mediaServerService.closeRTPServer(inviteInfo.getStreamInfo().getMediaServerId(), inviteInfo.getStream()); + } } - streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlay.getStream()); } } - SsrcTransaction ssrcTransactionForPlayBack = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, request.getCallIdHeader().getCallId(), null); - if (ssrcTransactionForPlayBack != null) { - // 释放ssrc - MediaServerItem mediaServerItem = mediaServerService.getOne(ssrcTransactionForPlayBack.getMediaServerId()); - if (mediaServerItem != null) { - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcTransactionForPlayBack.getSsrc()); - } - streamSession.remove(device.getDeviceId(), channelId, ssrcTransactionForPlayBack.getStream()); - } - } + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(null, null, request.getCallIdHeader().getCallId(), null); if (ssrcTransaction != null) { // 释放ssrc @@ -203,7 +210,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In // break; // case download: // break; - case broadcast: + case BROADCAST: String channelId1 = ssrcTransaction.getChannelId(); Device deviceFromTransaction = storager.queryVideoDevice(ssrcTransaction.getDeviceId()); @@ -255,7 +262,7 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In List ssrcTransactions = streamSession.getSsrcTransactionForAll(null, channelId1, null, null); if (ssrcTransactions.size() > 0) { for (SsrcTransaction transaction : ssrcTransactions) { - if (transaction.getType().equals(VideoStreamSessionManager.SessionType.broadcast)) { + if (transaction.getType().equals(InviteSessionType.BROADCAST)) { ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(transaction.getDeviceId()); if (parentPlatform != null) { try { 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 30ac3812d..97880a8b5 100644 --- 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 @@ -1,15 +1,14 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; -import com.alibaba.fastjson2.JSONObject; import com.genersoft.iot.vmp.common.VideoManagerConstants; +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.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.SsrcConfig; import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; +import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPSender; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; @@ -23,7 +22,12 @@ import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; import com.genersoft.iot.vmp.media.zlm.dto.*; -import com.genersoft.iot.vmp.service.*; +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.bean.InviteErrorCallback; +import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.service.bean.MessageForPushChannel; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener; @@ -78,6 +82,9 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private SSRCFactory ssrcFactory; + @Autowired private DynamicTask dynamicTask; @@ -90,8 +97,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements @Autowired private SIPSender sipSender; - @Autowired - private AudioBroadcastManager audioBroadcastManager; + @Autowired + private AudioBroadcastManager audioBroadcastManager; @Autowired private ZLMRTPServerFactory zlmrtpServerFactory; @@ -102,8 +109,8 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements @Autowired private ISIPCommander commander; - @Autowired - private ZLMRESTfulUtils zlmresTfulUtils; + @Autowired + private ZLMRESTfulUtils zlmresTfulUtils; @Autowired private ZlmHttpHookSubscribe zlmHttpHookSubscribe; @@ -111,29 +118,25 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements @Autowired private SIPProcessorObserver sipProcessorObserver; - @Autowired - private VideoStreamSessionManager sessionManager; - @Autowired private UserSetting userSetting; @Autowired private ZLMMediaListManager mediaListManager; - @Autowired - private DeferredResultHolder resultHolder; + @Autowired + private DeferredResultHolder resultHolder; - @Autowired - private ZlmHttpHookSubscribe subscribe; + @Autowired + private ZlmHttpHookSubscribe subscribe; - @Autowired - private SipConfig config; + @Autowired + private SipConfig config; @Autowired private VideoStreamSessionManager streamSession; - @Autowired private RedisGbPlayMsgListener redisGbPlayMsgListener; @@ -153,7 +156,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements public void process(RequestEvent evt) { // Invite Request消息实现,此消息一般为级联消息,上级给下级发送请求视频指令 try { - SIPRequest request = (SIPRequest)evt.getRequest(); + SIPRequest request = (SIPRequest) evt.getRequest(); String channelId = SipUtils.getChannelIdFromRequest(request); String requesterId = SipUtils.getUserIdFromFromHeader(request); CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME); @@ -167,27 +170,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } return; } - String ssrc = null; - SessionDescription sdp = null; - String ssrcDefault = "0000000000"; - if (channelId == null) { - // 解析sdp消息, 使用jainsip 自带的sdp解析方式 - String contentString = new String(request.getRawContent()); - - // jainSip不支持y=字段, 移除以解析。 - int ssrcIndex = contentString.indexOf("y="); - - if (ssrcIndex >= 0) { - //ssrc规定长度为10个字节,不取余下长度以避免后续还有“f=”字段 - ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); - String substring = contentString.substring(0, contentString.indexOf("y=")); - sdp = SdpFactory.getInstance().createSessionDescription(substring); - } else { - ssrc = ssrcDefault; - sdp = SdpFactory.getInstance().createSessionDescription(contentString); - } - channelId = sdp.getOrigin().getUsername(); - } // 查询请求是否来自上级平台\设备 @@ -249,7 +231,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); @@ -285,23 +267,21 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } return; } - if (sdp == null || ssrc == null) { - // 解析sdp消息, 使用jainsip 自带的sdp解析方式 - String contentString = new String(request.getRawContent()); + // 解析sdp消息, 使用jainsip 自带的sdp解析方式 + String contentString = new String(request.getRawContent()); - // jainSip不支持y=字段, 移除以解析。 - int ssrcIndex = contentString.indexOf("y="); - if (ssrcIndex >= 0) { - //ssrc规定长度为10个字节,不取余下长度以避免后续还有“f=”字段 - ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); - String substring = contentString.substring(0, contentString.indexOf("y=")); - sdp = SdpFactory.getInstance().createSessionDescription(substring); - } else { - ssrc = ssrcDefault; - sdp = SdpFactory.getInstance().createSessionDescription(contentString); - } + // jainSip不支持y=字段, 移除以解析。 + // 检查是否有y字段 + int ssrcIndex = contentString.indexOf("y="); + + SessionDescription sdp; + if (ssrcIndex >= 0) { + //ssrc规定长度为10个字节,不取余下长度以避免后续还有“f=”字段 + String substring = contentString.substring(0, ssrcIndex); + sdp = SdpFactory.getInstance().createSessionDescription(substring); + } else { + sdp = SdpFactory.getInstance().createSessionDescription(contentString); } - String sessionName = sdp.getSessionName().getValue(); Long startTime = null; @@ -363,7 +343,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements String username = sdp.getOrigin().getUsername(); String addressStr = sdp.getConnection().getAddress(); - logger.info("[上级点播]用户:{}, 通道:{}, 地址:{}:{}, ssrc:{}", username, channelId, addressStr, port, ssrc); + Device device = null; // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标 if (channel != null) { @@ -387,6 +367,25 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } return; } + + String ssrc; + if (userSetting.getUseCustomSsrcForParentInvite() || ssrcIndex < 0) { + // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 + ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId()); + } else { + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); + } + String streamTypeStr = null; + if (mediaTransmissionTCP) { + if (tcpActive) { + streamTypeStr = "TCP-ACTIVE"; + } else { + streamTypeStr = "TCP-PASSIVE"; + } + } else { + streamTypeStr = "UDP"; + } + logger.info("[上级Invite] {}, 平台:{}, 通道:{}, 收流地址:{}:{},收流方式:{}, ssrc:{}", sessionName, username, channelId, addressStr, port, streamTypeStr, ssrc); SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, device.getDeviceId(), channelId, mediaTransmissionTCP, platform.isRtcp()); @@ -407,11 +406,10 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements Long finalStartTime = startTime; Long finalStopTime = stopTime; - String finalChannelId = channelId; - ZlmHttpHookSubscribe.Event hookEvent = (mediaServerItemInUSe, responseJSON) -> { - String app = responseJSON.getString("app"); - String stream = responseJSON.getString("stream"); - logger.info("[上级点播]下级已经开始推流。 回复200OK(SDP), {}/{}", app, stream); + InviteErrorCallback hookEvent = (code, msg, data) -> { + StreamInfo streamInfo = (StreamInfo) data; + MediaServerItem mediaServerItemInUSe = mediaServerService.getOne(streamInfo.getMediaServerId()); + logger.info("[上级Invite]下级已经开始推流。 回复200OK(SDP), {}/{}", streamInfo.getApp(), streamInfo.getStream()); // * 0 等待设备推流上来 // * 1 下级已经推流,等待上级平台回复ack // * 2 推流中 @@ -420,7 +418,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements StringBuffer content = new StringBuffer(200); content.append("v=0\r\n"); - content.append("o=" + finalChannelId + " 0 0 IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n"); + content.append("o=" + channelId + " 0 0 IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n"); content.append("s=" + sessionName + "\r\n"); content.append("c=IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n"); if ("Playback".equalsIgnoreCase(sessionName)) { @@ -462,111 +460,118 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements logger.error("[命令发送失败] 国标级联 回复SdpAck", e); } }; - SipSubscribe.Event errorEvent = ((event) -> { + InviteErrorCallback errorEvent = ((statusCode, msg, data) -> { // 未知错误。直接转发设备点播的错误 try { - Response response = getMessageFactory().createResponse(event.statusCode, evt.getRequest()); - sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); - } catch (ParseException | SipException e) { + if (statusCode > 0) { + Response response = getMessageFactory().createResponse(statusCode, evt.getRequest()); + sipSender.transmitRequest(request.getLocalAddress().getHostAddress(), response); + } + } catch (ParseException | SipException e) { logger.error("未处理的异常 ", e); } }); sendRtpItem.setApp("rtp"); if ("Playback".equalsIgnoreCase(sessionName)) { sendRtpItem.setPlayType(InviteStreamType.PLAYBACK); - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, device.isSsrcCheck(), true); + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, null, null, device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam()); sendRtpItem.setStream(ssrcInfo.getStream()); // 写入redis, 超时时回复 redisCatchStorage.updateSendRTPSever(sendRtpItem); playService.playBack(mediaServerItem, ssrcInfo, device.getDeviceId(), channelId, DateUtil.formatter.format(start), - DateUtil.formatter.format(end), null, result -> { - if (result.getCode() != 0) { - logger.warn("录像回放失败"); - if (result.getEvent() != null) { - errorEvent.response(result.getEvent()); - } - redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), finalChannelId, callIdHeader.getCallId(), null); - try { - responseAck(request, Response.REQUEST_TIMEOUT); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 国标级联 录像回放 发送REQUEST_TIMEOUT: {}", e.getMessage()); - } + DateUtil.formatter.format(end), + (code, msg, data) -> { + 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()) { + logger.info("[录像回放]超时, 用户:{}, 通道:{}", username, channelId); + redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); + errorEvent.run(code, msg, data); } else { - if (result.getMediaServerItem() != null) { - hookEvent.response(result.getMediaServerItem(), result.getResponse()); - } + errorEvent.run(code, msg, data); + } + }); + } 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); + } + if (mediaDescription != null) { + downloadSpeed = mediaDescription.getAttribute("downloadspeed"); + } + + sendRtpItem.setPlayType(InviteStreamType.DOWNLOAD); + 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()) { + hookEvent.run(code, msg, data); + } 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 { + errorEvent.run(code, msg, data); } }); } else { sendRtpItem.setPlayType(InviteStreamType.PLAY); - SsrcTransaction playTransaction = sessionManager.getSsrcTransaction(device.getDeviceId(), channelId, "play", null); - if (playTransaction != null) { - Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, "rtp", playTransaction.getStream()); - if (!streamReady) { - boolean hasRtpServer = mediaServerService.checkRtpServer(mediaServerItem, "rtp", playTransaction.getStream()); - if (hasRtpServer) { - logger.info("[上级点播]已经开启rtpServer但是尚未收到流,开启监听流的到来"); - HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", playTransaction.getStream(), true, "rtsp", mediaServerItem.getId()); - zlmHttpHookSubscribe.addSubscribe(hookSubscribe, hookEvent); - }else { - playTransaction = null; - } - } - } - if (playTransaction == null) { - String streamId = null; - if (mediaServerItem.isRtpEnable()) { - streamId = String.format("%s_%s", device.getDeviceId(), channelId); - } - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, device.isSsrcCheck(), false); - logger.info(JSONObject.toJSONString(ssrcInfo)); - sendRtpItem.setStream(ssrcInfo.getStream()); - sendRtpItem.setSsrc(ssrc.equals(ssrcDefault) ? ssrcInfo.getSsrc() : ssrc); - - // 写入redis, 超时时回复 - redisCatchStorage.updateSendRTPSever(sendRtpItem); - playService.play(mediaServerItem, ssrcInfo, device, channelId, hookEvent, errorEvent, (code, msg) -> { - logger.info("[上级点播]超时, 用户:{}, 通道:{}", username, finalChannelId); - redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), finalChannelId, callIdHeader.getCallId(), null); - }); + String streamId = null; + if (mediaServerItem.isRtpEnable()) { + streamId = String.format("%s_%s", device.getDeviceId(), channelId); } else { - sendRtpItem.setStream(playTransaction.getStream()); - // 写入redis, 超时时回复 - redisCatchStorage.updateSendRTPSever(sendRtpItem); - JSONObject jsonObject = new JSONObject(); - jsonObject.put("app", sendRtpItem.getApp()); - jsonObject.put("stream", sendRtpItem.getStream()); - hookEvent.response(mediaServerItem, jsonObject); + streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase(); } + sendRtpItem.setStream(streamId); + redisCatchStorage.updateSendRTPSever(sendRtpItem); + playService.play(mediaServerItem, device.getDeviceId(), channelId, ((code, msg, data) -> { + 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()) { + logger.info("[上级点播]超时, 用户:{}, 通道:{}", username, channelId); + redisCatchStorage.deleteSendRTPServer(platform.getServerGBId(), channelId, callIdHeader.getCallId(), null); + errorEvent.run(code, msg, data); + } else { + errorEvent.run(code, msg, data); + } + })); + } } else if (gbStream != null) { - if(ssrc.equals(ssrcDefault)) - { - SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig(); - if(ssrcConfig != null) - { - ssrc = ssrcConfig.getPlaySsrc(); - ssrcConfig.releaseSsrc(ssrc); - } + + String ssrc; + if (userSetting.getUseCustomSsrcForParentInvite() || ssrcIndex < 0) { + // 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 + ssrc = "Play".equalsIgnoreCase(sessionName) ? ssrcFactory.getPlaySsrc(mediaServerItem.getId()) : ssrcFactory.getPlayBackSsrc(mediaServerItem.getId()); + } else { + ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); } - 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); } } @@ -586,42 +591,43 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements * 安排推流 */ private void pushProxyStream(RequestEvent evt, SIPRequest request, GbStream gbStream, ParentPlatform platform, - CallIdHeader callIdHeader, MediaServerItem mediaServerItem, - int port, Boolean tcpActive, boolean mediaTransmissionTCP, - String channelId, String addressStr, String ssrc, String requesterId) { - Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream()); - if (streamReady) { - // 自平台内容 - SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, - gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp()); + CallIdHeader callIdHeader, MediaServerItem mediaServerItem, + int port, Boolean tcpActive, boolean mediaTransmissionTCP, + String channelId, String addressStr, String ssrc, String requesterId) { + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream()); + if (streamReady != null && streamReady) { + // 自平台内容 + SendRtpItem sendRtpItem = zlmrtpServerFactory.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, @@ -629,7 +635,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements // 推流 if (streamPushItem.isSelf()) { Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream()); - if (streamReady) { + if (streamReady != null && streamReady) { // 自平台内容 SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, gbStream.getApp(), gbStream.getStream(), channelId, mediaTransmissionTCP, platform.isRtcp()); @@ -661,7 +667,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } 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); } @@ -671,6 +677,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements mediaTransmissionTCP, channelId, addressStr, ssrc, requesterId); } } + /** * 通知流上线 */ @@ -688,7 +695,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements String stream = responseJSON.getString("stream"); logger.info("[上级点播]拉流代理已经就绪, {}/{}", app, stream); 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(), () -> { @@ -707,7 +714,6 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } - } else if ("push".equals(gbStream.getStreamType())) { if (!platform.isStartOfflinePush()) { // 平台设置中关闭了拉起离线的推流则直接回复 @@ -811,7 +817,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(), null, responseSendItemMsg -> { SendRtpItem sendRtpItem = responseSendItemMsg.getSendRtpItem(); if (sendRtpItem == null || responseSendItemMsg.getMediaServerItem() == null) { logger.warn("服务器端口资源不足"); @@ -836,7 +842,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()); } @@ -877,8 +883,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"); @@ -953,7 +958,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } if (device != null) { logger.info("收到设备" + requesterId + "的语音广播Invite请求"); - String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + broadcastCatch.getChannelId(); + String key = VideoManagerConstants.BROADCAST_WAITE_INVITE + device.getDeviceId() + broadcastCatch.getChannelId(); dynamicTask.stop(key); try { responseAck(request, Response.TRYING); @@ -986,7 +991,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements boolean mediaTransmissionTCP = false; Boolean tcpActive = null; for (int i = 0; i < mediaDescriptions.size(); i++) { - MediaDescription mediaDescription = (MediaDescription)mediaDescriptions.get(i); + MediaDescription mediaDescription = (MediaDescription) mediaDescriptions.get(i); Media media = mediaDescription.getMedia(); Vector mediaFormats = media.getMediaFormats(false); @@ -1022,7 +1027,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } String addressStr = sdp.getOrigin().getAddress(); logger.info("设备{}请求语音流,地址:{}:{},ssrc:{}, {}", requesterId, addressStr, port, ssrc, - mediaTransmissionTCP ? (tcpActive? "TCP主动":"TCP被动") : "UDP"); + mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP"); MediaServerItem mediaServerItem = broadcastCatch.getMediaServerItem(); if (mediaServerItem == null) { @@ -1036,7 +1041,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements return; } logger.info("设备{}请求语音流, 收流地址:{}:{},ssrc:{}, {}, 对讲方式:{}", requesterId, addressStr, port, ssrc, - mediaTransmissionTCP ? (tcpActive? "TCP主动":"TCP被动") : "UDP", sdp.getSessionName().getValue()); + mediaTransmissionTCP ? (tcpActive ? "TCP主动" : "TCP被动") : "UDP", sdp.getSessionName().getValue()); SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId, device.getDeviceId(), broadcastCatch.getChannelId(), @@ -1076,7 +1081,7 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, broadcastCatch.getApp(), broadcastCatch.getStream()); if (streamReady) { sendOk(device, sendRtpItem, sdp, request, mediaServerItem, mediaTransmissionTCP, ssrc); - }else { + } else { logger.warn("[语音通话], 未发现待推送的流,app={},stream={}", broadcastCatch.getApp(), broadcastCatch.getStream()); try { responseAck(request, Response.GONE); @@ -1093,29 +1098,30 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements } 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 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("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("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("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"); @@ -1125,11 +1131,11 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements content.append("a=connection:new\r\n"); if (!sendRtpItem.isTcpActive()) { content.append("a=setup:active\r\n"); - }else { + } else { content.append("a=setup:passive\r\n"); } } - content.append("y="+ ssrc + "\r\n"); + content.append("y=" + ssrc + "\r\n"); content.append("f=v/////a/1/8/1\r\n"); ParentPlatform parentPlatform = new ParentPlatform(); 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 new file mode 100644 index 000000000..b9a41a571 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/NotifyRequestForCatalogProcessor.java @@ -0,0 +1,281 @@ +package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl; + +import com.genersoft.iot.vmp.conf.DynamicTask; +import com.genersoft.iot.vmp.conf.UserSetting; +import com.genersoft.iot.vmp.gb28181.bean.Device; +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; +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.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.IDeviceChannelService; +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.RequestEvent; +import javax.sip.header.FromHeader; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * SIP命令类型: NOTIFY请求中的目录请求处理 + */ +@Component +public class NotifyRequestForCatalogProcessor extends SIPRequestProcessorParent { + + + private final static Logger logger = LoggerFactory.getLogger(NotifyRequestForCatalogProcessor.class); + + private final List updateChannelOnlineList = new CopyOnWriteArrayList<>(); + private final List updateChannelOfflineList = new CopyOnWriteArrayList<>(); + private final Map updateChannelMap = new ConcurrentHashMap<>(); + + private final Map addChannelMap = new ConcurrentHashMap<>(); + private final List deleteChannelList = new CopyOnWriteArrayList<>(); + + + @Autowired + private UserSetting userSetting; + + @Autowired + private EventPublisher eventPublisher; + + @Autowired + private IRedisCatchStorage redisCatchStorage; + + @Autowired + private IDeviceChannelService deviceChannelService; + + @Autowired + private DynamicTask dynamicTask; + + private final static String talkKey = "notify-request-for-catalog-task"; + + public void process(RequestEvent evt) { + try { + long start = System.currentTimeMillis(); + FromHeader fromHeader = (FromHeader) evt.getRequest().getHeader(FromHeader.NAME); + String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader); + + Device device = redisCatchStorage.getDevice(deviceId); + if (device == null || device.getOnline() == 0) { + 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.channelContentHander(itemDevice, device, event); + + channel.setDeviceId(device.getDeviceId()); + logger.info("[收到目录订阅]:{}/{}", device.getDeviceId(), channel.getChannelId()); + switch (event) { + case CatalogEvent.ON: + // 上线 + logger.info("[收到通道上线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); + updateChannelOnlineList.add(channel); + if (updateChannelOnlineList.size() > 300) { + executeSaveForOnline(); + } + if (userSetting.getDeviceStatusNotify()) { + // 发送redis消息 + redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), true); + } + + break; + case CatalogEvent.OFF : + // 离线 + logger.info("[收到通道离线通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); + if (userSetting.getRefuseChannelStatusChannelFormNotify()) { + logger.info("[收到通道离线通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); + }else { + updateChannelOfflineList.add(channel); + if (updateChannelOfflineList.size() > 300) { + executeSaveForOffline(); + } + if (userSetting.getDeviceStatusNotify()) { + // 发送redis消息 + redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false); + } + } + break; + case CatalogEvent.VLOST: + // 视频丢失 + logger.info("[收到通道视频丢失通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); + if (userSetting.getRefuseChannelStatusChannelFormNotify()) { + logger.info("[收到通道视频丢失通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); + }else { + updateChannelOfflineList.add(channel); + if (updateChannelOfflineList.size() > 300) { + executeSaveForOffline(); + } + if (userSetting.getDeviceStatusNotify()) { + // 发送redis消息 + redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false); + } + } + break; + case CatalogEvent.DEFECT: + // 故障 + logger.info("[收到通道视频故障通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); + if (userSetting.getRefuseChannelStatusChannelFormNotify()) { + logger.info("[收到通道视频故障通知] 但是平台已配置拒绝此消息,来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); + }else { + updateChannelOfflineList.add(channel); + if (updateChannelOfflineList.size() > 300) { + executeSaveForOffline(); + } + if (userSetting.getDeviceStatusNotify()) { + // 发送redis消息 + redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), channel.getChannelId(), false); + } + } + break; + case CatalogEvent.ADD: + // 增加 + logger.info("[收到增加通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); + // 判断此通道是否存在 + DeviceChannel deviceChannel = deviceChannelService.getOne(deviceId, channel.getChannelId()); + if (deviceChannel != null) { + channel.setId(deviceChannel.getId()); + updateChannelMap.put(channel.getChannelId(), channel); + if (updateChannelMap.keySet().size() > 300) { + executeSaveForUpdate(); + } + }else { + addChannelMap.put(channel.getChannelId(), channel); + if (addChannelMap.keySet().size() > 300) { + executeSaveForAdd(); + } + } + + break; + case CatalogEvent.DEL: + // 删除 + logger.info("[收到删除通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); + deleteChannelList.add(channel); + if (deleteChannelList.size() > 300) { + executeSaveForDelete(); + } + break; + case CatalogEvent.UPDATE: + // 更新 + logger.info("[收到更新通道通知] 来自设备: {}, 通道 {}", device.getDeviceId(), channel.getChannelId()); + // 判断此通道是否存在 + DeviceChannel deviceChannelForUpdate = deviceChannelService.getOne(deviceId, channel.getChannelId()); + if (deviceChannelForUpdate != null) { + channel.setId(deviceChannelForUpdate.getId()); + updateChannelMap.put(channel.getChannelId(), channel); + if (updateChannelMap.keySet().size() > 300) { + executeSaveForUpdate(); + } + }else { + addChannelMap.put(channel.getChannelId(), channel); + if (addChannelMap.keySet().size() > 300) { + executeSaveForAdd(); + } + } + break; + default: + logger.warn("[ NotifyCatalog ] event not found : {}", event ); + + } + // 转发变化信息 + eventPublisher.catalogEventPublish(null, channel, event); + + if (updateChannelMap.keySet().size() > 0 + || addChannelMap.keySet().size() > 0 + || updateChannelOnlineList.size() > 0 + || updateChannelOfflineList.size() > 0 + || deleteChannelList.size() > 0) { + + if (!dynamicTask.contains(talkKey)) { + dynamicTask.startDelay(talkKey, this::executeSave, 1000); + } + } + } + } + } catch (DocumentException e) { + logger.error("未处理的异常 ", e); + } + } + + private void executeSave(){ + System.out.println("定时存储数据"); + executeSaveForUpdate(); + executeSaveForDelete(); + executeSaveForOnline(); + executeSaveForOffline(); + dynamicTask.stop(talkKey); + } + + private void executeSaveForUpdate(){ + if (updateChannelMap.values().size() > 0) { + ArrayList deviceChannels = new ArrayList<>(updateChannelMap.values()); + updateChannelMap.clear(); + deviceChannelService.batchUpdateChannel(deviceChannels); + } + + } + + private void executeSaveForAdd(){ + if (addChannelMap.values().size() > 0) { + ArrayList deviceChannels = new ArrayList<>(addChannelMap.values()); + addChannelMap.clear(); + deviceChannelService.batchAddChannel(deviceChannels); + } + } + + private void executeSaveForDelete(){ + if (deleteChannelList.size() > 0) { + deviceChannelService.deleteChannels(deleteChannelList); + deleteChannelList.clear(); + } + } + + private void executeSaveForOnline(){ + if (updateChannelOnlineList.size() > 0) { + deviceChannelService.channelsOnline(updateChannelOnlineList); + updateChannelOnlineList.clear(); + } + } + + private void executeSaveForOffline(){ + if (updateChannelOfflineList.size() > 0) { + 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 index a5e27c3b0..5dae82612 100644 --- 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 @@ -76,12 +76,17 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements @Autowired private IDeviceChannelService deviceChannelService; + @Autowired + private NotifyRequestForCatalogProcessor notifyRequestForCatalogProcessor; + private ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @Qualifier("taskExecutor") @Autowired private ThreadPoolTaskExecutor taskExecutor; + private int maxQueueCount = 30000; + @Override public void afterPropertiesSet() throws Exception { // 添加消息处理的订阅 @@ -91,43 +96,52 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements @Override public void process(RequestEvent evt) { try { - responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null); + + if (taskQueue.size() >= userSetting.getMaxNotifyCountQueue()) { + responseAck((SIPRequest) evt.getRequest(), Response.BUSY_HERE, null, null); + logger.error("[notify] 待处理消息队列已满 {},返回486 BUSY_HERE,消息不做处理", userSetting.getMaxNotifyCountQueue()); + return; + }else { + responseAck((SIPRequest) evt.getRequest(), Response.OK, null, null); + } + }catch (SipException | InvalidArgumentException | ParseException e) { logger.error("未处理的异常 ", e); } boolean runed = !taskQueue.isEmpty(); + logger.info("[notify] 待处理消息数量: {}", taskQueue.size()); taskQueue.offer(new HandlerCatchData(evt, null, null)); if (!runed) { taskExecutor.execute(()-> { - try { - while (!taskQueue.isEmpty()) { - try { - HandlerCatchData take = taskQueue.poll(); - Element rootElement = getRootElement(take.getEvt()); - if (rootElement == null) { - logger.error("处理NOTIFY消息时未获取到消息体,{}", take.getEvt().getRequest()); - continue; - } - String cmd = XmlUtil.getText(rootElement, "CmdType"); - - if (CmdType.CATALOG.equals(cmd)) { - logger.info("接收到Catalog通知"); - processNotifyCatalogList(take.getEvt()); - } else if (CmdType.ALARM.equals(cmd)) { - logger.info("接收到Alarm通知"); - processNotifyAlarm(take.getEvt()); - } else if (CmdType.MOBILE_POSITION.equals(cmd)) { - logger.info("接收到MobilePosition通知"); - processNotifyMobilePosition(take.getEvt()); - } else { - logger.info("接收到消息:" + cmd); - } - } catch (DocumentException e) { - logger.error("处理NOTIFY消息时错误", e); + while (!taskQueue.isEmpty()) { + try { + HandlerCatchData take = taskQueue.poll(); + if (take == null) { + continue; } + Element rootElement = getRootElement(take.getEvt()); + if (rootElement == null) { + logger.error("处理NOTIFY消息时未获取到消息体,{}", take.getEvt().getRequest()); + continue; + } + String cmd = XmlUtil.getText(rootElement, "CmdType"); + + if (CmdType.CATALOG.equals(cmd)) { + logger.info("接收到Catalog通知"); +// processNotifyCatalogList(take.getEvt()); + notifyRequestForCatalogProcessor.process(take.getEvt()); + } else if (CmdType.ALARM.equals(cmd)) { + logger.info("接收到Alarm通知"); + processNotifyAlarm(take.getEvt()); + } else if (CmdType.MOBILE_POSITION.equals(cmd)) { + logger.info("接收到MobilePosition通知"); + processNotifyMobilePosition(take.getEvt()); + } else { + logger.info("接收到消息:" + cmd); + } + } catch (DocumentException e) { + logger.error("处理NOTIFY消息时错误", e); } - }catch (Exception e) { - logger.error("处理NOTIFY消息时错误", e); } }); } @@ -135,7 +149,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements /** * 处理MobilePosition移动位置Notify - * + * * @param evt */ private void processNotifyMobilePosition(RequestEvent evt) { @@ -239,7 +253,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements /*** * 处理alarm设备报警Notify - * + * * @param evt */ private void processNotifyAlarm(RequestEvent evt) { @@ -349,7 +363,7 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements /*** * 处理catalog设备目录列表Notify - * + * * @param evt */ private void processNotifyCatalogList(RequestEvent evt) { 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 3fee5e539..69969b3f3 100644 --- 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 @@ -83,21 +83,7 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen public void process(RequestEvent evt) { try { RequestEventExt evtExt = (RequestEventExt) evt; - String requestAddress = evtExt.getRemoteIpAddress() + ":" + evtExt.getRemotePort(); -// MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer(); -// QueryExp protocol = Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")); -//// ObjectName name = new ObjectName("*:type=Connector,*"); -// ObjectName name = new ObjectName("*:*"); -// Set objectNames = beanServer.queryNames(name, protocol); -// for (ObjectName objectName : objectNames) { -// String catalina = objectName.getDomain(); -// if ("Catalina".equals(catalina)) { -// System.out.println(objectName.getKeyProperty("port")); -// } -// } - -// System.out.println(ServiceInfo.getServerPort()); SIPRequest request = (SIPRequest)evt.getRequest(); Response response = null; boolean passwordCorrect = false; @@ -107,12 +93,13 @@ public class RegisterRequestProcessor extends SIPRequestProcessorParent implemen AddressImpl address = (AddressImpl) fromHeader.getAddress(); SipUri uri = (SipUri) address.getURI(); String deviceId = uri.getUser(); - logger.info("[注册请求] 设备:{}, 开始处理: {}", deviceId, requestAddress); + Device device = deviceService.getDevice(deviceId); RemoteAddressInfo remoteAddressInfo = SipUtils.getRemoteAddressFromRequest(request, userSetting.getSipUseSourceIpAsRemoteAddress()); - + String requestAddress = remoteAddressInfo.getIp() + ":" + remoteAddressInfo.getPort(); + logger.info("[注册请求] 设备:{}, 开始处理: {}", deviceId, requestAddress); if (device != null && device.getSipTransactionInfo() != null && request.getCallIdHeader().getCallId().equals(device.getSipTransactionInfo().getCallId())) { 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 index f889b9e7d..62290c519 100644 --- 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,6 +1,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.info; -import com.genersoft.iot.vmp.common.StreamInfo; +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; @@ -9,6 +10,7 @@ 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; @@ -17,10 +19,12 @@ 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.*; +import javax.sip.header.CallIdHeader; +import javax.sip.header.ContentTypeHeader; import javax.sip.message.Response; import java.text.ParseException; @@ -43,6 +47,9 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private IInviteStreamService inviteStreamService; + @Autowired private IVideoManagerStorage storager; @@ -103,27 +110,30 @@ public class InfoRequestProcessor extends SIPRequestProcessorParent implements I if ("Application".equalsIgnoreCase(contentType) && "MANSRTSP".equalsIgnoreCase(contentSubType)) { SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, null, callIdHeader.getCallId()); String streamId = sendRtpItem.getStream(); - StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null); - if (null == streamInfo) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); + if (null == inviteInfo) { responseAck(request, Response.NOT_FOUND, "stream " + streamId + " not found"); return; } - Device device1 = storager.queryVideoDevice(streamInfo.getDeviceID()); - cmder.playbackControlCmd(device1,streamInfo,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()); - } - }); + 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) { 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 865b6623c..4d1a58e2b 100644 --- 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 @@ -69,6 +69,7 @@ public class KeepaliveNotifyMessageHandler extends SIPRequestProcessorParent imp 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()); device.setPort(remoteAddressInfo.getPort()); device.setHostAddress(remoteAddressInfo.getIp().concat(":").concat(String.valueOf(remoteAddressInfo.getPort()))); device.setIp(remoteAddressInfo.getIp()); 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 index d5d80e6f6..55094f981 100644 --- 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 @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify.cmd; -import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.common.InviteInfo; +import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; @@ -12,6 +13,10 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; 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.ZlmHttpHookSubscribe; +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; +import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; +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; @@ -58,6 +63,15 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i @Autowired private VideoStreamSessionManager sessionManager; + @Autowired + private ZlmHttpHookSubscribe subscribe; + + @Autowired + private IInviteStreamService inviteStreamService; + + @Autowired + private VideoStreamSessionManager streamSession; + @Override public void afterPropertiesSet() throws Exception { notifyMessageHandler.addHandler(cmdType, this); @@ -76,23 +90,24 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i String NotifyType =getText(rootElement, "NotifyType"); if ("121".equals(NotifyType)){ logger.info("[录像流]推送完毕,收到关流通知"); - // 查询是设备 - StreamInfo streamInfo = redisCatchStorage.queryDownload(null, null, null, callIdHeader.getCallId()); - if (streamInfo != null) { - // 设置进度100% - streamInfo.setProgress(1); - redisCatchStorage.startDownload(streamInfo, callIdHeader.getCallId()); - } - // 先从会话内查找 - SsrcTransaction ssrcTransaction = sessionManager.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); - if (ssrcTransaction != null) { // 兼容海康 媒体通知 消息from字段不是设备ID的问题 + SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(null, null, callIdHeader.getCallId(), null); + if (ssrcTransaction != null) { + logger.info("[录像流]推送完毕,关流通知, device: {}, channelId: {}", ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId()); + InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(), ssrcTransaction.getStream()); + if (inviteInfo.getStreamInfo() != null) { + inviteInfo.getStreamInfo().setProgress(1); + inviteStreamService.updateInviteInfo(inviteInfo); + } try { cmder.streamByeCmd(device, ssrcTransaction.getChannelId(), null, callIdHeader.getCallId()); } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { logger.error("[录像流]推送完毕,收到关流通知, 发送BYE失败 {}", e.getMessage()); } + // 去除监听流注销自动停止下载的监听 + HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcTransaction.getStream(), false, "rtsp", ssrcTransaction.getMediaServerId()); + subscribe.removeSubscribe(hookSubscribe); // 如果级联播放,需要给上级发送此通知 TODO 多个上级同时观看一个下级 可能存在停错的问题,需要将点播CallId进行上下级绑定 SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, ssrcTransaction.getChannelId(), null, null); @@ -108,6 +123,8 @@ public class MediaStatusNotifyMessageHandler extends SIPRequestProcessorParent i logger.error("[命令发送失败] 国标级联 录像播放完毕: {}", e.getMessage()); } } + }else { + logger.info("[录像流]推送完毕,关流通知, 但是未找到对应的下载信息"); } } } 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 index 7feac173a..75d298d09 100644 --- 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 @@ -18,6 +18,10 @@ 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; import javax.sip.message.Response; @@ -91,7 +95,7 @@ public class InviteResponseProcessor extends SIPResponseProcessorAbstract { } // 查看是否是来自设备的,此是回复 - SipURI requestUri = sipLayer.getSipFactory().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort()); + SipURI requestUri = SipFactory.getInstance().createAddressFactory().createSipURI(sdp.getOrigin().getUsername(), event.getRemoteIpAddress() + ":" + event.getRemotePort()); Request reqAck = headerProvider.createAckRequest(response.getLocalAddress().getHostAddress(), requestUri, response); logger.info("[回复ack] {}-> {}:{} ", sdp.getOrigin().getUsername(), event.getRemoteIpAddress(), event.getRemotePort()); 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 808fc1c14..e01107fe9 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 @@ -54,7 +54,7 @@ public class SipUtils { return "z9hG4bK" + System.currentTimeMillis(); } - public static UserAgentHeader createUserAgentHeader(SipFactory sipFactory, GitUtil gitUtil) throws PeerUnavailableException, ParseException { + public static UserAgentHeader createUserAgentHeader(GitUtil gitUtil) throws PeerUnavailableException, ParseException { List agentParam = new ArrayList<>(); agentParam.add("WVP-Pro "); if (gitUtil != null ) { @@ -66,7 +66,7 @@ public class SipUtils { agentParam.add(gitUtil.getCommitTime()); } } - return sipFactory.createHeaderFactory().createUserAgentHeader(agentParam); + return SipFactory.getInstance().createHeaderFactory().createUserAgentHeader(agentParam); } public static String getNewFromTag(){ @@ -153,8 +153,9 @@ public class SipUtils { String remoteAddress; int remotePort; if (sipUseSourceIpAsRemoteAddress) { - remoteAddress = request.getRemoteAddress().getHostAddress(); - remotePort = request.getRemotePort(); + remoteAddress = request.getPeerPacketSourceAddress().getHostAddress(); + remotePort = request.getPeerPacketSourcePort(); + }else { // 判断RPort是否改变,改变则说明路由nat信息变化,修改设备信息 // 获取到通信地址等信息 @@ -162,8 +163,8 @@ public class SipUtils { remotePort = request.getTopmostViaHeader().getRPort(); // 解析本地地址替代 if (ObjectUtils.isEmpty(remoteAddress) || remotePort == -1) { - remoteAddress = request.getTopmostViaHeader().getHost(); - remotePort = request.getTopmostViaHeader().getPort(); + remoteAddress = request.getPeerPacketSourceAddress().getHostAddress(); + remotePort = request.getPeerPacketSourcePort(); } } diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/annotation/MsgId.java b/src/main/java/com/genersoft/iot/vmp/jt1078/annotation/MsgId.java new file mode 100644 index 000000000..d5c2de46b --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/annotation/MsgId.java @@ -0,0 +1,15 @@ +package com.genersoft.iot.vmp.jt1078.annotation; + +import java.lang.annotation.*; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:31 + * @email qingtaij@163.com + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MsgId { + String id(); +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/cmd/JT1078Template.java b/src/main/java/com/genersoft/iot/vmp/jt1078/cmd/JT1078Template.java new file mode 100644 index 000000000..c55c62760 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/cmd/JT1078Template.java @@ -0,0 +1,115 @@ +package com.genersoft.iot.vmp.jt1078.cmd; + +import com.genersoft.iot.vmp.jt1078.proc.entity.Cmd; +import com.genersoft.iot.vmp.jt1078.proc.response.*; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; + +import java.util.Random; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:58 + * @email qingtaij@163.com + */ +public class JT1078Template { + + private final Random random = new Random(); + + private static final String H9101 = "9101"; + private static final String H9102 = "9102"; + private static final String H9201 = "9201"; + private static final String H9202 = "9202"; + private static final String H9205 = "9205"; + + private static final String H0001 = "0001"; + private static final String H1205 = "1205"; + + /** + * 开启直播视频 + * + * @param devId 设备号 + * @param j9101 开启视频参数 + */ + public String startLive(String devId, J9101 j9101, Integer timeOut) { + Cmd cmd = new Cmd.Builder() + .setDevId(devId) + .setPackageNo(randomInt()) + .setMsgId(H9101) + .setRespId(H0001) + .setRs(j9101) + .build(); + return SessionManager.INSTANCE.request(cmd, timeOut); + } + + /** + * 关闭直播视频 + * + * @param devId 设备号 + * @param j9102 关闭视频参数 + */ + public String stopLive(String devId, J9102 j9102, Integer timeOut) { + Cmd cmd = new Cmd.Builder() + .setDevId(devId) + .setPackageNo(randomInt()) + .setMsgId(H9102) + .setRespId(H0001) + .setRs(j9102) + .build(); + return SessionManager.INSTANCE.request(cmd, timeOut); + } + + /** + * 查询音视频列表 + * + * @param devId 设备号 + * @param j9205 查询音视频列表 + */ + public String queryBackTime(String devId, J9205 j9205, Integer timeOut) { + Cmd cmd = new Cmd.Builder() + .setDevId(devId) + .setPackageNo(randomInt()) + .setMsgId(H9205) + .setRespId(H1205) + .setRs(j9205) + .build(); + return SessionManager.INSTANCE.request(cmd, timeOut); + } + + /** + * 开启视频回放 + * + * @param devId 设备号 + * @param j9201 视频回放参数 + */ + public String startBackLive(String devId, J9201 j9201, Integer timeOut) { + Cmd cmd = new Cmd.Builder() + .setDevId(devId) + .setPackageNo(randomInt()) + .setMsgId(H9201) + .setRespId(H1205) + .setRs(j9201) + .build(); + return SessionManager.INSTANCE.request(cmd, timeOut); + } + + /** + * 视频回放控制 + * + * @param devId 设备号 + * @param j9202 控制视频回放参数 + */ + public String controlBackLive(String devId, J9202 j9202, Integer timeOut) { + Cmd cmd = new Cmd.Builder() + .setDevId(devId) + .setPackageNo(randomInt()) + .setMsgId(H9202) + .setRespId(H0001) + .setRs(j9202) + .build(); + return SessionManager.INSTANCE.request(cmd, timeOut); + } + + private Long randomInt() { + return (long) random.nextInt(1000) + 1; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/codec/decode/Jt808Decoder.java b/src/main/java/com/genersoft/iot/vmp/jt1078/codec/decode/Jt808Decoder.java new file mode 100644 index 000000000..4817c665f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/codec/decode/Jt808Decoder.java @@ -0,0 +1,146 @@ +package com.genersoft.iot.vmp.jt1078.codec.decode; + +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.factory.CodecFactory; +import com.genersoft.iot.vmp.jt1078.proc.request.Re; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.CompositeByteBuf; +import io.netty.buffer.UnpooledByteBufAllocator; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:10 + * @email qingtaij@163.com + */ +public class Jt808Decoder extends ByteToMessageDecoder { + private final static Logger log = LoggerFactory.getLogger(Jt808Decoder.class); + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + Session session = ctx.channel().attr(Session.KEY).get(); + log.info("> {} hex:{}", session, ByteBufUtil.hexDump(in)); + + try { + ByteBuf buf = unEscapeAndCheck(in); + + Header header = new Header(); + header.setMsgId(ByteBufUtil.hexDump(buf.readSlice(2))); + header.setMsgPro(buf.readUnsignedShort()); + if (header.is2019Version()) { + header.setVersion(buf.readUnsignedByte()); + String devId = ByteBufUtil.hexDump(buf.readSlice(10)); + header.setDevId(devId.replaceFirst("^0*", "")); + } else { + header.setDevId(ByteBufUtil.hexDump(buf.readSlice(6)).replaceFirst("^0*", "")); + } + header.setSn(buf.readUnsignedShort()); + + Re handler = CodecFactory.getHandler(header.getMsgId()); + if (handler == null) { + log.error("get msgId is null {}", header.getMsgId()); + return; + } + Rs decode = handler.decode(buf, header, session); + if (decode != null) { + out.add(decode); + } + } finally { + in.skipBytes(in.readableBytes()); + } + + + } + + + /** + * 转义与验证校验码 + * + * @param byteBuf 转义Buf + * @return 转义好的数据 + */ + public ByteBuf unEscapeAndCheck(ByteBuf byteBuf) throws Exception { + int low = byteBuf.readerIndex(); + int high = byteBuf.writerIndex(); + byte checkSum = 0; + int calculationCheckSum = 0; + + byte aByte = byteBuf.getByte(high - 2); + byte protocolEscapeFlag7d = 0x7d; + //0x7d转义 + byte protocolEscapeFlag01 = 0x01; + //0x7e转义 + byte protocolEscapeFlag02 = 0x02; + if (aByte == protocolEscapeFlag7d) { + byte b2 = byteBuf.getByte(high - 1); + if (b2 == protocolEscapeFlag01) { + checkSum = protocolEscapeFlag7d; + } else if (b2 == protocolEscapeFlag02) { + checkSum = 0x7e; + } else { + log.error("转义1异常:{}", ByteBufUtil.hexDump(byteBuf)); + throw new Exception("转义错误"); + } + high = high - 2; + } else { + high = high - 1; + checkSum = byteBuf.getByte(high); + } + List bufList = new ArrayList<>(); + int index = low; + while (index < high) { + byte b = byteBuf.getByte(index); + if (b == protocolEscapeFlag7d) { + byte c = byteBuf.getByte(index + 1); + if (c == protocolEscapeFlag01) { + ByteBuf slice = slice0x01(byteBuf, low, index); + bufList.add(slice); + b = protocolEscapeFlag7d; + } else if (c == protocolEscapeFlag02) { + ByteBuf slice = slice0x02(byteBuf, low, index); + bufList.add(slice); + b = 0x7e; + } else { + log.error("转义2异常:{}", ByteBufUtil.hexDump(byteBuf)); + throw new Exception("转义错误"); + } + index += 2; + low = index; + } else { + index += 1; + } + calculationCheckSum = calculationCheckSum ^ b; + } + + if (calculationCheckSum == checkSum) { + if (bufList.size() == 0) { + return byteBuf.slice(low, high); + } else { + bufList.add(byteBuf.slice(low, high - low)); + return new CompositeByteBuf(UnpooledByteBufAllocator.DEFAULT, false, bufList.size(), bufList); + } + } else { + log.info("{} 解析校验码:{}--计算校验码:{}", ByteBufUtil.hexDump(byteBuf), checkSum, calculationCheckSum); + throw new Exception("校验码错误!"); + } + } + + + private ByteBuf slice0x01(ByteBuf buf, int low, int sign) { + return buf.slice(low, sign - low + 1); + } + + private ByteBuf slice0x02(ByteBuf buf, int low, int sign) { + buf.setByte(sign, 0x7e); + return buf.slice(low, sign - low + 1); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808Encoder.java b/src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808Encoder.java new file mode 100644 index 000000000..afb1a7973 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808Encoder.java @@ -0,0 +1,33 @@ +package com.genersoft.iot.vmp.jt1078.codec.encode; + + +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:10 + * @email qingtaij@163.com + */ +public class Jt808Encoder extends MessageToByteEncoder { + private final static Logger log = LoggerFactory.getLogger(Jt808Encoder.class); + + @Override + protected void encode(ChannelHandlerContext ctx, Rs msg, ByteBuf out) throws Exception { + Session session = ctx.channel().attr(Session.KEY).get(); + + ByteBuf encode = Jt808EncoderCmd.encode(msg, session, session.nextSerialNo()); + if(encode!=null){ + log.info("< {} hex:{}", session, ByteBufUtil.hexDump(encode)); + out.writeBytes(encode); + } + } + + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808EncoderCmd.java b/src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808EncoderCmd.java new file mode 100644 index 000000000..0e9e11f6f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/codec/encode/Jt808EncoderCmd.java @@ -0,0 +1,151 @@ +package com.genersoft.iot.vmp.jt1078.codec.encode; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.entity.Cmd; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.util.Bin; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.CompositeByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import io.netty.util.ByteProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.util.LinkedList; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:25 + * @email qingtaij@163.com + */ +public class Jt808EncoderCmd extends MessageToByteEncoder { + private final static Logger log = LoggerFactory.getLogger(Jt808EncoderCmd.class); + + @Override + protected void encode(ChannelHandlerContext ctx, Cmd cmd, ByteBuf out) throws Exception { + Session session = ctx.channel().attr(Session.KEY).get(); + Rs msg = cmd.getRs(); + ByteBuf encode = encode(msg, session, cmd.getPackageNo().intValue()); + if (encode != null) { + log.info("< {} hex:{}", session, ByteBufUtil.hexDump(encode)); + out.writeBytes(encode); + } + } + + + public static ByteBuf encode(Rs msg, Session session, Integer packageNo) { + String id = msg.getClass().getAnnotation(MsgId.class).id(); + if (!StringUtils.hasLength(id)) { + log.error("Not find msgId"); + return null; + } + + ByteBuf byteBuf = Unpooled.buffer(); + + byteBuf.writeBytes(ByteBufUtil.decodeHexDump(id)); + + ByteBuf encode = msg.encode(); + + Header header = msg.getHeader(); + if (header == null) { + header = session.getHeader(); + } + + if (header.is2019Version()) { + // 消息体属性 + byteBuf.writeShort(encode.readableBytes() | 1 << 14); + + // 版本号 + byteBuf.writeByte(header.getVersion()); + + // 终端手机号 + byteBuf.writeBytes(ByteBufUtil.decodeHexDump(Bin.strHexPaddingLeft(header.getDevId(), 20))); + } else { + // 消息体属性 + byteBuf.writeShort(encode.readableBytes()); + + byteBuf.writeBytes(ByteBufUtil.decodeHexDump(Bin.strHexPaddingLeft(header.getDevId(), 12))); + } + + // 消息体流水号 + byteBuf.writeShort(packageNo); + + // 写入消息体 + byteBuf.writeBytes(encode); + + // 计算校验码,并反转义 + byteBuf = escapeAndCheck0(byteBuf); + return byteBuf; + } + + + private static final ByteProcessor searcher = value -> !(value == 0x7d || value == 0x7e); + + //转义与校验 + public static ByteBuf escapeAndCheck0(ByteBuf source) { + + sign(source); + + int low = source.readerIndex(); + int high = source.writerIndex(); + + LinkedList bufList = new LinkedList<>(); + int mark, len; + while ((mark = source.forEachByte(low, high - low, searcher)) > 0) { + + len = mark + 1 - low; + ByteBuf[] slice = slice(source, low, len); + bufList.add(slice[0]); + bufList.add(slice[1]); + low += len; + } + + if (bufList.size() > 0) { + bufList.add(source.slice(low, high - low)); + } else { + bufList.add(source); + } + + ByteBuf delimiter = Unpooled.buffer(1, 1).writeByte(0x7e).retain(); + bufList.addFirst(delimiter); + bufList.addLast(delimiter); + + CompositeByteBuf byteBufLs = Unpooled.compositeBuffer(bufList.size()); + byteBufLs.addComponents(true, bufList); + return byteBufLs; + } + + public static void sign(ByteBuf buf) { + byte checkCode = bcc(buf); + buf.writeByte(checkCode); + } + + public static byte bcc(ByteBuf byteBuf) { + byte cs = 0; + while (byteBuf.isReadable()) + cs ^= byteBuf.readByte(); + byteBuf.resetReaderIndex(); + return cs; + } + + protected static ByteBuf[] slice(ByteBuf byteBuf, int index, int length) { + byte first = byteBuf.getByte(index + length - 1); + + ByteBuf[] byteBufList = new ByteBuf[2]; + byteBufList[0] = byteBuf.retainedSlice(index, length); + + if (first == 0x7d) { + byteBufList[1] = Unpooled.buffer(1, 1).writeByte(0x01); + } else { + byteBuf.setByte(index + length - 1, 0x7d); + byteBufList[1] = Unpooled.buffer(1, 1).writeByte(0x02); + } + return byteBufList; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/Jt808Handler.java b/src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/Jt808Handler.java new file mode 100644 index 000000000..fd5030272 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/Jt808Handler.java @@ -0,0 +1,72 @@ +package com.genersoft.iot.vmp.jt1078.codec.netty; + +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:14 + * @email qingtaij@163.com + */ +public class Jt808Handler extends ChannelInboundHandlerAdapter { + + private final static Logger log = LoggerFactory.getLogger(Jt808Handler.class); + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof Rs) { + ctx.writeAndFlush(msg); + } else { + ctx.fireChannelRead(msg); + } + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + Channel channel = ctx.channel(); + Session session = SessionManager.INSTANCE.newSession(channel); + channel.attr(Session.KEY).set(session); + log.info("> Tcp connect {}", session); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + Session session = ctx.channel().attr(Session.KEY).get(); + log.info("< Tcp disconnect {}", session); + ctx.close(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { + Session session = ctx.channel().attr(Session.KEY).get(); + String message = e.getMessage(); + if (message.toLowerCase().contains("Connection reset by peer".toLowerCase())) { + log.info("< exception{} {}", session, e.getMessage()); + } else { + log.info("< exception{} {}", session, e.getMessage(), e); + } + + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt instanceof IdleStateEvent) { + IdleStateEvent event = (IdleStateEvent) evt; + IdleState state = event.state(); + if (state == IdleState.READER_IDLE || state == IdleState.WRITER_IDLE) { + Session session = ctx.channel().attr(Session.KEY).get(); + log.warn("< Proactively disconnect{}", session); + ctx.close(); + } + } + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/TcpServer.java b/src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/TcpServer.java new file mode 100644 index 000000000..a7e4df826 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/codec/netty/TcpServer.java @@ -0,0 +1,112 @@ +package com.genersoft.iot.vmp.jt1078.codec.netty; + +import com.genersoft.iot.vmp.jt1078.codec.decode.Jt808Decoder; +import com.genersoft.iot.vmp.jt1078.codec.encode.Jt808Encoder; +import com.genersoft.iot.vmp.jt1078.codec.encode.Jt808EncoderCmd; +import com.genersoft.iot.vmp.jt1078.proc.factory.CodecFactory; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioChannelOption; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.DelimiterBasedFrameDecoder; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.util.concurrent.Future; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:01 + * @email qingtaij@163.com + */ + +public class TcpServer { + private final static Logger log = LoggerFactory.getLogger(TcpServer.class); + + private final Integer port; + private boolean isRunning = false; + private EventLoopGroup bossGroup = null; + private EventLoopGroup workerGroup = null; + + private final ByteBuf DECODER_JT808 = Unpooled.wrappedBuffer(new byte[]{0x7e}); + + public TcpServer(Integer port) { + this.port = port; + } + + private void startTcpServer() { + try { + CodecFactory.init(); + this.bossGroup = new NioEventLoopGroup(); + this.workerGroup = new NioEventLoopGroup(); + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.channel(NioServerSocketChannel.class); + bootstrap.group(bossGroup, workerGroup); + + bootstrap.option(NioChannelOption.SO_BACKLOG, 1024) + .option(NioChannelOption.SO_REUSEADDR, true) + .childOption(NioChannelOption.TCP_NODELAY, true) + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(NioSocketChannel channel) { + channel.pipeline() + .addLast(new IdleStateHandler(10, 0, 0, TimeUnit.MINUTES)) + .addLast(new DelimiterBasedFrameDecoder(1024 * 2, DECODER_JT808)) + .addLast(new Jt808Decoder()) + .addLast(new Jt808Encoder()) + .addLast(new Jt808EncoderCmd()) + .addLast(new Jt808Handler()); + } + }); + ChannelFuture channelFuture = bootstrap.bind(port).sync(); + // 监听设备TCP端口是否启动成功 + channelFuture.addListener(future -> { + if (!future.isSuccess()) { + log.error("Binding port:{} fail! cause: {}", port, future.cause().getCause(), future.cause()); + } + }); + log.info("服务:JT808 Server 启动成功, port:{}", port); + channelFuture.channel().closeFuture().sync(); + } catch (Exception e) { + log.warn("服务:JT808 Server 启动异常, port:{},{}", port, e.getMessage(), e); + } finally { + stop(); + } + } + + /** + * 开启一个新的线程,拉起来Netty + */ + public synchronized void start() { + if (this.isRunning) { + log.warn("服务:JT808 Server 已经启动, port:{}", port); + return; + } + this.isRunning = true; + new Thread(this::startTcpServer).start(); + } + + public synchronized void stop() { + if (!this.isRunning) { + log.warn("服务:JT808 Server 已经停止, port:{}", port); + } + this.isRunning = false; + Future future = this.bossGroup.shutdownGracefully(); + if (!future.isSuccess()) { + log.warn("bossGroup 无法正常停止", future.cause()); + } + future = this.workerGroup.shutdownGracefully(); + if (!future.isSuccess()) { + log.warn("workerGroup 无法正常停止", future.cause()); + } + log.warn("服务:JT808 Server 已经停止, port:{}", port); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078AutoConfiguration.java b/src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078AutoConfiguration.java new file mode 100644 index 000000000..6cac30cec --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078AutoConfiguration.java @@ -0,0 +1,30 @@ +package com.genersoft.iot.vmp.jt1078.config; + +import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; +import com.genersoft.iot.vmp.jt1078.codec.netty.TcpServer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +/** + * @author QingtaiJiang + * @date 2023/4/27 19:35 + * @email qingtaij@163.com + */ +@Order(Integer.MIN_VALUE) +@Configuration +@ConditionalOnProperty(value = "jt1078.enable", havingValue = "true") +public class JT1078AutoConfiguration { + + @Bean(initMethod = "start", destroyMethod = "stop") + public TcpServer jt1078Server(@Value("${jt1078.port}") Integer port) { + return new TcpServer(port); + } + + @Bean + public JT1078Template jt1078Template() { + return new JT1078Template(); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078Controller.java b/src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078Controller.java new file mode 100644 index 000000000..0c71d26a9 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078Controller.java @@ -0,0 +1,51 @@ +package com.genersoft.iot.vmp.jt1078.config; + +import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; +import com.genersoft.iot.vmp.jt1078.proc.response.*; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * curl http://localhost:18080/api/jt1078/start/live/18864197066/1 + * + * @author QingtaiJiang + * @date 2023/4/27 18:12 + * @email qingtaij@163.com + */ +@ConditionalOnProperty(value = "jt1078.enable", havingValue = "true") +@RestController +@RequestMapping("/api/jt1078") +public class JT1078Controller { + + @Resource + JT1078Template jt1078Template; + + /** + * jt1078Template 调用示例 + */ + @GetMapping("/start/live/{deviceId}/{channelId}") + public WVPResult startLive(@PathVariable String deviceId, @PathVariable String channelId) { + J9101 j9101 = new J9101(); + j9101.setChannel(Integer.valueOf(channelId)); + j9101.setIp("192.168.1.1"); + j9101.setRate(1); + j9101.setTcpPort(7618); + j9101.setUdpPort(7618); + j9101.setType(0); + // TODO 分配ZLM,获取IP、端口 + String s = jt1078Template.startLive(deviceId, j9101, 6); + // TODO 设备响应成功后,封装拉流结果集 + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(200); + wvpResult.setData(String.format("http://192.168.1.1/rtp/%s_%s.live.mp4", deviceId, channelId)); + return wvpResult; + } + +} + diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/Header.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/Header.java new file mode 100644 index 000000000..86c5fff26 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/Header.java @@ -0,0 +1,76 @@ +package com.genersoft.iot.vmp.jt1078.proc; + +import com.genersoft.iot.vmp.jt1078.util.Bin; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:22 + * @email qingtaij@163.com + */ +public class Header { + // 消息ID + String msgId; + + // 消息体属性 + Integer msgPro; + + // 标识 + String devId; + + // 消息体流水号 + Integer sn; + + // 协议版本号 + Short version = -1; + + + public String getMsgId() { + return msgId; + } + + public void setMsgId(String msgId) { + this.msgId = msgId; + } + + public Integer getMsgPro() { + return msgPro; + } + + public void setMsgPro(Integer msgPro) { + this.msgPro = msgPro; + } + + public String getDevId() { + return devId; + } + + public void setDevId(String devId) { + this.devId = devId; + } + + public Integer getSn() { + return sn; + } + + public void setSn(Integer sn) { + this.sn = sn; + } + + public Short getVersion() { + return version; + } + + public void setVersion(Short version) { + this.version = version; + } + + /** + * 判断是否是2019的版本 + * + * @return true 2019后的版本。false 2013 + */ + public boolean is2019Version() { + return Bin.get(msgPro, 14); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/entity/Cmd.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/entity/Cmd.java new file mode 100644 index 000000000..28726e83b --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/entity/Cmd.java @@ -0,0 +1,116 @@ +package com.genersoft.iot.vmp.jt1078.proc.entity; + +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:23 + * @email qingtaij@163.com + */ +public class Cmd { + String devId; + Long packageNo; + String msgId; + String respId; + Rs rs; + + public Cmd() { + } + + public Cmd(Builder builder) { + this.devId = builder.devId; + this.packageNo = builder.packageNo; + this.msgId = builder.msgId; + this.respId = builder.respId; + this.rs = builder.rs; + } + + public String getDevId() { + return devId; + } + + public void setDevId(String devId) { + this.devId = devId; + } + + public Long getPackageNo() { + return packageNo; + } + + public void setPackageNo(Long packageNo) { + this.packageNo = packageNo; + } + + public String getMsgId() { + return msgId; + } + + public void setMsgId(String msgId) { + this.msgId = msgId; + } + + public String getRespId() { + return respId; + } + + public void setRespId(String respId) { + this.respId = respId; + } + + public Rs getRs() { + return rs; + } + + public void setRs(Rs rs) { + this.rs = rs; + } + + public static class Builder { + String devId; + Long packageNo; + String msgId; + String respId; + Rs rs; + + public Builder setDevId(String devId) { + this.devId = devId.replaceFirst("^0*", ""); + return this; + } + + public Builder setPackageNo(Long packageNo) { + this.packageNo = packageNo; + return this; + } + + public Builder setMsgId(String msgId) { + this.msgId = msgId; + return this; + } + + public Builder setRespId(String respId) { + this.respId = respId; + return this; + } + + public Builder setRs(Rs re) { + this.rs = re; + return this; + } + + public Cmd build() { + return new Cmd(this); + } + } + + + @Override + public String toString() { + return "Cmd{" + + "devId='" + devId + '\'' + + ", packageNo=" + packageNo + + ", msgId='" + msgId + '\'' + + ", respId='" + respId + '\'' + + ", rs=" + rs + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/factory/CodecFactory.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/factory/CodecFactory.java new file mode 100644 index 000000000..45d5fc71f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/factory/CodecFactory.java @@ -0,0 +1,44 @@ +package com.genersoft.iot.vmp.jt1078.proc.factory; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.request.Re; +import com.genersoft.iot.vmp.jt1078.util.ClassUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:29 + * @email qingtaij@163.com + */ + +public class CodecFactory { + private final static Logger log = LoggerFactory.getLogger(CodecFactory.class); + + private static Map> protocolHash; + + public static void init() { + protocolHash = new HashMap<>(); + List> classList = ClassUtil.getClassList("com.genersoft.iot.vmp.jt1078.proc", MsgId.class); + for (Class handlerClass : classList) { + String id = handlerClass.getAnnotation(MsgId.class).id(); + protocolHash.put(id, handlerClass); + } + if (log.isDebugEnabled()) { + log.debug("消息ID缓存表 protocolHash:{}", protocolHash); + } + } + + public static Re getHandler(String msgId) { + Class aClass = protocolHash.get(msgId); + Object bean = ClassUtil.getBean(aClass); + if (bean instanceof Re) { + return (Re) bean; + } + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0001.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0001.java new file mode 100644 index 000000000..1d7f85db0 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0001.java @@ -0,0 +1,50 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.alibaba.fastjson2.JSON; +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; + +/** + * 终端通用应答 + * + * @author QingtaiJiang + * @date 2023/4/27 18:04 + * @email qingtaij@163.com + */ +@MsgId(id = "0001") +public class J0001 extends Re { + int respNo; + String respId; + int result; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + respNo = buf.readUnsignedShort(); + respId = ByteBufUtil.hexDump(buf.readSlice(2)); + result = buf.readUnsignedByte(); + return null; + } + + @Override + protected Rs handler(Header header, Session session) { + SessionManager.INSTANCE.response(header.getDevId(), "0001", (long) respNo, JSON.toJSONString(this)); + return null; + } + + public int getRespNo() { + return respNo; + } + + public String getRespId() { + return respId; + } + + public int getResult() { + return result; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0002.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0002.java new file mode 100644 index 000000000..f52303a6f --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0002.java @@ -0,0 +1,32 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import io.netty.buffer.ByteBuf; + +/** + * 终端心跳 + * + * @author QingtaiJiang + * @date 2023/4/27 18:04 + * @email qingtaij@163.com + */ +@MsgId(id = "0002") +public class J0002 extends Re { + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + return null; + } + + @Override + protected Rs handler(Header header, Session session) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + j8001.setResult(J8001.SUCCESS); + return j8001; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0004.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0004.java new file mode 100644 index 000000000..0f00a8013 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0004.java @@ -0,0 +1,27 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import io.netty.buffer.ByteBuf; + +/** + * 查询服务器时间 + * + * @author QingtaiJiang + * @date 2023/4/27 18:06 + * @email qingtaij@163.com + */ +@MsgId(id = "0004") +public class J0004 extends Re { + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + return null; + } + + @Override + protected Rs handler(Header header, Session session) { + return null; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0100.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0100.java new file mode 100644 index 000000000..a731dda63 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0100.java @@ -0,0 +1,56 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8100; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import io.netty.buffer.ByteBuf; + +/** + * 终端注册 + * + * @author QingtaiJiang + * @date 2023/4/27 18:06 + * @email qingtaij@163.com + */ +@MsgId(id = "0100") +public class J0100 extends Re { + + private int provinceId; + + private int cityId; + + private String makerId; + + private String deviceModel; + + private String deviceId; + + private int plateColor; + + private String plateNo; + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + Short version = header.getVersion(); + provinceId = buf.readUnsignedShort(); + if (version > 1) { + cityId = buf.readUnsignedShort(); + // decode as 2019 + } else { + int i = buf.readUnsignedShort(); + // decode as 2013 + } + return null; + } + + @Override + protected Rs handler(Header header, Session session) { + J8100 j8100 = new J8100(); + j8100.setRespNo(header.getSn()); + j8100.setResult(J8100.SUCCESS); + j8100.setCode("WVP_YYDS"); + return j8100; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0102.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0102.java new file mode 100644 index 000000000..8e531ae41 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0102.java @@ -0,0 +1,36 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import io.netty.buffer.ByteBuf; + +/** + * 终端鉴权 + * + * @author QingtaiJiang + * @date 2023/4/27 18:06 + * @email qingtaij@163.com + */ +@MsgId(id = "0102") +public class J0102 extends Re { + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + int lenCode = buf.readUnsignedByte(); +// String code = buf.readCharSequence(lenCode, CharsetUtil.UTF_8).toString(); + // if 2019 to decode next + return null; + } + + @Override + protected Rs handler(Header header, Session session) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + j8001.setResult(J8001.SUCCESS); + return j8001; + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0200.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0200.java new file mode 100644 index 000000000..d027dd2e0 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J0200.java @@ -0,0 +1,32 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import io.netty.buffer.ByteBuf; + +/** + * 实时消息上报 + * + * @author QingtaiJiang + * @date 2023/4/27 18:06 + * @email qingtaij@163.com + */ +@MsgId(id = "0200") +public class J0200 extends Re { + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + return null; + } + + @Override + protected Rs handler(Header header, Session session) { + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + j8001.setResult(J8001.SUCCESS); + return j8001; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1205.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1205.java new file mode 100644 index 000000000..da0b89ec3 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/J1205.java @@ -0,0 +1,190 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.alibaba.fastjson2.JSON; +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.J8001; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import com.genersoft.iot.vmp.jt1078.session.SessionManager; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * 终端上传音视频资源列表 + * + * @author QingtaiJiang + * @date 2023/4/28 10:36 + * @email qingtaij@163.com + */ +@MsgId(id = "1205") +public class J1205 extends Re { + Integer respNo; + + private List recordList = new ArrayList(); + + @Override + protected Rs decode0(ByteBuf buf, Header header, Session session) { + respNo = buf.readUnsignedShort(); + long size = buf.readUnsignedInt(); + + for (int i = 0; i < size; i++) { + JRecordItem item = new JRecordItem(); + item.setChannelId(buf.readUnsignedByte()); + item.setStartTime(ByteBufUtil.hexDump(buf.readSlice(6))); + item.setEndTime(ByteBufUtil.hexDump(buf.readSlice(6))); + item.setWarn(buf.readLong()); + item.setMediaType(buf.readUnsignedByte()); + item.setStreamType(buf.readUnsignedByte()); + item.setStorageType(buf.readUnsignedByte()); + item.setSize(buf.readUnsignedInt()); + recordList.add(item); + } + + return null; + } + + @Override + protected Rs handler(Header header, Session session) { + SessionManager.INSTANCE.response(header.getDevId(), "1205", (long) respNo, JSON.toJSONString(this)); + + J8001 j8001 = new J8001(); + j8001.setRespNo(header.getSn()); + j8001.setRespId(header.getMsgId()); + j8001.setResult(J8001.SUCCESS); + return j8001; + } + + + public Integer getRespNo() { + return respNo; + } + + public void setRespNo(Integer respNo) { + this.respNo = respNo; + } + + public List getRecordList() { + return recordList; + } + + public void setRecordList(List recordList) { + this.recordList = recordList; + } + + public static class JRecordItem { + + // 逻辑通道号 + private int channelId; + + // 开始时间 + private String startTime; + + // 结束时间 + private String endTime; + + // 报警标志 + private long warn; + + // 音视频资源类型 + private int mediaType; + + // 码流类型 + private int streamType = 1; + + // 存储器类型 + private int storageType; + + // 文件大小 + private long size; + + public int getChannelId() { + return channelId; + } + + public void setChannelId(int channelId) { + this.channelId = channelId; + } + + 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 long getWarn() { + return warn; + } + + public void setWarn(long warn) { + this.warn = warn; + } + + public int getMediaType() { + return mediaType; + } + + public void setMediaType(int mediaType) { + this.mediaType = mediaType; + } + + public int getStreamType() { + return streamType; + } + + public void setStreamType(int streamType) { + this.streamType = streamType; + } + + public int getStorageType() { + return storageType; + } + + public void setStorageType(int storageType) { + this.storageType = storageType; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + @Override + public String toString() { + return "JRecordItem{" + + "channelId=" + channelId + + ", startTime='" + startTime + '\'' + + ", endTime='" + endTime + '\'' + + ", warn=" + warn + + ", mediaType=" + mediaType + + ", streamType=" + streamType + + ", storageType=" + storageType + + ", size=" + size + + '}'; + } + } + + @Override + public String toString() { + return "J1205{" + + "respNo=" + respNo + + ", recordList=" + recordList + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/Re.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/Re.java new file mode 100644 index 000000000..0a24ad274 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/request/Re.java @@ -0,0 +1,40 @@ +package com.genersoft.iot.vmp.jt1078.proc.request; + +import com.genersoft.iot.vmp.jt1078.proc.Header; +import com.genersoft.iot.vmp.jt1078.proc.response.Rs; +import com.genersoft.iot.vmp.jt1078.session.Session; +import io.netty.buffer.ByteBuf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:50 + * @email qingtaij@163.com + */ +public abstract class Re { + private final static Logger log = LoggerFactory.getLogger(Re.class); + + protected abstract Rs decode0(ByteBuf buf, Header header, Session session); + + protected abstract Rs handler(Header header, Session session); + + public Rs decode(ByteBuf buf, Header header, Session session) { + if (session != null && !StringUtils.hasLength(session.getDevId())) { + session.register(header.getDevId(), (int) header.getVersion(), header); + } + Rs rs = decode0(buf, header, session); + Rs rsHand = handler(header, session); + if (rs == null && rsHand != null) { + rs = rsHand; + } else if (rs != null && rsHand != null) { + log.warn("decode0:{} 与 handler:{} 返回值冲突,采用decode0返回值", rs, rsHand); + } + if (rs != null) { + rs.setHeader(header); + } + + return rs; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8001.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8001.java new file mode 100644 index 000000000..ec9e31f19 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8001.java @@ -0,0 +1,43 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:48 + * @email qingtaij@163.com + */ +@MsgId(id = "8001") +public class J8001 extends Rs { + public static final Integer SUCCESS = 0; + + Integer respNo; + String respId; + Integer result; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeShort(respNo); + buffer.writeBytes(ByteBufUtil.decodeHexDump(respId)); + buffer.writeByte(result); + + return buffer; + } + + + public void setRespNo(Integer respNo) { + this.respNo = respNo; + } + + public void setRespId(String respId) { + this.respId = respId; + } + + public void setResult(Integer result) { + this.result = result; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8100.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8100.java new file mode 100644 index 000000000..48a9c95e6 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J8100.java @@ -0,0 +1,41 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:40 + * @email qingtaij@163.com + */ +@MsgId(id = "8100") +public class J8100 extends Rs { + public static final Integer SUCCESS = 0; + + Integer respNo; + Integer result; + String code; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeShort(respNo); + buffer.writeByte(result); + buffer.writeCharSequence(code, CharsetUtil.UTF_8); + return buffer; + } + + public void setRespNo(Integer respNo) { + this.respNo = respNo; + } + + public void setResult(Integer result) { + this.result = result; + } + + public void setCode(String code) { + this.code = code; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9101.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9101.java new file mode 100644 index 000000000..77e90b726 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9101.java @@ -0,0 +1,112 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; + +/** + * 实时音视频传输请求 + * + * @author QingtaiJiang + * @date 2023/4/27 18:25 + * @email qingtaij@163.com + */ +@MsgId(id = "9101") +public class J9101 extends Rs { + String ip; + + // TCP端口 + Integer tcpPort; + + // UDP端口 + Integer udpPort; + + // 逻辑通道号 + Integer channel; + + // 数据类型 + /** + * 0:音视频,1:视频,2:双向对讲,3:监听,4:中心广播,5:透传 + */ + Integer type; + + // 码流类型 + /** + * 0:主码流,1:子码流 + */ + Integer rate; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(ip.getBytes().length); + buffer.writeCharSequence(ip, CharsetUtil.UTF_8); + buffer.writeShort(tcpPort); + buffer.writeShort(udpPort); + buffer.writeByte(channel); + buffer.writeByte(type); + buffer.writeByte(rate); + return buffer; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public Integer getTcpPort() { + return tcpPort; + } + + public void setTcpPort(Integer tcpPort) { + this.tcpPort = tcpPort; + } + + public Integer getUdpPort() { + return udpPort; + } + + public void setUdpPort(Integer udpPort) { + this.udpPort = udpPort; + } + + public Integer getChannel() { + return channel; + } + + public void setChannel(Integer channel) { + this.channel = channel; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public Integer getRate() { + return rate; + } + + public void setRate(Integer rate) { + this.rate = rate; + } + + @Override + public String toString() { + return "J9101{" + + "ip='" + ip + '\'' + + ", tcpPort=" + tcpPort + + ", udpPort=" + udpPort + + ", channel=" + channel + + ", type=" + type + + ", rate=" + rate + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9102.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9102.java new file mode 100644 index 000000000..8d560b20c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9102.java @@ -0,0 +1,99 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * 音视频实时传输控制 + * + * @author QingtaiJiang + * @date 2023/4/27 18:49 + * @email qingtaij@163.com + */ +@MsgId(id = "9102") +public class J9102 extends Rs { + + // 通道号 + Integer channel; + + // 控制指令 + /** + * 0:关闭音视频传输指令; + * 1:切换码流(增加暂停和继续); + * 2:暂停该通道所有流的发送; + * 3:恢复暂停前流的发送,与暂停前的流类型一致; + * 4:关闭双向对讲 + */ + Integer command; + + // 数据类型 + /** + * 0:关闭该通道有关的音视频数据; + * 1:只关闭该通道有关的音频,保留该通道 + * 有关的视频; + * 2:只关闭该通道有关的视频,保留该通道 + * 有关的音频 + */ + Integer closeType; + + // 数据类型 + /** + * 0:主码流; + * 1:子码流 + */ + Integer streamType; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(channel); + buffer.writeByte(command); + buffer.writeByte(closeType); + buffer.writeByte(streamType); + return buffer; + } + + + public Integer getChannel() { + return channel; + } + + public void setChannel(Integer channel) { + this.channel = channel; + } + + public Integer getCommand() { + return command; + } + + public void setCommand(Integer command) { + this.command = command; + } + + public Integer getCloseType() { + return closeType; + } + + public void setCloseType(Integer closeType) { + this.closeType = closeType; + } + + public Integer getStreamType() { + return streamType; + } + + public void setStreamType(Integer streamType) { + this.streamType = streamType; + } + + @Override + public String toString() { + return "J9102{" + + "channel=" + channel + + ", command=" + command + + ", closeType=" + closeType + + ", streamType=" + streamType + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9201.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9201.java new file mode 100644 index 000000000..8a66f3544 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9201.java @@ -0,0 +1,173 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; + +/** + * 回放请求 + * + * @author QingtaiJiang + * @date 2023/4/28 10:37 + * @email qingtaij@163.com + */ +@MsgId(id = "9201") +public class J9201 extends Rs { + // 服务器IP地址 + private String ip; + + // 实时视频服务器TCP端口号 + private int tcpPort; + + // 实时视频服务器UDP端口号 + private int udpPort; + + // 逻辑通道号 + private int channel; + + // 音视频资源类型:0.音视频 1.音频 2.视频 3.视频或音视频 + private int type; + + // 码流类型:0.所有码流 1.主码流 2.子码流(如果此通道只传输音频,此字段置0) + private int rate; + + // 存储器类型:0.所有存储器 1.主存储器 2.灾备存储器" + private int storageType; + + // 回放方式:0.正常回放 1.快进回放 2.关键帧快退回放 3.关键帧播放 4.单帧上传 + private int playbackType; + + // 快进或快退倍数:0.无效 1.1倍 2.2倍 3.4倍 4.8倍 5.16倍 (回放控制为1和2时,此字段内容有效,否则置0) + private int playbackSpeed; + + // 开始时间YYMMDDHHMMSS,回放方式为4时,该字段表示单帧上传时间 + private String startTime; + + // 结束时间YYMMDDHHMMSS,回放方式为4时,该字段无效,为0表示一直回放 + private String endTime; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(ip.getBytes().length); + buffer.writeCharSequence(ip, CharsetUtil.UTF_8); + buffer.writeShort(tcpPort); + buffer.writeShort(udpPort); + buffer.writeByte(channel); + buffer.writeByte(type); + buffer.writeByte(rate); + buffer.writeByte(storageType); + buffer.writeByte(playbackType); + buffer.writeByte(playbackSpeed); + buffer.writeBytes(ByteBufUtil.decodeHexDump(startTime)); + buffer.writeBytes(ByteBufUtil.decodeHexDump(endTime)); + return buffer; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getTcpPort() { + return tcpPort; + } + + public void setTcpPort(int tcpPort) { + this.tcpPort = tcpPort; + } + + public int getUdpPort() { + return udpPort; + } + + public void setUdpPort(int udpPort) { + this.udpPort = udpPort; + } + + public int getChannel() { + return channel; + } + + public void setChannel(int channel) { + this.channel = channel; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + public int getStorageType() { + return storageType; + } + + public void setStorageType(int storageType) { + this.storageType = storageType; + } + + public int getPlaybackType() { + return playbackType; + } + + public void setPlaybackType(int playbackType) { + this.playbackType = playbackType; + } + + public int getPlaybackSpeed() { + return playbackSpeed; + } + + public void setPlaybackSpeed(int playbackSpeed) { + this.playbackSpeed = playbackSpeed; + } + + 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; + } + + @Override + public String toString() { + return "J9201{" + + "ip='" + ip + '\'' + + ", tcpPort=" + tcpPort + + ", udpPort=" + udpPort + + ", channel=" + channel + + ", type=" + type + + ", rate=" + rate + + ", storageType=" + storageType + + ", playbackType=" + playbackType + + ", playbackSpeed=" + playbackSpeed + + ", startTime='" + startTime + '\'' + + ", endTime='" + endTime + '\'' + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9202.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9202.java new file mode 100644 index 000000000..7cb4e53ef --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9202.java @@ -0,0 +1,80 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; + +/** + * 平台下发远程录像回放控制 + * + * @author QingtaiJiang + * @date 2023/4/28 10:37 + * @email qingtaij@163.com + */ +@MsgId(id = "9202") +public class J9202 extends Rs { + // 逻辑通道号 + private int channel; + + // 回放控制:0.开始回放 1.暂停回放 2.结束回放 3.快进回放 4.关键帧快退回放 5.拖动回放 6.关键帧播放 + private int playbackType; + + // 快进或快退倍数:0.无效 1.1倍 2.2倍 3.4倍 4.8倍 5.16倍 (回放控制为3和4时,此字段内容有效,否则置0) + private int playbackSpeed; + + // 拖动回放位置(YYMMDDHHMMSS,回放控制为5时,此字段有效) + private String playbackTime; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + buffer.writeByte(channel); + buffer.writeByte(playbackType); + buffer.writeByte(playbackSpeed); + buffer.writeBytes(ByteBufUtil.decodeHexDump(playbackTime)); + return buffer; + } + + public int getChannel() { + return channel; + } + + public void setChannel(int channel) { + this.channel = channel; + } + + public int getPlaybackType() { + return playbackType; + } + + public void setPlaybackType(int playbackType) { + this.playbackType = playbackType; + } + + public int getPlaybackSpeed() { + return playbackSpeed; + } + + public void setPlaybackSpeed(int playbackSpeed) { + this.playbackSpeed = playbackSpeed; + } + + public String getPlaybackTime() { + return playbackTime; + } + + public void setPlaybackTime(String playbackTime) { + this.playbackTime = playbackTime; + } + + @Override + public String toString() { + return "J9202{" + + "channel=" + channel + + ", playbackType=" + playbackType + + ", playbackSpeed=" + playbackSpeed + + ", playbackTime='" + playbackTime + '\'' + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9205.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9205.java new file mode 100644 index 000000000..36b858ebc --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/J9205.java @@ -0,0 +1,94 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + +import com.genersoft.iot.vmp.jt1078.annotation.MsgId; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; + +/** + * 查询资源列表 + * + * @author QingtaiJiang + * @date 2023/4/28 10:36 + * @email qingtaij@163.com + */ +@MsgId(id = "9205") +public class J9205 extends Rs { + // 逻辑通道号 + private int channelId; + + // 开始时间YYMMDDHHMMSS,全0表示无起始时间 + private String startTime; + + // 结束时间YYMMDDHHMMSS,全0表示无终止时间 + private String endTime; + + // 报警标志 + private final int warnType = 0; + + // 音视频资源类型:0.音视频 1.音频 2.视频 3.视频或音视频 + private int mediaType; + + // 码流类型:0.所有码流 1.主码流 2.子码流 + private int streamType = 0; + + // 存储器类型:0.所有存储器 1.主存储器 2.灾备存储器 + private int storageType = 0; + + @Override + public ByteBuf encode() { + ByteBuf buffer = Unpooled.buffer(); + + buffer.writeByte(channelId); + buffer.writeBytes(ByteBufUtil.decodeHexDump(startTime)); + buffer.writeBytes(ByteBufUtil.decodeHexDump(endTime)); + buffer.writeLong(warnType); + buffer.writeByte(mediaType); + buffer.writeByte(streamType); + buffer.writeByte(storageType); + + return buffer; + } + + + public void setChannelId(int channelId) { + this.channelId = channelId; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public void setMediaType(int mediaType) { + this.mediaType = mediaType; + } + + public void setStreamType(int streamType) { + this.streamType = streamType; + } + + public void setStorageType(int storageType) { + this.storageType = storageType; + } + + public int getWarnType() { + return warnType; + } + + @Override + public String toString() { + return "J9205{" + + "channelId=" + channelId + + ", startTime='" + startTime + '\'' + + ", endTime='" + endTime + '\'' + + ", warnType=" + warnType + + ", mediaType=" + mediaType + + ", streamType=" + streamType + + ", storageType=" + storageType + + '}'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/Rs.java b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/Rs.java new file mode 100644 index 000000000..243cd9424 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/proc/response/Rs.java @@ -0,0 +1,27 @@ +package com.genersoft.iot.vmp.jt1078.proc.response; + + +import com.genersoft.iot.vmp.jt1078.proc.Header; +import io.netty.buffer.ByteBuf; + + +/** + * @author QingtaiJiang + * @date 2021/8/30 18:54 + * @email qingtaij@163.com + */ + +public abstract class Rs { + private Header header; + + public abstract ByteBuf encode(); + + + public Header getHeader() { + return header; + } + + public void setHeader(Header header) { + this.header = header; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/session/Session.java b/src/main/java/com/genersoft/iot/vmp/jt1078/session/Session.java new file mode 100644 index 000000000..f7df8de0d --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/session/Session.java @@ -0,0 +1,114 @@ +package com.genersoft.iot.vmp.jt1078.session; + +import com.genersoft.iot.vmp.jt1078.proc.Header; +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author QingtaiJiang + * @date 2023/4/27 18:54 + * @email qingtaij@163.com + */ +public class Session { + private final static Logger log = LoggerFactory.getLogger(Session.class); + + public static final AttributeKey KEY = AttributeKey.newInstance(Session.class.getName()); + + // Netty的channel + protected final Channel channel; + + // 原子类的自增ID + private final AtomicInteger serialNo = new AtomicInteger(0); + + // 是否注册成功 + private boolean registered = false; + + // 设备ID + private String devId; + + // 创建时间 + private final long creationTime; + + // 协议版本号 + private Integer protocolVersion; + + private Header header; + + protected Session(Channel channel) { + this.channel = channel; + this.creationTime = System.currentTimeMillis(); + } + + public void writeObject(Object message) { + log.info("<<<<<<<<<< cmd{},{}", this, message); + channel.writeAndFlush(message); + } + + /** + * 获得下一个流水号 + * + * @return 流水号 + */ + public int nextSerialNo() { + int current; + int next; + do { + current = serialNo.get(); + next = current > 0xffff ? 0 : current; + } while (!serialNo.compareAndSet(current, next + 1)); + return next; + } + + /** + * 注册session + * + * @param devId 设备ID + */ + public void register(String devId, Integer version, Header header) { + this.devId = devId; + this.registered = true; + this.protocolVersion = version; + this.header = header; + SessionManager.INSTANCE.put(devId, this); + } + + /** + * 获取设备号 + * + * @return 设备号 + */ + public String getDevId() { + return devId; + } + + + public boolean isRegistered() { + return registered; + } + + public long getCreationTime() { + return creationTime; + } + + public Integer getProtocolVersion() { + return protocolVersion; + } + + public Header getHeader() { + return header; + } + + @Override + public String toString() { + return "[" + + "devId=" + devId + + ", reg=" + registered + + ", version=" + protocolVersion + + ",ip=" + channel.remoteAddress() + + ']'; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/session/SessionManager.java b/src/main/java/com/genersoft/iot/vmp/jt1078/session/SessionManager.java new file mode 100644 index 000000000..c2876e561 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/session/SessionManager.java @@ -0,0 +1,127 @@ +package com.genersoft.iot.vmp.jt1078.session; + +import com.genersoft.iot.vmp.jt1078.proc.entity.Cmd; +import io.netty.channel.Channel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; + + +/** + * @author QingtaiJiang + * @date 2023/4/27 19:54 + * @email qingtaij@163.com + */ +public enum SessionManager { + INSTANCE; + private final static Logger log = LoggerFactory.getLogger(SessionManager.class); + + // 用与消息的缓存 + private final Map> topicSubscribers = new ConcurrentHashMap<>(); + + // session的缓存 + private final Map sessionMap; + + SessionManager() { + this.sessionMap = new ConcurrentHashMap<>(); + } + + /** + * 创建新的Session + * + * @param channel netty通道 + * @return 创建的session对象 + */ + public Session newSession(Channel channel) { + return new Session(channel); + } + + + /** + * 获取指定设备的Session + * + * @param clientId 设备Id + * @return Session + */ + public Session get(Object clientId) { + return sessionMap.get(clientId); + } + + /** + * 放入新设备连接的session + * + * @param clientId 设备ID + * @param newSession session + */ + protected void put(Object clientId, Session newSession) { + sessionMap.put(clientId, newSession); + } + + + /** + * 发送同步消息,接收响应 + * 默认超时时间6秒 + */ + public String request(Cmd cmd) { + // 默认6秒 + int timeOut = 6000; + return request(cmd, timeOut); + } + + public String request(Cmd cmd, Integer timeOut) { + Session session = this.get(cmd.getDevId()); + if (session == null) { + log.error("DevId: {} not online!", cmd.getDevId()); + return null; + } + String requestKey = requestKey(cmd.getDevId(), cmd.getRespId(), cmd.getPackageNo()); + SynchronousQueue subscribe = subscribe(requestKey); + if (subscribe == null) { + log.error("DevId: {} key:{} send repaid", cmd.getDevId(), requestKey); + return null; + } + session.writeObject(cmd); + try { + return subscribe.poll(timeOut, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.warn("<<<<<<<<<< timeout" + session, e); + } finally { + this.unsubscribe(requestKey); + } + return null; + } + + public Boolean response(String devId, String respId, Long responseNo, String data) { + String requestKey = requestKey(devId, respId, responseNo); + SynchronousQueue queue = topicSubscribers.get(requestKey); + if (queue != null) { + try { + return queue.offer(data, 2, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.error("{}", e.getMessage(), e); + } + } + log.warn("Not find response,key:{} data:{} ", requestKey, data); + return false; + } + + private void unsubscribe(String key) { + topicSubscribers.remove(key); + } + + private SynchronousQueue subscribe(String key) { + SynchronousQueue queue = null; + if (!topicSubscribers.containsKey(key)) + topicSubscribers.put(key, queue = new SynchronousQueue()); + return queue; + } + + private String requestKey(String devId, String respId, Long requestNo) { + return String.join("_", devId.replaceFirst("^0*", ""), respId, requestNo.toString()); + } + +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/util/Bin.java b/src/main/java/com/genersoft/iot/vmp/jt1078/util/Bin.java new file mode 100644 index 000000000..31f8b9301 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/util/Bin.java @@ -0,0 +1,41 @@ +package com.genersoft.iot.vmp.jt1078.util; + +/** + * 32位整型的二进制读写 + */ +public class Bin { + + private static final int[] bits = new int[32]; + + static { + bits[0] = 1; + for (int i = 1; i < bits.length; i++) { + bits[i] = bits[i - 1] << 1; + } + } + + /** + * 读取n的第i位 + * + * @param n int32 + * @param i 取值范围0-31 + */ + public static boolean get(int n, int i) { + return (n & bits[i]) == bits[i]; + } + + /** + * 不足位数从左边加0 + */ + public static String strHexPaddingLeft(String data, int length) { + int dataLength = data.length(); + if (dataLength < length) { + StringBuilder dataBuilder = new StringBuilder(data); + for (int i = dataLength; i < length; i++) { + dataBuilder.insert(0, "0"); + } + data = dataBuilder.toString(); + } + return data; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/jt1078/util/ClassUtil.java b/src/main/java/com/genersoft/iot/vmp/jt1078/util/ClassUtil.java new file mode 100644 index 000000000..3dcb1b860 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/jt1078/util/ClassUtil.java @@ -0,0 +1,112 @@ +package com.genersoft.iot.vmp.jt1078.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; + +import java.lang.annotation.Annotation; +import java.util.LinkedList; +import java.util.List; + +public class ClassUtil { + + private static final Logger logger = LoggerFactory.getLogger(ClassUtil.class); + + + public static Object getBean(Class clazz) { + if (clazz != null) { + try { + return clazz.getDeclaredConstructor().newInstance(); + } catch (Exception ex) { + logger.error("ClassUtil:找不到指定的类", ex); + } + } + return null; + } + + + public static Object getBean(String className) { + Class clazz = null; + try { + clazz = Class.forName(className); + } catch (Exception ex) { + logger.error("ClassUtil:找不到指定的类"); + } + if (clazz != null) { + try { + //获取声明的构造器--》创建实例 + return clazz.getDeclaredConstructor().newInstance(); + } catch (Exception ex) { + logger.error("ClassUtil:找不到指定的类", ex); + } + } + return null; + } + + + /** + * 获取包下所有带注解的class + * + * @param packageName 包名 + * @param annotationClass 注解类型 + * @return list + */ + public static List> getClassList(String packageName, Class annotationClass) { + List> classList = getClassList(packageName); + classList.removeIf(next -> !next.isAnnotationPresent(annotationClass)); + return classList; + } + + public static List> getClassList(String... packageName) { + List> classList = new LinkedList<>(); + for (String s : packageName) { + List> c = getClassList(s); + classList.addAll(c); + } + return classList; + } + + public static List> getClassList(String packageName) { + List> classList = new LinkedList<>(); + try { + ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = resourcePatternResolver.getResources(packageName.replace(".", "/") + "/**/*.class"); + for (Resource resource : resources) { + String url = resource.getURL().toString(); + + String[] split = url.split(packageName.replace(".", "/")); + String s = split[split.length - 1]; + String className = s.replace("/", "."); + className = className.substring(0, className.lastIndexOf(".")); + doAddClass(classList, packageName + className); + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + return classList; + } + + private static void doAddClass(List> classList, String className) { + Class cls = loadClass(className, false); + classList.add(cls); + } + + public static Class loadClass(String className, boolean isInitialized) { + Class cls; + try { + cls = Class.forName(className, isInitialized, getClassLoader()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + return cls; + } + + + public static ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + +} 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 db40961c3..400a941ac 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -2,12 +2,16 @@ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; +import com.genersoft.iot.vmp.common.InviteInfo; +import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; 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; @@ -21,10 +25,8 @@ import com.genersoft.iot.vmp.media.zlm.dto.hook.*; import com.genersoft.iot.vmp.service.*; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; -import com.genersoft.iot.vmp.vmanager.bean.DeferredResultEx; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; -import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -57,14 +59,14 @@ public class ZLMHttpHookListener { @Autowired private SIPCommander cmder; - @Autowired - private ISIPCommanderForPlatform commanderFroPlatform; + @Autowired + private ISIPCommanderForPlatform commanderFroPlatform; - @Autowired - private AudioBroadcastManager audioBroadcastManager; + @Autowired + private AudioBroadcastManager audioBroadcastManager; - @Autowired - private ZLMRTPServerFactory zlmrtpServerFactory; + @Autowired + private ZLMRTPServerFactory zlmrtpServerFactory; @Autowired private IPlayService playService; @@ -75,6 +77,9 @@ public class ZLMHttpHookListener { @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private IInviteStreamService inviteStreamService; + @Autowired private IDeviceService deviceService; @@ -111,6 +116,9 @@ public class ZLMHttpHookListener { @Autowired private AssistRESTfulUtils assistRESTfulUtils; + @Autowired + private SSRCFactory ssrcFactory; + @Qualifier("taskExecutor") @Autowired private ThreadPoolTaskExecutor taskExecutor; @@ -255,13 +263,13 @@ public class ZLMHttpHookListener { result.setEnable_audio(deviceChannel.isHasAudio()); } // 如果是录像下载就设置视频间隔十秒 - if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.download) { + if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) { result.setMp4_max_second(10); result.setEnable_audio(true); result.setEnable_mp4(true); } // 如果是talk对讲,则默认获取声音 - if (ssrcTransactionForAll.get(0).getType() == VideoStreamSessionManager.SessionType.talk) { + if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.TALK) { result.setEnable_audio(true); } @@ -269,7 +277,7 @@ public class ZLMHttpHookListener { 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 ) { + if (info != null && info.getInteger("code") != null && info.getInteger("code") == 0) { JSONObject dataJson = info.getJSONObject("data"); if (dataJson != null) { String recordPath = dataJson.getString("record"); @@ -301,29 +309,30 @@ public class ZLMHttpHookListener { logger.info("[ZLM HOOK] 流注销, {}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); } - JSONObject ret = new JSONObject(); - ret.put("code", 0); - ret.put("msg", "success"); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + ret.put("msg", "success"); MediaServerItem mediaInfo = mediaServerService.getOne(param.getMediaServerId()); JSONObject json = (JSONObject) JSON.toJSON(param); taskExecutor.execute(() -> { ZlmHttpHookSubscribe.Event subscribe = this.subscribe.sendNotify(HookType.on_stream_changed, json); + if (mediaInfo == null) { + logger.info("[ZLM HOOK] 流变化未找到ZLM, {}", param.getMediaServerId()); + return; + } if (subscribe != null) { - - if (mediaInfo != null) { - subscribe.response(mediaInfo, json); - } + subscribe.response(mediaInfo, json); } 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); @@ -337,123 +346,122 @@ public class ZLMHttpHookListener { 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()); - } - if (param.getOriginType() == OriginType.PULL.ordinal() - || param.getOriginType() == OriginType.FFMPEG_PULL.ordinal()) { - // 设置拉流代理上线/离线 - streamProxyService.updateStatus(param.isRegist(), param.getApp(), param.getStream()); - } - if ("rtp".equals(param.getApp()) && !param.isRegist() ) { - StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(param.getStream()); - if (streamInfo!=null){ - redisCatchStorage.stopPlay(streamInfo); - storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); - }else{ - streamInfo = redisCatchStorage.queryPlayback(null, null, param.getStream(), null); - if (streamInfo != null) { - redisCatchStorage.stopPlayback(streamInfo.getDeviceID(), streamInfo.getChannelId(), - streamInfo.getStream(), null); - } - } - }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)) { + 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) { + + } + + if ("rtp".equals(param.getApp()) && !param.isRegist()) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); + if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) { + 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); } - // 开启语音对讲通道 - try { - playService.audioBroadcastCmd(device, channelId, mediaInfo, param.getApp(), param.getStream(), 60, false, (msg)->{ + } 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); }); - } catch (InvalidArgumentException | ParseException | SipException e) { - logger.error("[命令发送失败] 语音对讲: {}", e.getMessage()); + } else { + // 流注销 + playService.stopTalk(device, channelId, param.isRegist()); } - }else { - // 流注销 - playService.stopAudioBroadcast(deviceId, channelId); + } else { + logger.info("[语音对讲] 未找到设备:{}", deviceId); } - } 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) { + + } else { + if (!"rtp".equals(param.getApp())) { + String type = OriginType.values()[param.getOriginType()].getType(); + MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); + + if (mediaServerItem != null) { if (param.isRegist()) { - if (audioBroadcastManager.exit(deviceId, channelId)) { - playService.stopAudioBroadcast(deviceId, channelId); + StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); + String callId = null; + if (streamAuthorityInfo != null) { + callId = streamAuthorityInfo.getCallId(); } - // 开启语音对讲通道 - 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(); - MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId()); - - if (mediaServerItem != null){ - if (param.isRegist()) { - StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream()); - String callId = null; - if (streamAuthorityInfo != null) { - callId = streamAuthorityInfo.getCallId(); - } - StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaServerItem, - param.getApp(), param.getStream(), param.getTracks(), callId); - param.setStreamInfo(new StreamContent(streamInfoByAppAndStream)); - redisCatchStorage.addStream(mediaServerItem, type, param.getApp(), param.getStream(), param); - if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal() - || param.getOriginType() == OriginType.RTMP_PUSH.ordinal() - || param.getOriginType() == OriginType.RTC_PUSH.ordinal() ) { - param.setSeverId(userSetting.getServerId()); - zlmMediaListManager.addPush(param); - } - }else { - // 兼容流注销时类型从redis记录获取 - OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo( - param.getApp(), param.getStream(), param.getMediaServerId()); - if (onStreamChangedHookParam != null) { - type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType(); - redisCatchStorage.removeStream(mediaServerItem.getId(), type, param.getApp(), param.getStream()); - } - GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream()); - if (gbStream != null) { + StreamInfo streamInfoByAppAndStream = mediaService.getStreamInfoByAppAndStream(mediaServerItem, + param.getApp(), param.getStream(), param.getTracks(), callId); + param.setStreamInfo(new StreamContent(streamInfoByAppAndStream)); + redisCatchStorage.addStream(mediaServerItem, type, param.getApp(), param.getStream(), param); + if (param.getOriginType() == OriginType.RTSP_PUSH.ordinal() + || param.getOriginType() == OriginType.RTMP_PUSH.ordinal() + || param.getOriginType() == OriginType.RTC_PUSH.ordinal()) { + param.setSeverId(userSetting.getServerId()); + zlmMediaListManager.addPush(param); + } + } else { + // 兼容流注销时类型从redis记录获取 + OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo( + param.getApp(), param.getStream(), param.getMediaServerId()); + if (onStreamChangedHookParam != null) { + type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType(); + redisCatchStorage.removeStream(mediaServerItem.getId(), type, param.getApp(), param.getStream()); + } + GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream()); + if (gbStream != null) { // eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF); } zlmMediaListManager.removeMedia(param.getApp(), param.getStream()); } + GbStream gbStream = storager.getGbStream(param.getApp(), param.getStream()); + if (gbStream != null) { + eventPublisher.catalogEventPublishForStream(null, gbStream, param.isRegist() ? CatalogEvent.ON : CatalogEvent.OFF); + } if (type != null) { // 发送流变化redis消息 JSONObject jsonObject = new JSONObject(); @@ -466,38 +474,35 @@ public class ZLMHttpHookListener { } } } - } - if (!param.isRegist()) { - List sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); - if (sendRtpItems.size() > 0) { - for (SendRtpItem sendRtpItem : sendRtpItems) { - if (sendRtpItem.getApp().equals(param.getApp())) { - String platformId = sendRtpItem.getPlatformId(); - ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId); - Device device = deviceService.getDevice(platformId); + if (!param.isRegist()) { + List sendRtpItems = redisCatchStorage.querySendRTPServerByStream(param.getStream()); + if (sendRtpItems.size() > 0) { + for (SendRtpItem sendRtpItem : sendRtpItems) { + if (sendRtpItem != null && sendRtpItem.getApp().equals(param.getApp())) { + String platformId = sendRtpItem.getPlatformId(); + ParentPlatform platform = storager.queryParentPlatByServerGBId(platformId); + Device device = deviceService.getDevice(platformId); - - if (platform != null) { - try { + try { + if (platform != null) { commanderFroPlatform.streamByeCmd(platform, sendRtpItem); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); - } - } else { - try { + redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(), + sendRtpItem.getCallId(), sendRtpItem.getStream()); + } else { cmder.streamByeCmd(device, sendRtpItem.getChannelId(), param.getStream(), sendRtpItem.getCallId()); - } catch (SipException | InvalidArgumentException | ParseException | - SsrcTransactionNotFoundException e) { - logger.error("[命令发送失败] 发送BYE: {}", e.getMessage()); } + } catch (SipException | InvalidArgumentException | ParseException | + SsrcTransactionNotFoundException e) { + logger.error("[命令发送失败] 发送BYE: {}", e.getMessage()); } + } } } } } + } }); - return HookResult.SUCCESS(); } @@ -508,82 +513,65 @@ 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(), param.getApp(), param.getStream()); - JSONObject ret = new JSONObject(); - ret.put("code", 0); - // 国标类型的流 - if ("rtp".equals(param.getApp())){ - ret.put("close", userSetting.getStreamOnDemand()); - // 国标流, 点播/录像回放/录像下载 - StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(param.getStream()); - // 点播 - if (streamInfoForPlayCatch != null) { - // 收到无人观看说明流也没有在往上级推送 - if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) { - List sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId(streamInfoForPlayCatch.getChannelId()); - if (sendRtpItems.size() > 0) { - for (SendRtpItem sendRtpItem : sendRtpItems) { - ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); - if (parentPlatform == null) { - continue; - } - try { - commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); - } catch (SipException | InvalidArgumentException | ParseException e) { - logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); - } - redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), - sendRtpItem.getCallId(), sendRtpItem.getStream()); - } - } - } - Device device = deviceService.getDevice(streamInfoForPlayCatch.getDeviceID()); - if (device != null) { - try { - cmder.streamByeCmd(device, streamInfoForPlayCatch.getChannelId(), - streamInfoForPlayCatch.getStream(), null); - } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { - logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage()); - } - } + logger.info("[ZLM HOOK]流无人观看:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), + param.getApp(), param.getStream()); + JSONObject ret = new JSONObject(); + ret.put("code", 0); + // 国标类型的流 + if ("rtp".equals(param.getApp())) { + ret.put("close", userSetting.getStreamOnDemand()); + // 国标流, 点播/录像回放/录像下载 +// StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(param.getStream()); - redisCatchStorage.stopPlay(streamInfoForPlayCatch); - storager.stopPlay(streamInfoForPlayCatch.getDeviceID(), streamInfoForPlayCatch.getChannelId()); - return ret; - } - // 录像回放 - StreamInfo streamInfoForPlayBackCatch = redisCatchStorage.queryPlayback(null, null, param.getStream(), null); - if (streamInfoForPlayBackCatch != null) { - if (streamInfoForPlayBackCatch.isPause()) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, param.getStream()); + // 点播 + if (inviteInfo != null) { + // 录像下载 + if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) { ret.put("close", false); - } else { - Device device = deviceService.getDevice(streamInfoForPlayBackCatch.getDeviceID()); - if (device != null) { - try { - cmder.streamByeCmd(device, streamInfoForPlayBackCatch.getChannelId(), - streamInfoForPlayBackCatch.getStream(), null); - } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { - logger.error("[无人观看]回放, 发送BYE失败 {}", e.getMessage()); + return ret; + } + // 收到无人观看说明流也没有在往上级推送 + if (redisCatchStorage.isChannelSendingRTP(inviteInfo.getChannelId())) { + List sendRtpItems = redisCatchStorage.querySendRTPServerByChnnelId( + inviteInfo.getChannelId()); + if (sendRtpItems.size() > 0) { + for (SendRtpItem sendRtpItem : sendRtpItems) { + ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); + try { + commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); + } catch (SipException | InvalidArgumentException | ParseException e) { + logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage()); + } + redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(), + sendRtpItem.getCallId(), sendRtpItem.getStream()); } } - redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch.getDeviceID(), - streamInfoForPlayBackCatch.getChannelId(), streamInfoForPlayBackCatch.getStream(), null); } - return ret; - } - // 录像下载 - StreamInfo streamInfoForDownload = redisCatchStorage.queryDownload(null, null, param.getStream(), null); - // 进行录像下载时无人观看不断流 - if (streamInfoForDownload != null) { - ret.put("close", false); + Device device = deviceService.getDevice(inviteInfo.getDeviceId()); + if (device != null) { + try { + if (inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()) != null) { + cmder.streamByeCmd(device, inviteInfo.getChannelId(), + inviteInfo.getStream(), null); + } + } catch (InvalidArgumentException | ParseException | SipException | + SsrcTransactionNotFoundException e) { + logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage()); + } + } + + inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), + inviteInfo.getChannelId(), inviteInfo.getStream()); + storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); return ret; } SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, param.getStream(), null); - if ("talk".equals(sendRtpItem.getApp())){ + if ("talk".equals(sendRtpItem.getApp())) { ret.put("close", false); return ret; } - }else if ("talk".equals(param.getApp()) || "broadcast".equals(param.getApp())){ + } else if ("talk".equals(param.getApp()) || "broadcast".equals(param.getApp())) { ret.put("close", false); } else { // 非国标流 推流/拉流代理 @@ -652,6 +640,7 @@ public class ZLMHttpHookListener { return defaultResult; } logger.info("[ZLM HOOK] 流未找到, 发起自动点播:{}->{}->{}/{}", param.getMediaServerId(), param.getSchema(), param.getApp(), param.getStream()); + RequestMessage msg = new RequestMessage(); String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; boolean exist = resultHolder.exist(key, null); @@ -659,31 +648,22 @@ public class ZLMHttpHookListener { String uuid = UUID.randomUUID().toString(); msg.setId(uuid); DeferredResult result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); - DeferredResultEx deferredResultEx = new DeferredResultEx<>(result); result.onTimeout(() -> { - logger.info("点播接口等待超时"); + logger.info("[ZLM HOOK] 自动点播, 等待超时"); // 释放rtpserver msg.setData(new HookResult(ErrorCode.ERROR100.getCode(), "点播超时")); resultHolder.invokeResult(msg); }); - // TODO 在点播未成功的情况下在此调用接口点播会导致返回的流地址ip错误 - deferredResultEx.setFilter(result1 -> { - WVPResult wvpResult1 = (WVPResult) result1; - HookResult resultForEnd = new HookResult(); - resultForEnd.setCode(wvpResult1.getCode()); - resultForEnd.setMsg(wvpResult1.getMsg()); - return resultForEnd; - }); // 录像查询以channelId作为deviceId查询 - resultHolder.put(key, uuid, deferredResultEx); + resultHolder.put(key, uuid, result); if (!exist) { - playService.play(mediaInfo, deviceId, channelId, null, eventResult -> { - msg.setData(new HookResult(eventResult.statusCode, eventResult.msg)); + playService.play(mediaInfo, deviceId, channelId, (code, message, data) -> { + msg.setData(new HookResult(code, message)); resultHolder.invokeResult(msg); - }, null); + }); } return result; } else { @@ -740,6 +720,7 @@ public class ZLMHttpHookListener { if (sendRtpItems.size() > 0) { for (SendRtpItem sendRtpItem : sendRtpItems) { ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId()); + ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); try { commanderFroPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId()); } catch (SipException | InvalidArgumentException | ParseException e) { @@ -759,7 +740,8 @@ public class ZLMHttpHookListener { */ @ResponseBody @PostMapping(value = "/on_rtp_server_timeout", produces = "application/json;charset=UTF-8") - public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam param) { + public HookResult onRtpServerTimeout(HttpServletRequest request, @RequestBody OnRtpServerTimeoutHookParam + param) { logger.info("[ZLM HOOK] rtpServer收流超时:{}->{}({})", param.getMediaServerId(), param.getStream_id(), param.getSsrc()); taskExecutor.execute(() -> { 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 index db2beb0c0..8e9b3d0b9 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMMediaListManager.java @@ -97,7 +97,8 @@ public class ZLMMediaListManager { public void sendStreamEvent(String app, String stream, String mediaServerId) { MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); // 查看推流状态 - if (zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream)) { + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, app, stream); + if (streamReady != null && streamReady) { ChannelOnlineEvent channelOnlineEventLister = getChannelOnlineEventLister(app, stream); if (channelOnlineEventLister != null) { try { 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 9a4677839..bd39c5595 100644 --- 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,6 +25,8 @@ public class ZLMRESTfulUtils { private OkHttpClient client; + + public interface RequestCallback{ void run(JSONObject response); } @@ -279,6 +281,10 @@ public class ZLMRESTfulUtils { return sendPost(mediaServerItem, "closeRtpServer",param, null); } + public void closeRtpServer(MediaServerItem mediaServerItem, Map param, RequestCallback callback) { + sendPost(mediaServerItem, "closeRtpServer",param, callback); + } + public JSONObject listRtpServer(MediaServerItem mediaServerItem) { return sendPost(mediaServerItem, "listRtpServer",null, null); } @@ -353,4 +359,19 @@ public class ZLMRESTfulUtils { param.put("stream_id", streamId); return sendPost(mediaServerItem, "resumeRtpCheck",param, null); } + + public JSONObject connectRtpServer(MediaServerItem mediaServerItem, String dst_url, int dst_port, String stream_id) { + Map param = new HashMap<>(1); + param.put("dst_url", dst_url); + param.put("dst_port", dst_port); + param.put("stream_id", stream_id); + return sendPost(mediaServerItem, "connectRtpServer",param, null); + } + + public JSONObject updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc) { + Map param = new HashMap<>(1); + param.put("ssrc", ssrc); + param.put("stream_id", streamId); + return sendPost(mediaServerItem, "updateRtpServerSSRC",param, null); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java index a1524dd70..7fef8ee4c 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRTPServerFactory.java @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; @@ -92,7 +93,17 @@ public class ZLMRTPServerFactory { return result; } - public int createRTPServer(MediaServerItem mediaServerItem, String streamId, int ssrc, Integer port, Boolean onlyAuto) { + /** + * 开启rtpServer + * @param mediaServerItem zlm服务实例 + * @param streamId 流Id + * @param ssrc ssrc + * @param port 端口, 0/null为使用随机 + * @param reUsePort 是否重用端口 + * @param tcpMode 0/null udp 模式,1 tcp 被动模式, 2 tcp 主动模式。 + * @return + */ + public int createRTPServer(MediaServerItem mediaServerItem, String streamId, int ssrc, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) { int result = -1; // 查询此rtp server 是否已经存在 JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId); @@ -108,7 +119,7 @@ public class ZLMRTPServerFactory { JSONObject jsonObject = zlmresTfulUtils.closeRtpServer(mediaServerItem, param); if (jsonObject != null ) { if (jsonObject.getInteger("code") == 0) { - return createRTPServer(mediaServerItem, streamId, ssrc, port, onlyAuto); + return createRTPServer(mediaServerItem, streamId, ssrc, port,onlyAuto, reUsePort, tcpMode); }else { logger.warn("[开启rtpServer], 重启RtpServer错误"); } @@ -122,8 +133,14 @@ public class ZLMRTPServerFactory { Map param = new HashMap<>(); - param.put("enable_tcp", 1); + if (tcpMode == null) { + tcpMode = 0; + } + param.put("tcp_mode", tcpMode); param.put("stream_id", streamId); + if (reUsePort != null) { + param.put("re_use_port", reUsePort?"1":"0"); + } // 推流端口设置0则使用随机端口 if (port == null) { param.put("port", 0); @@ -169,6 +186,31 @@ public class ZLMRTPServerFactory { return result; } + public void closeRtpServer(MediaServerItem serverItem, String streamId, CommonCallback callback) { + if (serverItem == null) { + callback.run(false); + return; + } + Map param = new HashMap<>(); + param.put("stream_id", streamId); + zlmresTfulUtils.closeRtpServer(serverItem, param, jsonObject -> { + if (jsonObject != null ) { + if (jsonObject.getInteger("code") == 0) { + callback.run(jsonObject.getInteger("hit") == 1); + return; + }else { + logger.error("关闭RTP Server 失败: " + jsonObject.getString("msg")); + } + }else { + // 检查ZLM状态 + logger.error("关闭RTP Server 失败: 请检查ZLM服务"); + } + callback.run(false); + }); + + + } + /** * 创建一个国标推流 @@ -256,11 +298,14 @@ public class ZLMRTPServerFactory { if (jsonObject.getInteger("code") == 0) { localPort = jsonObject.getInteger("port"); HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(ssrc, null, serverItem.getId()); - // 订阅 zlm启动事件, 新的zlm也会从这里进入系统 hookSubscribe.addSubscribe(hookSubscribeForRtpServerTimeout, (MediaServerItem mediaServerItem, JSONObject response)->{ - logger.info("[保持端口] {}->监听端口到期继续保持监听", ssrc); - keepPort(serverItem, ssrc); + logger.info("[上级点播] {}->监听端口到期继续保持监听", ssrc); + int port = keepPort(serverItem, ssrc); + if (port == 0) { + logger.info("[上级点播] {}->监听端口失败,移除监听", ssrc); + hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout); + } }); logger.info("[保持端口] {}->监听端口: {}", ssrc, localPort); logger.info("[保持端口] {}->监听端口: {}", ssrc, localPort); @@ -305,6 +350,9 @@ public class ZLMRTPServerFactory { */ public Boolean isRtpReady(MediaServerItem mediaServerItem, String streamId) { JSONObject mediaInfo = zlmresTfulUtils.getMediaInfo(mediaServerItem,"rtp", "rtsp", streamId); + if (mediaInfo.getInteger("code") == -2) { + return null; + } return (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online")); } @@ -313,8 +361,10 @@ public class ZLMRTPServerFactory { */ public Boolean isStreamReady(MediaServerItem mediaServerItem, String app, String streamId) { JSONObject mediaInfo = zlmresTfulUtils.getMediaList(mediaServerItem, app, streamId); - return mediaInfo != null && (mediaInfo.getInteger("code") == 0 - + if (mediaInfo == null || (mediaInfo.getInteger("code") == -2)) { + return null; + } + return (mediaInfo.getInteger("code") == 0 && mediaInfo.getJSONArray("data") != null && mediaInfo.getJSONArray("data").size() > 0); } @@ -406,4 +456,19 @@ public class ZLMRTPServerFactory { } return startSendRtpStreamResult; } + + public Boolean updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc) { + boolean result = false; + JSONObject jsonObject = zlmresTfulUtils.updateRtpServerSSRC(mediaServerItem, streamId, ssrc); + if (jsonObject == null) { + logger.error("[更新RTPServer] 失败: 请检查ZLM服务"); + } else if (jsonObject.getInteger("code") == 0) { + result= true; + logger.info("[更新RTPServer] 成功"); + } else { + logger.error("[更新RTPServer] 失败: {}, streamId:{},ssrc:{}->\r\n{}",jsonObject.getString("msg"), + streamId, ssrc, jsonObject); + } + return result; + } } 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 index a0f4ab68b..890401815 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZlmHttpHookSubscribe.java @@ -100,7 +100,10 @@ public class ZlmHttpHookSubscribe { if (!CollectionUtils.isEmpty(entriesToRemove)) { for (Map.Entry entry : entriesToRemove) { - entries.remove(entry); + eventMap.remove(entry.getKey()); + } + if (eventMap.size() == 0) { + allSubscribes.remove(hookSubscribe.getHookType()); } } @@ -136,9 +139,9 @@ public class ZlmHttpHookSubscribe { /** * 对订阅数据进行过期清理 */ - @Scheduled(cron="0 0/5 * * * ?") //每5分钟执行一次 +// @Scheduled(cron="0 0/5 * * * ?") //每5分钟执行一次 + @Scheduled(fixedRate = 2 * 1000) public void execute(){ - Instant instant = Instant.now().minusMillis(TimeUnit.MINUTES.toMillis(5)); int total = 0; for (HookType hookType : allSubscribes.keySet()) { 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 index f3eb3d679..e6bbb5fac 100644 --- 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 @@ -1,13 +1,10 @@ package com.genersoft.iot.vmp.media.zlm.dto; -import com.genersoft.iot.vmp.gb28181.session.SsrcConfig; import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig; import io.swagger.v3.oas.annotations.media.Schema; import org.springframework.util.ObjectUtils; -import java.util.HashMap; - @Schema(description = "流媒体服务信息") public class MediaServerItem{ @@ -80,20 +77,10 @@ public class MediaServerItem{ @Schema(description = "是否是默认ZLM") private boolean defaultServer; - @Schema(description = "SSRC信息") - private SsrcConfig ssrcConfig; - @Schema(description = "当前使用到的端口") private int currentPort; - /** - * 每一台ZLM都有一套独立的SSRC列表 - * 在ApplicationCheckRunner里对mediaServerSsrcMap进行初始化 - */ - @Schema(description = "ID") - private HashMap mediaServerSsrcMap; - public MediaServerItem() { } @@ -279,22 +266,6 @@ public class MediaServerItem{ this.updateTime = updateTime; } - public HashMap getMediaServerSsrcMap() { - return mediaServerSsrcMap; - } - - public void setMediaServerSsrcMap(HashMap mediaServerSsrcMap) { - this.mediaServerSsrcMap = mediaServerSsrcMap; - } - - public SsrcConfig getSsrcConfig() { - return ssrcConfig; - } - - public void setSsrcConfig(SsrcConfig ssrcConfig) { - this.ssrcConfig = ssrcConfig; - } - public int getCurrentPort() { return currentPort; } 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 index a2da561d8..b327f13a4 100644 --- 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 @@ -18,6 +18,10 @@ public class HookResult { return new HookResult(0, "success"); } + public static HookResult Fail(){ + return new HookResult(-1, "fail"); + } + public int getCode() { return code; } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java b/src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java index c192dd5a1..66dbe0772 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IDeviceChannelService.java @@ -56,4 +56,35 @@ public interface IDeviceChannelService { * 查询通道所属的设备 */ List getDeviceByChannelId(String channelId); + + /** + * 批量删除通道 + * @param deleteChannelList 待删除的通道列表 + */ + int deleteChannels(List deleteChannelList); + + /** + * 批量上线 + */ + int channelsOnline(List channels); + + /** + * 批量下线 + */ + int channelsOffline(List channels); + + /** + * 获取一个通道 + */ + DeviceChannel getOne(String deviceId, String channelId); + + /** + * 直接批量更新通道 + */ + void batchUpdateChannel(List channels); + + /** + * 直接批量添加 + */ + void batchAddChannel(List deviceChannels); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java b/src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java new file mode 100644 index 000000000..ca90095d9 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/IInviteStreamService.java @@ -0,0 +1,68 @@ +package com.genersoft.iot.vmp.service; + +import com.genersoft.iot.vmp.common.InviteInfo; +import com.genersoft.iot.vmp.common.InviteSessionType; +import com.genersoft.iot.vmp.service.bean.InviteErrorCallback; + +/** + * 记录国标点播的状态,包括实时预览,下载,录像回放 + */ +public interface IInviteStreamService { + + /** + * 更新点播的状态信息 + */ + void updateInviteInfo(InviteInfo inviteInfo); + + /** + * 获取点播的状态信息 + */ + InviteInfo getInviteInfo(InviteSessionType type, + String deviceId, + String channelId, + String stream); + + /** + * 移除点播的状态信息 + */ + void removeInviteInfo(InviteSessionType type, + String deviceId, + String channelId, + String stream); + /** + * 移除点播的状态信息 + */ + void removeInviteInfo(InviteInfo inviteInfo); + /** + * 移除点播的状态信息 + */ + void removeInviteInfoByDeviceAndChannel(InviteSessionType inviteSessionType, String deviceId, String channelId); + + /** + * 获取点播的状态信息 + */ + InviteInfo getInviteInfoByDeviceAndChannel(InviteSessionType type, + String deviceId, + String channelId); + + /** + * 获取点播的状态信息 + */ + InviteInfo getInviteInfoByStream(InviteSessionType type, String stream); + + + /** + * 添加一个invite回调 + */ + void once(InviteSessionType type, String deviceId, String channelId, String stream, InviteErrorCallback callback); + + /** + * 调用一个invite回调 + */ + void call(InviteSessionType type, String deviceId, String channelId, String stream, int code, String msg, Object data); + + /** + * 清空一个设备的所有invite信息 + */ + void clearInviteInfo(String deviceId); +} diff --git a/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java b/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java index bac3d50ae..657f294d6 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.service; +import com.genersoft.iot.vmp.common.CommonCallback; import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.ServerKeepaliveData; @@ -43,14 +44,16 @@ public interface IMediaServerService { void updateVmServer(List mediaServerItemList); - SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck, boolean isPlayback); - - SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback); + 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); + void closeRTPServer(MediaServerItem mediaServerItem, String streamId, CommonCallback callback); + Boolean updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc); + void closeRTPServer(String mediaServerId, String streamId); void clearRTPServer(MediaServerItem mediaServerItem); diff --git a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java index 796bb99a0..ecbb4bb2c 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java @@ -3,12 +3,11 @@ 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.*; -import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; -import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe; +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.media.zlm.dto.MediaServerItem; -import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback; -import com.genersoft.iot.vmp.service.bean.PlayBackCallback; +import com.genersoft.iot.vmp.service.bean.InviteErrorCallback; 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; @@ -25,12 +24,11 @@ import java.util.Map; */ public interface IPlayService { - void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId); - void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, - ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent, - InviteTimeOutCallback timeoutCallback); - void play(MediaServerItem mediaServerItem, String deviceId, String channelId, ZlmHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent, Runnable timeoutCallback); + InviteErrorCallback callback); + SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, InviteErrorCallback callback); + + StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId); MediaServerItem getNewMediaServerItem(Device device); @@ -39,15 +37,13 @@ public interface IPlayService { */ MediaServerItem getNewMediaServerItemHasAssist(Device device); - void onPublishHandlerForDownload(InviteStreamInfo inviteStreamInfo, String deviceId, String channelId, String toString); - - void playBack(String deviceId, String channelId, String startTime, String endTime, InviteStreamCallback infoCallBack, PlayBackCallback playBackCallback); - void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack); + void playBack(String deviceId, String channelId, String startTime, String endTime, InviteErrorCallback callback); + void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, InviteErrorCallback callback); void zlmServerOffline(String mediaServerId); - void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback playBackCallback); - void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack); + void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteErrorCallback callback); + void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteErrorCallback callback); StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream); diff --git a/src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCallback.java b/src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCallback.java new file mode 100644 index 000000000..974057e5c --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCallback.java @@ -0,0 +1,6 @@ +package com.genersoft.iot.vmp.service.bean; + +public interface InviteErrorCallback { + + void run(int code, String msg, T data); +} 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 new file mode 100644 index 000000000..16c112b6e --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/bean/InviteErrorCode.java @@ -0,0 +1,36 @@ +package com.genersoft.iot.vmp.service.bean; + +/** + * 全局错误码 + */ +public enum InviteErrorCode { + SUCCESS(0, "成功"), + ERROR_FOR_SIGNALLING_TIMEOUT(-1, "信令超时"), + ERROR_FOR_STREAM_TIMEOUT(-2, "收流超时"), + ERROR_FOR_RESOURCE_EXHAUSTION(-3, "资源耗尽"), + ERROR_FOR_CATCH_DATA(-4, "缓存数据异常"), + ERROR_FOR_SIGNALLING_ERROR(-5, "收到信令错误"), + ERROR_FOR_STREAM_PARSING_EXCEPTIONS(-6, "流地址解析错误"), + ERROR_FOR_SDP_PARSING_EXCEPTIONS(-7, "SDP信息解析失败"), + ERROR_FOR_SSRC_UNAVAILABLE(-8, "SSRC不可用"), + ERROR_FOR_RESET_SSRC(-9, "重新设置收流信息失败"), + ERROR_FOR_SIP_SENDING_FAILED(-10, "命令发送失败"), + ERROR_FOR_ASSIST_NOT_READY(-11, "没有可用的assist服务"), + ERROR_FOR_PARAMETER_ERROR(-13, "参数异常"); + + private final int code; + private final String msg; + + InviteErrorCode(int code, String msg) { + this.code = code; + this.msg = msg; + } + + public int getCode() { + return code; + } + + public String getMsg() { + return msg; + } +} 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 index 336082f50..73adf2e49 100644 --- 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,10 +1,12 @@ package com.genersoft.iot.vmp.service.impl; -import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.common.InviteInfo; +import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.utils.Coordtransform; import com.genersoft.iot.vmp.service.IDeviceChannelService; +import com.genersoft.iot.vmp.service.IInviteStreamService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.storager.dao.DeviceMapper; @@ -32,6 +34,9 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private IInviteStreamService inviteStreamService; + @Autowired private DeviceChannelMapper channelMapper; @@ -45,6 +50,8 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { device = deviceMapper.getDeviceByDeviceId(deviceChannel.getDeviceId()); } + + if ("WGS84".equals(device.getGeoCoordSys())) { deviceChannel.setLongitudeWgs84(deviceChannel.getLongitude()); deviceChannel.setLatitudeWgs84(deviceChannel.getLatitude()); @@ -76,9 +83,10 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { public void updateChannel(String deviceId, DeviceChannel channel) { String channelId = channel.getChannelId(); channel.setDeviceId(deviceId); - StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId); - if (streamInfo != null) { - channel.setStreamId(streamInfo.getStream()); +// StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId); + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); + if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { + channel.setStreamId(inviteInfo.getStreamInfo().getStream()); } String now = DateUtil.getNow(); channel.setUpdateTime(now); @@ -104,9 +112,9 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { if (channelList.size() == 0) { for (DeviceChannel channel : channels) { channel.setDeviceId(deviceId); - StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channel.getChannelId()); - if (streamInfo != null) { - channel.setStreamId(streamInfo.getStream()); + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channel.getChannelId()); + if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { + channel.setStreamId(inviteInfo.getStreamInfo().getStream()); } String now = DateUtil.getNow(); channel.setUpdateTime(now); @@ -120,9 +128,9 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { } for (DeviceChannel channel : channels) { channel.setDeviceId(deviceId); - StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channel.getChannelId()); - if (streamInfo != null) { - channel.setStreamId(streamInfo.getStream()); + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channel.getChannelId()); + if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { + channel.setStreamId(inviteInfo.getStreamInfo().getStream()); } String now = DateUtil.getNow(); channel.setUpdateTime(now); @@ -207,6 +215,47 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService { @Override public List getDeviceByChannelId(String channelId) { + return channelMapper.getDeviceByChannelId(channelId); } + + @Override + public int deleteChannels(List deleteChannelList) { + return channelMapper.batchDel(deleteChannelList); + } + + @Override + public int channelsOnline(List channels) { + return channelMapper.batchOnline(channels); + } + + @Override + public int channelsOffline(List channels) { + return channelMapper.batchOffline(channels); + } + + @Override + public DeviceChannel getOne(String deviceId, String channelId){ + return channelMapper.queryChannel(deviceId, channelId); + } + + @Override + public void batchUpdateChannel(List channels) { + channelMapper.batchUpdate(channels); + for (DeviceChannel channel : channels) { + if (channel.getParentId() != null) { + channelMapper.updateChannelSubCount(channel.getDeviceId(), channel.getParentId()); + } + } + } + + @Override + public void batchAddChannel(List channels) { + channelMapper.batchAdd(channels); + for (DeviceChannel channel : channels) { + if (channel.getParentId() != null) { + channelMapper.updateChannelSubCount(channel.getDeviceId(), channel.getParentId()); + } + } + } } 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 index 98dbaf849..592f4f8f0 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/DeviceServiceImpl.java @@ -15,6 +15,7 @@ import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.IDeviceChannelService; import com.genersoft.iot.vmp.service.IDeviceService; +import com.genersoft.iot.vmp.service.IInviteStreamService; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; @@ -59,6 +60,9 @@ public class DeviceServiceImpl implements IDeviceService { @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private IInviteStreamService inviteStreamService; + @Autowired private DeviceMapper deviceMapper; @@ -104,7 +108,7 @@ public class DeviceServiceImpl implements IDeviceService { String now = DateUtil.getNow(); if (deviceInRedis != null && deviceInDb == null) { // redis 存在脏数据 - redisCatchStorage.clearCatchByDeviceId(device.getDeviceId()); + inviteStreamService.clearInviteInfo(device.getDeviceId()); } device.setUpdateTime(now); if (device.getKeepaliveIntervalTime() == 0) { @@ -172,6 +176,11 @@ public class DeviceServiceImpl implements IDeviceService { 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); + } + } @Override @@ -200,6 +209,11 @@ public class DeviceServiceImpl implements IDeviceService { // 移除订阅 removeCatalogSubscribe(device); removeMobilePositionSubscribe(device); + if (userSetting.getDeviceStatusNotify()) { + // 发送redis消息 + redisCatchStorage.sendDeviceOrChannelStatus(device.getDeviceId(), null, false); + } + List audioBroadcastCatches = audioBroadcastManager.get(deviceId); if (audioBroadcastCatches.size() > 0) { for (AudioBroadcastCatch audioBroadcastCatch : audioBroadcastCatches) { @@ -512,8 +526,10 @@ public class DeviceServiceImpl implements IDeviceService { node.setBasicData(channel); node.setParent(false); if (channel.getChannelId().length() > 8) { - String gbCodeType = channel.getChannelId().substring(10, 13); - node.setParent(gbCodeType.equals(ChannelIdType.BUSINESS_GROUP) || gbCodeType.equals(ChannelIdType.VIRTUAL_ORGANIZATION) ); + if (channel.getChannelId().length() > 13) { + String gbCodeType = channel.getChannelId().substring(10, 13); + node.setParent(gbCodeType.equals(ChannelIdType.BUSINESS_GROUP) || gbCodeType.equals(ChannelIdType.VIRTUAL_ORGANIZATION) ); + } }else { node.setParent(true); } @@ -669,4 +685,6 @@ public class DeviceServiceImpl implements IDeviceService { public List getAll() { return deviceMapper.getAll(); } + + } 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 new file mode 100644 index 000000000..f067cf066 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/InviteStreamServiceImpl.java @@ -0,0 +1,182 @@ +package com.genersoft.iot.vmp.service.impl; + +import com.alibaba.fastjson2.JSON; +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.VideoManagerConstants; +import com.genersoft.iot.vmp.service.IInviteStreamService; +import com.genersoft.iot.vmp.service.bean.InviteErrorCallback; +import com.genersoft.iot.vmp.utils.redis.RedisUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +@Service +public class InviteStreamServiceImpl implements IInviteStreamService { + + private final Logger logger = LoggerFactory.getLogger(InviteStreamServiceImpl.class); + + private final Map>> inviteErrorCallbackMap = new ConcurrentHashMap<>(); + + @Autowired + private RedisTemplate redisTemplate; + + @Override + public void updateInviteInfo(InviteInfo inviteInfo) { + if (inviteInfo == null || (inviteInfo.getDeviceId() == null || inviteInfo.getChannelId() == null)) { + logger.warn("[更新Invite信息],参数不全: {}", JSON.toJSON(inviteInfo)); + return; + } + InviteInfo inviteInfoForUpdate = null; + + if (InviteSessionStatus.ready == inviteInfo.getStatus()) { + if (inviteInfo.getDeviceId() == null + || inviteInfo.getChannelId() == null + || inviteInfo.getType() == null + || inviteInfo.getStream() == null + ) { + return; + } + inviteInfoForUpdate = inviteInfo; + } else { + InviteInfo inviteInfoInRedis = getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), + inviteInfo.getChannelId(), inviteInfo.getStream()); + if (inviteInfoInRedis == null) { + logger.warn("[更新Invite信息],未从缓存中读取到Invite信息: deviceId: {}, channel: {}, stream: {}", + inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()); + return; + } + if (inviteInfo.getStreamInfo() != null) { + inviteInfoInRedis.setStreamInfo(inviteInfo.getStreamInfo()); + } + if (inviteInfo.getSsrcInfo() != null) { + inviteInfoInRedis.setSsrcInfo(inviteInfo.getSsrcInfo()); + } + if (inviteInfo.getStreamMode() != null) { + inviteInfoInRedis.setStreamMode(inviteInfo.getStreamMode()); + } + if (inviteInfo.getReceiveIp() != null) { + inviteInfoInRedis.setReceiveIp(inviteInfo.getReceiveIp()); + } + if (inviteInfo.getReceivePort() != null) { + inviteInfoInRedis.setReceivePort(inviteInfo.getReceivePort()); + } + if (inviteInfo.getStatus() != null) { + inviteInfoInRedis.setStatus(inviteInfo.getStatus()); + } + + inviteInfoForUpdate = inviteInfoInRedis; + + } + String key = VideoManagerConstants.INVITE_PREFIX + + "_" + inviteInfoForUpdate.getType() + + "_" + inviteInfoForUpdate.getDeviceId() + + "_" + inviteInfoForUpdate.getChannelId() + + "_" + inviteInfoForUpdate.getStream(); + redisTemplate.opsForValue().set(key, inviteInfoForUpdate); + } + + @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 : "*"); + List scanResult = RedisUtil.scan(redisTemplate, key); + if (scanResult.size() != 1) { + return null; + } + + return (InviteInfo) redisTemplate.opsForValue().get(scanResult.get(0)); + } + + @Override + public InviteInfo getInviteInfoByDeviceAndChannel(InviteSessionType type, String deviceId, String channelId) { + return getInviteInfo(type, deviceId, channelId, null); + } + + @Override + public InviteInfo getInviteInfoByStream(InviteSessionType type, String stream) { + return getInviteInfo(type, null, null, stream); + } + + @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 : "*"); + List scanResult = RedisUtil.scan(redisTemplate, scanKey); + if (scanResult.size() > 0) { + for (Object keyObj : scanResult) { + String key = (String) keyObj; + InviteInfo inviteInfo = (InviteInfo) redisTemplate.opsForValue().get(key); + if (inviteInfo == null) { + continue; + } + redisTemplate.delete(key); + inviteErrorCallbackMap.remove(buildKey(type, deviceId, channelId, inviteInfo.getStream())); + } + } + } + + @Override + public void removeInviteInfoByDeviceAndChannel(InviteSessionType inviteSessionType, String deviceId, String channelId) { + removeInviteInfo(inviteSessionType, deviceId, channelId, null); + } + + @Override + public void removeInviteInfo(InviteInfo inviteInfo) { + removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream()); + } + + @Override + public void once(InviteSessionType type, String deviceId, String channelId, String stream, InviteErrorCallback callback) { + String key = buildKey(type, deviceId, channelId, stream); + List> callbacks = inviteErrorCallbackMap.get(key); + if (callbacks == null) { + callbacks = new CopyOnWriteArrayList<>(); + inviteErrorCallbackMap.put(key, callbacks); + } + callbacks.add(callback); + + } + + @Override + public void call(InviteSessionType type, String deviceId, String channelId, String stream, int code, String msg, Object data) { + String key = buildKey(type, deviceId, channelId, stream); + List> callbacks = inviteErrorCallbackMap.get(key); + if (callbacks == null) { + return; + } + for (InviteErrorCallback callback : callbacks) { + callback.run(code, msg, data); + } + inviteErrorCallbackMap.remove(key); + } + + private String buildKey(InviteSessionType type, String deviceId, String channelId, String stream) { + String key = type + "_" + deviceId + "_" + channelId; + // 如果ssrc未null那么可以实现一个通道只能一次操作,ssrc不为null则可以支持一个通道多次invite + if (stream != null) { + key += ("_" + stream); + } + return key; + } + + + @Override + public void clearInviteInfo(String deviceId) { + removeInviteInfo(null, deviceId, null, null); + } +} 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 index c38c3134a..cdec462ff 100644 --- 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,13 +3,14 @@ package com.genersoft.iot.vmp.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +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.SipConfig; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.gb28181.event.EventPublisher; -import com.genersoft.iot.vmp.gb28181.session.SsrcConfig; +import com.genersoft.iot.vmp.gb28181.session.SSRCFactory; import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; @@ -56,6 +57,9 @@ public class MediaServerServiceImpl implements IMediaServerService { @Autowired private SipConfig sipConfig; + @Autowired + private SSRCFactory ssrcFactory; + @Value("${server.ssl.enabled:false}") private boolean sslEnabled; @@ -96,6 +100,9 @@ public class MediaServerServiceImpl implements IMediaServerService { @Autowired private RedisTemplate redisTemplate; + + + /** * 初始化 */ @@ -107,10 +114,8 @@ public class MediaServerServiceImpl implements IMediaServerService { continue; } // 更新 - if (mediaServerItem.getSsrcConfig() == null) { - SsrcConfig ssrcConfig = new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getDomain()); - mediaServerItem.setSsrcConfig(ssrcConfig); - redisTemplate.opsForValue().set(VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItem.getId(), mediaServerItem); + if (ssrcFactory.hasMediaServerSSRC(mediaServerItem.getId())) { + ssrcFactory.initMediaServerSSRC(mediaServerItem.getId(), null); } // 查询redis是否存在此mediaServer String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItem.getId(); @@ -122,56 +127,44 @@ public class MediaServerServiceImpl implements IMediaServerService { } } - @Override - public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, boolean ssrcCheck, boolean isPlayback) { - return openRTPServer(mediaServerItem, streamId, null, ssrcCheck,isPlayback); - } @Override public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck, - boolean isPlayback, Integer port, Boolean onlyAuto) { + 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; } // 获取mediaServer可用的ssrc - String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItem.getId(); - - SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig(); - if (ssrcConfig == null) { - logger.info("media server [ {} ] ssrcConfig is null", mediaServerItem.getId()); - return null; + String ssrc; + if (presetSsrc != null) { + ssrc = presetSsrc; }else { - String ssrc; - if (presetSsrc != null) { - ssrc = presetSsrc; + if (isPlayback) { + ssrc = ssrcFactory.getPlayBackSsrc(mediaServerItem.getId()); }else { - if (isPlayback) { - ssrc = ssrcConfig.getPlayBackSsrc(); - }else { - ssrc = ssrcConfig.getPlaySsrc(); - } + ssrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId()); } - - if (streamId == null) { - streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase(); - } - int rtpServerPort; - if (mediaServerItem.isRtpEnable()) { - rtpServerPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(ssrc):0, port, onlyAuto); - } else { - rtpServerPort = mediaServerItem.getRtpProxyPort(); - } - redisTemplate.opsForValue().set(key, mediaServerItem); - return new SSRCInfo(rtpServerPort, ssrc, streamId); } + + if (streamId == null) { + streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase(); + } + int rtpServerPort; + if (mediaServerItem.isRtpEnable()) { + rtpServerPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck?Integer.parseInt(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) { + public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto) { return openRTPServer(mediaServerItem, streamId, ssrc, ssrcCheck, isPlayback, null, null); } + @Override public void closeRTPServer(MediaServerItem mediaServerItem, String streamId) { if (mediaServerItem == null) { @@ -180,23 +173,33 @@ public class MediaServerServiceImpl implements IMediaServerService { zlmrtpServerFactory.closeRtpServer(mediaServerItem, streamId); } + @Override + public void closeRTPServer(MediaServerItem mediaServerItem, String streamId, CommonCallback callback) { + if (mediaServerItem == null) { + callback.run(false); + return; + } + zlmrtpServerFactory.closeRtpServer(mediaServerItem, streamId, callback); + } + @Override public void closeRTPServer(String mediaServerId, String streamId) { MediaServerItem mediaServerItem = this.getOne(mediaServerId); closeRTPServer(mediaServerItem, streamId); } + @Override + public Boolean updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc) { + return zlmrtpServerFactory.updateRtpServerSSRC(mediaServerItem, streamId, ssrc); + } + @Override public void releaseSsrc(String mediaServerItemId, String ssrc) { MediaServerItem mediaServerItem = getOne(mediaServerItemId); if (mediaServerItem == null || ssrc == null) { return; } - SsrcConfig ssrcConfig = mediaServerItem.getSsrcConfig(); - ssrcConfig.releaseSsrc(ssrc); - mediaServerItem.setSsrcConfig(ssrcConfig); - String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItem.getId(); - redisTemplate.opsForValue().set(key, mediaServerItem); + ssrcFactory.releaseSsrc(mediaServerItemId, ssrc); } /** @@ -204,8 +207,7 @@ public class MediaServerServiceImpl implements IMediaServerService { */ @Override public void clearRTPServer(MediaServerItem mediaServerItem) { - mediaServerItem.setSsrcConfig(new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getDomain())); - redisTemplate.opsForZSet().add(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId(), mediaServerItem.getId(), 0); + ssrcFactory.reset(mediaServerItem.getId()); } @@ -215,16 +217,8 @@ public class MediaServerServiceImpl implements IMediaServerService { mediaServerMapper.update(mediaSerItem); MediaServerItem mediaServerItemInRedis = getOne(mediaSerItem.getId()); MediaServerItem mediaServerItemInDataBase = mediaServerMapper.queryOne(mediaSerItem.getId()); - if (mediaServerItemInRedis != null && mediaServerItemInRedis.getSsrcConfig() != null) { - mediaServerItemInDataBase.setSsrcConfig(mediaServerItemInRedis.getSsrcConfig()); - }else { - mediaServerItemInDataBase.setSsrcConfig( - new SsrcConfig( - mediaServerItemInDataBase.getId(), - null, - sipConfig.getDomain() - ) - ); + if (mediaServerItemInRedis == null || ssrcFactory.hasMediaServerSSRC(mediaSerItem.getId())) { + ssrcFactory.initMediaServerSSRC(mediaServerItemInDataBase.getId(),null); } String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItemInDataBase.getId(); redisTemplate.opsForValue().set(key, mediaServerItemInDataBase); @@ -406,14 +400,8 @@ public class MediaServerServiceImpl implements IMediaServerService { } mediaServerMapper.update(serverItem); String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + zlmServerConfig.getGeneralMediaServerId(); - if (redisTemplate.opsForValue().get(key) == null) { - SsrcConfig ssrcConfig = new SsrcConfig(zlmServerConfig.getGeneralMediaServerId(), null, sipConfig.getDomain()); - serverItem.setSsrcConfig(ssrcConfig); - }else { - MediaServerItem mediaServerItemInRedis = JsonUtil.redisJsonToObject(redisTemplate, key, MediaServerItem.class); - if (Objects.nonNull(mediaServerItemInRedis)) { - serverItem.setSsrcConfig(mediaServerItemInRedis.getSsrcConfig()); - } + if (ssrcFactory.hasMediaServerSSRC(serverItem.getId())) { + ssrcFactory.initMediaServerSSRC(zlmServerConfig.getGeneralMediaServerId(), null); } redisTemplate.opsForValue().set(key, serverItem); resetOnlineServerItem(serverItem); @@ -711,8 +699,7 @@ public class MediaServerServiceImpl implements IMediaServerService { } // zlm连接重试 logger.warn("[更新ZLM 保活信息]尝试链接zml id {}", mediaServerId); - SsrcConfig ssrcConfig = new SsrcConfig(mediaServerItem.getId(), null, sipConfig.getDomain()); - mediaServerItem.setSsrcConfig(ssrcConfig); + ssrcFactory.initMediaServerSSRC(mediaServerItem.getId(), null); String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItem.getId(); redisTemplate.opsForValue().set(key, mediaServerItem); clearRTPServer(mediaServerItem); @@ -761,4 +748,5 @@ public class MediaServerServiceImpl implements IMediaServerService { result.setGbSend(redisCatchStorage.getGbSendCount(mediaServerItem.getId())); return result; } + } 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 index ffe272bed..b5d0d7130 100644 --- 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,12 +1,14 @@ package com.genersoft.iot.vmp.service.impl; import com.alibaba.fastjson2.JSONObject; -import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.common.InviteInfo; +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.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.ZLMRTPServerFactory; @@ -14,6 +16,7 @@ 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.service.IInviteStreamService; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IPlatformService; import com.genersoft.iot.vmp.service.IPlayService; @@ -65,6 +68,9 @@ public class PlatformServiceImpl implements IPlatformService { @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private SSRCFactory ssrcFactory; + @Autowired private IMediaServerService mediaServerService; @@ -96,6 +102,8 @@ public class PlatformServiceImpl implements IPlatformService { @Autowired private IPlayService playService; + @Autowired + private IInviteStreamService inviteStreamService; @Override @@ -198,6 +206,7 @@ public class PlatformServiceImpl implements IPlatformService { // 保存时启用就发送注册 // 注册成功时由程序直接调用了online方法 try { + logger.info("[国标级联] 平台注册 {}", parentPlatform.getDeviceGBId()); commanderForPlatform.register(parentPlatform, eventResult -> { logger.info("[国标级联] {},添加向上级注册失败,请确定上级平台可用时重新保存", parentPlatform.getServerGBId()); }, null); @@ -349,6 +358,7 @@ public class PlatformServiceImpl implements IPlatformService { List sendRtpItems = redisCatchStorage.querySendRTPServer(platformId); if (sendRtpItems != null && sendRtpItems.size() > 0) { for (SendRtpItem sendRtpItem : sendRtpItems) { + ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc()); redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(), null, null); MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId()); Map param = new HashMap<>(3); @@ -420,20 +430,22 @@ public class PlatformServiceImpl implements IPlatformService { logger.info("[国标级联] 语音喊话未找到可用的zlm. platform: {}", platform.getServerGBId()); return; } - StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(platform.getServerGBId(), channelId); - if (streamInfo != null) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, platform.getServerGBId(), channelId); + + + if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { // 如果zlm不存在这个流,则删除数据即可 - MediaServerItem mediaServerItemForStreamInfo = mediaServerService.getOne(streamInfo.getMediaServerId()); + MediaServerItem mediaServerItemForStreamInfo = mediaServerService.getOne(inviteInfo.getStreamInfo().getMediaServerId()); if (mediaServerItemForStreamInfo != null) { - Boolean ready = zlmrtpServerFactory.isStreamReady(mediaServerItemForStreamInfo, streamInfo.getApp(), streamInfo.getStream()); + Boolean ready = zlmrtpServerFactory.isStreamReady(mediaServerItemForStreamInfo, inviteInfo.getStreamInfo().getApp(), inviteInfo.getStreamInfo().getStream()); if (!ready) { // 错误存在于redis中的数据 - redisCatchStorage.stopPlay(streamInfo); + inviteStreamService.removeInviteInfo(inviteInfo); }else { // 流确实尚在推流,直接回调结果 JSONObject json = new JSONObject(); - json.put("app", streamInfo.getApp()); - json.put("stream", streamInfo.getStream()); + json.put("app", inviteInfo.getStreamInfo().getApp()); + json.put("stream", inviteInfo.getStreamInfo().getStream()); hookEvent.response(mediaServerItemForStreamInfo, json); return; } @@ -449,7 +461,11 @@ public class PlatformServiceImpl implements IPlatformService { SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, ssrcCheck, false, null, true); if (ssrcInfo == null || ssrcInfo.getPort() < 0) { logger.info("[国标级联] 发起语音喊话 开启端口监听失败, platform: {}, channel: {}", platform.getServerGBId(), channelId); - errorEvent.response(new SipSubscribe.EventResult(-1, "端口监听失败")); + 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校验:{}", @@ -458,7 +474,8 @@ public class PlatformServiceImpl implements IPlatformService { String timeOutTaskKey = UUID.randomUUID().toString(); dynamicTask.startDelay(timeOutTaskKey, () -> { // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况 - if (redisCatchStorage.queryPlayByDevice(platform.getServerGBId(), channelId) == null) { + 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 { @@ -501,7 +518,7 @@ public class PlatformServiceImpl implements IPlatformService { if (!mediaServerItem.isRtpEnable()) { logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); - if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { + if (!ssrcFactory.checkSsrc(mediaServerItem.getId(), ssrcInResponse)) { // ssrc 不可用 // 释放ssrc mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); 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 ad79c200f..be9d3b1de 100644 --- 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,8 +1,10 @@ package com.genersoft.iot.vmp.service.impl; -import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; 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.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.DynamicTask; import com.genersoft.iot.vmp.conf.SipConfig; @@ -13,9 +15,9 @@ 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.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.utils.SipUtils; @@ -27,11 +29,11 @@ import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRtpServerTimeout; import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange; 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.IMediaService; -import com.genersoft.iot.vmp.service.IPlayService; -import com.genersoft.iot.vmp.service.bean.*; +import com.genersoft.iot.vmp.service.*; +import com.genersoft.iot.vmp.service.bean.InviteErrorCallback; +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.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; @@ -39,7 +41,6 @@ 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.bean.WVPResult; import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent; import gov.nist.javax.sip.message.SIPResponse; import org.slf4j.Logger; @@ -51,6 +52,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.util.ObjectUtils; +import javax.sdp.*; import javax.sip.InvalidArgumentException; import javax.sip.ResponseEvent; import javax.sip.SipException; @@ -87,6 +89,9 @@ public class PlayServiceImpl implements IPlayService { @Autowired private ZLMRTPServerFactory zlmrtpServerFactory; + @Autowired + private IInviteStreamService inviteStreamService; + @Autowired private DeferredResultHolder resultHolder; @@ -131,126 +136,82 @@ public class PlayServiceImpl implements IPlayService { @Autowired private ZlmHttpHookSubscribe hookSubscribe; + @Autowired + private SSRCFactory ssrcFactory; + @Autowired private RedisTemplate redisTemplate; @Override - public void play(MediaServerItem mediaServerItem, String deviceId, String channelId, - ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent, - Runnable timeoutCallback) { + public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, InviteErrorCallback callback) { if (mediaServerItem == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm"); } - String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; - - RequestMessage msg = new RequestMessage(); - msg.setKey(key); Device device = redisCatchStorage.getDevice(deviceId); - StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId); + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); - if (streamInfo != null) { - String streamId = streamInfo.getStream(); - if (streamId == null) { - WVPResult wvpResult = new WVPResult(); - wvpResult.setCode(ErrorCode.ERROR100.getCode()); - wvpResult.setMsg("点播失败, redis缓存streamId等于null"); - msg.setData(wvpResult); - resultHolder.invokeAllResult(msg); - return; - } - String mediaServerId = streamInfo.getMediaServerId(); - MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); + if (inviteInfo != null ) { + if (inviteInfo.getStreamInfo() == null) { + // 点播发起了但是尚未成功, 仅注册回调等待结果即可 + inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback); + return inviteInfo.getSsrcInfo(); + }else { + StreamInfo streamInfo = inviteInfo.getStreamInfo(); + String streamId = streamInfo.getStream(); + if (streamId == null) { + callback.run(InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null); + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, + InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), + "点播失败, redis缓存streamId等于null", + null); + return inviteInfo.getSsrcInfo(); + } + String mediaServerId = streamInfo.getMediaServerId(); + MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); - JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId); - if (rtpInfo.getInteger("code") == 0) { - if (rtpInfo.getBoolean("exist")) { - int localPort = rtpInfo.getInteger("local_port"); - if (localPort == 0) { - logger.warn("[点播],点播时发现rtpServerC存在,但是尚未开始推流"); - // 此时说明rtpServer已经创建但是流还没有推上来 - WVPResult wvpResult = new WVPResult(); - wvpResult.setCode(ErrorCode.ERROR100.getCode()); - wvpResult.setMsg("点播已经在进行中,请稍候重试"); - msg.setData(wvpResult); - - resultHolder.invokeAllResult(msg); - return; - } else { - WVPResult wvpResult = new WVPResult(); - wvpResult.setCode(ErrorCode.SUCCESS.getCode()); - wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); - wvpResult.setData(streamInfo); - msg.setData(wvpResult); - resultHolder.invokeAllResult(msg); - if (hookEvent != null) { - hookEvent.response(mediaServerItem, JSON.parseObject(JSON.toJSONString(streamInfo))); - } - } - - } else { - redisCatchStorage.stopPlay(streamInfo); + Boolean ready = zlmrtpServerFactory.isStreamReady(mediaInfo, "rtp", streamId); + if (ready != null && ready) { + callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, + InviteErrorCode.SUCCESS.getCode(), + InviteErrorCode.SUCCESS.getMsg(), + streamInfo); + return inviteInfo.getSsrcInfo(); + }else { + // 点播发起了但是尚未成功, 仅注册回调等待结果即可 + inviteStreamService.once(InviteSessionType.PLAY, deviceId, channelId, null, callback); storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); - streamInfo = null; + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); } - } else { - //zlm连接失败 - redisCatchStorage.stopPlay(streamInfo); - storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); - streamInfo = null; } } - if (streamInfo == null) { - String streamId = null; - if (mediaServerItem.isRtpEnable()) { - streamId = String.format("%s_%s", device.getDeviceId(), channelId); - } - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false); - if (ssrcInfo == null) { - WVPResult wvpResult = new WVPResult(); - wvpResult.setCode(ErrorCode.ERROR100.getCode()); - wvpResult.setMsg("开启收流失败"); - msg.setData(wvpResult); - resultHolder.invokeAllResult(msg); - return; - } - play(mediaServerItem, ssrcInfo, device, channelId, (mediaServerItemInUse, response) -> { - if (hookEvent != null) { - hookEvent.response(mediaServerItem, response); - } - }, event -> { - // sip error错误 - WVPResult wvpResult = new WVPResult(); - wvpResult.setCode(ErrorCode.ERROR100.getCode()); - wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg)); - msg.setData(wvpResult); - resultHolder.invokeAllResult(msg); - if (errorEvent != null) { - errorEvent.response(event); - } - }, (code, msgStr) -> { - // invite点播超时 - WVPResult wvpResult = new WVPResult(); - wvpResult.setCode(ErrorCode.ERROR100.getCode()); - if (code == 0) { - wvpResult.setMsg("点播超时,请稍候重试"); - } else if (code == 1) { - wvpResult.setMsg("收流超时,请稍候重试"); - } - msg.setData(wvpResult); - // 回复之前所有的点播请求 - resultHolder.invokeAllResult(msg); - }); + String streamId = null; + if (mediaServerItem.isRtpEnable()) { + streamId = String.format("%s_%s", device.getDeviceId(), channelId); } + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, 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, + InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), + InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), + null); + return null; + } + // TODO 记录点播的状态 + play(mediaServerItem, ssrcInfo, device, channelId, 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 = mediaServerItem.getSsrcConfig().getPlaySsrc(); + String playSsrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId()); + if (playSsrc == null) { audioEvent.call("ssrc已经用尽"); return; @@ -353,7 +314,7 @@ public class PlayServiceImpl implements IPlayService { streamSession.put(device.getDeviceId(), channelId, "talk", sendRtpItem.getStream(), sendRtpItem.getSsrc(), sendRtpItem.getMediaServerId(), - response, VideoStreamSessionManager.SessionType.talk); + response, InviteSessionType.TALK); } else { logger.error("[语音对讲]收到的消息错误,response不是SIPResponse"); } @@ -378,7 +339,9 @@ public class PlayServiceImpl implements IPlayService { mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc()); streamSession.remove(device.getDeviceId(), channelId, sendRtpItem.getStream()); - SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null)); + SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(); + eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent; + eventResult.statusCode = -1; eventResult.msg = "命令发送失败"; errorEvent.response(eventResult); } @@ -390,24 +353,62 @@ public class PlayServiceImpl implements IPlayService { @Override public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, - ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent, - InviteTimeOutCallback timeoutCallback) { + InviteErrorCallback callback) { - logger.info("[点播开始] deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); + if (mediaServerItem == null || ssrcInfo == null) { + callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), + InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), + null); + return; + } + logger.info("[点播开始] deviceId: {}, channelId: {},收流端口:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); + + //端口获取失败的ssrcInfo 没有必要发送点播指令 + if (ssrcInfo.getPort() <= 0) { + logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo); + // 释放ssrc + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + + callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null); + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, + InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "点播端口分配异常", null); + return; + } + + // 初始化redis中的invite消息状态 + InviteInfo inviteInfo = InviteInfo.getinviteInfo(device.getDeviceId(), channelId, ssrcInfo.getStream(), ssrcInfo, + mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAY, + InviteSessionStatus.ready); + inviteStreamService.updateInviteInfo(inviteInfo); // 超时处理 String timeOutTaskKey = UUID.randomUUID().toString(); dynamicTask.startDelay(timeOutTaskKey, () -> { // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况 - if (redisCatchStorage.queryPlayByDevice(device.getDeviceId(), channelId) == null) { + InviteInfo inviteInfoForTimeOut = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); + if (inviteInfoForTimeOut == null || inviteInfoForTimeOut.getStreamInfo() == null) { logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, 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, + InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null); + + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); try { cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), 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(device.getDeviceId(), channelId, ssrcInfo.getStream()); @@ -418,28 +419,26 @@ public class PlayServiceImpl implements IPlayService { } } }, userSetting.getPlayTimeout()); - //端口获取失败的ssrcInfo 没有必要发送点播指令 - if (ssrcInfo.getPort() <= 0) { - logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo); - dynamicTask.stop(timeOutTaskKey); - // 释放ssrc - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); - streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); - RequestMessage msg = new RequestMessage(); - msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + device.getDeviceId() + channelId); - msg.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), "点播端口分配异常")); - resultHolder.invokeAllResult(msg); - return; - } try { cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> { logger.info("收到订阅消息: " + response.toJSONString()); dynamicTask.stop(timeOutTaskKey); - // hook响应 - onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId); - hookEvent.response(mediaServerItemInuse, response); + StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, response, 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); logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId); String streamUrl; if (mediaServerItemInuse.getRtspPort() != 0) { @@ -454,6 +453,8 @@ public class PlayServiceImpl implements IPlayService { zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName); }, (event) -> { + inviteInfo.setStatus(InviteSessionStatus.ok); + ResponseEvent responseEvent = (ResponseEvent) event.event; String contentString = new String(responseEvent.getResponse().getRawContent()); // 获取ssrc @@ -464,44 +465,124 @@ public class PlayServiceImpl implements IPlayService { 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); - - if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { + if (!ssrcFactory.checkSsrc(mediaServerItem.getId(),ssrcInResponse)) { // ssrc 不可用 + logger.info("[点播消息] SSRC修正时发现ssrc不可使用 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); // 释放ssrc - mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); + ssrcFactory.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); - event.msg = "下级自定义了ssrc,但是此ssrc不可用"; - event.statusCode = 400; - errorEvent.response(event); + + callback.run(InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getCode(), + InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getMsg(), null); + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, + InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getCode(), + InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getMsg(), null); + return; } - - // 单端口模式streamId也有变化,需要重新设置监听 + // 单端口模式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()); + String stream = String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase(); + hookSubscribe.getContent().put("stream", stream); + inviteInfo.setStream(stream); subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> { logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString()); dynamicTask.stop(timeOutTaskKey); // hook响应 - onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId); - hookEvent.response(mediaServerItemInUse, response); + StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInUse, response, 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(), null); + inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null, + InviteErrorCode.SUCCESS.getCode(), + InviteErrorCode.SUCCESS.getMsg(), + streamInfo); }); + return; } - // 关闭rtp server - mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); - // 重新开启ssrc server - mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false, ssrcInfo.getPort(), false); + // 更新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 { + ssrcInfo.setSsrc(ssrcInResponse); + inviteInfo.setSsrcInfo(ssrcInfo); + inviteInfo.setStream(ssrcInfo.getStream()); + } + }else { + logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正"); } } + inviteStreamService.updateInviteInfo(inviteInfo); }, (event) -> { dynamicTask.stop(timeOutTaskKey); mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); @@ -509,7 +590,14 @@ public class PlayServiceImpl implements IPlayService { mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); - errorEvent.response(event); + + 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, + InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(), + String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null); + + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); }); } catch (InvalidArgumentException | SipException | ParseException e) { @@ -520,63 +608,58 @@ public class PlayServiceImpl implements IPlayService { mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); - SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null)); - eventResult.msg = "命令发送失败"; - errorEvent.response(eventResult); + + 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, + InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getCode(), + InviteErrorCode.ERROR_FOR_SIP_SENDING_FAILED.getMsg(), null); + + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, device.getDeviceId(), channelId); } } @Override - public void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId) { + public StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId) { StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId); - RequestMessage msg = new RequestMessage(); - msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId); if (streamInfo != null) { DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); if (deviceChannel != null) { deviceChannel.setStreamId(streamInfo.getStream()); storager.startPlay(deviceId, channelId, streamInfo.getStream()); } - redisCatchStorage.startPlay(streamInfo); - - WVPResult wvpResult = new WVPResult(); - wvpResult.setCode(ErrorCode.SUCCESS.getCode()); - wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); - wvpResult.setData(streamInfo); - - msg.setData(wvpResult); - resultHolder.invokeAllResult(msg); - - } else { - logger.warn("设备预览API调用失败!"); - msg.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), "设备预览API调用失败!")); - resultHolder.invokeAllResult(msg); + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); + if (inviteInfo != null) { + inviteInfo.setStatus(InviteSessionStatus.ok); + inviteInfo.setStreamInfo(streamInfo); + inviteStreamService.updateInviteInfo(inviteInfo); + } } + return streamInfo; + } - private void onPublishHandlerForPlayback(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId, PlayBackCallback playBackCallback) { + private StreamInfo onPublishHandlerForPlayback(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId, String startTime, String endTime) { StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId); - PlayBackResult playBackResult = new PlayBackResult<>(); if (streamInfo != null) { + streamInfo.setStartTime(startTime); + streamInfo.setEndTime(endTime); DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId); if (deviceChannel != null) { deviceChannel.setStreamId(streamInfo.getStream()); storager.startPlay(deviceId, channelId, streamInfo.getStream()); } - redisCatchStorage.startPlay(streamInfo); + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAYBACK, deviceId, channelId); + if (inviteInfo != null) { + inviteInfo.setStatus(InviteSessionStatus.ok); + inviteInfo.setStreamInfo(streamInfo); + inviteStreamService.updateInviteInfo(inviteInfo); + } - playBackResult.setCode(ErrorCode.SUCCESS.getCode()); - playBackResult.setMsg(ErrorCode.SUCCESS.getMsg()); - playBackResult.setData(streamInfo); - playBackCallback.call(playBackResult); - } else { - logger.warn("录像回放调用失败!"); - playBackResult.setCode(ErrorCode.ERROR100.getCode()); - playBackResult.setMsg("录像回放调用失败!"); - playBackCallback.call(playBackResult); } + return streamInfo; } @Override @@ -615,24 +698,24 @@ public class PlayServiceImpl implements IPlayService { @Override public void playBack(String deviceId, String channelId, String startTime, - String endTime, InviteStreamCallback inviteStreamCallback, - PlayBackCallback callback) { + String endTime, InviteErrorCallback callback) { Device device = storager.queryVideoDevice(deviceId); if (device == null) { return; } MediaServerItem newMediaServerItem = getNewMediaServerItem(device); - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, device.isSsrcCheck(), true); - - playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, inviteStreamCallback, callback); + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, 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, InviteStreamCallback infoCallBack, - PlayBackCallback playBackCallback) { + String deviceId, String channelId, String startTime, + String endTime, InviteErrorCallback callback) { if (mediaServerItem == null || ssrcInfo == null) { + callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), + InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), + null); return; } @@ -640,115 +723,176 @@ public class PlayServiceImpl implements IPlayService { if (device == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备: " + deviceId + "不存在"); } - - PlayBackResult playBackResult = new PlayBackResult<>(); + logger.info("[录像回放] deviceId: {}, channelId: {}, 开始时间: {}, 结束时间: {}, 收流端口:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", + device.getDeviceId(), channelId, startTime, endTime, ssrcInfo.getPort(), device.getStreamMode(), + ssrcInfo.getSsrc(), device.isSsrcCheck()); + // 初始化redis中的invite消息状态 + InviteInfo inviteInfo = InviteInfo.getinviteInfo(device.getDeviceId(), channelId, ssrcInfo.getStream(), ssrcInfo, + mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.PLAYBACK, + InviteSessionStatus.ready); + inviteStreamService.updateInviteInfo(inviteInfo); String playBackTimeOutTaskKey = UUID.randomUUID().toString(); dynamicTask.startDelay(playBackTimeOutTaskKey, () -> { - logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId)); - playBackResult.setCode(ErrorCode.ERROR100.getCode()); - playBackResult.setMsg("回放超时"); + logger.warn("[录像回放] 超时,deviceId:{} ,channelId:{}", deviceId, channelId); + inviteStreamService.removeInviteInfo(inviteInfo); + callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getMsg(), null); try { cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null); } catch (InvalidArgumentException | ParseException | SipException e) { - logger.error("[录像流]回放超时 发送BYE失败 {}", e.getMessage()); + logger.error("[录像回放] 超时 发送BYE失败 {}", e.getMessage()); } catch (SsrcTransactionNotFoundException e) { // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); streamSession.remove(deviceId, channelId, ssrcInfo.getStream()); } - // 回复之前所有的点播请求 - playBackCallback.call(playBackResult); }, userSetting.getPlayTimeout()); SipSubscribe.Event errorEvent = event -> { + logger.info("[录像回放] 失败,{} {}", event.statusCode, event.msg); dynamicTask.stop(playBackTimeOutTaskKey); - playBackResult.setCode(ErrorCode.ERROR100.getCode()); - playBackResult.setMsg(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)); - playBackResult.setEvent(event); - playBackCallback.call(playBackResult); + callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_ERROR.getCode(), + String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg), null); + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); + mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + inviteStreamService.removeInviteInfo(inviteInfo); }; - InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> { - logger.info("收到回放订阅消息: " + inviteStreamInfo.getResponse().toJSONString()); + ZlmHttpHookSubscribe.Event hookEvent = (mediaServerItemInuse, jsonObject) -> { + logger.info("收到回放订阅消息: " + jsonObject); dynamicTask.stop(playBackTimeOutTaskKey); - StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId); + StreamInfo streamInfo = onPublishHandlerForPlayback(mediaServerItemInuse, jsonObject, deviceId, channelId, startTime, endTime); if (streamInfo == null) { logger.warn("设备回放API调用失败!"); - playBackResult.setCode(ErrorCode.ERROR100.getCode()); - playBackResult.setMsg("设备回放API调用失败!"); - playBackCallback.call(playBackResult); + callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(), + InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null); return; } - redisCatchStorage.startPlayback(streamInfo, inviteStreamInfo.getCallId()); - playBackResult.setCode(ErrorCode.SUCCESS.getCode()); - playBackResult.setMsg(ErrorCode.SUCCESS.getMsg()); - playBackResult.setData(streamInfo); - playBackResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem()); - playBackResult.setResponse(inviteStreamInfo.getResponse()); - playBackCallback.call(playBackResult); + callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo); + logger.info("[录像回放] 成功 deviceId: {}, channelId: {}, 开始时间: {}, 结束时间: {}", device.getDeviceId(), channelId, startTime, endTime); }; try { - cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack, + cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, hookEvent, eventResult -> { - if (eventResult.type == SipSubscribe.EventResultType.response) { - 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)) { - return; - } - logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); - if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { - logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); + 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(); - if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { - // ssrc 不可用 + 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()); - eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用"; - eventResult.statusCode = 400; - errorEvent.response(eventResult); - return; + + 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); + + if (!ssrcFactory.checkSsrc(mediaServerItem.getId(),ssrcInResponse)) { + // ssrc 不可用 + logger.info("[录像回放] SSRC修正时发现ssrc不可使用 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); + // 释放ssrc + dynamicTask.stop(playBackTimeOutTaskKey); + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + callback.run(InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getCode(), + InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getMsg(), null); + return; + } + + // 单端口模式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); + inviteInfo.setStream(stream); + subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> { + logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString()); + dynamicTask.stop(playBackTimeOutTaskKey); + // hook响应 + hookEvent.response(mediaServerItemInUse, response); + }); + } + // 更新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()); + } - // 单端口模式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, (MediaServerItem mediaServerItemInUse, JSONObject response) -> { - logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString()); - dynamicTask.stop(playBackTimeOutTaskKey); - // hook响应 - onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, playBackCallback); - hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream())); - }); - } - // 关闭rtp server - mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); - // 重新开启ssrc server - mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort(), false); + 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 { + ssrcInfo.setSsrc(ssrcInResponse); + inviteInfo.setSsrcInfo(ssrcInfo); + inviteInfo.setStream(ssrcInfo.getStream()); } + }else { + logger.info("[点播消息] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正"); } } - + inviteStreamService.updateInviteInfo(inviteInfo); }, errorEvent); } catch (InvalidArgumentException | SipException | ParseException e) { logger.error("[命令发送失败] 回放: {}", e.getMessage()); - SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null)); + SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(); + eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent; + eventResult.statusCode = -1; eventResult.msg = "命令发送失败"; errorEvent.response(eventResult); } @@ -756,43 +900,50 @@ public class PlayServiceImpl implements IPlayService { @Override - public void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback playBackCallback) { + public void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteErrorCallback callback) { Device device = storager.queryVideoDevice(deviceId); if (device == null) { return; } MediaServerItem newMediaServerItem = getNewMediaServerItemHasAssist(device); if (newMediaServerItem == null) { - PlayBackResult downloadResult = new PlayBackResult<>(); - downloadResult.setCode(ErrorCode.ERROR100.getCode()); - downloadResult.setMsg("未找到assist服务"); - playBackCallback.call(downloadResult); + callback.run(InviteErrorCode.ERROR_FOR_ASSIST_NOT_READY.getCode(), + InviteErrorCode.ERROR_FOR_ASSIST_NOT_READY.getMsg(), + null); return; } - SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, device.isSsrcCheck(), true); - - download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed, infoCallBack, playBackCallback); + SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(), true, 0, false,false, device.getStreamModeForParam()); + download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed, callback); } @Override - public void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack) { + public void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteErrorCallback callback) { if (mediaServerItem == null || ssrcInfo == null) { + callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), + InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(), + null); return; } - Device device = storager.queryVideoDevice(deviceId); if (device == null) { - throw new ControllerException(ErrorCode.ERROR400.getCode(), "设备:" + deviceId + "不存在"); + callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(), + "设备:" + deviceId + "不存在", + null); + return; } - PlayBackResult downloadResult = new PlayBackResult<>(); - + logger.info("[录像下载] deviceId: {}, channelId: {}, 下载速度:{}, 收流端口:{}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, downloadSpeed, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck()); + // 初始化redis中的invite消息状态 + InviteInfo inviteInfo = InviteInfo.getinviteInfo(device.getDeviceId(), channelId, ssrcInfo.getStream(), ssrcInfo, + mediaServerItem.getSdpIp(), ssrcInfo.getPort(), device.getStreamMode(), InviteSessionType.DOWNLOAD, + InviteSessionStatus.ready); + inviteStreamService.updateInviteInfo(inviteInfo); String downLoadTimeOutTaskKey = UUID.randomUUID().toString(); dynamicTask.startDelay(downLoadTimeOutTaskKey, () -> { logger.warn(String.format("录像下载请求超时,deviceId:%s ,channelId:%s", deviceId, channelId)); - downloadResult.setCode(ErrorCode.ERROR100.getCode()); - downloadResult.setMsg("录像下载请求超时"); - hookCallBack.call(downloadResult); + inviteStreamService.removeInviteInfo(inviteInfo); + callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode(), + InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getMsg(), null); // 点播超时回复BYE 同时释放ssrc以及此次点播的资源 try { @@ -808,85 +959,137 @@ public class PlayServiceImpl implements IPlayService { SipSubscribe.Event errorEvent = event -> { dynamicTask.stop(downLoadTimeOutTaskKey); - downloadResult.setCode(ErrorCode.ERROR100.getCode()); - downloadResult.setMsg(String.format("录像下载失败, 错误码: %s, %s", event.statusCode, event.msg)); - downloadResult.setEvent(event); - hookCallBack.call(downloadResult); + callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_TIMEOUT.getCode(), + String.format("录像下载失败, 错误码: %s, %s", event.statusCode, event.msg), null); streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + inviteStreamService.removeInviteInfo(inviteInfo); }; - InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> { - logger.info("收到订阅消息: " + inviteStreamInfo.getCallId()); + ZlmHttpHookSubscribe.Event hookEvent = (mediaServerItemInuse, jsonObject) -> { + logger.info("[录像下载]收到订阅消息: " + jsonObject); dynamicTask.stop(downLoadTimeOutTaskKey); - StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId); - streamInfo.setStartTime(startTime); - streamInfo.setEndTime(endTime); - redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId()); - downloadResult.setCode(ErrorCode.SUCCESS.getCode()); - downloadResult.setMsg(ErrorCode.SUCCESS.getMsg()); - downloadResult.setData(streamInfo); - downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem()); - downloadResult.setResponse(inviteStreamInfo.getResponse()); - hookCallBack.call(downloadResult); + StreamInfo streamInfo = onPublishHandlerForDownload(mediaServerItemInuse, jsonObject, deviceId, channelId, startTime, endTime); + if (streamInfo == null) { + logger.warn("[录像下载] 获取流地址信息失败"); + callback.run(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); + logger.info("[录像下载] 调用成功 deviceId: {}, channelId: {}, 开始时间: {}, 结束时间: {}", device.getDeviceId(), channelId, startTime, endTime); }; try { - cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack, - hookEvent, errorEvent, eventResult -> - { - if (eventResult.type == SipSubscribe.EventResultType.response) { - 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)) { - return; - } - logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse); - if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) { - logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse); + 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(); - if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) { - // ssrc 不可用 + 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()); + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); - eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用"; - eventResult.statusCode = 400; - errorEvent.response(eventResult); - return; + + 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); + + if (!ssrcFactory.checkSsrc(mediaServerItem.getId(),ssrcInResponse)) { + // ssrc 不可用 + // 释放ssrc + mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc()); + streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream()); + callback.run(InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getCode(), + InviteErrorCode.ERROR_FOR_SSRC_UNAVAILABLE.getMsg(), null); + return; + } + + // 单端口模式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, (MediaServerItem mediaServerItemInUse, JSONObject response) -> { + logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString()); + dynamicTask.stop(downLoadTimeOutTaskKey); + hookEvent.response(mediaServerItemInUse, response); + }); + } + + // 更新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()); } - // 单端口模式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, (MediaServerItem mediaServerItemInUse, JSONObject response) -> { - logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString()); - dynamicTask.stop(downLoadTimeOutTaskKey); - // hook响应 - onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, hookCallBack); - hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream())); - }); - } - // 关闭rtp server - mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream()); - // 重新开启ssrc server - mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort(), false); + 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 { + ssrcInfo.setSsrc(ssrcInResponse); + inviteInfo.setSsrcInfo(ssrcInfo); + inviteInfo.setStream(ssrcInfo.getStream()); } + }else { + logger.info("[录像下载] 收到invite 200, 下级自定义了ssrc, 但是当前模式无需修正"); } } - }); } catch (InvalidArgumentException | SipException | ParseException e) { logger.error("[命令发送失败] 录像下载: {}", e.getMessage()); - SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null)); + SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(); + eventResult.type = SipSubscribe.EventResultType.cmdSendFailEvent; + eventResult.statusCode = -1; eventResult.msg = "命令发送失败"; errorEvent.response(eventResult); } @@ -894,21 +1097,22 @@ public class PlayServiceImpl implements IPlayService { @Override public StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream) { - StreamInfo streamInfo = redisCatchStorage.queryDownload(deviceId, channelId, stream, null); - if (streamInfo != null) { - if (streamInfo.getProgress() == 1) { - return streamInfo; + InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, stream); + + if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { + if (inviteInfo.getStreamInfo().getProgress() == 1) { + return inviteInfo.getStreamInfo(); } // 获取当前已下载时长 - String mediaServerId = streamInfo.getMediaServerId(); + 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, streamInfo.getApp(), streamInfo.getStream(), null); + JSONObject jsonObject = assistRESTfulUtils.fileDuration(mediaServerItem, inviteInfo.getStreamInfo().getApp(), inviteInfo.getStreamInfo().getStream(), null); if (jsonObject == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "连接Assist服务失败"); } @@ -916,10 +1120,10 @@ public class PlayServiceImpl implements IPlayService { long duration = jsonObject.getLong("data"); if (duration == 0) { - streamInfo.setProgress(0); + inviteInfo.getStreamInfo().setProgress(0); } else { - String startTime = streamInfo.getStartTime(); - String endTime = streamInfo.getEndTime(); + 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); @@ -927,31 +1131,33 @@ public class PlayServiceImpl implements IPlayService { BigDecimal totalCount = new BigDecimal(end - start); BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP); double process = divide.doubleValue(); - streamInfo.setProgress(process); + inviteInfo.getStreamInfo().setProgress(process); } + inviteStreamService.updateInviteInfo(inviteInfo); } } + return inviteInfo.getStreamInfo(); + } + return null; + } + + private StreamInfo onPublishHandlerForDownload(MediaServerItem mediaServerItemInuse, JSONObject response, String deviceId, String channelId, String startTime, String endTime) { + StreamInfo streamInfo = onPublishHandler(mediaServerItemInuse, response, deviceId, channelId); + if (streamInfo != null) { + streamInfo.setProgress(0); + streamInfo.setStartTime(startTime); + streamInfo.setEndTime(endTime); + InviteInfo inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, streamInfo.getStream()); + if (inviteInfo != null) { + logger.info("[录像下载] 更新invite消息中的stream信息"); + inviteInfo.setStatus(InviteSessionStatus.ok); + inviteInfo.setStreamInfo(streamInfo); + inviteStreamService.updateInviteInfo(inviteInfo); + } } return streamInfo; } - @Override - public void onPublishHandlerForDownload(InviteStreamInfo inviteStreamInfo, String deviceId, String channelId, String uuid) { - RequestMessage msg = new RequestMessage(); - msg.setKey(DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId); - msg.setId(uuid); - StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId); - if (streamInfo != null) { - redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId()); - msg.setData(JSON.toJSONString(streamInfo)); - resultHolder.invokeResult(msg); - } else { - logger.warn("设备预览API调用失败!"); - msg.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), "设备预览API调用失败!")); - resultHolder.invokeResult(msg); - } - } - public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId) { String streamId = resonse.getString("stream"); @@ -1189,15 +1395,14 @@ public class PlayServiceImpl implements IPlayService { @Override public void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException { - String key = redisCatchStorage.queryPlaybackForKey(null, null, streamId, null); - StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null); - if (null == streamInfo) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); + if (null == inviteInfo || inviteInfo.getStreamInfo() == null) { logger.warn("streamId不存在!"); throw new ServiceException("streamId不存在"); } - streamInfo.setPause(true); - redisTemplate.opsForValue().set(key, streamInfo); - MediaServerItem mediaServerItem = mediaServerService.getOne(streamInfo.getMediaServerId()); + inviteInfo.getStreamInfo().setPause(true); + inviteStreamService.updateInviteInfo(inviteInfo); + MediaServerItem mediaServerItem = mediaServerService.getOne(inviteInfo.getStreamInfo().getMediaServerId()); if (null == mediaServerItem) { logger.warn("mediaServer 不存在!"); throw new ServiceException("mediaServer不存在"); @@ -1207,21 +1412,20 @@ public class PlayServiceImpl implements IPlayService { if (jsonObject == null || jsonObject.getInteger("code") != 0) { throw new ServiceException("暂停RTP接收失败"); } - Device device = storager.queryVideoDevice(streamInfo.getDeviceID()); - cmder.playPauseCmd(device, streamInfo); + Device device = storager.queryVideoDevice(inviteInfo.getDeviceId()); + cmder.playPauseCmd(device, inviteInfo.getStreamInfo()); } @Override public void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException { - String key = redisCatchStorage.queryPlaybackForKey(null, null, streamId, null); - StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null); - if (null == streamInfo) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); + if (null == inviteInfo || inviteInfo.getStreamInfo() == null) { logger.warn("streamId不存在!"); throw new ServiceException("streamId不存在"); } - streamInfo.setPause(false); - redisTemplate.opsForValue().set(key, streamInfo); - MediaServerItem mediaServerItem = mediaServerService.getOne(streamInfo.getMediaServerId()); + inviteInfo.getStreamInfo().setPause(false); + inviteStreamService.updateInviteInfo(inviteInfo); + MediaServerItem mediaServerItem = mediaServerService.getOne(inviteInfo.getStreamInfo().getMediaServerId()); if (null == mediaServerItem) { logger.warn("mediaServer 不存在!"); throw new ServiceException("mediaServer不存在"); @@ -1231,8 +1435,8 @@ public class PlayServiceImpl implements IPlayService { if (jsonObject == null || jsonObject.getInteger("code") != 0) { throw new ServiceException("继续RTP接收失败"); } - Device device = storager.queryVideoDevice(streamInfo.getDeviceID()); - cmder.playResumeCmd(device, streamInfo); + Device device = storager.queryVideoDevice(inviteInfo.getDeviceId()); + cmder.playResumeCmd(device, inviteInfo.getStreamInfo()); } @Override @@ -1308,7 +1512,7 @@ public class PlayServiceImpl implements IPlayService { 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")); } else { - logger.error("RTP推流失败: {}, 参数:{}", jsonObject.getString("msg"), JSON.toJSONString(param)); + 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()); @@ -1419,7 +1623,7 @@ public class PlayServiceImpl implements IPlayService { zlmrtpServerFactory.stopSendRtpStream(mediaServer, param); } - mediaServer.getSsrcConfig().releaseSsrc(sendRtpItem.getSsrc()); + ssrcFactory.releaseSsrc(mediaServerId, sendRtpItem.getSsrc()); SsrcTransaction ssrcTransaction = streamSession.getSsrcTransaction(device.getDeviceId(), channelId, null, sendRtpItem.getStream()); if (ssrcTransaction != null) { 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 index 868e861d8..7399b2a77 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisGbPlayMsgListener.java @@ -264,8 +264,8 @@ public class RedisGbPlayMsgListener implements MessageListener { return; } // 确定流是否在线 - boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, content.getApp(), content.getStream()); - if (streamReady) { + Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, content.getApp(), content.getStream()); + if (streamReady != null && streamReady) { logger.info("[回复推流信息] {}/{}", content.getApp(), content.getStream()); responseSendItem(mediaServerItem, content, toId, serial); }else { @@ -301,9 +301,6 @@ public class RedisGbPlayMsgListener implements MessageListener { String key = VideoManagerConstants.VM_MSG_STREAM_PUSH_REQUESTED; logger.info("[redis发送通知] 推流被请求 {}: {}/{}", key, messageForPushChannel.getApp(), messageForPushChannel.getStream()); redisTemplate.convertAndSend(key, JSON.toJSON(messageForPushChannel)); - -// redisCatchStorage.sendStreamPushRequestedMsg(messageForPushChannel); - } } 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 index d7e02f594..c8f4b2ae8 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusMsgListener.java +++ b/src/main/java/com/genersoft/iot/vmp/service/redisMsg/RedisPushStreamStatusMsgListener.java @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.service.redisMsg; import com.alibaba.fastjson2.JSON; 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.service.IStreamPushService; import com.genersoft.iot.vmp.service.bean.PushStreamStatusChangeFromRedisDto; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; @@ -38,6 +39,9 @@ public class RedisPushStreamStatusMsgListener implements MessageListener, Applic @Autowired private DynamicTask dynamicTask; + @Autowired + private UserSetting userSetting; + private ConcurrentLinkedQueue taskQueue = new ConcurrentLinkedQueue<>(); @@ -89,13 +93,15 @@ public class RedisPushStreamStatusMsgListener implements MessageListener, Applic @Override public void run(ApplicationArguments args) throws Exception { - // 启动时设置所有推流通道离线,发起查询请求 - redisCatchStorage.sendStreamPushRequestedMsgForStatus(); - dynamicTask.startDelay(VideoManagerConstants.VM_MSG_GET_ALL_ONLINE_REQUESTED, ()->{ - logger.info("[REDIS消息]未收到redis回复推流设备状态,执行推流设备离线"); - // 五秒收不到请求就设置通道离线,然后通知上级离线 - streamPushService.allStreamOffline(); - }, 5000); + if (!userSetting.isUsePushingAsStatus()) { + // 启动时设置所有推流通道离线,发起查询请求 + redisCatchStorage.sendStreamPushRequestedMsgForStatus(); + dynamicTask.startDelay(VideoManagerConstants.VM_MSG_GET_ALL_ONLINE_REQUESTED, ()->{ + logger.info("[REDIS消息]未收到redis回复推流设备状态,执行推流设备离线"); + // 五秒收不到请求就设置通道离线,然后通知上级离线 + streamPushService.allStreamOffline(); + }, 5000); + } } } 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 1e10469fb..ec93e78ad 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java @@ -1,14 +1,16 @@ package com.genersoft.iot.vmp.storager; import com.alibaba.fastjson2.JSONObject; -import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.SystemAllInfo; -import com.genersoft.iot.vmp.gb28181.bean.*; -import com.genersoft.iot.vmp.media.zlm.dto.*; +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.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; 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.service.bean.ThirdPartyGB; import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; import java.util.List; @@ -23,42 +25,6 @@ public interface IRedisCatchStorage { */ Long getCSEQ(); - /** - * 开始播放时将流存入 - * - * @param stream 流信息 - * @return - */ - boolean startPlay(StreamInfo stream); - - - /** - * 停止播放时删除 - * - * @return - */ - boolean stopPlay(StreamInfo streamInfo); - - /** - * 查询播放列表 - * @return - */ - StreamInfo queryPlay(StreamInfo streamInfo); - - StreamInfo queryPlayByStreamId(String steamId); - - StreamInfo queryPlayByDevice(String deviceId, String channelId); - - Map queryPlayByDeviceId(String deviceId); - - boolean startPlayback(StreamInfo stream, String callId); - - boolean stopPlayback(String deviceId, String channelId, String stream, String callId); - - StreamInfo queryPlayback(String deviceId, String channelID, String stream, String callId); - - String queryPlaybackForKey(String deviceId, String channelId, String stream, String callId); - void updatePlatformCatchInfo(ParentPlatformCatch parentPlatformCatch); ParentPlatformCatch queryPlatformCatchInfo(String platformGbId); @@ -75,8 +41,6 @@ public interface IRedisCatchStorage { void delPlatformRegisterInfo(String callId); - void cleanPlatformRegisterInfos(); - void updateSendRTPSever(SendRtpItem sendRtpItem); /** @@ -102,12 +66,6 @@ public interface IRedisCatchStorage { */ boolean isChannelSendingRTP(String channelId); - /** - * 清空某个设备的所有缓存 - * @param deviceId 设备ID - */ - void clearCatchByDeviceId(String deviceId); - /** * 在redis添加wvp的信息 */ @@ -148,23 +106,6 @@ public interface IRedisCatchStorage { */ void removeStream(String mediaServerId, String type); - /** - * 开始下载录像时存入 - * @param streamInfo - */ - boolean startDownload(StreamInfo streamInfo, String callId); - - StreamInfo queryDownload(String deviceId, String channelId, String stream, String callId); - - boolean stopDownload(String deviceId, String channelId, String stream, String callId); - - /** - * 查找第三方系统留下的国标预设值 - * @param queryKey - * @return - */ - ThirdPartyGB queryMemberNoGBId(String queryKey); - List getStreams(String mediaServerId, String pull); /** @@ -261,4 +202,6 @@ public interface IRedisCatchStorage { List getAllDevices(); void removeAllDevice(); + + void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online); } 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 index 93f2a0975..f81d1f87c 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/DeviceChannelMapper.java @@ -197,6 +197,81 @@ public interface DeviceChannelMapper { @Update(value = {"UPDATE device_channel SET status=0 WHERE deviceId=#{deviceId}"}) void offlineByDeviceId(String deviceId); +// @Insert("") +// int batchAdd(List addChannels); + + + @Insert("") + int batchAdd(List addChannels); + + @Insert("") - int batchAdd(List addChannels); + int batchAddOrUpdate(List addChannels); @Update(value = {"UPDATE device_channel SET status=1 WHERE deviceId=#{deviceId} AND channelId=#{channelId}"}) void online(String deviceId, String channelId); @@ -264,7 +339,7 @@ public interface DeviceChannelMapper { ", owner=#{item.owner}" + ", civilCode=#{item.civilCode}" + ", block=#{item.block}" + - ", block=#{item.subCount}" + + ", subCount=#{item.subCount}" + ", address=#{item.address}" + ", parental=#{item.parental}" + ", parentId=#{item.parentId}" + @@ -289,7 +364,8 @@ public interface DeviceChannelMapper { ", latitudeWgs84=#{item.latitudeWgs84}" + ", businessGroupId=#{item.businessGroupId}" + ", gpsTime=#{item.gpsTime}" + - "WHERE deviceId=#{item.deviceId} AND channelId=#{item.channelId}"+ + "WHERE id=#{item.id}" + + "WHERE deviceId=#{item.deviceId} AND channelId=#{item.channelId}" + "" + ""}) int batchUpdate(List updateChannels); @@ -403,4 +479,26 @@ public interface DeviceChannelMapper { @Select("select de.* from device de left join device_channel dc on de.deviceId = dc.deviceId where dc.channelId=#{channelId}") List getDeviceByChannelId(String channelId); + + + @Delete({""}) + int batchDel(List deleteChannelList); + + @Update({""}) + int batchOnline(List channels); + + @Update({""}) + int batchOffline(List channels); } 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 768938266..79f5aa965 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/RedisCatchStorageImpl.java @@ -2,17 +2,18 @@ package com.genersoft.iot.vmp.storager.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; -import com.genersoft.iot.vmp.common.StreamInfo; 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.*; -import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam; +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.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo; +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.service.bean.ThirdPartyGB; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.storager.dao.dto.PlatformRegisterInfo; @@ -24,8 +25,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; +import java.time.Duration; import java.util.*; @SuppressWarnings("rawtypes") @@ -43,6 +46,9 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Autowired private RedisTemplate redisTemplate; + @Autowired + private StringRedisTemplate stringRedisTemplate; + @Override public Long getCSEQ() { String key = VideoManagerConstants.SIP_CSEQ_PREFIX + userSetting.getServerId(); @@ -87,240 +93,6 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { } } - /** - * 开始播放时将流存入redis - */ - @Override - public boolean startPlay(StreamInfo stream) { - - redisTemplate.opsForValue().set(String.format("%S_%s_%s_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, userSetting.getServerId(), - stream.getMediaServerId(), stream.getStream(), stream.getDeviceID(), stream.getChannelId()), - stream); - return true; - } - - /** - * 停止播放时从redis删除 - */ - @Override - public boolean stopPlay(StreamInfo streamInfo) { - if (streamInfo == null) { - return false; - } - Boolean result = redisTemplate.delete(String.format("%S_%s_%s_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, - userSetting.getServerId(), - streamInfo.getMediaServerId(), - streamInfo.getStream(), - streamInfo.getDeviceID(), - streamInfo.getChannelId())); - return result != null && result; - } - - /** - * 查询播放列表 - */ - @Override - public StreamInfo queryPlay(StreamInfo streamInfo) { - return (StreamInfo)redisTemplate.opsForValue().get(String.format("%S_%s_%s_%s_%s_%s", - VideoManagerConstants.PLAYER_PREFIX, - userSetting.getServerId(), - streamInfo.getMediaServerId(), - streamInfo.getStream(), - streamInfo.getDeviceID(), - streamInfo.getChannelId())); - } - @Override - public StreamInfo queryPlayByStreamId(String streamId) { - List playLeys = RedisUtil.scan(redisTemplate, String.format("%S_%s_*_%s_*", VideoManagerConstants.PLAYER_PREFIX, userSetting.getServerId(), streamId)); - if (playLeys.size() == 0) { - return null; - } - return (StreamInfo)redisTemplate.opsForValue().get(playLeys.get(0).toString()); - } - - @Override - public StreamInfo queryPlayByDevice(String deviceId, String channelId) { - List playLeys = RedisUtil.scan(redisTemplate, String.format("%S_%s_*_*_%s_%s", VideoManagerConstants.PLAYER_PREFIX, - userSetting.getServerId(), - deviceId, - channelId)); - if (playLeys.size() == 0) { - return null; - } - return (StreamInfo)redisTemplate.opsForValue().get(playLeys.get(0).toString()); - } - - @Override - public Map queryPlayByDeviceId(String deviceId) { - Map streamInfos = new HashMap<>(); - List players = RedisUtil.scan(redisTemplate, String.format("%S_%s_*_*_%s_*", VideoManagerConstants.PLAYER_PREFIX, userSetting.getServerId(),deviceId)); - if (players.size() == 0) { - return streamInfos; - } - for (Object player : players) { - String key = (String) player; - StreamInfo streamInfo = JsonUtil.redisJsonToObject(redisTemplate, key, StreamInfo.class); - if (Objects.isNull(streamInfo)) { - continue; - } - streamInfos.put(streamInfo.getDeviceID() + "_" + streamInfo.getChannelId(), streamInfo); - } - return streamInfos; - } - - - @Override - public boolean startPlayback(StreamInfo stream, String callId) { - redisTemplate.opsForValue().set(String.format("%S_%s_%s_%s_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, - userSetting.getServerId(), stream.getMediaServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId), stream); - return true; - } - - @Override - public boolean startDownload(StreamInfo stream, String callId) { - String key=String.format("%S_%s_%s_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX, - userSetting.getServerId(), stream.getMediaServerId(), stream.getDeviceID(), stream.getChannelId(), stream.getStream(), callId); - if (stream.getProgress() == 1) { - logger.debug("添加下载缓存==已完成下载=》{}",key); - redisTemplate.opsForValue().set(key, stream); - }else { - logger.debug("添加下载缓存==未完成下载=》{}",key); - redisTemplate.opsForValue().set(key, stream, 60*60); - } - return true; - } - @Override - public boolean stopDownload(String deviceId, String channelId, String stream, String callId) { - DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(deviceId, channelId); - if (deviceChannel != null) { - deviceChannel.setStreamId(null); - deviceChannel.setDeviceId(deviceId); - deviceChannelMapper.update(deviceChannel); - } - if (deviceId == null) { - deviceId = "*"; - } - if (channelId == null) { - channelId = "*"; - } - if (stream == null) { - stream = "*"; - } - if (callId == null) { - callId = "*"; - } - String key = String.format("%S_%s_*_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX, - userSetting.getServerId(), - deviceId, - channelId, - stream, - callId - ); - List scan = RedisUtil.scan(redisTemplate, key); - if (scan.size() > 0) { - for (Object keyObj : scan) { - redisTemplate.delete(keyObj); - } - } - return true; - } - - @Override - public boolean stopPlayback(String deviceId, String channelId, String stream, String callId) { - DeviceChannel deviceChannel = deviceChannelMapper.queryChannel(deviceId, channelId); - if (deviceChannel != null) { - deviceChannel.setStreamId(null); - deviceChannel.setDeviceId(deviceId); - deviceChannelMapper.update(deviceChannel); - } - if (deviceId == null) { - deviceId = "*"; - } - if (channelId == null) { - channelId = "*"; - } - if (stream == null) { - stream = "*"; - } - if (callId == null) { - callId = "*"; - } - String key = String.format("%S_%s_*_%s_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, - userSetting.getServerId(), - deviceId, - channelId, - stream, - callId - ); - List scan = RedisUtil.scan(redisTemplate, key); - if (scan.size() > 0) { - for (Object keyObj : scan) { - redisTemplate.delete(keyObj); - } - } - return true; - } - - @Override - public StreamInfo queryPlayback(String deviceId, String channelId, String stream, String callId) { - if (stream == null && callId == null) { - return null; - } - if (deviceId == null) { - deviceId = "*"; - } - if (channelId == null) { - channelId = "*"; - } - if (stream == null) { - stream = "*"; - } - if (callId == null) { - callId = "*"; - } - String key = String.format("%S_%s_*_%s_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, - userSetting.getServerId(), - deviceId, - channelId, - stream, - callId - ); - List streamInfoScan = RedisUtil.scan(redisTemplate, key); - if (streamInfoScan.size() > 0) { - return (StreamInfo) redisTemplate.opsForValue().get(streamInfoScan.get(0)); - }else { - return null; - } - } - - @Override - public String queryPlaybackForKey(String deviceId, String channelId, String stream, String callId) { - if (stream == null && callId == null) { - return null; - } - if (deviceId == null) { - deviceId = "*"; - } - if (channelId == null) { - channelId = "*"; - } - if (stream == null) { - stream = "*"; - } - if (callId == null) { - callId = "*"; - } - String key = String.format("%S_%s_*_%s_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, - userSetting.getServerId(), - deviceId, - channelId, - stream, - callId - ); - List streamInfoScan = RedisUtil.scan(redisTemplate, key); - return (String) streamInfoScan.get(0); - } - @Override public void updatePlatformCatchInfo(ParentPlatformCatch parentPlatformCatch) { String key = VideoManagerConstants.PLATFORM_CATCH_PREFIX + userSetting.getServerId() + "_" + parentPlatformCatch.getId(); @@ -351,7 +123,8 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Override public void updatePlatformRegisterInfo(String callId, PlatformRegisterInfo platformRegisterInfo) { String key = VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + callId; - redisTemplate.opsForValue().set(key, platformRegisterInfo, 30); + Duration duration = Duration.ofSeconds(30L); + redisTemplate.opsForValue().set(key, platformRegisterInfo, duration); } @@ -365,14 +138,6 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { redisTemplate.delete(VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + callId); } - @Override - public void cleanPlatformRegisterInfos() { - List regInfos = RedisUtil.scan(redisTemplate, VideoManagerConstants.PLATFORM_REGISTER_INFO_PREFIX + userSetting.getServerId() + "_" + "*"); - for (Object key : regInfos) { - redisTemplate.delete(key.toString()); - } - } - @Override public void updateSendRTPSever(SendRtpItem sendRtpItem) { @@ -529,40 +294,11 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { return RtpStreams.size() > 0; } - @Override - public void clearCatchByDeviceId(String deviceId) { - List playLeys = RedisUtil.scan(redisTemplate, String.format("%S_%s_*_%s_*", VideoManagerConstants.PLAYER_PREFIX, - userSetting.getServerId(), - deviceId)); - if (playLeys.size() > 0) { - for (Object key : playLeys) { - redisTemplate.delete(key.toString()); - } - } - - List playBackers = RedisUtil.scan(redisTemplate, String.format("%S_%s_*_%s_*_*_*", VideoManagerConstants.PLAY_BLACK_PREFIX, - userSetting.getServerId(), - deviceId)); - if (playBackers.size() > 0) { - for (Object key : playBackers) { - redisTemplate.delete(key.toString()); - } - } - - List deviceCache = RedisUtil.scan(redisTemplate, String.format("%S%s_%s", VideoManagerConstants.DEVICE_PREFIX, - userSetting.getServerId(), - deviceId)); - if (deviceCache.size() > 0) { - for (Object key : deviceCache) { - redisTemplate.delete(key.toString()); - } - } - } - @Override public void updateWVPInfo(JSONObject jsonObject, int time) { String key = VideoManagerConstants.WVP_SERVER_PREFIX + userSetting.getServerId(); - redisTemplate.opsForValue().set(key, jsonObject, time); + Duration duration = Duration.ofSeconds(time); + redisTemplate.opsForValue().set(key, jsonObject, duration); } @Override @@ -589,44 +325,6 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { redisTemplate.delete(key); } - @Override - public StreamInfo queryDownload(String deviceId, String channelId, String stream, String callId) { - if (stream == null && callId == null) { - return null; - } - if (deviceId == null) { - deviceId = "*"; - } - if (channelId == null) { - channelId = "*"; - } - if (stream == null) { - stream = "*"; - } - if (callId == null) { - callId = "*"; - } - String key = String.format("%S_%s_*_%s_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX, - userSetting.getServerId(), - deviceId, - channelId, - stream, - callId - ); - List streamInfoScan = RedisUtil.scan(redisTemplate, key); - if (streamInfoScan.size() > 0) { - return (StreamInfo) redisTemplate.opsForValue().get(streamInfoScan.get(0)); - }else { - return null; - } - } - - @Override - public ThirdPartyGB queryMemberNoGBId(String queryKey) { - String key = VideoManagerConstants.WVP_STREAM_GB_ID_PREFIX + queryKey; - return JsonUtil.redisJsonToObject(redisTemplate, key, ThirdPartyGB.class); - } - @Override public void removeStream(String mediaServerId, String type) { String key = VideoManagerConstants.WVP_SERVER_STREAM_PREFIX + userSetting.getServerId() + "_" + type + "_*_*_" + mediaServerId; @@ -694,7 +392,8 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Override public void updateGpsMsgInfo(GPSMsgInfo gpsMsgInfo) { String key = VideoManagerConstants.WVP_STREAM_GPS_MSG_PREFIX + userSetting.getServerId() + "_" + gpsMsgInfo.getId(); - redisTemplate.opsForValue().set(key, gpsMsgInfo, 60); // 默认GPS消息保存1分钟 + Duration duration = Duration.ofSeconds(60L); + redisTemplate.opsForValue().set(key, gpsMsgInfo, duration); // 默认GPS消息保存1分钟 } @Override @@ -902,4 +601,23 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { + userSetting.getServerId() + "_*_" + id + "_*"; return RedisUtil.scan(redisTemplate, key).size(); } + + @Override + public void sendDeviceOrChannelStatus(String deviceId, String channelId, boolean online) { + String key = VideoManagerConstants.VM_MSG_SUBSCRIBE_DEVICE_STATUS; + if (channelId == null) { + logger.info("[redis通知] 推送设备状态, {}-{}", deviceId, online); + }else { + logger.info("[redis通知] 推送通道状态, {}/{}-{}", deviceId, channelId, online); + } + + StringBuilder msg = new StringBuilder(); + msg.append(deviceId); + if (channelId != null) { + msg.append(":").append(channelId); + } + msg.append(" ").append(online? "ON":"OFF"); + // 使用 RedisTemplate 发送字符串消息会导致发送的消息多带了双引号 + stringRedisTemplate.convertAndSend(key, msg.toString()); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java index eac229c8f..2ffb4bc53 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStorageImpl.java @@ -177,10 +177,10 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { if (i + limitCount > channels.size()) { toIndex = channels.size(); } - result = result || deviceChannelMapper.batchAdd(channels.subList(i, toIndex)) < 0; + result = result || deviceChannelMapper.batchAddOrUpdate(channels.subList(i, toIndex)) < 0; } }else { - result = result || deviceChannelMapper.batchAdd(channels) < 0; + result = result || deviceChannelMapper.batchAddOrUpdate(channels) < 0; } } if (result) { @@ -278,10 +278,10 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { if (i + limitCount > addChannels.size()) { toIndex = addChannels.size(); } - result = result || deviceChannelMapper.batchAdd(addChannels.subList(i, toIndex)) < 0; + result = result || deviceChannelMapper.batchAddOrUpdate(addChannels.subList(i, toIndex)) < 0; } }else { - result = result || deviceChannelMapper.batchAdd(addChannels) < 0; + result = result || deviceChannelMapper.batchAddOrUpdate(addChannels) < 0; } } if (updateChannels.size() > 0) { @@ -434,9 +434,6 @@ public class VideoManagerStorageImpl implements IVideoManagerStorage { */ @Override public synchronized boolean insertMobilePosition(MobilePosition mobilePosition) { - if (mobilePosition.getDeviceId().equals(mobilePosition.getChannelId())) { - mobilePosition.setChannelId(null); - } return deviceMobilePositionMapper.insertNewPosition(mobilePosition) > 0; } diff --git a/src/main/java/com/genersoft/iot/vmp/utils/ConfigConst.java b/src/main/java/com/genersoft/iot/vmp/utils/ConfigConst.java deleted file mode 100644 index 125d8180d..000000000 --- a/src/main/java/com/genersoft/iot/vmp/utils/ConfigConst.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.genersoft.iot.vmp.utils; - -public class ConfigConst { - /** - * 播流最大并发个数 - */ - public static final Integer MAX_STRTEAM_COUNT = 10000; -} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java index d0accf407..0ff8ba11b 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceQuery.java @@ -14,6 +14,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.service.IDeviceChannelService; import com.genersoft.iot.vmp.service.IDeviceService; +import com.genersoft.iot.vmp.service.IInviteStreamService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.vmanager.bean.BaseTree; @@ -62,6 +63,9 @@ public class DeviceQuery { @Autowired private IRedisCatchStorage redisCatchStorage; + + @Autowired + private IInviteStreamService inviteStreamService; @Autowired private SIPCommander cmder; @@ -184,7 +188,7 @@ public class DeviceQuery { // 清除redis记录 boolean isSuccess = deviceService.delete(deviceId); if (isSuccess) { - redisCatchStorage.clearCatchByDeviceId(deviceId); + inviteStreamService.clearInviteInfo(deviceId); // 停止此设备的订阅更新 Set allKeys = dynamicTask.getAllKeys(); for (String key : allKeys) { diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java index 7727729b2..778b9ca43 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/play/PlayController.java @@ -2,6 +2,9 @@ package com.genersoft.iot.vmp.vmanager.gb28181.play; import com.alibaba.fastjson2.JSONArray; 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.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; @@ -14,12 +17,17 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.service.IInviteStreamService; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IMediaService; import com.genersoft.iot.vmp.service.IPlayService; +import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.vmanager.bean.*; +import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; +import com.genersoft.iot.vmp.vmanager.bean.StreamContent; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -60,6 +68,9 @@ public class PlayController { @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private IInviteStreamService inviteStreamService; + @Autowired private ZLMRESTfulUtils zlmresTfulUtils; @@ -89,14 +100,12 @@ public class PlayController { Device device = storager.queryVideoDevice(deviceId); MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); - RequestMessage msg = new RequestMessage(); + RequestMessage requestMessage = new RequestMessage(); String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; - boolean exist = resultHolder.exist(key, null); - msg.setKey(key); + requestMessage.setKey(key); String uuid = UUID.randomUUID().toString(); - msg.setId(uuid); + requestMessage.setId(uuid); DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); - DeferredResultEx> deferredResultEx = new DeferredResultEx<>(result); result.onTimeout(()->{ logger.info("点播接口等待超时"); @@ -104,32 +113,33 @@ public class PlayController { WVPResult wvpResult = new WVPResult<>(); wvpResult.setCode(ErrorCode.ERROR100.getCode()); wvpResult.setMsg("点播超时"); - msg.setData(wvpResult); - resultHolder.invokeResult(msg); + requestMessage.setData(wvpResult); + resultHolder.invokeResult(requestMessage); }); - // TODO 在点播未成功的情况下在此调用接口点播会导致返回的流地址ip错误 - deferredResultEx.setFilter(result1 -> { - WVPResult wvpResult1 = (WVPResult)result1; - WVPResult resultStream = new WVPResult<>(); - resultStream.setCode(wvpResult1.getCode()); - resultStream.setMsg(wvpResult1.getMsg()); - if (wvpResult1.getCode() == ErrorCode.SUCCESS.getCode()) { - StreamInfo data = wvpResult1.getData().clone(); - if (userSetting.getUseSourceIpAsStreamIp()) { - data.channgeStreamIp(request.getLocalName()); - } - resultStream.setData(new StreamContent(wvpResult1.getData())); - } - return resultStream; - }); - // 录像查询以channelId作为deviceId查询 - resultHolder.put(key, uuid, deferredResultEx); + resultHolder.put(key, uuid, result); - if (!exist) { - playService.play(newMediaServerItem, deviceId, channelId, null, null, null); - } + playService.play(newMediaServerItem, deviceId, channelId, (code, msg, data) -> { + WVPResult wvpResult = new WVPResult<>(); + if (code == InviteErrorCode.SUCCESS.getCode()) { + wvpResult.setCode(ErrorCode.SUCCESS.getCode()); + wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); + + if (data != null) { + StreamInfo streamInfo = (StreamInfo)data; + if (userSetting.getUseSourceIpAsStreamIp()) { + streamInfo.channgeStreamIp(request.getLocalName()); + } + wvpResult.setData(new StreamContent(streamInfo)); + } + }else { + wvpResult.setCode(code); + wvpResult.setMsg(msg); + } + requestMessage.setData(wvpResult); + resultHolder.invokeResult(requestMessage); + }); return result; } @@ -150,21 +160,22 @@ public class PlayController { throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备[" + deviceId + "]不存在"); } - StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId); - if (streamInfo == null) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); + if (inviteInfo == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "点播未找到"); } - - try { - logger.warn("[停止点播] {}/{}", device.getDeviceId(), channelId); - cmder.streamByeCmd(device, channelId, streamInfo.getStream(), null, null); - } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) { - logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage()); - throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); + if (InviteSessionStatus.ok == inviteInfo.getStatus()) { + try { + logger.warn("[停止点播] {}/{}", device.getDeviceId(), channelId); + cmder.streamByeCmd(device, channelId, inviteInfo.getStream(), null, null); + } catch (InvalidArgumentException | SipException | ParseException | SsrcTransactionNotFoundException e) { + logger.error("[命令发送失败] 停止点播, 发送BYE: {}", e.getMessage()); + throw new ControllerException(ErrorCode.ERROR100.getCode(), "命令发送失败: " + e.getMessage()); + } } - redisCatchStorage.stopPlay(streamInfo); + inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId); - storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); + storager.stopPlay(deviceId, channelId); JSONObject json = new JSONObject(); json.put("deviceId", deviceId); json.put("channelId", channelId); @@ -179,15 +190,14 @@ public class PlayController { @Parameter(name = "streamId", description = "视频流ID", required = true) @PostMapping("/convert/{streamId}") public JSONObject playConvert(@PathVariable String streamId) { - StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); - if (streamInfo == null) { - streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null); - } - if (streamInfo == null) { +// StreamInfo streamInfo = redisCatchStorage.queryPlayByStreamId(streamId); + + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, streamId); + if (inviteInfo == null || inviteInfo.getStreamInfo() == null) { logger.warn("视频转码API调用失败!, 视频流已经停止!"); throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到视频流信息, 视频流可能已经停止"); } - MediaServerItem mediaInfo = mediaServerService.getOne(streamInfo.getMediaServerId()); + MediaServerItem mediaInfo = mediaServerService.getOne(inviteInfo.getStreamInfo().getMediaServerId()); JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId); if (!rtpInfo.getBoolean("exist")) { logger.warn("视频转码API调用失败!, 视频流已停止推流!"); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java index 8bab3fd0c..9db641768 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/PlaybackController.java @@ -1,15 +1,22 @@ package com.genersoft.iot.vmp.vmanager.gb28181.playback; +import com.genersoft.iot.vmp.common.InviteInfo; +import com.genersoft.iot.vmp.common.InviteSessionType; import com.genersoft.iot.vmp.common.StreamInfo; 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.Device; 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.impl.SIPCommander; import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory; -import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.service.IInviteStreamService; import com.genersoft.iot.vmp.service.IPlayService; +import com.genersoft.iot.vmp.service.bean.InviteErrorCode; +import com.genersoft.iot.vmp.storager.IRedisCatchStorage; +import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; import com.genersoft.iot.vmp.vmanager.bean.StreamContent; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; @@ -20,17 +27,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.ObjectUtils; -import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; - -import com.genersoft.iot.vmp.gb28181.bean.Device; -import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; -import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import org.springframework.web.context.request.async.DeferredResult; +import javax.servlet.http.HttpServletRequest; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; @@ -59,6 +62,9 @@ public class PlaybackController { @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private IInviteStreamService inviteStreamService; + @Autowired private IPlayService playService; @@ -74,8 +80,8 @@ public class PlaybackController { @Parameter(name = "startTime", description = "开始时间", required = true) @Parameter(name = "endTime", description = "结束时间", required = true) @GetMapping("/start/{deviceId}/{channelId}") - public DeferredResult> start(@PathVariable String deviceId, @PathVariable String channelId, - String startTime, String endTime) { + public DeferredResult> start(HttpServletRequest request, @PathVariable String deviceId, @PathVariable String channelId, + String startTime, String endTime) { if (logger.isDebugEnabled()) { logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId)); @@ -86,22 +92,31 @@ public class PlaybackController { DeferredResult> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue()); resultHolder.put(key, uuid, result); - WVPResult wvpResult = new WVPResult<>(); + RequestMessage requestMessage = new RequestMessage(); + requestMessage.setKey(key); + requestMessage.setId(uuid); - RequestMessage msg = new RequestMessage(); - msg.setKey(key); - msg.setId(uuid); + playService.playBack(deviceId, channelId, startTime, endTime, + (code, msg, data)->{ - playService.playBack(deviceId, channelId, startTime, endTime, null, - playBackResult->{ - wvpResult.setCode(playBackResult.getCode()); - wvpResult.setMsg(playBackResult.getMsg()); - if (playBackResult.getCode() == ErrorCode.SUCCESS.getCode()) { - StreamInfo streamInfo = (StreamInfo)playBackResult.getData(); - wvpResult.setData(new StreamContent(streamInfo)); + WVPResult wvpResult = new WVPResult<>(); + if (code == InviteErrorCode.SUCCESS.getCode()) { + wvpResult.setCode(ErrorCode.SUCCESS.getCode()); + wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); + + if (data != null) { + StreamInfo streamInfo = (StreamInfo)data; + if (userSetting.getUseSourceIpAsStreamIp()) { + streamInfo.channgeStreamIp(request.getLocalName()); + } + wvpResult.setData(new StreamContent(streamInfo)); + } + }else { + wvpResult.setCode(code); + wvpResult.setMsg(msg); } - msg.setData(wvpResult); - resultHolder.invokeResult(msg); + requestMessage.setData(wvpResult); + resultHolder.invokeResult(requestMessage); }); return result; @@ -169,14 +184,15 @@ public class PlaybackController { @GetMapping("/seek/{streamId}/{seekTime}") public void playSeek(@PathVariable String streamId, @PathVariable long seekTime) { logger.info("playSeek: "+streamId+", "+seekTime); - StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null); - if (null == streamInfo) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); + + if (null == inviteInfo || inviteInfo.getStreamInfo() == null) { logger.warn("streamId不存在!"); throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在"); } - Device device = storager.queryVideoDevice(streamInfo.getDeviceID()); + Device device = storager.queryVideoDevice(inviteInfo.getDeviceId()); try { - cmder.playSeekCmd(device, streamInfo, seekTime); + cmder.playSeekCmd(device, inviteInfo.getStreamInfo(), seekTime); } catch (InvalidArgumentException | ParseException | SipException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } @@ -188,8 +204,9 @@ public class PlaybackController { @GetMapping("/speed/{streamId}/{speed}") public void playSpeed(@PathVariable String streamId, @PathVariable Double speed) { logger.info("playSpeed: "+streamId+", "+speed); - StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null); - if (null == streamInfo) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, streamId); + + if (null == inviteInfo || inviteInfo.getStreamInfo() == null) { logger.warn("streamId不存在!"); throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在"); } @@ -197,9 +214,9 @@ public class PlaybackController { logger.warn("不支持的speed: " + speed); throw new ControllerException(ErrorCode.ERROR100.getCode(), "不支持的speed(0.25 0.5 1、2、4)"); } - Device device = storager.queryVideoDevice(streamInfo.getDeviceID()); + Device device = storager.queryVideoDevice(inviteInfo.getDeviceId()); try { - cmder.playSpeedCmd(device, streamInfo, speed); + cmder.playSpeedCmd(device, inviteInfo.getStreamInfo(), speed); } catch (InvalidArgumentException | ParseException | SipException e) { throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage()); } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java index 0dcc5ad6e..6de54837d 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/record/GBRecordController.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.vmanager.gb28181.record; import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.conf.UserSetting; import com.genersoft.iot.vmp.conf.exception.ControllerException; import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException; import com.genersoft.iot.vmp.gb28181.bean.Device; @@ -10,6 +11,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.service.IDeviceService; import com.genersoft.iot.vmp.service.IPlayService; +import com.genersoft.iot.vmp.service.bean.InviteErrorCode; import com.genersoft.iot.vmp.storager.IVideoManagerStorage; import com.genersoft.iot.vmp.utils.DateUtil; import com.genersoft.iot.vmp.vmanager.bean.ErrorCode; @@ -27,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; +import javax.servlet.http.HttpServletRequest; import javax.sip.InvalidArgumentException; import javax.sip.SipException; import java.text.ParseException; @@ -55,8 +58,8 @@ public class GBRecordController { @Autowired private IDeviceService deviceService; - - + @Autowired + private UserSetting userSetting; @Operation(summary = "录像查询") @Parameter(name = "deviceId", description = "设备国标编号", required = true) @@ -119,8 +122,8 @@ public class GBRecordController { @Parameter(name = "endTime", description = "结束时间", required = true) @Parameter(name = "downloadSpeed", description = "下载倍速", required = true) @GetMapping("/download/start/{deviceId}/{channelId}") - public DeferredResult> download(@PathVariable String deviceId, @PathVariable String channelId, - String startTime, String endTime, String downloadSpeed) { + public DeferredResult> download(HttpServletRequest request, @PathVariable String deviceId, @PathVariable String channelId, + String startTime, String endTime, String downloadSpeed) { if (logger.isDebugEnabled()) { logger.debug(String.format("历史媒体下载 API调用,deviceId:%s,channelId:%s,downloadSpeed:%s", deviceId, channelId, downloadSpeed)); @@ -130,22 +133,32 @@ public class GBRecordController { String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId; DeferredResult> result = new DeferredResult<>(30000L); resultHolder.put(key, uuid, result); - RequestMessage msg = new RequestMessage(); - msg.setId(uuid); - msg.setKey(key); + RequestMessage requestMessage = new RequestMessage(); + requestMessage.setId(uuid); + requestMessage.setKey(key); - WVPResult wvpResult = new WVPResult<>(); - playService.download(deviceId, channelId, startTime, endTime, Integer.parseInt(downloadSpeed), null, playBackResult->{ + playService.download(deviceId, channelId, startTime, endTime, Integer.parseInt(downloadSpeed), + (code, msg, data)->{ - wvpResult.setCode(playBackResult.getCode()); - wvpResult.setMsg(playBackResult.getMsg()); - if (playBackResult.getCode() == ErrorCode.SUCCESS.getCode()) { - StreamInfo streamInfo = (StreamInfo)playBackResult.getData(); - wvpResult.setData(new StreamContent(streamInfo)); + WVPResult wvpResult = new WVPResult<>(); + if (code == InviteErrorCode.SUCCESS.getCode()) { + wvpResult.setCode(ErrorCode.SUCCESS.getCode()); + wvpResult.setMsg(ErrorCode.SUCCESS.getMsg()); + + if (data != null) { + StreamInfo streamInfo = (StreamInfo)data; + if (userSetting.getUseSourceIpAsStreamIp()) { + streamInfo.channgeStreamIp(request.getLocalName()); + } + wvpResult.setData(new StreamContent(streamInfo)); + } + }else { + wvpResult.setCode(code); + wvpResult.setMsg(msg); } - msg.setData(wvpResult); - resultHolder.invokeResult(msg); + requestMessage.setData(wvpResult); + resultHolder.invokeResult(requestMessage); }); return result; diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java index 37f9aba2c..82d8fd009 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/user/UserController.java @@ -57,7 +57,7 @@ public class UserController { if (user == null) { throw new ControllerException(ErrorCode.ERROR100.getCode(), "用户名或密码错误"); }else { - String jwt = JwtUtils.createToken(username, password); + String jwt = JwtUtils.createToken(username, password, user.getRole().getId()); response.setHeader(JwtUtils.getHeader(), jwt); user.setAccessToken(jwt); } 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 a5f6055fb..8e35d04f0 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 @@ -1,7 +1,8 @@ package com.genersoft.iot.vmp.web.gb28181; import com.alibaba.fastjson2.JSONObject; -import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.common.InviteInfo; +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.Device; @@ -9,13 +10,18 @@ import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.IDeviceService; +import com.genersoft.iot.vmp.service.IInviteStreamService; import com.genersoft.iot.vmp.service.IPlayService; +import com.genersoft.iot.vmp.service.bean.InviteErrorCode; 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.web.bind.annotation.*; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import javax.sip.InvalidArgumentException; @@ -45,6 +51,9 @@ public class ApiStreamController { @Autowired private IRedisCatchStorage redisCatchStorage; + @Autowired + private IInviteStreamService inviteStreamService; + @Autowired private IDeviceService deviceService; @@ -111,46 +120,53 @@ public class ApiStreamController { return resultDeferredResult; } MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); - playService.play(newMediaServerItem, serial, code, (mediaServerItem, response)->{ - StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(serial, code); - JSONObject result = new JSONObject(); - result.put("StreamID", streamInfo.getStream()); - result.put("DeviceID", device.getDeviceId()); - result.put("ChannelID", code); - result.put("ChannelName", deviceChannel.getName()); - result.put("ChannelCustomName", ""); - result.put("FLV", streamInfo.getFlv().getUrl()); - result.put("WS_FLV", streamInfo.getWs_flv().getUrl()); - result.put("RTMP", streamInfo.getRtmp().getUrl()); - result.put("HLS", streamInfo.getHls().getUrl()); - result.put("RTSP", streamInfo.getRtsp().getUrl()); - result.put("WEBRTC", streamInfo.getRtc().getUrl()); - result.put("CDN", ""); - result.put("SnapURL", ""); - result.put("Transport", device.getTransport()); - result.put("StartAt", ""); - result.put("Duration", ""); - result.put("SourceVideoCodecName", ""); - result.put("SourceVideoWidth", ""); - result.put("SourceVideoHeight", ""); - result.put("SourceVideoFrameRate", ""); - result.put("SourceAudioCodecName", ""); - result.put("SourceAudioSampleRate", ""); - result.put("AudioEnable", ""); - result.put("Ondemand", ""); - result.put("InBytes", ""); - result.put("InBitRate", ""); - result.put("OutBytes", ""); - result.put("NumOutputs", ""); - result.put("CascadeSize", ""); - result.put("RelaySize", ""); - result.put("ChannelPTZType", "0"); - resultDeferredResult.setResult(result); - }, (eventResult) -> { - JSONObject result = new JSONObject(); - result.put("error", "channel[ " + code + " ] " + eventResult.msg); - resultDeferredResult.setResult(result); - }, null); + + + playService.play(newMediaServerItem, serial, code, (errorCode, msg, data) -> { + if (errorCode == InviteErrorCode.SUCCESS.getCode()) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, serial, code); + if (inviteInfo != null && inviteInfo.getStreamInfo() != null) { + JSONObject result = new JSONObject(); + result.put("StreamID", inviteInfo.getStreamInfo().getStream()); + result.put("DeviceID", device.getDeviceId()); + result.put("ChannelID", code); + result.put("ChannelName", deviceChannel.getName()); + result.put("ChannelCustomName", ""); + result.put("FLV", inviteInfo.getStreamInfo().getFlv().getUrl()); + result.put("WS_FLV", inviteInfo.getStreamInfo().getWs_flv().getUrl()); + result.put("RTMP", inviteInfo.getStreamInfo().getRtmp().getUrl()); + result.put("HLS", inviteInfo.getStreamInfo().getHls().getUrl()); + result.put("RTSP", inviteInfo.getStreamInfo().getRtsp().getUrl()); + result.put("WEBRTC", inviteInfo.getStreamInfo().getRtc().getUrl()); + result.put("CDN", ""); + result.put("SnapURL", ""); + result.put("Transport", device.getTransport()); + result.put("StartAt", ""); + result.put("Duration", ""); + result.put("SourceVideoCodecName", ""); + result.put("SourceVideoWidth", ""); + result.put("SourceVideoHeight", ""); + result.put("SourceVideoFrameRate", ""); + result.put("SourceAudioCodecName", ""); + result.put("SourceAudioSampleRate", ""); + result.put("AudioEnable", ""); + result.put("Ondemand", ""); + result.put("InBytes", ""); + result.put("InBitRate", ""); + result.put("OutBytes", ""); + result.put("NumOutputs", ""); + result.put("CascadeSize", ""); + result.put("RelaySize", ""); + result.put("ChannelPTZType", "0"); + resultDeferredResult.setResult(result); + } + }else { + JSONObject result = new JSONObject(); + result.put("error", "channel[ " + code + " ] " + msg); + resultDeferredResult.setResult(result); + } + }); + return resultDeferredResult; } @@ -171,8 +187,8 @@ public class ApiStreamController { ){ - StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(serial, code); - if (streamInfo == null) { + InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, serial, code); + if (inviteInfo == null) { JSONObject result = new JSONObject(); result.put("error","未找到流信息"); return result; @@ -184,14 +200,14 @@ public class ApiStreamController { return result; } try { - cmder.streamByeCmd(device, code, streamInfo.getStream(), null); - } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { + cmder.streamByeCmd(device, code, inviteInfo.getStream(), null); + } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) { JSONObject result = new JSONObject(); result.put("error","发送BYE失败:" + e.getMessage()); return result; } - redisCatchStorage.stopPlay(streamInfo); - storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId()); + inviteStreamService.removeInviteInfo(inviteInfo); + storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId()); return null; } diff --git a/src/main/resources/all-application.yml b/src/main/resources/all-application.yml index d8bbc5ec6..8a8c7cc24 100644 --- a/src/main/resources/all-application.yml +++ b/src/main/resources/all-application.yml @@ -65,8 +65,11 @@ server: # 作为28181服务器的配置 sip: - # [必须修改] 本机的IP - ip: 192.168.0.100 + # [必须修改] 本机的IP,对应你的网卡,监听什么ip就是使用什么网卡, + # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4 + # 如果不明白,就使用0.0.0.0,大部分情况都是可以的 + # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。 + ip: 0.0.0.0 # [可选] 没有任何业务需求,仅仅是在前端展示的时候用 show-ip: 192.168.0.100 # [可选] 28181服务监听的端口 @@ -89,6 +92,15 @@ sip: # 是否存储alarm信息 alarm: false +# 做为JT1078服务器的配置 +jt1078: + #[必须修改] 是否开启1078的服务 + enable: true + #[必修修改] 1708设备接入的端口 + port: 21078 + #[可选] 设备鉴权的密码 + password: admin123 + #zlm 默认服务器配置 media: # [必须修改] zlm服务器唯一id,用于触发hook时区别是哪台服务器,general.mediaServerId @@ -182,6 +194,12 @@ user-settings: send-to-platforms-when-id-lost: true # 保持通道状态,不接受notify通道状态变化, 兼容海康平台发送错误消息 refuse-channel-status-channel-form-notify: false + # 设置notify缓存队列最大长度,超过此长度的数据将返回486 BUSY_HERE,消息丢弃, 默认10000 + max-notify-count-queue: 10000 + # 设备/通道状态变化时发送消息 + device-status-notify: false + # 上级平台点播时不使用上级平台指定的ssrc,使用自定义的ssrc,参考国标文档-点播外域设备媒体流SSRC处理方式 + use-custom-ssrc-for-parent-invite: true # 跨域配置,配置你访问前端页面的地址即可, 可以配置多个 allowed-origins: - http://localhost:8008 diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index f06bd3e9b..04f7742df 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -18,29 +18,19 @@ spring: timeout: 10000 # mysql数据源 datasource: - type: com.alibaba.druid.pool.DruidDataSource + type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/wvp2?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&serverTimezone=PRC&useSSL=false&allowMultiQueries=true username: root password: 123456 - druid: + hikari: + connection-timeout: 20000 # 是客户端等待连接池连接的最大毫秒数 initialSize: 10 # 连接池初始化连接数 - maxActive: 200 # 连接池最大连接数 - minIdle: 5 # 连接池最小空闲连接数 - maxWait: 60000 # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 - keepAlive: true # 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。 - validationQuery: select 1 # 检测连接是否有效sql,要求是查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 - testWhileIdle: true # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 - testOnBorrow: false # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 - testOnReturn: false # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 - poolPreparedStatements: false # 是否開啟PSCache,並且指定每個連線上PSCache的大小 - timeBetweenEvictionRunsMillis: 60000 # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 - minEvictableIdleTimeMillis: 300000 # 配置一個連線在池中最小生存的時間,單位是毫秒 - filters: stat,slf4j # 配置监控统计拦截的filters,监控统计用的filter:sta, 日志用的filter:log4j - useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据 - # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 - connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=1000 - #stat-view-servlet.url-pattern: /admin/druid/* + maximum-pool-size: 200 # 连接池最大连接数 + minimum-idle: 5 # 连接池最小空闲连接数 + idle-timeout: 300000 # 允许连接在连接池中空闲的最长时间(以毫秒为单位) + max-lifetime: 1200000 # 是池中连接关闭后的最长生命周期(以毫秒为单位) + #[可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口 server: @@ -48,7 +38,10 @@ server: # 作为28181服务器的配置 sip: - # [必须修改] 本机的IP + # [必须修改] 本机的IP,对应你的网卡,监听什么ip就是使用什么网卡, + # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4 + # 如果不明白,就使用0.0.0.0,大部分情况都是可以的 + # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。 ip: 192.168.41.16 # [可选] 28181服务监听的端口 port: 5060 diff --git a/src/main/resources/application-docker.yml b/src/main/resources/application-docker.yml index a722435c4..0e0c0ad35 100644 --- a/src/main/resources/application-docker.yml +++ b/src/main/resources/application-docker.yml @@ -23,7 +23,7 @@ spring: url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true&allowMultiQueries=true&useSSL=false&allowMultiQueries=true username: root password: root - type: com.alibaba.druid.pool.DruidDataSource + type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver # [可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口 @@ -32,7 +32,10 @@ server: # 作为28181服务器的配置 sip: - # [必须修改] 本机的IP + # [必须修改] 本机的IP,对应你的网卡,监听什么ip就是使用什么网卡, + # 如果要监听多张网卡,可以使用逗号分隔多个IP, 例如: 192.168.1.4,10.0.0.4 + # 如果不明白,就使用0.0.0.0,大部分情况都是可以的 + # 请不要使用127.0.0.1,任何包括localhost在内的域名都是不可以的。 ip: ${WVP_HOST:127.0.0.1} # [可选] 28181服务监听的端口 port: ${WVP_PORT:5060} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c0615df6f..3f478442e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,5 +2,4 @@ spring: application: name: wvp profiles: - active: local - include: device-compatible \ No newline at end of file + active: local \ No newline at end of file diff --git a/src/test/java/com/genersoft/iot/vmp/jt1078/JT1078ServerTest.java b/src/test/java/com/genersoft/iot/vmp/jt1078/JT1078ServerTest.java new file mode 100644 index 000000000..8986f91e6 --- /dev/null +++ b/src/test/java/com/genersoft/iot/vmp/jt1078/JT1078ServerTest.java @@ -0,0 +1,103 @@ +package com.genersoft.iot.vmp.jt1078; + +import com.genersoft.iot.vmp.jt1078.cmd.JT1078Template; +import com.genersoft.iot.vmp.jt1078.codec.netty.TcpServer; +import com.genersoft.iot.vmp.jt1078.proc.response.J9102; +import com.genersoft.iot.vmp.jt1078.proc.response.J9201; +import com.genersoft.iot.vmp.jt1078.proc.response.J9202; +import com.genersoft.iot.vmp.jt1078.proc.response.J9205; + +import java.util.Scanner; + +/** + * @author QingtaiJiang + * @date 2023/4/28 14:22 + * @email qingtaij@163.com + */ +public class JT1078ServerTest { + + private static final JT1078Template jt1078Template = new JT1078Template(); + + public static void main(String[] args) { + System.out.println("Starting jt1078 server..."); + TcpServer tcpServer = new TcpServer(21078); + tcpServer.start(); + System.out.println("Start jt1078 server success!"); + + + Scanner s = new Scanner(System.in); + while (true) { + String code = s.nextLine(); + switch (code) { + case "1": + test9102(); + break; + case "2": + test9201(); + break; + case "3": + test9202(); + break; + case "4": + test9205(); + break; + default: + break; + } + } + } + + private static void test9102() { + J9102 j9102 = new J9102(); + j9102.setChannel(1); + j9102.setCommand(0); + j9102.setCloseType(0); + j9102.setStreamType(0); + + String s = jt1078Template.stopLive("18864197066", j9102, 6); + System.out.println(s); + } + + private static void test9201() { + J9201 j9201 = new J9201(); + j9201.setIp("192.168.1.1"); + j9201.setChannel(1); + j9201.setTcpPort(7618); + j9201.setUdpPort(7618); + j9201.setType(0); + j9201.setRate(0); + j9201.setStorageType(0); + j9201.setPlaybackType(0); + j9201.setPlaybackSpeed(0); + j9201.setStartTime("230428134100"); + j9201.setEndTime("230428134200"); + + String s = jt1078Template.startBackLive("18864197066", j9201, 6); + System.out.println(s); + } + + private static void test9202() { + J9202 j9202 = new J9202(); + + j9202.setChannel(1); + j9202.setPlaybackType(2); + j9202.setPlaybackSpeed(0); + j9202.setPlaybackTime("230428134100"); + + String s = jt1078Template.controlBackLive("18864197066", j9202, 6); + System.out.println(s); + } + + private static void test9205() { + J9205 j9205 = new J9205(); + j9205.setChannelId(1); + j9205.setStartTime("230428134100"); + j9205.setEndTime("230428134100"); + j9205.setMediaType(0); + j9205.setStreamType(0); + j9205.setStorageType(0); + + String s = jt1078Template.queryBackTime("18864197066", j9205, 6); + System.out.println(s); + } +} diff --git a/web_src/src/components/DeviceList.vue b/web_src/src/components/DeviceList.vue index 29e049daf..bff0d1dd1 100644 --- a/web_src/src/components/DeviceList.vue +++ b/web_src/src/components/DeviceList.vue @@ -25,11 +25,13 @@ + + diff --git a/web_src/src/components/GBRecordDetail.vue b/web_src/src/components/GBRecordDetail.vue index 6fe29a893..f9fce20d8 100644 --- a/web_src/src/components/GBRecordDetail.vue +++ b/web_src/src/components/GBRecordDetail.vue @@ -182,9 +182,11 @@ this.playerStyle["height"] = this.winHeight + "px"; this.chooseDate = moment().format('YYYY-MM-DD') this.dateChange(); + window.addEventListener('beforeunload', this.stopPlayRecord) }, destroyed() { this.$destroy('recordVideoPlayer'); + window.removeEventListener('beforeunload', this.stopPlayRecord) }, methods: { dateChange(){ @@ -338,14 +340,18 @@ }); }, stopPlayRecord: function (callback) { - this.$refs["recordVideoPlayer"].pause(); - this.videoUrl = ''; - this.$axios({ - method: 'get', - url: '/api/playback/stop/' + this.deviceId + "/" + this.channelId + "/" + this.streamId - }).then(function (res) { - if (callback) callback() - }); + console.log("停止录像回放") + if (this.streamId !== "") { + this.$refs["recordVideoPlayer"].pause(); + this.videoUrl = ''; + this.$axios({ + method: 'get', + url: '/api/playback/stop/' + this.deviceId + "/" + this.channelId + "/" + this.streamId + }).then(function (res) { + if (callback) callback() + }); + } + }, getDataWidth(item){ let timeForFile = this.getTimeForFile(item); @@ -423,8 +429,14 @@ return hStr + ":" + mStr + ":" + sStr }, goBack(){ + // 如果正在进行录像回放则,发送停止 + if (this.streamId !== "") { + this.stopPlayRecord(()=> { + this.streamId = ""; + }) + } window.history.go(-1); - } + }, } }; diff --git a/web_src/src/components/channelList.vue b/web_src/src/components/channelList.vue index 563f43f3d..938638654 100644 --- a/web_src/src/components/channelList.vue +++ b/web_src/src/components/channelList.vue @@ -123,7 +123,6 @@ diff --git a/web_src/src/layout/UiHeader.vue b/web_src/src/layout/UiHeader.vue index 3e9cca03d..2cdca02c0 100644 --- a/web_src/src/layout/UiHeader.vue +++ b/web_src/src/layout/UiHeader.vue @@ -40,6 +40,7 @@ import changePasswordDialog from '../components/dialog/changePassword.vue' import userService from '../components/service/UserService' +import {Notification} from 'element-ui'; export default { name: "UiHeader",