diff --git a/.github/ISSUE_TEMPLATE/-------.md b/.github/ISSUE_TEMPLATE/-------.md new file mode 100644 index 000000000..461ce76a6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/-------.md @@ -0,0 +1,10 @@ +--- +name: "[ 新功能 ]" +about: 新功能 +title: '' +labels: '' +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/--bug---.md b/.github/ISSUE_TEMPLATE/--bug---.md new file mode 100644 index 000000000..ff09d5cf2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/--bug---.md @@ -0,0 +1,29 @@ +--- +name: "[ BUG ] " +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**描述错误** +描述下您遇到的问题 + +**如何复现** +有明确复现步骤的问题会很容易被解决 + +**预期行为** +清晰简洁的描述您期望发生的事情 + +**截图** + + +**环境信息:** + - 1. 部署方式 wvp-pro docker / zlm(docker) + 编译wvp-pro/ wvp-prp + zlm都是编译部署/ + - 2. 部署环境 windows / ubuntu/ centos ... + - 3. 端口开放情况 + - 4. 是否是公网部署 + - 5. 是否使用https + - 6. 方便的话提供下使用的设备品牌或平台 + - 7. 你做过哪些尝试 diff --git a/.gitmodules b/.gitmodules index 202b14c87..94f5d6832 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "be.teletask.onvif-java"] path = be.teletask.onvif-java - url = https://gitee.com/18010473990/be.teletask.onvif-java.git + url = https://gitee.com/pan648540858/be.teletask.onvif-java.git diff --git a/DOCKERFILE b/DOCKERFILE index af421661b..c7031740d 100644 --- a/DOCKERFILE +++ b/DOCKERFILE @@ -30,25 +30,25 @@ RUN apt-get update && \ cmake curl vim ca-certificates tzdata libmysqlclient-dev redis-server libssl-dev libx264-dev libfaac-dev ffmpeg WORKDIR /home -RUN git clone https://gitee.com/18010473990/maven.git && \ +RUN git clone https://gitee.com/pan648540858/maven.git && \ cp maven/settings.xml /usr/share/maven/conf/ && \ - git clone https://gitee.com/18010473990/wvp-GB28181.git && \ - git clone https://gitee.com/18010473990/wvp-pro-assist.git + git clone https://gitee.com/pan648540858/wvp-GB28181-pro.git && \ + git clone https://gitee.com/pan648540858/wvp-pro-assist.git # 编译前端界面 -WORKDIR /home/wvp-GB28181/web_src +WORKDIR /home/wvp-GB28181-pro/web_src RUN npm install && \ npm run build && \ mkdir -p /opt/wvp/config && \ mkdir -p /opt/assist/config && \ - cp /home/wvp-GB28181/src/main/resources/application-dev.yml /opt/wvp/config/application.yml && \ + cp /home/wvp-GB28181-pro/src/main/resources/application-docker.yml /opt/wvp/config/application.yml && \ cp /home/wvp-pro-assist/src/main/resources/application-dev.yml /opt/assist/config/application.yml # wvp打包 -WORKDIR /home/wvp-GB28181 +WORKDIR /home/wvp-GB28181-pro RUN mvn compile && \ mvn package && \ - cp /home/wvp-GB28181/target/wvp*.jar /opt/wvp/ + cp /home/wvp-GB28181-pro/target/wvp*.jar /opt/wvp/ # wvp 录像管理打包 WORKDIR /home/wvp-pro-assist @@ -72,7 +72,7 @@ RUN mkdir -p /opt/media && \ # 清理 RUN rm -rf /home/wiki && \ - rm -rf /home/wvp-GB28181 && \ + rm -rf /home/wvp-GB28181-pro && \ apt-get autoremove -y git maven nodejs npm && \ apt-get clean -y && \ rm -rf /var/lib/apt/lists/*dic @@ -89,7 +89,7 @@ RUN echo '#!/bin/bash' > run.sh && \ echo 'if [${WVP_CONFIG}]; then' >> run.sh && \ echo ' java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 ${WVP_CONFIG}' >> run.sh && \ echo 'else' >> run.sh && \ - echo ' java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 --media.ip=127.0.0.1 --media.sdp-ip=${WVP_IP} --sip.ip=${WVP_IP} --media.stream-ip=${WVP_IP}' >> run.sh && \ + echo ' java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 --media.ip=127.0.0.1 --media.hook-ip=127.0.0.1 --media.sdp-ip=${WVP_IP} --sip.ip=${WVP_IP} --media.stream-ip=${WVP_IP}' >> run.sh && \ echo 'fi' >> run.sh RUN chmod +x run.sh diff --git a/README.md b/README.md index 399a47725..e7645c60b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![logo](https://gitee.com/18010473990/wvp-GB28181/raw/wvp-28181-2.0/web_src/static/logo.png) +![logo](https://gitee.com/pan648540858/wvp-GB28181-pro/raw/wvp-28181-2.0/web_src/static/logo.png) # 开箱即用的的28181协议视频平台 [![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit) @@ -13,13 +13,7 @@ WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网 流媒体服务基于ZLMediaKit-https://github.com/xiongziliang/ZLMediaKit 前端页面基于MediaServerUI进行修改. -# 快速体验 -```shell -docker pull 648540858/wvp_pro -docker run --env WVP_IP="你的IP" -it -p 18080:18080 -p 30000-30500:30000-30500/udp -p 30000-30500:30000-30500/tcp -p 80:80 -p 5060:5060 -p 5060:5060/udp 648540858/wvp_pro -``` -docker使用详情查看:[https://hub.docker.com/r/648540858/wvp_pro](https://hub.docker.com/r/648540858/wvp_pro) # 应用场景: 支持浏览器无插件播放摄像头视频。 支持摄像机、平台、NVR等设备接入。 @@ -34,7 +28,7 @@ docker使用详情查看:[https://hub.docker.com/r/648540858/wvp_pro](https:// [https://github.com/648540858/wvp-GB28181-pro/wiki](https://github.com/648540858/wvp-GB28181-pro/wiki) # gitee同步仓库 -https://gitee.com/18010473990/wvp-GB28181.git +https://gitee.com/pan648540858/wvp-GB28181-pro.git # 截图 ![build_1.png](https://github.com/648540858/wiki/blob/master/images/Screenshot_1.png) @@ -112,10 +106,16 @@ https://gitee.com/18010473990/wvp-GB28181.git - [ ] 添加用户管理 - [X] WEB端支持播放H264与H265,音频支持G.711A/G.711U/AAC,覆盖国标常用编码格式。 +# docker快速体验 +```shell +docker pull 648540858/wvp_pro +docker run --env WVP_IP="你的IP" -it -p 18080:18080 -p 30000-30500:30000-30500/udp -p 30000-30500:30000-30500/tcp -p 80:80 -p 5060:5060 -p 5060:5060/udp 648540858/wvp_pro +``` +docker使用详情查看:[https://hub.docker.com/r/648540858/wvp_pro](https://hub.docker.com/r/648540858/wvp_pro) # gitee同步仓库 -https://gitee.com/18010473990/wvp-GB28181.git +https://gitee.com/pan648540858/wvp-GB28181-pro.git # 使用帮助 QQ群: 901799015, 690854210(ZLM大群) diff --git a/docker/wvp/Dockerfile b/docker/wvp/Dockerfile index 6728e4ab4..16d0c81d8 100644 --- a/docker/wvp/Dockerfile +++ b/docker/wvp/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:20.04 AS build -ARG gitUrl="https://gitee.com/18010473990" +ARG gitUrl="https://gitee.com/pan648540858" ARG zlmGitUrl="https://gitee.com/xia-chu/ZLMediaKit" RUN export DEBIAN_FRONTEND=noninteractive &&\ @@ -9,9 +9,9 @@ RUN export DEBIAN_FRONTEND=noninteractive &&\ cmake ca-certificates openssl ffmpeg RUN cd /home && \ - git clone "https://gitee.com/18010473990/maven.git" && \ + git clone "https://gitee.com/pan648540858/maven.git" && \ cp maven/settings.xml /usr/share/maven/conf/ && \ - git clone "${gitUrl}/wvp-GB28181.git" && \ + git clone "${gitUrl}/wvp-GB28181-pro.git" && \ git clone "${gitUrl}/wvp-pro-assist.git" && \ git clone --depth=1 "${zlmGitUrl}" && \ mkdir -p /opt/wvp/config /opt/assist/config /opt/media/www/record @@ -23,7 +23,7 @@ RUN cd /home/wvp-GB28181/web_src && \ RUN cd /home/wvp-GB28181 && \ mvn clean package -Dmaven.test.skip=true && \ cp /home/wvp-GB28181/target/*.jar /opt/wvp/ && \ - cp /home/wvp-GB28181/src/main/resources/application-dev.yml /opt/wvp/config/application.yml + cp /home/wvp-GB28181/src/main/resources/application-docker.yml /opt/wvp/config/application.yml RUN cd /home/wvp-pro-assist && \ mvn clean package -Dmaven.test.skip=true && \ diff --git a/libs/onvif-java-1.0.2.jar b/libs/onvif-java-1.0.2.jar deleted file mode 100644 index dd62a2385..000000000 Binary files a/libs/onvif-java-1.0.2.jar and /dev/null differ diff --git a/pom.xml b/pom.xml index 5cd5f048c..7a04892bc 100644 --- a/pom.xml +++ b/pom.xml @@ -212,17 +212,6 @@ - - - - - be.teletask - onvif-java - 1.0.2 - system - ${project.basedir}/libs/onvif-java-1.0.2.jar - - org.springframework.boot spring-boot-starter-test diff --git a/sql/mysql.sql b/sql/mysql.sql index cd5bbea7c..38cc88140 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -208,6 +208,7 @@ create table stream_proxy enable_hls bit null, enable_mp4 bit null, enable bit not null, + enable_remove_none_reader bit not null, createTime varchar(50) not null, primary key (app, stream) ); diff --git a/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java b/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java index 56038bd53..bfe584194 100644 --- a/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java +++ b/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java @@ -6,6 +6,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import springfox.documentation.oas.annotations.EnableOpenApi; diff --git a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java index c5e2a24a6..e16c1addf 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/common/StreamInfo.java @@ -30,7 +30,7 @@ public class StreamInfo { private String rtsps; private String rtc; private String mediaServerId; - private JSONArray tracks; + private Object tracks; public static class TransactionInfo{ public String callId; @@ -105,11 +105,11 @@ public class StreamInfo { this.rtsp = rtsp; } - public JSONArray getTracks() { + public Object getTracks() { return tracks; } - public void setTracks(JSONArray tracks) { + public void setTracks(Object tracks) { this.tracks = tracks; } 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 5fd9c7725..b40027141 100644 --- a/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java +++ b/src/main/java/com/genersoft/iot/vmp/common/VideoManagerConstants.java @@ -8,7 +8,9 @@ package com.genersoft.iot.vmp.common; */ public class VideoManagerConstants { - public static final String WVP_SERVER_PREFIX = "VMP_wvp_server"; + public static final String WVP_SERVER_PREFIX = "VMP_SIGNALLING_SERVER_INFO_"; + + public static final String WVP_SERVER_STREAM_PUSH_PREFIX = "VMP_SIGNALLING_STREAM_PUSH_"; public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_"; @@ -25,6 +27,7 @@ public class VideoManagerConstants { 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 PLATFORM_KEEPLIVEKEY_PREFIX = "VMP_PLATFORM_KEEPLIVE_"; @@ -51,4 +54,7 @@ public class VideoManagerConstants { public static final String MEDIA_SSRC_USED_PREFIX = "VMP_media_used_ssrc_"; public static final String MEDIA_TRANSACTION_USED_PREFIX = "VMP_media_transaction_"; + + //************************** redis 消息********************************* + public static final String WVP_MSG_STREAM_PUSH_CHANGE_PREFIX = "WVP_MSG_STREAM_PUSH_CHANGE"; } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java b/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java index 145c29b54..10dfc0866 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/DynamicTask.java @@ -26,9 +26,10 @@ public class DynamicTask { return new ThreadPoolTaskScheduler(); } - public String startCron(String key, Runnable task, String corn) { + public String startCron(String key, Runnable task, int cycleForCatalog) { stopCron(key); - ScheduledFuture future = threadPoolTaskScheduler.schedule(task, new CronTrigger(corn)); + // scheduleWithFixedDelay 必须等待上一个任务结束才开始计时period, cycleForCatalog表示执行的间隔 + ScheduledFuture future = threadPoolTaskScheduler.scheduleWithFixedDelay(task, cycleForCatalog * 1000L); futureMap.put(key, future); return "startCron"; } 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 5d8acce9f..dd99d0205 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/ProxyServletConfig.java @@ -163,9 +163,9 @@ public class ProxyServletConfig { * 异常处理 */ @Override - protected void handleRequestException(HttpRequest proxyRequest, HttpResponse proxyResonse, Exception e){ + protected void handleRequestException(HttpRequest proxyRequest, HttpResponse proxyResponse, Exception e){ try { - super.handleRequestException(proxyRequest, proxyResonse, e); + super.handleRequestException(proxyRequest, proxyResponse, e); } catch (ServletException servletException) { logger.error("录像服务 代理失败: ", e); } catch (IOException ioException) { diff --git a/src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java new file mode 100644 index 000000000..2ef332307 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/conf/ThreadPoolTaskConfig.java @@ -0,0 +1,59 @@ +package com.genersoft.iot.vmp.conf; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration +@EnableAsync(proxyTargetClass = true) +public class ThreadPoolTaskConfig { + + public static final int cpuNum = Runtime.getRuntime().availableProcessors(); + + /** + * 默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务, + * 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中; + * 当队列满了,就继续创建线程,当线程数量大于等于maxPoolSize后,开始使用拒绝策略拒绝 + */ + + /** + * 核心线程数(默认线程数) + */ + private static final int corePoolSize = cpuNum; + /** + * 最大线程数 + */ + private static final int maxPoolSize = cpuNum*2; + /** + * 允许线程空闲时间(单位:默认为秒) + */ + private static final int keepAliveTime = 30; + /** + * 缓冲队列大小 + */ + private static final int queueCapacity = 500; + /** + * 线程池名前缀 + */ + private static final String threadNamePrefix = "wvp-sip-handle-"; + + @Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名 + public ThreadPoolTaskExecutor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxPoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setKeepAliveSeconds(keepAliveTime); + executor.setThreadNamePrefix(threadNamePrefix); + + // 线程池对拒绝任务的处理策略 + // CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 初始化 + executor.initialize(); + return executor; + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/UserSetup.java b/src/main/java/com/genersoft/iot/vmp/conf/UserSetup.java index 8b1b5b0fe..13831b452 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/UserSetup.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/UserSetup.java @@ -27,6 +27,8 @@ public class UserSetup { private Boolean logInDatebase = Boolean.TRUE; + private String serverId = "000000"; + private List interfaceAuthenticationExcludes = new ArrayList<>(); public Boolean getSavePositionHistory() { @@ -104,4 +106,12 @@ public class UserSetup { public void setLogInDatebase(Boolean logInDatebase) { this.logInDatebase = logInDatebase; } + + public String getServerId() { + return serverId; + } + + public void setServerId(String serverId) { + this.serverId = serverId; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/conf/VManagerConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/VManagerConfig.java deleted file mode 100644 index 85c158c08..000000000 --- a/src/main/java/com/genersoft/iot/vmp/conf/VManagerConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.genersoft.iot.vmp.conf; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; - -/** - * @description: 获取数据库配置 - * @author: swwheihei - * @date: 2020年5月6日 下午2:46:00 - */ -@Configuration("vmConfig") -public class VManagerConfig { - - @Value("${spring.application.database:redis}") - private String database; - - - public String getDatabase() { - return database; - } - - public void setDatabase(String database) { - this.database = database; - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/conf/WVPTimerTask.java b/src/main/java/com/genersoft/iot/vmp/conf/WVPTimerTask.java index 72f0db6c2..3e72e29e0 100644 --- a/src/main/java/com/genersoft/iot/vmp/conf/WVPTimerTask.java +++ b/src/main/java/com/genersoft/iot/vmp/conf/WVPTimerTask.java @@ -1,7 +1,10 @@ package com.genersoft.iot.vmp.conf; +import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -12,13 +15,22 @@ public class WVPTimerTask { private IRedisCatchStorage redisCatchStorage; @Autowired - private SipConfig sipConfig; + private IMediaServerService mediaServerService; @Autowired - private MediaConfig mediaConfig; + private UserSetup userSetup; -// @Scheduled(cron="0/2 * * * * ? ") //每3秒执行一次 -// public void execute(){ -//// redisCatchStorage.updateWVPInfo(); -// } + @Value("${server.port}") + private int serverPort; + + @Autowired + private SipConfig sipConfig; + + @Scheduled(fixedRate = 2 * 1000) //每3秒执行一次 + public void execute(){ + JSONObject jsonObject = new JSONObject(); + jsonObject.put("ip", sipConfig.getIp()); + jsonObject.put("port", serverPort); + redisCatchStorage.updateWVPInfo(userSetup.getServerId(), jsonObject, 3); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java index 3d9b827bf..420a30a7a 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/SipLayer.java @@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; +import com.genersoft.iot.vmp.gb28181.transmit.ISIPProcessorObserver; import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver; import gov.nist.javax.sip.SipProviderImpl; import gov.nist.javax.sip.SipStackImpl; @@ -28,28 +29,12 @@ public class SipLayer{ private SipConfig sipConfig; @Autowired - private SIPProcessorObserver sipProcessorObserver; - - @Autowired - private SipSubscribe sipSubscribe; + private ISIPProcessorObserver sipProcessorObserver; private SipStackImpl sipStack; private SipFactory sipFactory; - /** - * 消息处理器线程池 - */ - private ThreadPoolExecutor processThreadPool; - - public SipLayer() { - int processThreadNum = Runtime.getRuntime().availableProcessors() * 10; - LinkedBlockingQueue processQueue = new LinkedBlockingQueue<>(10000); - processThreadPool = new ThreadPoolExecutor(processThreadNum,processThreadNum, - 0L,TimeUnit.MILLISECONDS,processQueue, - new ThreadPoolExecutor.CallerRunsPolicy()); - } - @Bean("sipFactory") private SipFactory createSipFactory() { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java index dcac49d3b..24fc22128 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/RecordInfo.java @@ -16,6 +16,8 @@ public class RecordInfo { private String channelId; + private String sn; + private String name; private int sumNum; @@ -61,4 +63,12 @@ public class RecordInfo { public void setChannelId(String channelId) { this.channelId = channelId; } + + public String getSn() { + return sn; + } + + public void setSn(String sn) { + this.sn = sn; + } } 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 4245bddb6..f341548ee 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 @@ -6,6 +6,8 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.sip.*; +import javax.sip.header.CallIdHeader; +import javax.sip.message.Response; import java.util.Calendar; import java.util.Date; import java.util.Map; @@ -23,6 +25,7 @@ public class SipSubscribe { private Map timeSubscribes = new ConcurrentHashMap<>(); // @Scheduled(cron="*/5 * * * * ?") //每五秒执行一次 +// @Scheduled(fixedRate= 100 * 60 * 60 ) @Scheduled(cron="0 0 * * * ?") //每小时执行一次, 每个整点 public void execute(){ logger.info("[定时任务] 清理过期的订阅信息"); @@ -58,11 +61,15 @@ public class SipSubscribe { this.event = event; if (event instanceof ResponseEvent) { ResponseEvent responseEvent = (ResponseEvent)event; - this.type = "response"; - this.msg = responseEvent.getResponse().getReasonPhrase(); - this.statusCode = responseEvent.getResponse().getStatusCode(); - this.callId = responseEvent.getDialog().getCallId().getCallId(); - this.dialog = responseEvent.getDialog(); + Response response = responseEvent.getResponse(); + this.dialog = responseEvent.getDialog(); + this.type = "response"; + if (response != null) { + this.msg = response.getReasonPhrase(); + this.statusCode = response.getStatusCode(); + } + this.callId = ((CallIdHeader)response.getHeader(CallIdHeader.NAME)).getCallId(); + }else if (event instanceof TimeoutEvent) { TimeoutEvent timeoutEvent = (TimeoutEvent)event; this.type = "timeout"; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/event/platformKeepaliveExpire/PlatformKeepaliveExpireEventLister.java b/src/main/java/com/genersoft/iot/vmp/gb28181/event/platformKeepaliveExpire/PlatformKeepaliveExpireEventLister.java index 6522387d9..00926574d 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/event/platformKeepaliveExpire/PlatformKeepaliveExpireEventLister.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/event/platformKeepaliveExpire/PlatformKeepaliveExpireEventLister.java @@ -66,6 +66,7 @@ public class PlatformKeepaliveExpireEventLister implements ApplicationListener

(msg.getData(),HttpStatus.OK)); + result.setResult(ResponseEntity.ok().body(msg.getData())); } map.remove(msg.getKey()); 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 9f4137796..6e96dac31 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 @@ -1,5 +1,6 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd; +import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; @@ -121,6 +122,26 @@ public interface ISIPCommander { void streamByeCmd(String deviceId, String channelId, SipSubscribe.Event okEvent); void streamByeCmd(String deviceId, String channelId); + /** + * 回放暂停 + */ + void playPauseCmd(Device device, StreamInfo streamInfo); + + /** + * 回放恢复 + */ + void playResumeCmd(Device device, StreamInfo streamInfo); + + /** + * 回放拖动播放 + */ + void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime); + + /** + * 回放倍速播放 + */ + void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed); + /** * 语音广播 * @@ -235,8 +256,9 @@ public interface ISIPCommander { * @param device 视频设备 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss + * @param sn */ - boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime); + boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, SipSubscribe.Event errorEvent); /** * 查询报警信息 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 bb62902a3..98ea7c9c5 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 @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd; import java.text.ParseException; import java.util.ArrayList; +import javax.sip.Dialog; import javax.sip.InvalidArgumentException; import javax.sip.PeerUnavailableException; import javax.sip.SipFactory; @@ -11,6 +12,9 @@ import javax.sip.address.SipURI; import javax.sip.header.*; import javax.sip.message.Request; +import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager; +import com.genersoft.iot.vmp.vmanager.gb28181.session.InfoCseqCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -30,6 +34,9 @@ public class SIPRequestHeaderProvider { @Autowired private SipFactory sipFactory; + + @Autowired + private VideoStreamSessionManager streamSession; public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException { Request request = null; @@ -210,4 +217,50 @@ public class SIPRequestHeaderProvider { request.setContent(content, contentTypeHeader); return request; } + + public Request createInfoRequest(Device device, StreamInfo streamInfo, String content) + throws PeerUnavailableException, ParseException, InvalidArgumentException { + Request request = null; + Dialog dialog = streamSession.getDialog(streamInfo.getDeviceID(), streamInfo.getChannelId()); + + SipURI requestLine = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(), + device.getHostAddress()); + // via + ArrayList viaHeaders = new ArrayList(); + ViaHeader viaHeader = sipFactory.createHeaderFactory().createViaHeader(device.getIp(), device.getPort(), + device.getTransport(), null); + viaHeader.setRPort(); + viaHeaders.add(viaHeader); + // from + SipURI fromSipURI = sipFactory.createAddressFactory().createSipURI(sipConfig.getId(), + sipConfig.getDomain()); + Address fromAddress = sipFactory.createAddressFactory().createAddress(fromSipURI); + FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(fromAddress, dialog.getLocalTag()); + // to + SipURI toSipURI = sipFactory.createAddressFactory().createSipURI(streamInfo.getChannelId(), + sipConfig.getDomain()); + Address toAddress = sipFactory.createAddressFactory().createAddress(toSipURI); + ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(toAddress, dialog.getRemoteTag()); + + // callid + CallIdHeader callIdHeader = dialog.getCallId(); + + // Forwards + MaxForwardsHeader maxForwards = sipFactory.createHeaderFactory().createMaxForwardsHeader(70); + + // ceq + CSeqHeader cSeqHeader = sipFactory.createHeaderFactory() + .createCSeqHeader(InfoCseqCache.CSEQCACHE.get(streamInfo.getStreamId()), Request.INFO); + + request = sipFactory.createMessageFactory().createRequest(requestLine, Request.INFO, callIdHeader, cSeqHeader, + fromHeader, toHeader, viaHeaders, maxForwards); + Address concatAddress = sipFactory.createAddressFactory().createAddress(sipFactory.createAddressFactory() + .createSipURI(sipConfig.getId(), sipConfig.getIp() + ":" + sipConfig.getPort())); + request.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress)); + + ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("Application", + "MANSRTSP"); + request.setContent(content, contentTypeHeader); + 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 7a0e901fe..2f90deed1 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.fastjson.JSONObject; +import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.UserSetup; import com.genersoft.iot.vmp.gb28181.bean.Device; @@ -17,6 +18,7 @@ import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; +import com.genersoft.iot.vmp.vmanager.gb28181.session.InfoCseqCache; import gov.nist.javax.sip.SipProviderImpl; import gov.nist.javax.sip.SipStackImpl; import gov.nist.javax.sip.message.SIPRequest; @@ -1194,14 +1196,15 @@ public class SIPCommander implements ISIPCommander { * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss */ @Override - public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime) { - + public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, SipSubscribe.Event errorEvent) { + + try { StringBuffer recordInfoXml = new StringBuffer(200); recordInfoXml.append("\r\n"); recordInfoXml.append("\r\n"); recordInfoXml.append("RecordInfo\r\n"); - recordInfoXml.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); + recordInfoXml.append("" + sn + "\r\n"); recordInfoXml.append("" + channelId + "\r\n"); recordInfoXml.append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "\r\n"); recordInfoXml.append("" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "\r\n"); @@ -1218,7 +1221,7 @@ public class SIPCommander implements ISIPCommander { Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(), "z9hG4bK-ViaRecordInfo-" + tm, "fromRec" + tm, null, callIdHeader); - transmitRequest(device, request); + transmitRequest(device, request, errorEvent); } catch (SipException | ParseException | InvalidArgumentException e) { e.printStackTrace(); return false; @@ -1486,7 +1489,7 @@ public class SIPCommander implements ISIPCommander { StringBuffer cmdXml = new StringBuffer(200); cmdXml.append("\r\n"); cmdXml.append("\r\n"); - cmdXml.append("CataLog\r\n"); + cmdXml.append("Catalog\r\n"); cmdXml.append("" + (int)((Math.random()*9+1)*100000) + "\r\n"); cmdXml.append("" + device.getDeviceId() + "\r\n"); cmdXml.append("\r\n"); @@ -1496,7 +1499,7 @@ public class SIPCommander implements ISIPCommander { CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId() : udpSipProvider.getNewCallId(); - Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), "z9hG4bK-viaPos-" + tm, "fromTagPos" + tm, null, device.getSubscribeCycleForCatalog(), "presence" , callIdHeader); + Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), "z9hG4bK-viaPos-" + tm, "fromTagPos" + tm, null, device.getSubscribeCycleForCatalog(), "Catalog" , callIdHeader); transmitRequest(device, request, errorEvent, okEvent); return true; @@ -1543,4 +1546,111 @@ public class SIPCommander implements ISIPCommander { clientTransaction.sendRequest(); return clientTransaction; } + + /** + * 回放暂停 + */ + @Override + public void playPauseCmd(Device device, StreamInfo streamInfo) { + try { + + StringBuffer content = new StringBuffer(200); + content.append("PAUSE RTSP/1.0\r\n"); + content.append("CSeq: " + InfoCseqCache.CSEQCACHE.get(streamInfo.getStreamId()) + "\r\n"); + content.append("PauseTime: now\r\n"); + Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString()); + logger.info(request.toString()); + ClientTransaction clientTransaction = null; + if ("TCP".equals(device.getTransport())) { + clientTransaction = tcpSipProvider.getNewClientTransaction(request); + } else if ("UDP".equals(device.getTransport())) { + clientTransaction = udpSipProvider.getNewClientTransaction(request); + } + if (clientTransaction != null) { + clientTransaction.sendRequest(); + } + + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + } + + /** + * 回放恢复 + */ + @Override + public void playResumeCmd(Device device, StreamInfo streamInfo) { + try { + StringBuffer content = new StringBuffer(200); + content.append("PLAY RTSP/1.0\r\n"); + content.append("CSeq: " + InfoCseqCache.CSEQCACHE.get(streamInfo.getStreamId()) + "\r\n"); + content.append("Range: npt=now-\r\n"); + Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString()); + logger.info(request.toString()); + ClientTransaction clientTransaction = null; + if ("TCP".equals(device.getTransport())) { + clientTransaction = tcpSipProvider.getNewClientTransaction(request); + } else if ("UDP".equals(device.getTransport())) { + clientTransaction = udpSipProvider.getNewClientTransaction(request); + } + + clientTransaction.sendRequest(); + + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + } + + /** + * 回放拖动播放 + */ + @Override + public void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) { + try { + StringBuffer content = new StringBuffer(200); + content.append("PLAY RTSP/1.0\r\n"); + content.append("CSeq: " + InfoCseqCache.CSEQCACHE.get(streamInfo.getStreamId()) + "\r\n"); + content.append("Range: npt=" + Math.abs(seekTime) + "-\r\n"); + + Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString()); + logger.info(request.toString()); + ClientTransaction clientTransaction = null; + if ("TCP".equals(device.getTransport())) { + clientTransaction = tcpSipProvider.getNewClientTransaction(request); + } else if ("UDP".equals(device.getTransport())) { + clientTransaction = udpSipProvider.getNewClientTransaction(request); + } + + clientTransaction.sendRequest(); + + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + } + + /** + * 回放倍速播放 + */ + @Override + public void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) { + try { + StringBuffer content = new StringBuffer(200); + content.append("PLAY RTSP/1.0\r\n"); + content.append("CSeq: " + InfoCseqCache.CSEQCACHE.get(streamInfo.getStreamId()) + "\r\n"); + content.append("Scale: " + String.format("%.1f",speed) + "\r\n"); + Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString()); + logger.info(request.toString()); + ClientTransaction clientTransaction = null; + if ("TCP".equals(device.getTransport())) { + clientTransaction = tcpSipProvider.getNewClientTransaction(request); + } else if ("UDP".equals(device.getTransport())) { + clientTransaction = udpSipProvider.getNewClientTransaction(request); + } + + clientTransaction.sendRequest(); + + } catch (SipException | ParseException | InvalidArgumentException e) { + e.printStackTrace(); + } + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java index 8363d6f33..127ef29a1 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/AckRequestProcessor.java @@ -25,10 +25,8 @@ import javax.sip.header.ToHeader; import java.util.HashMap; import java.util.Map; -/** - * @description:ACK请求处理器 - * @author: swwheihei - * @date: 2020年5月3日 下午5:31:45 +/** + * SIP命令类型: ACK请求 */ @Component public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { 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 c97b55a21..feb44c540 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 @@ -28,15 +28,14 @@ import java.text.ParseException; import java.util.HashMap; import java.util.Map; -/** - * @description: BYE请求处理器 - * @author: lawrencehj - * @date: 2021年3月9日 +/** + * SIP命令类型: BYE请求 */ @Component public class ByeRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { - private Logger logger = LoggerFactory.getLogger(ByeRequestProcessor.class); + private final Logger logger = LoggerFactory.getLogger(ByeRequestProcessor.class); + private final String method = "BYE"; @Autowired private ISIPCommander cmder; @@ -53,8 +52,6 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In @Autowired private IMediaServerService mediaServerService; - private String method = "BYE"; - @Autowired private SIPProcessorObserver sipProcessorObserver; diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java index 2e98e335f..0a818ee6e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/CancelRequestProcessor.java @@ -9,10 +9,8 @@ import org.springframework.stereotype.Component; import javax.sip.RequestEvent; -/** - * @description:CANCEL请求处理器 - * @author: swwheihei - * @date: 2020年5月3日 下午5:32:23 +/** + * SIP命令类型: CANCEL请求 */ @Component public class CancelRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { 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 5eda75dda..1cb4af597 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 @@ -33,10 +33,8 @@ import javax.sip.message.Response; import java.text.ParseException; import java.util.Vector; -/** - * @description:处理INVITE请求 - * @author: panll - * @date: 2021年1月14日 +/** + * SIP命令类型: INVITE请求 */ @SuppressWarnings("rawtypes") @Component @@ -140,12 +138,21 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements // 解析sdp消息, 使用jainsip 自带的sdp解析方式 String contentString = new String(request.getRawContent()); - // jainSip不支持y=字段, 移除移除以解析。 + // jainSip不支持y=字段, 移除以解析。 int ssrcIndex = contentString.indexOf("y="); - //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 - String ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12); - String substring = contentString.substring(0, contentString.indexOf("y=")); - SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring); + // 检查是否有y字段 + String ssrcDefault = "0000000000"; + String ssrc; + SessionDescription sdp; + 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); + } // 获取支持的格式 Vector mediaDescriptions = sdp.getMediaDescriptions(true); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/MessageRequestProcessor1.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/MessageRequestProcessor1.java index 847f7e18c..2a908414e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/MessageRequestProcessor1.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/MessageRequestProcessor1.java @@ -914,21 +914,20 @@ public class MessageRequestProcessor1 extends SIPRequestProcessorParent implemen String uuid = UUID.randomUUID().toString().replace("-", ""); RecordInfo recordInfo = new RecordInfo(); Element rootElement = getRootElement(evt); - Element deviceIdElement = rootElement.element("DeviceID"); - String channelId = deviceIdElement.getText().toString(); - String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + channelId; + String sn = getText(rootElement, "SN"); + String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn; if (device != null ) { rootElement = getRootElement(evt, device.getCharset()); } recordInfo.setDeviceId(deviceId); - recordInfo.setChannelId(channelId); + recordInfo.setSn(sn); recordInfo.setName(getText(rootElement, "Name")); if (getText(rootElement, "SumNum")== null || getText(rootElement, "SumNum") =="") { recordInfo.setSumNum(0); } else { recordInfo.setSumNum(Integer.parseInt(getText(rootElement, "SumNum"))); } - String sn = getText(rootElement, "SN"); + Element recordListElement = rootElement.element("RecordList"); if (recordListElement == null || recordInfo.getSumNum() == 0) { logger.info("无录像数据"); 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 de97c1266..faa392413 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 @@ -35,9 +35,7 @@ import java.text.ParseException; import java.util.Iterator; /** - * @description: Notify请求处理器 - * @author: lawrencehj - * @date: 2021年1月27日 + * SIP命令类型: NOTIFY请求 */ @Component public class NotifyRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { @@ -230,8 +228,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader); Element rootElement = getRootElement(evt); - Element deviceIdElement = rootElement.element("DeviceID"); - String channelId = deviceIdElement.getText(); Device device = storager.queryVideoDevice(deviceId); if (device == null) { return; @@ -254,22 +250,23 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements continue; } Element eventElement = itemDevice.element("Event"); + DeviceChannel channel = channelContentHander(itemDevice); switch (eventElement.getText().toUpperCase()) { case "ON" : // 上线 - logger.info("收到来自设备【{}】的通道上线【{}】通知", device.getDeviceId(), channelId); - storager.deviceChannelOnline(deviceId, channelId); + logger.info("收到来自设备【{}】的通道【{}】上线通知", device.getDeviceId(), channel.getChannelId()); + storager.deviceChannelOnline(deviceId, channel.getChannelId()); // 回复200 OK responseAck(evt, Response.OK); break; case "OFF" : // 离线 - logger.info("收到来自设备【{}】的通道离线【{}】通知", device.getDeviceId(), channelId); - storager.deviceChannelOffline(deviceId, channelId); + logger.info("收到来自设备【{}】的通道【{}】离线通知", device.getDeviceId(), channel.getChannelId()); + storager.deviceChannelOffline(deviceId, channel.getChannelId()); // 回复200 OK responseAck(evt, Response.OK); break; case "VLOST" : // 视频丢失 - logger.info("收到来自设备【{}】的通道视频丢失【{}】通知", device.getDeviceId(), channelId); - storager.deviceChannelOffline(deviceId, channelId); + logger.info("收到来自设备【{}】的通道【{}】视频丢失通知", device.getDeviceId(), channel.getChannelId()); + storager.deviceChannelOffline(deviceId, channel.getChannelId()); // 回复200 OK responseAck(evt, Response.OK); break; @@ -278,19 +275,17 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements responseAck(evt, Response.OK); break; case "ADD" : // 增加 - logger.info("收到来自设备【{}】的增加通道【{}】通知", device.getDeviceId(), channelId); - DeviceChannel deviceChannel = channelContentHander(itemDevice, channelId); - storager.updateChannel(deviceId, deviceChannel); + logger.info("收到来自设备【{}】的增加通道【{}】通知", device.getDeviceId(), channel.getChannelId()); + storager.updateChannel(deviceId, channel); responseAck(evt, Response.OK); break; case "DEL" : // 删除 - logger.info("收到来自设备【{}】的删除通道【{}】通知", device.getDeviceId(), channelId); - storager.delChannel(deviceId, channelId); + logger.info("收到来自设备【{}】的删除通道【{}】通知", device.getDeviceId(), channel.getChannelId()); + storager.delChannel(deviceId, channel.getChannelId()); responseAck(evt, Response.OK); break; case "UPDATE" : // 更新 - logger.info("收到来自设备【{}】的更新通道【{}】通知", device.getDeviceId(), channelId); - DeviceChannel channel = channelContentHander(itemDevice, channelId); + logger.info("收到来自设备【{}】的更新通道【{}】通知", device.getDeviceId(), channel.getChannelId()); storager.updateChannel(deviceId, channel); responseAck(evt, Response.OK); break; @@ -316,13 +311,15 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements } } - public DeviceChannel channelContentHander(Element itemDevice, String channelId){ + public DeviceChannel channelContentHander(Element itemDevice){ Element channdelNameElement = itemDevice.element("Name"); String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim().toString() : ""; Element statusElement = itemDevice.element("Status"); String status = statusElement != null ? statusElement.getTextTrim().toString() : "ON"; DeviceChannel deviceChannel = new DeviceChannel(); deviceChannel.setName(channelName); + Element channdelIdElement = itemDevice.element("DeviceID"); + String channelId = channdelIdElement != null ? channdelIdElement.getTextTrim().toString() : ""; deviceChannel.setChannelId(channelId); // ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理 if (status.equals("ON") || status.equals("On") || status.equals("ONLINE")) { 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 10e99cb7d..3f14e23e8 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 @@ -35,10 +35,8 @@ import java.text.ParseException; import java.util.Calendar; import java.util.Locale; -/** - * @description:收到注册请求 处理 - * @author: swwheihei - * @date: 2020年5月3日 下午4:47:25 +/** + * SIP命令类型: REGISTER请求 */ @Component public class RegisterRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java index ba835dbe9..be4b2ce99 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/SubscribeRequestProcessor.java @@ -18,10 +18,8 @@ import javax.sip.message.Request; import javax.sip.message.Response; import java.text.ParseException; -/** - * @description:SUBSCRIBE请求处理器 - * @author: swwheihei - * @date: 2020年5月3日 下午5:31:20 +/** + * SIP命令类型: SUBSCRIBE请求 */ @Component public class SubscribeRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java index efc8259cf..afaa7cb39 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/MessageHandlerAbstract.java @@ -14,10 +14,7 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText; public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent implements IMessageHandler{ - public static Map messageHandlerMap = new ConcurrentHashMap<>(); - - @Autowired - public MessageRequestProcessor messageRequestProcessor; + public Map messageHandlerMap = new ConcurrentHashMap<>(); public void addHandler(String cmdType, IMessageHandler messageHandler) { messageHandlerMap.put(cmdType, messageHandler); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/ControlMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/ControlMessageHandler.java index b533082a0..235a47705 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/ControlMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/ControlMessageHandler.java @@ -6,6 +6,12 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +/** + * 命令类型: 控制命令 + * 命令类型: 设备控制: 远程启动, 录像控制(TODO), 报警布防/撤防命令(TODO), 报警复位命令(TODO), + * 强制关键帧命令(TODO), 拉框放大/缩小控制命令(TODO), 看守位控制(TODO), 报警复位(TODO) + * 命令类型: 设备配置: SVAC编码配置(TODO), 音频参数(TODO), SVAC解码配置(TODO) + */ @Component public class ControlMessageHandler extends MessageHandlerAbstract implements InitializingBean { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceControlQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java similarity index 95% rename from src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceControlQueryMessageHandler.java rename to src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java index 1c4a166b2..980ec5d79 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/DeviceControlQueryMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/control/cmd/DeviceControlQueryMessageHandler.java @@ -1,4 +1,4 @@ -package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd; +package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.control.cmd; import com.genersoft.iot.vmp.VManageBootstrap; import com.genersoft.iot.vmp.gb28181.bean.Device; @@ -7,6 +7,7 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform; import com.genersoft.iot.vmp.gb28181.transmit.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.control.ControlMessageHandler; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.QueryMessageHandler; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import com.genersoft.iot.vmp.utils.SpringBeanFactory; @@ -37,7 +38,7 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent private final String cmdType = "DeviceControl"; @Autowired - private QueryMessageHandler queryMessageHandler; + private ControlMessageHandler controlMessageHandler; @Autowired private IVideoManagerStorager storager; @@ -50,7 +51,7 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent @Override public void afterPropertiesSet() throws Exception { - queryMessageHandler.addHandler(cmdType, this); + controlMessageHandler.addHandler(cmdType, this); } @Override diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/NotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/NotifyMessageHandler.java index 56c020bec..c546057ee 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/NotifyMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/NotifyMessageHandler.java @@ -1,14 +1,23 @@ package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify; import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageHandlerAbstract; +import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageRequestProcessor; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +/** + * 命令类型: 通知命令 + * 命令类型: 状态信息(心跳)报送, 报警通知, 媒体通知, 移动设备位置数据,语音广播通知(TODO), 设备预置位(TODO) + */ @Component public class NotifyMessageHandler extends MessageHandlerAbstract implements InitializingBean { private final String messageType = "Notify"; + @Autowired + private MessageRequestProcessor messageRequestProcessor; + @Override public void afterPropertiesSet() throws Exception { messageRequestProcessor.addHandler(messageType, this); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java index 497f421b5..c6c1ab9c5 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/notify/cmd/CatalogNotifyMessageHandler.java @@ -46,9 +46,6 @@ public class CatalogNotifyMessageHandler extends SIPRequestProcessorParent imple @Autowired private SipConfig config; - @Autowired - private EventPublisher publisher; - @Override public void afterPropertiesSet() throws Exception { notifyMessageHandler.addHandler(cmdType, this); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/QueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/QueryMessageHandler.java index ab111b550..9a29955c9 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/QueryMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/QueryMessageHandler.java @@ -6,6 +6,10 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +/** + * 命令类型: 查询指令 + * 命令类型: 设备状态, 设备目录信息, 设备信息, 文件目录检索(TODO), 报警(TODO), 设备配置(TODO), 设备预置位(TODO), 移动设备位置数据(TODO) + */ @Component public class QueryMessageHandler extends MessageHandlerAbstract implements InitializingBean { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/AlarmQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/AlarmQueryMessageHandler.java new file mode 100644 index 000000000..60cf4d073 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/AlarmQueryMessageHandler.java @@ -0,0 +1,77 @@ +package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.query.cmd; + +import com.genersoft.iot.vmp.conf.SipConfig; +import com.genersoft.iot.vmp.gb28181.bean.Device; +import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; +import com.genersoft.iot.vmp.gb28181.bean.GbStream; +import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; +import com.genersoft.iot.vmp.gb28181.event.EventPublisher; +import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; +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.query.QueryMessageHandler; +import com.genersoft.iot.vmp.storager.IVideoManagerStorager; +import com.genersoft.iot.vmp.vmanager.gb28181.platform.bean.ChannelReduce; +import org.dom4j.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sip.InvalidArgumentException; +import javax.sip.RequestEvent; +import javax.sip.SipException; +import javax.sip.header.FromHeader; +import javax.sip.message.Response; +import java.text.ParseException; +import java.util.List; + +@Component +public class AlarmQueryMessageHandler extends SIPRequestProcessorParent implements InitializingBean, IMessageHandler { + + private Logger logger = LoggerFactory.getLogger(AlarmQueryMessageHandler.class); + private final String cmdType = "Alarm"; + + @Autowired + private QueryMessageHandler queryMessageHandler; + + @Autowired + private IVideoManagerStorager storager; + + @Autowired + private SIPCommanderFroPlatform cmderFroPlatform; + + @Autowired + private SipConfig config; + + @Autowired + private EventPublisher publisher; + + @Override + public void afterPropertiesSet() throws Exception { + queryMessageHandler.addHandler(cmdType, this); + } + + @Override + public void handForDevice(RequestEvent evt, Device device, Element element) { + + } + + @Override + public void handForPlatform(RequestEvent evt, ParentPlatform parentPlatform, Element rootElement) { + + logger.info("不支持alarm查询"); + try { + responseAck(evt, Response.NOT_FOUND, "not support alarm query"); + } catch (SipException e) { + e.printStackTrace(); + } catch (InvalidArgumentException e) { + e.printStackTrace(); + } catch (ParseException e) { + e.printStackTrace(); + } + + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/ResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/ResponseMessageHandler.java index 13c8ac760..18da9cde9 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/ResponseMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/ResponseMessageHandler.java @@ -6,6 +6,10 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +/** + * 命令类型: 请求动作的应答 + * 命令类型: 设备控制, 报警通知, 设备目录信息查询, 目录信息查询, 目录收到, 设备信息查询, 设备状态信息查询 ...... + */ @Component public class ResponseMessageHandler extends MessageHandlerAbstract implements InitializingBean { diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java index f1919da4d..f0f842138 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/RecordInfoResponseMessageHandler.java @@ -64,18 +64,16 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent rootElement = getRootElement(evt, device.getCharset()); String uuid = UUID.randomUUID().toString().replace("-", ""); RecordInfo recordInfo = new RecordInfo(); - Element deviceIdElement = rootElement.element("DeviceID"); - String channelId = deviceIdElement.getText(); - String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + device.getDeviceId() + channelId; + String sn = getText(rootElement, "SN"); + String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + device.getDeviceId() + sn; recordInfo.setDeviceId(device.getDeviceId()); - recordInfo.setChannelId(channelId); + recordInfo.setSn(sn); recordInfo.setName(getText(rootElement, "Name")); if (getText(rootElement, "SumNum") == null || getText(rootElement, "SumNum") == "") { recordInfo.setSumNum(0); } else { recordInfo.setSumNum(Integer.parseInt(getText(rootElement, "SumNum"))); } - String sn = getText(rootElement, "SN"); Element recordListElement = rootElement.element("RecordList"); if (recordListElement == null || recordInfo.getSumNum() == 0) { logger.info("无录像数据"); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java index d44c1a9d1..64933b80b 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/response/impl/ByeResponseProcessor.java @@ -42,7 +42,6 @@ public class ByeResponseProcessor extends SIPResponseProcessorAbstract { @Override public void process(ResponseEvent evt) { // TODO Auto-generated method stub - System.out.println("收到bye"); } 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 f74dcfa76..233416a8d 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 @@ -3,13 +3,18 @@ package com.genersoft.iot.vmp.media.zlm; import java.util.List; import java.util.UUID; +import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.conf.MediaConfig; import com.genersoft.iot.vmp.conf.UserSetup; import com.genersoft.iot.vmp.gb28181.bean.Device; +import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; +import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.service.IMediaServerService; +import com.genersoft.iot.vmp.service.IMediaService; +import com.genersoft.iot.vmp.service.IStreamProxyService; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; @@ -56,6 +61,12 @@ public class ZLMHttpHookListener { @Autowired private IMediaServerService mediaServerService; + @Autowired + private IStreamProxyService streamProxyService; + + @Autowired + private IMediaService mediaService; + @Autowired private ZLMRESTfulUtils zlmresTfulUtils; @@ -153,12 +164,20 @@ public class ZLMHttpHookListener { subscribe.response(mediaInfo, json); } } + String app = json.getString("app"); + String stream = json.getString("stream"); + StreamInfo streamInfo = redisCatchStorage.queryPlaybackByStreamId(stream); JSONObject ret = new JSONObject(); + // 录像回放时不进行录像下载 + if (streamInfo != null) { + ret.put("enableMP4", false); + }else { + ret.put("enableMP4", userSetup.isRecordPushLive()); + } ret.put("code", 0); ret.put("msg", "success"); ret.put("enableHls", true); ret.put("enableMP4", userSetup.isRecordPushLive()); - ret.put("enableRtxp", true); return new ResponseEntity(ret.toString(), HttpStatus.OK); } @@ -254,12 +273,13 @@ public class ZLMHttpHookListener { */ @ResponseBody @PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") - public ResponseEntity onStreamChanged(@RequestBody JSONObject json){ + public ResponseEntity onStreamChanged(@RequestBody MediaItem item){ if (logger.isDebugEnabled()) { - logger.debug("ZLM HOOK on_stream_changed API调用,参数:" + json.toString()); + logger.debug("ZLM HOOK on_stream_changed API调用,参数:" + JSONObject.toJSONString(item)); } - String mediaServerId = json.getString("mediaServerId"); + String mediaServerId = item.getMediaServerId(); + JSONObject json = (JSONObject) JSON.toJSON(item); ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, json); if (subscribe != null ) { MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); @@ -268,13 +288,12 @@ public class ZLMHttpHookListener { } } - // 流消失移除redis play - String app = json.getString("app"); - String streamId = json.getString("stream"); - String schema = json.getString("schema"); - JSONArray tracks = json.getJSONArray("tracks"); - boolean regist = json.getBoolean("regist"); + String app = item.getApp(); + String streamId = item.getStream(); + String schema = item.getSchema(); + List tracks = item.getTracks(); + boolean regist = item.isRegist(); if (tracks != null) { logger.info("[stream: " + streamId + "] on_stream_changed->>" + schema); } @@ -294,12 +313,34 @@ public class ZLMHttpHookListener { redisCatchStorage.stopPlayback(streamInfo); } }else { - if (!"rtp".equals(app) ){ + if (!"rtp".equals(app)){ + + boolean pushChange = false; + MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); if (regist) { - zlmMediaListManager.addMedia(mediaServerItem, app, streamId); + if ((item.getOriginType() == 1 || item.getOriginType() == 2 || item.getOriginType() == 8)) { + pushChange = true; + zlmMediaListManager.addMedia(item); + StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem, app, streamId, tracks); + redisCatchStorage.addPushStream(mediaServerItem, app, streamId, streamInfo); + } }else { - zlmMediaListManager.removeMedia( app, streamId); + int result = zlmMediaListManager.removeMedia( app, streamId); + redisCatchStorage.removePushStream(mediaServerItem, app, streamId); + if (result > 0) { + pushChange = true; + } + } + if(pushChange) { + // 发送流变化redis消息 + JSONObject jsonObject = new JSONObject(); + jsonObject.put("serverId", userSetup.getServerId()); + jsonObject.put("app", app); + jsonObject.put("stream", streamId); + jsonObject.put("register", regist); + jsonObject.put("mediaServerId", mediaServerId); + redisCatchStorage.sendStreamChangeMsg(jsonObject); } } } @@ -325,14 +366,13 @@ public class ZLMHttpHookListener { String mediaServerId = json.getString("mediaServerId"); String streamId = json.getString("stream"); String app = json.getString("app"); - - // TODO 如果在给上级推流,也不停止。 + JSONObject ret = new JSONObject(); + ret.put("code", 0); if ("rtp".equals(app)){ - JSONObject ret = new JSONObject(); - ret.put("code", 0); ret.put("close", true); StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(streamId); if (streamInfoForPlayCatch != null) { + // 如果在给上级推流,也不停止。 if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) { ret.put("close", false); } else { @@ -345,6 +385,12 @@ public class ZLMHttpHookListener { if (streamInfoForPlayBackCatch != null) { cmder.streamByeCmd(streamInfoForPlayBackCatch.getDeviceID(), streamInfoForPlayBackCatch.getChannelId()); redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch); + }else { + StreamInfo streamInfoForDownload = redisCatchStorage.queryDownloadByStreamId(streamId); + // 进行录像下载时无人观看不断流 + if (streamInfoForDownload != null) { + ret.put("close", false); + } } } MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); @@ -353,9 +399,15 @@ public class ZLMHttpHookListener { } return new ResponseEntity(ret.toString(),HttpStatus.OK); }else { - JSONObject ret = new JSONObject(); - ret.put("code", 0); - ret.put("close", false); + StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, streamId); + if (streamProxyItem != null && streamProxyItem.isEnable_remove_none_reader()) { + ret.put("close", true); + streamProxyService.del(app, streamId); + String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url(); + logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", app, streamId, url); + }else { + ret.put("close", false); + } return new ResponseEntity(ret.toString(),HttpStatus.OK); } 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 ea1123cbd..49fe098e0 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 @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; @@ -87,6 +88,10 @@ public class ZLMMediaListManager { updateMedia(mediaServerItem, app, streamId); } + public void addMedia(MediaItem mediaItem) { + storager.updateMedia(streamPushService.transform(mediaItem)); + } + public void updateMedia(MediaServerItem mediaServerItem, String app, String streamId) { //使用异步更新推流 @@ -113,14 +118,16 @@ public class ZLMMediaListManager { } - public void removeMedia(String app, String streamId) { + public int removeMedia(String app, String streamId) { // 查找是否关联了国标, 关联了不删除, 置为离线 StreamProxyItem streamProxyItem = gbStreamMapper.selectOne(app, streamId); + int result = 0; if (streamProxyItem == null) { - storager.removeMedia(app, streamId); + result = storager.removeMedia(app, streamId); }else { - storager.mediaOutline(app, streamId); + result =storager.mediaOutline(app, streamId); } + return result; } // public void clearAllSessions() { 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 2c95216ac..e4bcd31ac 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 @@ -29,7 +29,6 @@ public class ZLMRESTfulUtils { OkHttpClient client = new OkHttpClient(); String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); JSONObject responseJSON = null; - logger.debug(url); FormBody.Builder builder = new FormBody.Builder(); builder.add("secret",mediaServerItem.getSecret()); @@ -51,8 +50,9 @@ public class ZLMRESTfulUtils { try { Response response = client.newCall(request).execute(); if (response.isSuccessful()) { - String responseStr = response.body().string(); - if (responseStr != null) { + ResponseBody responseBody = response.body(); + if (responseBody != null) { + String responseStr = responseBody.string(); responseJSON = JSON.parseObject(responseStr); } }else { @@ -100,7 +100,11 @@ public class ZLMRESTfulUtils { public void sendGetForImg(MediaServerItem mediaServerItem, String api, Map params, String targetPath, String fileName) { String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); logger.debug(url); - HttpUrl.Builder httpBuilder = HttpUrl.parse(url).newBuilder(); + HttpUrl parseUrl = HttpUrl.parse(url); + if (parseUrl == null) { + return; + } + HttpUrl.Builder httpBuilder = parseUrl.newBuilder(); httpBuilder.addQueryParameter("secret", mediaServerItem.getSecret()); if (params != null) { @@ -123,16 +127,20 @@ public class ZLMRESTfulUtils { if (targetPath != null) { File snapFolder = new File(targetPath); if (!snapFolder.exists()) { - snapFolder.mkdirs(); + if (!snapFolder.mkdirs()) { + logger.warn("{}路径创建失败", snapFolder.getAbsolutePath()); + } + } File snapFile = new File(targetPath + "/" + fileName); FileOutputStream outStream = new FileOutputStream(snapFile); - outStream.write(response.body().bytes()); + + outStream.write(Objects.requireNonNull(response.body()).bytes()); outStream.close(); } else { logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); } - response.body().close(); + Objects.requireNonNull(response.body()).close(); } else { logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java index 6d9ceee91..4685d1fd6 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/MediaItem.java @@ -4,6 +4,11 @@ import java.util.List; public class MediaItem { + /** + * 注册/注销 + */ + private boolean regist; + /** * 应用名 */ @@ -53,6 +58,11 @@ public class MediaItem { */ private String originUrl; + /** + * 服务器id + */ + private String mediaServerId; + /** * GMT unix系统时间戳,单位秒 */ @@ -78,6 +88,14 @@ public class MediaItem { */ private String vhost; + public boolean isRegist() { + return regist; + } + + public void setRegist(boolean regist) { + this.regist = regist; + } + /** * 是否是docker部署, docker部署不会自动更新zlm使用的端口,需要自己手动修改 */ @@ -376,4 +394,12 @@ public class MediaItem { public void setDocker(boolean docker) { this.docker = docker; } + + public String getMediaServerId() { + return mediaServerId; + } + + public void setMediaServerId(String mediaServerId) { + this.mediaServerId = mediaServerId; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java index 40ba215fb..38e44a98c 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/dto/StreamProxyItem.java @@ -17,6 +17,7 @@ public class StreamProxyItem extends GbStream { private boolean enable; private boolean enable_hls; private boolean enable_mp4; + private boolean enable_remove_none_reader; // 无人观看时删除 private String platformGbId; private String createTime; @@ -142,4 +143,12 @@ public class StreamProxyItem extends GbStream { public void setCreateTime(String createTime) { this.createTime = createTime; } + + public boolean isEnable_remove_none_reader() { + return enable_remove_none_reader; + } + + public void setEnable_remove_none_reader(boolean enable_remove_none_reader) { + this.enable_remove_none_reader = enable_remove_none_reader; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/onvif/IONVIFServer.java b/src/main/java/com/genersoft/iot/vmp/onvif/IONVIFServer.java deleted file mode 100644 index eb81a3635..000000000 --- a/src/main/java/com/genersoft/iot/vmp/onvif/IONVIFServer.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.genersoft.iot.vmp.onvif; - -import be.teletask.onvif.models.OnvifDevice; -import com.genersoft.iot.vmp.onvif.dto.ONVIFCallBack; - -import java.util.List; - -public interface IONVIFServer { - - void search(int timeout, ONVIFCallBack> callBack); - - void getRTSPUrl(int timeout, OnvifDevice device, ONVIFCallBack callBack); -} diff --git a/src/main/java/com/genersoft/iot/vmp/onvif/dto/ONVIFCallBack.java b/src/main/java/com/genersoft/iot/vmp/onvif/dto/ONVIFCallBack.java deleted file mode 100644 index 3fdbde5b9..000000000 --- a/src/main/java/com/genersoft/iot/vmp/onvif/dto/ONVIFCallBack.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.genersoft.iot.vmp.onvif.dto; - -public interface ONVIFCallBack { - void run(int errorCode, T t); -} diff --git a/src/main/java/com/genersoft/iot/vmp/onvif/impl/ONVIFServerIMpl.java b/src/main/java/com/genersoft/iot/vmp/onvif/impl/ONVIFServerIMpl.java deleted file mode 100644 index d952cc8fb..000000000 --- a/src/main/java/com/genersoft/iot/vmp/onvif/impl/ONVIFServerIMpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.genersoft.iot.vmp.onvif.impl; - - -import be.teletask.onvif.DiscoveryManager; -import be.teletask.onvif.OnvifManager; -import be.teletask.onvif.listeners.*; -import be.teletask.onvif.models.*; -import be.teletask.onvif.responses.OnvifResponse; -import com.genersoft.iot.vmp.onvif.IONVIFServer; -import com.genersoft.iot.vmp.onvif.dto.ONVIFCallBack; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@SuppressWarnings("rawtypes") -/** - * 处理onvif的各种操作 - */ -@Service -public class ONVIFServerIMpl implements IONVIFServer { - - private final static Logger logger = LoggerFactory.getLogger(ONVIFServerIMpl.class); - - @Override - public void search(int timeout, ONVIFCallBack> callBack) { - DiscoveryManager manager = new DiscoveryManager(); - manager.setDiscoveryTimeout(timeout); - Map deviceMap = new HashMap<>(); - // 搜索设备 - manager.discover(new DiscoveryListener() { - @Override - public void onDiscoveryStarted() { - logger.info("Discovery started"); - } - - @Override - public void onDevicesFound(List devices) { - if (devices == null || devices.size() == 0) return; - for (Device device : devices){ - logger.info(device.getHostName()); - deviceMap.put(device.getHostName(), device); - } - } - - // 搜索结束 - @Override - public void onDiscoveryFinished() { - ArrayList result = new ArrayList<>(); - for (Device device : deviceMap.values()) { - logger.info(device.getHostName()); - result.add(device.getHostName()); - } - callBack.run(0, result); - } - }); - } - - @Override - public void getRTSPUrl(int timeout, OnvifDevice device, ONVIFCallBack callBack) { - if (device.getHostName() == null ){ - callBack.run(400, null); - } - OnvifManager onvifManager = new OnvifManager(); - onvifManager.setOnvifResponseListener(new OnvifResponseListener(){ - - @Override - public void onResponse(OnvifDevice onvifDevice, OnvifResponse response) { - logger.info("[RESPONSE] " + onvifDevice.getHostName() - + "======" + response.getErrorCode() - + "======" + response.getErrorMessage()); - } - - @Override - public void onError(OnvifDevice onvifDevice, int errorCode, String errorMessage) { - logger.info("[ERROR] " + onvifDevice.getHostName() + "======" + errorCode + "=======" + errorMessage); - callBack.run(errorCode, errorMessage); - } - }); - - try { - onvifManager.getServices(device, (OnvifDevice onvifDevice, OnvifServices services) -> { - if (services.getProfilesPath().equals("/onvif/Media")) { - onvifDevice.setPath(services); - onvifManager.getMediaProfiles(onvifDevice, new OnvifMediaProfilesListener() { - @Override - public void onMediaProfilesReceived(OnvifDevice device, List mediaProfiles) { - for (OnvifMediaProfile mediaProfile : mediaProfiles) { - logger.info(mediaProfile.getName()); - logger.info(mediaProfile.getToken()); - if (mediaProfile.getName().equals("mainStream")) { - onvifManager.getMediaStreamURI(device, mediaProfile, (OnvifDevice onvifDevice, - OnvifMediaProfile profile, String uri) -> { - - uri = uri.replace("rtsp://", "rtsp://"+ device.getUsername() + ":"+ device.getPassword() + "@"); - logger.info(onvifDevice.getHostName() + "的地址" + uri); - callBack.run(0, uri); - }); - } - } - } - }); - } - }); - }catch (Exception e) { - callBack.run(400, e.getMessage()); - } - - - } -} 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 f42b86791..657d28085 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IMediaServerService.java @@ -61,4 +61,6 @@ public interface IMediaServerService { boolean checkMediaRecordServer(String ip, int port); void delete(String id); + + MediaServerItem getDefaultMediaServer(); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IMediaService.java b/src/main/java/com/genersoft/iot/vmp/service/IMediaService.java index 54f8315e5..8c05b85f2 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/IMediaService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IMediaService.java @@ -32,7 +32,7 @@ public interface IMediaService { * @param stream * @return */ - StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaServerItem, String app, String stream, JSONArray tracks); + StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaServerItem, String app, String stream, Object tracks); /** * 根据应用名和流ID获取播放地址, 只是地址拼接,返回的ip使用远程访问ip,适用与zlm与wvp在一台主机的情况 @@ -40,5 +40,5 @@ public interface IMediaService { * @param stream * @return */ - StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, JSONArray tracks, String addr); + StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr); } 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 9e5c44422..8a7437cdd 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IPlayService.java @@ -18,4 +18,6 @@ public interface IPlayService { PlayResult play(MediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent); MediaServerItem getNewMediaServerItem(Device device); + + void onPublishHandlerForDownload(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId, String toString); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java b/src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java index 12e489832..60f3303b1 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IStreamProxyService.java @@ -1,8 +1,10 @@ package com.genersoft.iot.vmp.service; import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; public interface IStreamProxyService { @@ -11,7 +13,7 @@ public interface IStreamProxyService { * 保存视频代理 * @param param */ - String save(StreamProxyItem param); + WVPResult save(StreamProxyItem param); /** * 添加视频代理到zlm @@ -63,4 +65,10 @@ public interface IStreamProxyService { * @return */ JSONObject getFFmpegCMDs(MediaServerItem mediaServerItem); + + /** + * 根据app与stream获取streamProxy + * @return + */ + StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId); } diff --git a/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java b/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java index 899da98ba..94e7d691f 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java +++ b/src/main/java/com/genersoft/iot/vmp/service/IStreamPushService.java @@ -1,6 +1,7 @@ package com.genersoft.iot.vmp.service; import com.genersoft.iot.vmp.gb28181.bean.GbStream; +import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; import com.github.pagehelper.PageInfo; @@ -32,4 +33,6 @@ public interface IStreamPushService { * @return */ PageInfo getPushList(Integer page, Integer count); + + StreamPushItem transform(MediaItem item); } 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 595f38c88..f9143135b 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 @@ -10,6 +10,9 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +/** + * 设备业务(目录订阅) + */ @Service public class DeviceServiceImpl implements IDeviceService { @@ -31,8 +34,11 @@ public class DeviceServiceImpl implements IDeviceService { CatalogSubscribeTask catalogSubscribeTask = new CatalogSubscribeTask(device, sipCommander); catalogSubscribeTask.run(); // 提前开始刷新订阅 - String cron = getCron(device.getSubscribeCycleForCatalog() - 60); - dynamicTask.startCron(device.getDeviceId(), catalogSubscribeTask, cron); + // TODO 使用jain sip的当时刷新订阅 + int subscribeCycleForCatalog = device.getSubscribeCycleForCatalog(); + // 设置最小值为30 + subscribeCycleForCatalog = Math.max(subscribeCycleForCatalog, 30); + dynamicTask.startCron(device.getDeviceId(), catalogSubscribeTask, subscribeCycleForCatalog - 5); return true; } @@ -41,21 +47,10 @@ public class DeviceServiceImpl implements IDeviceService { if (device == null || device.getSubscribeCycleForCatalog() < 0) { return false; } + logger.info("移除目录订阅: {}", device.getDeviceId()); dynamicTask.stopCron(device.getDeviceId()); + device.setSubscribeCycleForCatalog(0); + sipCommander.catalogSubscribe(device, null, null); return true; } - - public String getCron(int time) { - if (time <= 59) { - return "0/" + time +" * * * * ?"; - }else if (time <= 60* 59) { - int minute = time/(60); - return "0 0/" + minute +" * * * ?"; - }else if (time <= 60* 60* 59) { - int hour = time/(60*60); - return "0 0 0/" + hour +" * * ?"; - }else { - return "0 0/10 * * * ?"; - } - } } 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 4ba361c03..51f99d92e 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 @@ -47,7 +47,7 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR private boolean sslEnabled; @Value("${server.port}") - private String serverPort; + private Integer serverPort; @Autowired private MediaConfig mediaConfig; @@ -241,6 +241,11 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR return mediaServerMapper.queryOneByHostAndPort(host, port); } + @Override + public MediaServerItem getDefaultMediaServer() { + return mediaServerMapper.queryDefault(); + } + @Override public void clearMediaServerForOnline() { String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX; diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java index 51c5979e8..9e5221bce 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/MediaServiceImpl.java @@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; +import com.genersoft.iot.vmp.media.zlm.dto.MediaItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; @@ -31,14 +32,20 @@ public class MediaServiceImpl implements IMediaService { @Override - public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, JSONArray tracks) { + public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks) { return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null); } @Override public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, String addr) { StreamInfo streamInfo = null; - MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); + + MediaServerItem mediaInfo; + if (mediaServerId == null) { + mediaInfo = mediaServerService.getDefaultMediaServer(); + }else { + mediaInfo = mediaServerService.getOne(mediaServerId); + } if (mediaInfo == null) { return streamInfo; } @@ -55,13 +62,15 @@ public class MediaServiceImpl implements IMediaService { return streamInfo; } + + @Override public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId) { return getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, null); } @Override - public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, JSONArray tracks, String addr) { + public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr) { StreamInfo streamInfoResult = new StreamInfo(); streamInfoResult.setStreamId(stream); streamInfoResult.setApp(app); 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 d7b8ffe82..640e99aac 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 @@ -34,12 +34,8 @@ import org.springframework.stereotype.Service; import org.springframework.util.ResourceUtils; import org.springframework.web.context.request.async.DeferredResult; -import javax.sip.DialogTerminatedEvent; -import javax.sip.ResponseEvent; -import javax.sip.TimeoutEvent; -import javax.sip.TransactionTerminatedEvent; -import javax.sip.message.Response; import java.io.FileNotFoundException; +import java.util.Objects; import java.util.UUID; @SuppressWarnings(value = {"rawtypes", "unchecked"}) @@ -85,7 +81,13 @@ public class PlayServiceImpl implements IPlayService { RequestMessage msg = new RequestMessage(); String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; msg.setKey(key); - msg.setId(playResult.getUuid()); + String uuid = UUID.randomUUID().toString(); + msg.setId(uuid); + playResult.setUuid(uuid); + DeferredResult> result = new DeferredResult<>(userSetup.getPlayTimeout()); + playResult.setResult(result); + // 录像查询以channelId作为deviceId查询 + resultHolder.put(key, uuid, result); if (mediaServerItem == null) { WVPResult wvpResult = new WVPResult(); wvpResult.setCode(-1); @@ -94,16 +96,9 @@ public class PlayServiceImpl implements IPlayService { resultHolder.invokeResult(msg); return playResult; } - Device device = storager.queryVideoDevice(deviceId); StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId); playResult.setDevice(device); - String uuid = UUID.randomUUID().toString(); - playResult.setUuid(uuid); - DeferredResult> result = new DeferredResult>(userSetup.getPlayTimeout()); - playResult.setResult(result); - // 录像查询以channelId作为deviceId查询 - resultHolder.put(key, uuid, result); // 超时处理 result.onTimeout(()->{ logger.warn(String.format("设备点播超时,deviceId:%s ,channelId:%s", deviceId, channelId)); @@ -134,18 +129,18 @@ public class PlayServiceImpl implements IPlayService { classPath = classPath.substring(0, classPath.lastIndexOf("/") + 1); } if (classPath.startsWith("file:")) { - classPath = classPath.substring(classPath.indexOf(":") + 1, classPath.length()); + classPath = classPath.substring(classPath.indexOf(":") + 1); } String path = classPath + "static/static/snap/"; // 兼容Windows系统路径(去除前面的“/”) if(System.getProperty("os.name").contains("indows")) { - path = path.substring(1, path.length()); + path = path.substring(1); } String fileName = deviceId + "_" + channelId + ".jpg"; ResponseEntity responseEntity = (ResponseEntity)result.getResult(); if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) { WVPResult wvpResult = (WVPResult)responseEntity.getBody(); - if (wvpResult.getCode() == 0) { + if (Objects.requireNonNull(wvpResult).getCode() == 0) { StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData(); MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId()); String streamUrl = streamInfoForSuccess.getFmp4(); @@ -169,7 +164,7 @@ public class PlayServiceImpl implements IPlayService { // 发送点播消息 cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInUse, JSONObject response) -> { logger.info("收到订阅消息: " + response.toJSONString()); - onPublishHandlerForPlay(mediaServerItemInUse, response, deviceId, channelId, uuid.toString()); + onPublishHandlerForPlay(mediaServerItemInUse, response, deviceId, channelId, uuid); if (hookEvent != null) { hookEvent.response(mediaServerItem, response); } @@ -192,7 +187,7 @@ public class PlayServiceImpl implements IPlayService { if (streamId == null) { WVPResult wvpResult = new WVPResult(); wvpResult.setCode(-1); - wvpResult.setMsg(String.format("点播失败, redis缓存streamId等于null")); + wvpResult.setMsg("点播失败, redis缓存streamId等于null"); msg.setData(wvpResult); resultHolder.invokeAllResult(msg); return playResult; @@ -226,7 +221,7 @@ public class PlayServiceImpl implements IPlayService { cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> { logger.info("收到订阅消息: " + response.toJSONString()); - onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, uuid.toString()); + onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, uuid); }, (event) -> { mediaServerService.closeRTPServer(playResult.getDevice(), channelId); WVPResult wvpResult = new WVPResult(); @@ -274,7 +269,7 @@ public class PlayServiceImpl implements IPlayService { public MediaServerItem getNewMediaServerItem(Device device) { if (device == null) return null; String mediaServerId = device.getMediaServerId(); - MediaServerItem mediaServerItem = null; + MediaServerItem mediaServerItem; if (mediaServerId == null) { mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(); }else { @@ -286,16 +281,35 @@ public class PlayServiceImpl implements IPlayService { return mediaServerItem; } + @Override public void onPublishHandlerForPlayBack(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) { RequestMessage msg = new RequestMessage(); - msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId); + msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId); msg.setId(uuid); StreamInfo streamInfo = onPublishHandler(mediaServerItem, resonse, deviceId, channelId, uuid); if (streamInfo != null) { redisCatchStorage.startPlayback(streamInfo); msg.setData(JSON.toJSONString(streamInfo)); resultHolder.invokeResult(msg); + } else { + logger.warn("设备回放API调用失败!"); + msg.setData("设备回放API调用失败!"); + resultHolder.invokeResult(msg); + } + } + + + @Override + public void onPublishHandlerForDownload(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId, String uuid) { + RequestMessage msg = new RequestMessage(); + msg.setKey(DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId); + msg.setId(uuid); + StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId, uuid); + if (streamInfo != null) { + redisCatchStorage.startDownload(streamInfo); + msg.setData(JSON.toJSONString(streamInfo)); + resultHolder.invokeResult(msg); } else { logger.warn("设备预览API调用失败!"); msg.setData("设备预览API调用失败!"); @@ -303,6 +317,7 @@ public class PlayServiceImpl implements IPlayService { } } + public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) { String streamId = resonse.getString("stream"); JSONArray tracks = resonse.getJSONArray("tracks"); diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java index bbcad1c39..3ffc68e39 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamProxyServiceImpl.java @@ -1,18 +1,21 @@ package com.genersoft.iot.vmp.service.impl; import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.GbStream; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.service.IGbStreamService; import com.genersoft.iot.vmp.service.IMediaServerService; +import com.genersoft.iot.vmp.service.IMediaService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import com.genersoft.iot.vmp.storager.dao.GbStreamMapper; import com.genersoft.iot.vmp.storager.dao.PlatformGbStreamMapper; import com.genersoft.iot.vmp.storager.dao.StreamProxyMapper; import com.genersoft.iot.vmp.service.IStreamProxyService; +import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.github.pagehelper.PageInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +37,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService { private IVideoManagerStorager videoManagerStorager; @Autowired - private IRedisCatchStorage redisCatchStorage; + private IMediaService mediaService; @Autowired private ZLMRESTfulUtils zlmresTfulUtils;; @@ -56,8 +59,10 @@ public class StreamProxyServiceImpl implements IStreamProxyService { @Override - public String save(StreamProxyItem param) { + public WVPResult save(StreamProxyItem param) { MediaServerItem mediaInfo; + WVPResult wvpResult = new WVPResult<>(); + wvpResult.setCode(0); if ("auto".equals(param.getMediaServerId())){ mediaInfo = mediaServerService.getMediaServerForMinimumLoad(); }else { @@ -65,7 +70,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService { } if (mediaInfo == null) { logger.warn("保存代理未找到在线的ZLM..."); - return "保存失败"; + wvpResult.setMsg("保存失败"); + return wvpResult; } String dstUrl = String.format("rtmp://%s:%s/%s/%s", "127.0.0.1", mediaInfo.getRtmpPort(), param.getApp(), param.getStream() ); @@ -83,6 +89,10 @@ public class StreamProxyServiceImpl implements IStreamProxyService { result.append(", 但是启用失败,请检查流地址是否可用"); param.setEnable(false); videoManagerStorager.updateStreamProxy(param); + }else { + StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream( + mediaInfo, param.getApp(), param.getStream(), null); + wvpResult.setData(streamInfo); } } } @@ -97,6 +107,10 @@ public class StreamProxyServiceImpl implements IStreamProxyService { result.append(", 但是启用失败,请检查流地址是否可用"); param.setEnable(false); videoManagerStorager.updateStreamProxy(param); + }else { + StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream( + mediaInfo, param.getApp(), param.getStream(), null); + wvpResult.setData(streamInfo); } } }else { @@ -113,7 +127,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService { result.append(", 关联国标平台[ " + param.getPlatformGbId() + " ]失败"); } } - return result.toString(); + wvpResult.setMsg(result.toString()); + return wvpResult; } @Override @@ -213,4 +228,10 @@ public class StreamProxyServiceImpl implements IStreamProxyService { return result; } + + + @Override + public StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId) { + return videoManagerStorager.getStreamProxyByAppAndStream(app, streamId); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java index aabf35f35..28207212e 100644 --- a/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/service/impl/StreamPushServiceImpl.java @@ -51,33 +51,38 @@ public class StreamPushServiceImpl implements IStreamPushService { for (MediaItem item : mediaItems) { // 不保存国标推理以及拉流代理的流 - if (item.getOriginType() == 3 || item.getOriginType() == 4 || item.getOriginType() == 5) { - continue; - } - String key = item.getApp() + "_" + item.getStream(); - StreamPushItem streamPushItem = result.get(key); - if (streamPushItem == null) { - streamPushItem = new StreamPushItem(); - streamPushItem.setApp(item.getApp()); - streamPushItem.setMediaServerId(mediaServerItem.getId()); - streamPushItem.setStream(item.getStream()); - streamPushItem.setAliveSecond(item.getAliveSecond()); - streamPushItem.setCreateStamp(item.getCreateStamp()); - streamPushItem.setOriginSock(item.getOriginSock()); - streamPushItem.setTotalReaderCount(item.getTotalReaderCount()); - streamPushItem.setOriginType(item.getOriginType()); - streamPushItem.setOriginTypeStr(item.getOriginTypeStr()); - streamPushItem.setOriginUrl(item.getOriginUrl()); - streamPushItem.setCreateStamp(item.getCreateStamp()); - streamPushItem.setAliveSecond(item.getAliveSecond()); - streamPushItem.setStatus(true); - streamPushItem.setVhost(item.getVhost()); - result.put(key, streamPushItem); + if (item.getOriginType() == 1 || item.getOriginType() == 2 || item.getOriginType() == 8) { + String key = item.getApp() + "_" + item.getStream(); + StreamPushItem streamPushItem = result.get(key); + if (streamPushItem == null) { + streamPushItem = transform(item); + result.put(key, streamPushItem); + } } + } return new ArrayList<>(result.values()); } + @Override + public StreamPushItem transform(MediaItem item) { + StreamPushItem streamPushItem = new StreamPushItem(); + streamPushItem.setApp(item.getApp()); + streamPushItem.setMediaServerId(item.getMediaServerId()); + streamPushItem.setStream(item.getStream()); + streamPushItem.setAliveSecond(item.getAliveSecond()); + streamPushItem.setCreateStamp(item.getCreateStamp()); + streamPushItem.setOriginSock(item.getOriginSock()); + streamPushItem.setTotalReaderCount(item.getTotalReaderCount()); + streamPushItem.setOriginType(item.getOriginType()); + streamPushItem.setOriginTypeStr(item.getOriginTypeStr()); + streamPushItem.setOriginUrl(item.getOriginUrl()); + streamPushItem.setCreateStamp(item.getCreateStamp()); + streamPushItem.setAliveSecond(item.getAliveSecond()); + streamPushItem.setStatus(true); + streamPushItem.setVhost(item.getVhost()); + return streamPushItem; + } @Override public PageInfo getPushList(Integer page, Integer count) { 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 e4313d905..5878339bf 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IRedisCatchStorage.java @@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform; 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 java.util.List; import java.util.Map; @@ -120,5 +121,35 @@ public interface IRedisCatchStorage { /** * 在redis添加wvp的信息 */ - void updateWVPInfo(JSONObject jsonObject); + void updateWVPInfo(String id, JSONObject jsonObject, int time); + + /** + * 发送推流生成与推流消失消息 + * @param jsonObject 消息内容 + */ + void sendStreamChangeMsg(JSONObject jsonObject); + + /** + * 添加流信息到redis + * @param mediaServerItem + * @param app + * @param streamId + */ + void addPushStream(MediaServerItem mediaServerItem, String app, String streamId, StreamInfo streamInfo); + + /** + * 移除流信息从redis + * @param mediaServerItem + * @param app + * @param streamId + */ + void removePushStream(MediaServerItem mediaServerItem, String app, String streamId); + + /** + * 开始下载录像时存入 + * @param streamInfo + */ + boolean startDownload(StreamInfo streamInfo); + + StreamInfo queryDownloadByStreamId(String streamId); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java index 931530aef..570718b30 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/IVideoManagerStorager.java @@ -353,7 +353,7 @@ public interface IVideoManagerStorager { * @param app * @param stream */ - void removeMedia(String app, String stream); + int removeMedia(String app, String stream); /** @@ -366,7 +366,7 @@ public interface IVideoManagerStorager { * @param app * @param streamId */ - void mediaOutline(String app, String streamId); + int mediaOutline(String app, String streamId); /** * 设置平台在线/离线 @@ -406,4 +406,12 @@ public interface IVideoManagerStorager { * @param channelId 通道ID */ void deviceChannelOffline(String deviceId, String channelId); + + /** + * 通过app与stream获取StreamProxy + * @param app + * @param streamId + * @return + */ + StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java index 230afbc9f..ebf4239af 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/GbStreamMapper.java @@ -28,7 +28,7 @@ public interface GbStreamMapper { "latitude=#{latitude}," + "mediaServerId=#{mediaServerId}," + "status=${status} " + - "WHERE app=#{app} AND stream=#{stream} AND gbId=#{gbId}") + "WHERE app=#{app} AND stream=#{stream}") int update(GbStream gbStream); @Delete("DELETE FROM gb_stream WHERE app=#{app} AND stream=#{stream}") @@ -53,7 +53,7 @@ public interface GbStreamMapper { @Update("UPDATE gb_stream " + "SET status=${status} " + "WHERE app=#{app} AND stream=#{stream}") - void setStatus(String app, String stream, boolean status); + int setStatus(String app, String stream, boolean status); @Select("SELECT gs.*, pgs.platformId FROM gb_stream gs LEFT JOIN platform_gb_stream pgs ON gs.app = pgs.app AND gs.stream = pgs.stream WHERE mediaServerId=#{mediaServerId} ") List selectAllByMediaServerId(String mediaServerId); diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java index 8d45b0543..8cd5d6a9a 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/MediaServerMapper.java @@ -105,4 +105,7 @@ public interface MediaServerMapper { @Select("SELECT * FROM media_server WHERE ip='${host}' and httpPort=${port}") MediaServerItem queryOneByHostAndPort(String host, int port); + + @Select("SELECT * FROM media_server WHERE defaultServer=1") + MediaServerItem queryDefault(); } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java b/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java index 7346da5cd..11753f7e1 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/dao/StreamProxyMapper.java @@ -11,9 +11,10 @@ import java.util.List; public interface StreamProxyMapper { @Insert("INSERT INTO stream_proxy (type, app, stream,mediaServerId, url, src_url, dst_url, " + - "timeout_ms, ffmpeg_cmd_key, rtp_type, enable_hls, enable_mp4, enable, createTime) VALUES" + + "timeout_ms, ffmpeg_cmd_key, rtp_type, enable_hls, enable_mp4, enable, enable_remove_none_reader, createTime) VALUES" + "('${type}','${app}', '${stream}', '${mediaServerId}','${url}', '${src_url}', '${dst_url}', " + - "'${timeout_ms}', '${ffmpeg_cmd_key}', '${rtp_type}', ${enable_hls}, ${enable_mp4}, ${enable}, '${createTime}' )") + "'${timeout_ms}', '${ffmpeg_cmd_key}', '${rtp_type}', ${enable_hls}, ${enable_mp4}, ${enable}, " + + "${enable_remove_none_reader}, '${createTime}' )") int add(StreamProxyItem streamProxyDto); @Update("UPDATE stream_proxy " + @@ -29,6 +30,7 @@ public interface StreamProxyMapper { "rtp_type=#{rtp_type}, " + "enable_hls=#{enable_hls}, " + "enable=#{enable}, " + + "enable_remove_none_reader=#{enable_remove_none_reader}, " + "enable_mp4=#{enable_mp4} " + "WHERE app=#{app} AND stream=#{stream}") int update(StreamProxyItem streamProxyDto); 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 e5b1269df..e98d8fad3 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 @@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.gb28181.bean.*; +import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.utils.redis.RedisUtil; @@ -63,15 +64,15 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { streamInfo.getChannelId())); } @Override - public StreamInfo queryPlayByStreamId(String steamId) { - List playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, steamId)); + public StreamInfo queryPlayByStreamId(String streamId) { + List playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, streamId)); if (playLeys == null || playLeys.size() == 0) return null; return (StreamInfo)redis.get(playLeys.get(0).toString()); } @Override - public StreamInfo queryPlaybackByStreamId(String steamId) { - List playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, steamId)); + public StreamInfo queryPlaybackByStreamId(String streamId) { + List playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, streamId)); if (playLeys == null || playLeys.size() == 0) return null; return (StreamInfo)redis.get(playLeys.get(0).toString()); } @@ -103,10 +104,15 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { @Override public boolean startPlayback(StreamInfo stream) { - return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, stream.getStreamId(),stream.getDeviceID(), stream.getChannelId()), - stream); + return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, stream.getStreamId(), + stream.getDeviceID(), stream.getChannelId()), stream); } + @Override + public boolean startDownload(StreamInfo streamInfo) { + return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX, streamInfo.getStreamId(), + streamInfo.getDeviceID(), streamInfo.getChannelId()), streamInfo); + } @Override public boolean stopPlayback(StreamInfo streamInfo) { @@ -295,8 +301,33 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage { } @Override - public void updateWVPInfo(JSONObject jsonObject) { - + public void updateWVPInfo(String id, JSONObject jsonObject, int time) { + String key = VideoManagerConstants.WVP_SERVER_PREFIX + id; + redis.set(key, jsonObject, time); } + @Override + public void sendStreamChangeMsg(JSONObject jsonObject) { + String key = VideoManagerConstants.WVP_MSG_STREAM_PUSH_CHANGE_PREFIX; + redis.convertAndSend(key, jsonObject); + } + + @Override + public void addPushStream(MediaServerItem mediaServerItem, String app, String streamId, StreamInfo streamInfo) { + String key = VideoManagerConstants.WVP_SERVER_STREAM_PUSH_PREFIX + app + "_" + streamId + "_" + mediaServerItem.getId(); + redis.set(key, streamInfo); + } + + @Override + public void removePushStream(MediaServerItem mediaServerItem, String app, String streamId) { + String key = VideoManagerConstants.WVP_SERVER_STREAM_PUSH_PREFIX + app + "_" + streamId + "_" + mediaServerItem.getId(); + redis.del(key); + } + + @Override + public StreamInfo queryDownloadByStreamId(String streamId) { + List playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.DOWNLOAD_PREFIX, streamId)); + if (playLeys == null || playLeys.size() == 0) return null; + return (StreamInfo)redis.get(playLeys.get(0).toString()); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java index 0e24942df..9ad44d40d 100644 --- a/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/storager/impl/VideoManagerStoragerImpl.java @@ -605,8 +605,8 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager { } @Override - public void removeMedia(String app, String stream) { - streamPushMapper.del(app, stream); + public int removeMedia(String app, String stream) { + return streamPushMapper.del(app, stream); } @Override @@ -615,8 +615,8 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager { } @Override - public void mediaOutline(String app, String streamId) { - gbStreamMapper.setStatus(app, streamId, false); + public int mediaOutline(String app, String streamId) { + return gbStreamMapper.setStatus(app, streamId, false); } @Override @@ -651,4 +651,9 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager { } return result; } + + @Override + public StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId) { + return streamProxyMapper.selectOne(app, streamId); + } } diff --git a/src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java b/src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java index 35da67881..da098513b 100644 --- a/src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java +++ b/src/main/java/com/genersoft/iot/vmp/utils/redis/RedisUtil.java @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.utils.redis; import java.util.*; import java.util.concurrent.TimeUnit; +import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.*; import org.springframework.stereotype.Component; @@ -729,4 +730,11 @@ public class RedisUtil { return new ArrayList<>(keys); } + // ============================== 消息发送与订阅 ============================== + public void convertAndSend(String channel, JSONObject msg) { +// redisTemplate.convertAndSend(channel, msg); + redisTemplate.convertAndSend(channel, msg); + + } + } diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java index 54b49550c..20aa95762 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/device/DeviceControl.java @@ -108,10 +108,10 @@ public class DeviceControl { msg.setData("Timeout. Device did not response to this command."); resultHolder.invokeAllResult(msg); }); - resultHolder.put(key, uuid, result); if (resultHolder.exist(key, null)){ return result; } + resultHolder.put(key, uuid, result); cmder.recordCmd(device, channelId, recordCmdStr, event -> { RequestMessage msg = new RequestMessage(); msg.setId(uuid); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java index b4b746904..d5caab298 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/media/MediaController.java @@ -44,11 +44,11 @@ public class MediaController { @ApiImplicitParams({ @ApiImplicitParam(name = "app", value = "应用名", dataTypeClass = String.class), @ApiImplicitParam(name = "stream", value = "流id", dataTypeClass = String.class), - @ApiImplicitParam(name = "mediaServerId", value = "媒体服务器id", dataTypeClass = String.class), + @ApiImplicitParam(name = "mediaServerId", value = "媒体服务器id", dataTypeClass = String.class, required = false), }) @GetMapping(value = "/stream_info_by_app_and_stream") @ResponseBody - public WVPResult getStreamInfoByAppAndStream(@RequestParam String app, @RequestParam String stream, @RequestParam String mediaServerId){ + public WVPResult getStreamInfoByAppAndStream(@RequestParam String app, @RequestParam String stream, @RequestParam(required = false) String mediaServerId){ StreamInfo streamInfoByAppAndStreamWithCheck = mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId); WVPResult result = new WVPResult<>(); if (streamInfoByAppAndStreamWithCheck != null){ diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java index 314bbae34..3f846c60e 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java @@ -76,7 +76,7 @@ public class DownloadController { if (logger.isDebugEnabled()) { logger.debug(String.format("历史媒体下载 API调用,deviceId:%s,channelId:%s,downloadSpeed:%s", deviceId, channelId, downloadSpeed)); } - String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; + String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId; String uuid = UUID.randomUUID().toString(); DeferredResult> result = new DeferredResult>(30000L); // 超时处理 @@ -88,10 +88,10 @@ public class DownloadController { msg.setData("Timeout"); resultHolder.invokeAllResult(msg); }); - resultHolder.put(key, uuid, result); if(resultHolder.exist(key, null)) { return result; } + resultHolder.put(key, uuid, result); Device device = storager.queryVideoDevice(deviceId); StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, channelId); if (streamInfo != null) { @@ -114,7 +114,7 @@ public class DownloadController { cmder.downloadStreamCmd(newMediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, (MediaServerItem mediaServerItem, JSONObject response) -> { logger.info("收到订阅消息: " + response.toJSONString()); - playService.onPublishHandlerForPlayBack(mediaServerItem, response, deviceId, channelId, uuid.toString()); + playService.onPublishHandlerForDownload(mediaServerItem, response, deviceId, channelId, uuid.toString()); }, event -> { RequestMessage msg = new RequestMessage(); msg.setId(uuid); 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 98df8ddff..fd1f2ab3e 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 @@ -9,6 +9,7 @@ import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.service.IPlayService; +import com.genersoft.iot.vmp.vmanager.gb28181.session.InfoCseqCache; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; @@ -77,7 +78,7 @@ public class PlaybackController { logger.debug(String.format("设备回放 API调用,deviceId:%s ,channelId:%s", deviceId, channelId)); } String uuid = UUID.randomUUID().toString(); - String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; + String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId; DeferredResult> result = new DeferredResult>(30000L); Device device = storager.queryVideoDevice(deviceId); if (device == null) { @@ -152,4 +153,103 @@ public class PlaybackController { return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); } } + + @ApiOperation("回放暂停") + @ApiImplicitParams({ + @ApiImplicitParam(name = "streamId", value = "回放流ID", dataTypeClass = String.class), + }) + @GetMapping("/pause/{streamId}") + public ResponseEntity playPause(@PathVariable String streamId) { + logger.info("playPause: "+streamId); + JSONObject json = new JSONObject(); + StreamInfo streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId); + if (null == streamInfo) { + json.put("msg", "streamId不存在"); + logger.warn("streamId不存在!"); + return new ResponseEntity(json.toString(), HttpStatus.BAD_REQUEST); + } + setCseq(streamId); + Device device = storager.queryVideoDevice(streamInfo.getDeviceID()); + cmder.playPauseCmd(device, streamInfo); + json.put("msg", "ok"); + return new ResponseEntity(json.toString(), HttpStatus.OK); + } + + @ApiOperation("回放恢复") + @ApiImplicitParams({ + @ApiImplicitParam(name = "streamId", value = "回放流ID", dataTypeClass = String.class), + }) + @GetMapping("/resume/{streamId}") + public ResponseEntity playResume(@PathVariable String streamId) { + logger.info("playResume: "+streamId); + JSONObject json = new JSONObject(); + StreamInfo streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId); + if (null == streamInfo) { + json.put("msg", "streamId不存在"); + logger.warn("streamId不存在!"); + return new ResponseEntity(json.toString(), HttpStatus.BAD_REQUEST); + } + setCseq(streamId); + Device device = storager.queryVideoDevice(streamInfo.getDeviceID()); + cmder.playResumeCmd(device, streamInfo); + json.put("msg", "ok"); + return new ResponseEntity(json.toString(), HttpStatus.OK); + } + + @ApiOperation("回放拖动播放") + @ApiImplicitParams({ + @ApiImplicitParam(name = "streamId", value = "回放流ID", dataTypeClass = String.class), + @ApiImplicitParam(name = "seekTime", value = "拖动偏移量,单位s", dataTypeClass = Long.class), + }) + @GetMapping("/seek/{streamId}/{seekTime}") + public ResponseEntity playSeek(@PathVariable String streamId, @PathVariable long seekTime) { + logger.info("playSeek: "+streamId+", "+seekTime); + JSONObject json = new JSONObject(); + StreamInfo streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId); + if (null == streamInfo) { + json.put("msg", "streamId不存在"); + logger.warn("streamId不存在!"); + return new ResponseEntity(json.toString(), HttpStatus.BAD_REQUEST); + } + setCseq(streamId); + Device device = storager.queryVideoDevice(streamInfo.getDeviceID()); + cmder.playSeekCmd(device, streamInfo, seekTime); + json.put("msg", "ok"); + return new ResponseEntity(json.toString(), HttpStatus.OK); + } + + @ApiOperation("回放倍速播放") + @ApiImplicitParams({ + @ApiImplicitParam(name = "streamId", value = "回放流ID", dataTypeClass = String.class), + @ApiImplicitParam(name = "speed", value = "倍速0.25 0.5 1、2、4", dataTypeClass = Double.class), + }) + @GetMapping("/speed/{streamId}/{speed}") + public ResponseEntity playSpeed(@PathVariable String streamId, @PathVariable Double speed) { + logger.info("playSpeed: "+streamId+", "+speed); + JSONObject json = new JSONObject(); + StreamInfo streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId); + if (null == streamInfo) { + json.put("msg", "streamId不存在"); + logger.warn("streamId不存在!"); + return new ResponseEntity(json.toString(), HttpStatus.BAD_REQUEST); + } + if(speed != 0.25 && speed != 0.5 && speed != 1 && speed != 2.0 && speed != 4.0) { + json.put("msg", "不支持的speed(0.25 0.5 1、2、4)"); + logger.warn("不支持的speed: " + speed); + return new ResponseEntity(json.toString(), HttpStatus.BAD_REQUEST); + } + setCseq(streamId); + Device device = storager.queryVideoDevice(streamInfo.getDeviceID()); + cmder.playSpeedCmd(device, streamInfo, speed); + json.put("msg", "ok"); + return new ResponseEntity(json.toString(), HttpStatus.OK); + } + + public void setCseq(String streamId) { + if (InfoCseqCache.CSEQCACHE.containsKey(streamId)) { + InfoCseqCache.CSEQCACHE.put(streamId, InfoCseqCache.CSEQCACHE.get(streamId) + 1); + } else { + InfoCseqCache.CSEQCACHE.put(streamId, 2L); + } + } } 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 4ecb3119a..a8675e8e3 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 @@ -56,17 +56,22 @@ public class GBRecordController { } Device device = storager.queryVideoDevice(deviceId); - cmder.recordInfoQuery(device, channelId, startTime, endTime); // 指定超时时间 1分钟30秒 DeferredResult> result = new DeferredResult<>(90*1000L); String uuid = UUID.randomUUID().toString(); - String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + channelId; + int sn = (int)((Math.random()*9+1)*100000); + String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn; + RequestMessage msg = new RequestMessage(); + msg.setId(uuid); + msg.setKey(key); + cmder.recordInfoQuery(device, channelId, startTime, endTime, sn, (eventResult -> { + msg.setData("查询录像失败, status: " + eventResult.statusCode + ", message: " + eventResult.msg ); + resultHolder.invokeResult(msg); + })); + // 录像查询以channelId作为deviceId查询 resultHolder.put(key, uuid, result); result.onTimeout(()->{ - RequestMessage msg = new RequestMessage(); - msg.setId(uuid); - msg.setKey(key); msg.setData("timeout"); resultHolder.invokeResult(msg); }); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/session/InfoCseqCache.java b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/session/InfoCseqCache.java new file mode 100644 index 000000000..051f98178 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/session/InfoCseqCache.java @@ -0,0 +1,14 @@ +package com.genersoft.iot.vmp.vmanager.gb28181.session; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @ClassName: InfoCseqCache + * @Description: INFO类型的Sip中cseq的缓存 + */ +public class InfoCseqCache { + + public static Map CSEQCACHE = new ConcurrentHashMap<>(); + +} \ No newline at end of file diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/onvif/ONVIFController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/onvif/ONVIFController.java deleted file mode 100644 index 0fa2f6b0e..000000000 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/onvif/ONVIFController.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.genersoft.iot.vmp.vmanager.onvif; - -import be.teletask.onvif.models.OnvifDevice; -import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; -import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; -import com.genersoft.iot.vmp.onvif.IONVIFServer; -import com.genersoft.iot.vmp.vmanager.bean.WVPResult; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiImplicitParam; -import io.swagger.annotations.ApiImplicitParams; -import io.swagger.annotations.ApiOperation; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.context.request.async.DeferredResult; - -import java.util.List; -import java.util.UUID; - -@SuppressWarnings(value = {"rawtypes", "unchecked"}) -@Api(tags = "onvif设备") -@CrossOrigin -@RestController -@RequestMapping("/api/onvif") -public class ONVIFController { - - - @Autowired - private DeferredResultHolder resultHolder; - - @Autowired - private IONVIFServer onvifServer; - - - @ApiOperation("搜索") - @ApiImplicitParams({ - @ApiImplicitParam(name="timeout", value = "超时时间", required = true, dataTypeClass = Integer.class), - }) - @GetMapping(value = "/search") - @ResponseBody - public DeferredResult> search(@RequestParam(required = false)Integer timeout){ - DeferredResult> result = new DeferredResult<>(timeout + 10L); - String uuid = UUID.randomUUID().toString(); - result.onTimeout(()->{ - RequestMessage msg = new RequestMessage(); - msg.setKey(DeferredResultHolder.CALLBACK_ONVIF ); - msg.setId(uuid); - WVPResult wvpResult = new WVPResult(); - wvpResult.setCode(0); - wvpResult.setMsg("搜索超时"); - msg.setData(wvpResult); - resultHolder.invokeResult(msg); - }); - resultHolder.put(DeferredResultHolder.CALLBACK_ONVIF, uuid, result); - - onvifServer.search(timeout, (errorCode, onvifDevices) ->{ - RequestMessage msg = new RequestMessage(); - msg.setId(DeferredResultHolder.CALLBACK_ONVIF + uuid); - WVPResult> resultData = new WVPResult(); - resultData.setCode(errorCode); - if (errorCode == 0) { - resultData.setMsg("success"); - resultData.setData(onvifDevices); - }else { - resultData.setMsg("fail"); - } - msg.setData(resultData); - msg.setData(resultData); - resultHolder.invokeResult(msg); - }); - - return result; - } - - @ApiOperation("获取onvif的rtsp地址") - @ApiImplicitParams({ - @ApiImplicitParam(name="timeout", value = "超时时间", required = true, dataTypeClass = Integer.class), - @ApiImplicitParam(name="hostname", value = "onvif地址", required = true, dataTypeClass = String.class), - @ApiImplicitParam(name="username", value = "用户名", required = true, dataTypeClass = String.class), - @ApiImplicitParam(name="password", value = "密码", required = true, dataTypeClass = String.class), - }) - @GetMapping(value = "/rtsp") - @ResponseBody - public DeferredResult> getRTSPUrl(@RequestParam(value="timeout", required=false, defaultValue="3000") Integer timeout, - @RequestParam(required = true) String hostname, - @RequestParam(required = false) String username, - @RequestParam(required = false) String password - ){ - - DeferredResult> result = new DeferredResult<>(timeout + 10L); - String uuid = UUID.randomUUID().toString(); - result.onTimeout(()->{ - RequestMessage msg = new RequestMessage(); - msg.setId(uuid); - msg.setKey(DeferredResultHolder.CALLBACK_ONVIF); - WVPResult wvpResult = new WVPResult(); - wvpResult.setCode(0); - wvpResult.setMsg("获取onvif的rtsp地址超时"); - msg.setData(wvpResult); - resultHolder.invokeResult(msg); - }); - resultHolder.put(DeferredResultHolder.CALLBACK_ONVIF, uuid, result); - OnvifDevice onvifDevice = new OnvifDevice(hostname, username, password); - onvifServer.getRTSPUrl(timeout, onvifDevice, (errorCode, url) ->{ - RequestMessage msg = new RequestMessage(); - msg.setId(DeferredResultHolder.CALLBACK_ONVIF + uuid); - WVPResult resultData = new WVPResult(); - resultData.setCode(errorCode); - if (errorCode == 0) { - resultData.setMsg("success"); - resultData.setData(url); - }else { - resultData.setMsg(url); - } - msg.setData(resultData); - - resultHolder.invokeResult(msg); - }); - - return result; - } - -} diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/streamProxy/StreamProxyController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/streamProxy/StreamProxyController.java index f8c01dd73..0c4c2ff78 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/streamProxy/StreamProxyController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/streamProxy/StreamProxyController.java @@ -1,9 +1,11 @@ package com.genersoft.iot.vmp.vmanager.streamProxy; import com.alibaba.fastjson.JSONObject; +import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.service.IMediaServerService; +import com.genersoft.iot.vmp.service.IMediaService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.service.IStreamProxyService; import com.genersoft.iot.vmp.vmanager.bean.WVPResult; @@ -68,10 +70,8 @@ public class StreamProxyController { public WVPResult save(@RequestBody StreamProxyItem param){ logger.info("添加代理: " + JSONObject.toJSONString(param)); if (StringUtils.isEmpty(param.getMediaServerId())) param.setMediaServerId("auto"); - String msg = streamProxyService.save(param); - WVPResult result = new WVPResult<>(); - result.setCode(0); - result.setMsg(msg); + if (StringUtils.isEmpty(param.getType())) param.setType("default"); + WVPResult result = streamProxyService.save(param); return result; } diff --git a/src/main/java/com/genersoft/iot/vmp/web/ApiCompatibleController.java b/src/main/java/com/genersoft/iot/vmp/web/ApiCompatibleController.java deleted file mode 100644 index c2a26b164..000000000 --- a/src/main/java/com/genersoft/iot/vmp/web/ApiCompatibleController.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.genersoft.iot.vmp.web; - -import com.genersoft.iot.vmp.common.StreamInfo; -import com.genersoft.iot.vmp.service.IMediaService; -import com.genersoft.iot.vmp.vmanager.bean.WVPResult; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.HttpServletRequest; - -@CrossOrigin -@RestController -public class ApiCompatibleController { - - private final static Logger logger = LoggerFactory.getLogger(ApiCompatibleController.class); - - @Autowired - private IMediaService mediaService; - - @GetMapping(value = "/api/v1/stream_info_by_app_and_stream") - @ResponseBody - public WVPResult getStreamInfoByAppAndStream(HttpServletRequest request, @RequestParam String app, @RequestParam String stream){ - String localAddr = request.getLocalAddr(); - StreamInfo streamINfo = mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream, localAddr); - WVPResult wvpResult = new WVPResult<>(); - wvpResult.setCode(0); - wvpResult.setMsg("success"); - wvpResult.setData(streamINfo); - return wvpResult; - } -} diff --git a/src/main/java/com/genersoft/iot/vmp/web/ApiControlController.java b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiControlController.java similarity index 78% rename from src/main/java/com/genersoft/iot/vmp/web/ApiControlController.java rename to src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiControlController.java index a8e0de4f8..4c124cf31 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/ApiControlController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiControlController.java @@ -1,4 +1,4 @@ -package com.genersoft.iot.vmp.web; +package com.genersoft.iot.vmp.web.gb28181; import com.alibaba.fastjson.JSONObject; import com.genersoft.iot.vmp.gb28181.bean.Device; @@ -45,49 +45,46 @@ public class ApiControlController { serial, code, command, speed)); } Device device = storager.queryVideoDevice(serial); - int leftRight = 0; - int upDown = 0; - int inOut = 0; - switch (command) { + int cmdCode = 0; + switch (command){ case "left": - leftRight = 1; + cmdCode = 2; break; case "right": - leftRight = 2; + cmdCode = 1; break; case "up": - upDown = 1; + cmdCode = 8; break; case "down": - upDown = 2; + cmdCode = 4; break; case "upleft": - upDown = 1; - leftRight = 1; + cmdCode = 10; + break; case "upright": - upDown = 1; - leftRight = 2; + cmdCode = 9; break; case "downleft": - upDown = 2; - leftRight = 1; + cmdCode = 6; break; case "downright": - upDown = 2; - leftRight = 2; + cmdCode = 5; break; case "zoomin": - inOut = 2; + cmdCode = 16; break; case "zoomout": - inOut = 1; + cmdCode = 32; break; case "stop": + cmdCode = 0; + break; + default: break; - } // 默认值 50 - cmder.ptzCmd(device, code, leftRight, upDown, inOut, speed==0 ? 129 : speed, 50); + cmder.frontEndCmd(device, code, cmdCode, speed, speed, speed); return null; } } diff --git a/src/main/java/com/genersoft/iot/vmp/web/ApiController.java b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiController.java similarity index 98% rename from src/main/java/com/genersoft/iot/vmp/web/ApiController.java rename to src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiController.java index cafa40cf1..faf873e34 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/ApiController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiController.java @@ -1,4 +1,4 @@ -package com.genersoft.iot.vmp.web; +package com.genersoft.iot.vmp.web.gb28181; import com.alibaba.fastjson.JSONObject; import com.genersoft.iot.vmp.conf.SipConfig; diff --git a/src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java similarity index 97% rename from src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java rename to src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java index ef8397639..2a021a2ff 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/ApiDeviceController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiDeviceController.java @@ -1,4 +1,4 @@ -package com.genersoft.iot.vmp.web; +package com.genersoft.iot.vmp.web.gb28181; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; @@ -73,7 +73,7 @@ public class ApiDeviceController { deviceJsonObject.put("ChannelCount", device.getChannelCount()); deviceJsonObject.put("RecvStreamIP", ""); deviceJsonObject.put("CatalogInterval", 3600); // 通道目录抓取周期 - deviceJsonObject.put("SubscribeInterval", 0); // 订阅周期(秒), 0 表示后台不周期订阅 + deviceJsonObject.put("SubscribeInterval", device.getSubscribeCycleForCatalog()); // 订阅周期(秒), 0 表示后台不周期订阅 deviceJsonObject.put("Online", device.getOnline() == 1); deviceJsonObject.put("Password", ""); deviceJsonObject.put("MediaTransport", device.getTransport()); diff --git a/src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java similarity index 82% rename from src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java rename to src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java index ae7e9212e..70f98114c 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/ApiStreamController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/gb28181/ApiStreamController.java @@ -1,18 +1,20 @@ -package com.genersoft.iot.vmp.web; +package com.genersoft.iot.vmp.web.gb28181; -import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.conf.UserSetup; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; +import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; 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.IPlayService; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; -import com.genersoft.iot.vmp.vmanager.gb28181.play.PlayController; +import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; @@ -34,15 +36,13 @@ public class ApiStreamController { private IVideoManagerStorager storager; @Autowired - private IRedisCatchStorage redisCatchStorage; - - - // @Autowired - // private ZLMRESTfulUtils zlmresTfulUtils; - + private UserSetup userSetup; @Autowired - private PlayController playController; + private IRedisCatchStorage redisCatchStorage; + + @Autowired + private IPlayService playService; /** * 实时直播 - 开始直播 @@ -69,7 +69,7 @@ public class ApiStreamController { @RequestParam(required = false)String timeout ){ - DeferredResult resultDeferredResult = new DeferredResult(); + DeferredResult resultDeferredResult = new DeferredResult<>(userSetup.getPlayTimeout() + 10); Device device = storager.queryVideoDevice(serial); if (device == null ) { JSONObject result = new JSONObject(); @@ -99,11 +99,9 @@ public class ApiStreamController { result.put("error","channel[ " + code + " ]offline"); resultDeferredResult.setResult(result); } - DeferredResult> play = playController.play(serial, code); - - play.setResultHandler((Object o)->{ - ResponseEntity responseEntity = (ResponseEntity)o; - StreamInfo streamInfo = JSON.parseObject(responseEntity.getBody(), StreamInfo.class); + MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); + PlayResult play = playService.play(newMediaServerItem, serial, code, (mediaServerItem, response)->{ + StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(serial, code); JSONObject result = new JSONObject(); result.put("StreamID", streamInfo.getStreamId()); result.put("DeviceID", device.getDeviceId()); @@ -134,7 +132,23 @@ public class ApiStreamController { result.put("NumOutputs", ""); result.put("CascadeSize", ""); result.put("RelaySize", ""); - result.put("ChannelPTZType", 0); + result.put("ChannelPTZType", "0"); + resultDeferredResult.setResult(result); +// Class aClass = responseEntity.getClass().getSuperclass(); +// Field body = null; +// try { +// // 使用反射动态修改返回的body +// body = aClass.getDeclaredField("body"); +// body.setAccessible(true); +// body.set(responseEntity, result); +// } catch (NoSuchFieldException e) { +// e.printStackTrace(); +// } catch (IllegalAccessException e) { +// e.printStackTrace(); +// } + }, (eventResult) -> { + JSONObject result = new JSONObject(); + result.put("error", "channel[ " + code + " ] " + eventResult.msg); resultDeferredResult.setResult(result); }); return resultDeferredResult; diff --git a/src/main/java/com/genersoft/iot/vmp/web/AuthController.java b/src/main/java/com/genersoft/iot/vmp/web/gb28181/AuthController.java similarity index 93% rename from src/main/java/com/genersoft/iot/vmp/web/AuthController.java rename to src/main/java/com/genersoft/iot/vmp/web/gb28181/AuthController.java index f4a2af868..abefa42bf 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/AuthController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/gb28181/AuthController.java @@ -1,4 +1,4 @@ -package com.genersoft.iot.vmp.web; +package com.genersoft.iot.vmp.web.gb28181; import com.genersoft.iot.vmp.service.IUserService; import com.genersoft.iot.vmp.storager.dao.dto.User; diff --git a/src/main/resources/all-application.yml b/src/main/resources/all-application.yml index e7d681067..6328bc377 100644 --- a/src/main/resources/all-application.yml +++ b/src/main/resources/all-application.yml @@ -137,6 +137,8 @@ logging: com.genersoft.iot.vmp.gb28181: info # [根据业务需求配置] user-settings: + # [可选] 服务ID,不写则为000000 + server-id: # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true auto-apply-play: false # [可选] 部分设备需要扩展SDP,需要打开此设置 diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index ff60f5af7..3477cab05 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -2,11 +2,11 @@ spring: # REDIS数据库配置 redis: # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1 - host: ${REDIS_HOST:127.0.0.1} + host: 127.0.0.1 # [必须修改] 端口号 - port: ${REDIS_PORT:6379} + port: 6379 # [可选] 数据库 DB - database: ${REDIS_DB:6} + database: 6 # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 password: ${REDIS_PWD:} # [可选] 超时时间 @@ -43,11 +43,11 @@ sip: # 后两位为行业编码,定义参照附录D.3 # 3701020049标识山东济南历下区 信息行业接入 # [可选] - domain: ${WVP_DOMAIN:4401020049} + domain: 4401020049 # [可选] - id: ${WVP_ID:44010200492000000001} + id: 44010200492000000001 # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 - password: ${WVP_PWD:admin123} + password: admin123 #zlm 默认服务器配置 media: diff --git a/src/main/resources/application-docker.yml b/src/main/resources/application-docker.yml new file mode 100644 index 000000000..4c9d18660 --- /dev/null +++ b/src/main/resources/application-docker.yml @@ -0,0 +1,98 @@ +spring: + # REDIS数据库配置 + redis: + # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1 + host: ${REDIS_HOST:127.0.0.1} + # [必须修改] 端口号 + port: ${REDIS_PORT:6379} + # [可选] 数据库 DB + database: ${REDIS_DB:6} + # [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接 + password: ${REDIS_PWD:} + # [可选] 超时时间 + timeout: 10000 + # [可选] jdbc数据库配置, 项目使用sqlite作为数据库,一般不需要配置 + datasource: + # 使用mysql 打开23-28行注释, 删除29-36行 + # name: wvp + # url: jdbc:mysql://127.0.0.1:3306/wvp?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true + # username: + # password: + # type: com.alibaba.druid.pool.DruidDataSource + # driver-class-name: com.mysql.cj.jdbc.Driver + name: eiot + url: jdbc:sqlite::resource:wvp.sqlite + username: + password: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: org.sqlite.JDBC + max-active: 1 + min-idle: 1 + +# [可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口 +server: + port: 18080 + +# 作为28181服务器的配置 +sip: + # [必须修改] 本机的IP + ip: ${WVP_HOST} + # [可选] 28181服务监听的端口 + port: 5060 + # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) + # 后两位为行业编码,定义参照附录D.3 + # 3701020049标识山东济南历下区 信息行业接入 + # [可选] + domain: ${WVP_DOMAIN:4401020049} + # [可选] + id: ${WVP_ID:44010200492000000001} + # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 + password: ${WVP_PWD:admin123} + +#zlm 默认服务器配置 +media: + # [必须修改] zlm服务器的内网IP + ip: 127.0.0.1 + # [必须修改] zlm服务器的http.port + http-port: 80 + # [可选] zlm服务器的hook.admin_params=secret + secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc + # 启用多端口模式, 多端口模式使用端口区分每路流,兼容性更好。 单端口使用流的ssrc区分, 点播超时建议使用多端口测试 + rtp: + # [可选] 是否启用多端口模式, 开启后会在portRange范围内选择端口用于媒体流传输 + enable: true + # [可选] 在此范围内选择端口用于媒体流传输, + port-range: 30000,30500 # 端口范围 + # [可选] 国标级联在此范围内选择端口发送媒体流, + send-port-range: 30000,30500 # 端口范围 + # 录像辅助服务, 部署此服务可以实现zlm录像的管理与下载, 0 表示不使用 + record-assist-port: 18081 + sdp-ip: ${WVP_HOST} + stream-ip: ${WVP_HOST} +# [可选] 日志配置, 一般不需要改 +logging: + file: + name: logs/wvp.log + max-history: 30 + max-size: 10MB + total-size-cap: 300MB + level: + com.genersoft.iot: debug + com.genersoft.iot.vmp.storager.dao: info + com.genersoft.iot.vmp.gb28181: info + +# [根据业务需求配置] +user-settings: + # 推流直播是否录制 + record-push-live: true + auto-apply-play: false + +# 在线文档: swagger-ui(生产环境建议关闭) +swagger-ui: + enabled: true + +# 版本信息, 不需修改 +version: + version: "@project.version@" + description: "@project.description@" + artifact-id: "@project.artifactId@" diff --git a/src/main/resources/wvp.sqlite b/src/main/resources/wvp.sqlite index 88aea8ed9..f969514a3 100644 Binary files a/src/main/resources/wvp.sqlite and b/src/main/resources/wvp.sqlite differ diff --git a/web_src/src/components/CloudRecord.vue b/web_src/src/components/CloudRecord.vue index f5d052a30..78f8a46e1 100644 --- a/web_src/src/components/CloudRecord.vue +++ b/web_src/src/components/CloudRecord.vue @@ -6,7 +6,9 @@
- 云端录像 + 云端录像 + +
节点选择: @@ -20,7 +22,6 @@
-
@@ -53,7 +54,7 @@ :total="total">
- +
@@ -72,6 +73,7 @@ return { mediaServerList: [], // 滅体节点列表 mediaServerId: null, // 媒体服务 + mediaServerPath: null, // 媒体服务地址 recordList: [], // 设备列表 chooseRecord: null, // 媒体服务 @@ -115,6 +117,11 @@ that.mediaServerList = data.data; if (that.mediaServerList.length > 0) { that.mediaServerId = that.mediaServerList[0].id + let port = that.mediaServerList[0].httpPort; + if (location.protocol === "https:" && that.mediaServerList[0].httpSSlPort) { + port = that.mediaServerList[0].httpSSlPort + } + that.mediaServerPath = location.protocol + "//" + that.mediaServerList[0].streamIp + ":" + port that.getRecordList(); } }) diff --git a/web_src/src/components/CloudRecordDetail.vue b/web_src/src/components/CloudRecordDetail.vue index 5d6ca90bb..fe534ec80 100644 --- a/web_src/src/components/CloudRecordDetail.vue +++ b/web_src/src/components/CloudRecordDetail.vue @@ -15,7 +15,8 @@ {{ item.substring(0,17)}} - + + @@ -75,7 +76,7 @@
  • @@ -111,10 +112,10 @@ components: { uiHeader, player }, - props: ['recordFile', 'mediaServerId', 'dateFiles'], + props: ['recordFile', 'mediaServerId', 'dateFiles', 'mediaServerPath'], data() { return { - basePath: process.env.NODE_ENV === 'development'?`${location.origin}/debug/zlm/${this.mediaServerId}`:`${location.origin}/zlm/${this.mediaServerId}`, + basePath: `${this.mediaServerPath}/record`, dateFilesObj: [], detailFiles: [], chooseDate: null, @@ -264,7 +265,7 @@ this.videoUrl = ""; }else { // TODO 控制列表滚动条 - this.videoUrl = `${this.basePath}/${this.mediaServerId}/record/${this.recordFile.app}/${this.recordFile.stream}/${this.chooseDate}/${this.choosedFile}` + this.videoUrl = `${this.basePath}/${this.recordFile.app}/${this.recordFile.stream}/${this.chooseDate}/${this.choosedFile}` console.log(this.videoUrl) } diff --git a/web_src/src/components/StreamProxyList.vue b/web_src/src/components/StreamProxyList.vue index 746bcdba6..27dab67c9 100644 --- a/web_src/src/components/StreamProxyList.vue +++ b/web_src/src/components/StreamProxyList.vue @@ -66,6 +66,14 @@ + + + diff --git a/web_src/src/components/UiHeader.vue b/web_src/src/components/UiHeader.vue index b2e9bbbdf..6391fe8c5 100644 --- a/web_src/src/components/UiHeader.vue +++ b/web_src/src/components/UiHeader.vue @@ -15,7 +15,7 @@ - + @@ -35,11 +35,23 @@ export default { components: { Notification, changePasswordDialog }, data() { return { - alarmNotify: true, + alarmNotify: false, sseSource: null, activeIndex: this.$route.path, }; }, + created(){ + if (this.$route.path.startsWith("/channelList")){ + this.activeIndex = "/deviceList" + } + + }, + mounted() { + window.addEventListener('beforeunload', e => this.beforeunloadHandler(e)) + // window.addEventListener('unload', e => this.unloadHandler(e)) + this.alarmNotify = this.getAlarmSwitchStatus() === "true"; + this.sseControl(); + }, methods:{ loginout(){ this.$axios({ @@ -65,6 +77,10 @@ export default { beforeunloadHandler() { this.sseSource.close(); }, + alarmNotifyChannge(){ + this.setAlarmSwitchStatus() + this.sseControl() + }, sseControl() { let that = this; if (this.alarmNotify) { @@ -90,31 +106,35 @@ export default { } }, false); } else { - this.sseSource.removeEventListener('open', null); - this.sseSource.removeEventListener('message', null); - this.sseSource.removeEventListener('error', null); - this.sseSource.close(); + if (this.sseSource != null) { + this.sseSource.removeEventListener('open', null); + this.sseSource.removeEventListener('message', null); + this.sseSource.removeEventListener('error', null); + this.sseSource.close(); + } + } + }, + getAlarmSwitchStatus(){ + if (localStorage.getItem("alarmSwitchStatus") == null) { + localStorage.setItem("alarmSwitchStatus", false); + } + return localStorage.getItem("alarmSwitchStatus"); + }, + setAlarmSwitchStatus(){ + localStorage.setItem("alarmSwitchStatus", this.alarmNotify); } }, - created(){ - if (this.$route.path.startsWith("/channelList")){ - this.activeIndex = "/deviceList" - } - }, - mounted() { - window.addEventListener('beforeunload', e => this.beforeunloadHandler(e)) - // window.addEventListener('unload', e => this.unloadHandler(e)) - this.sseControl(); - }, destroyed() { window.removeEventListener('beforeunload', e => this.beforeunloadHandler(e)) - this.sseSource.removeEventListener('open', null); - this.sseSource.removeEventListener('message', null); - this.sseSource.removeEventListener('error', null); - this.sseSource.close(); - // window.removeEventListener('unload', e => this.unloadHandler(e)) + if (this.sseSource != null) { + this.sseSource.removeEventListener('open', null); + this.sseSource.removeEventListener('message', null); + this.sseSource.removeEventListener('error', null); + this.sseSource.close(); + } }, + } diff --git a/web_src/src/components/dialog/StreamProxyEdit.vue b/web_src/src/components/dialog/StreamProxyEdit.vue index ea3a64f9b..fa09cf8d2 100644 --- a/web_src/src/components/dialog/StreamProxyEdit.vue +++ b/web_src/src/components/dialog/StreamProxyEdit.vue @@ -106,6 +106,7 @@ + @@ -160,7 +161,7 @@ export default { type: "default", app: null, stream: null, - url: "rtmp://58.200.131.2/livetv/cctv5hd", + url: "", src_url: null, timeout_ms: null, ffmpeg_cmd_key: null, @@ -169,6 +170,7 @@ export default { enable: true, enable_hls: true, enable_mp4: false, + enable_remove_none_reader: false, platformGbId: null, mediaServerId: "auto", }, diff --git a/web_src/src/components/dialog/devicePlayer.vue b/web_src/src/components/dialog/devicePlayer.vue index 60cd60014..74c14c810 100644 --- a/web_src/src/components/dialog/devicePlayer.vue +++ b/web_src/src/components/dialog/devicePlayer.vue @@ -39,7 +39,34 @@ - +
    +
    + 录像控制 + + + + + + 倍速 + + + 0.25倍速 + 0.5倍速 + 1倍速 + 2倍速 + 4倍速 + + + + +
    +
    + {{showTimeText}} + +
    +
    + + @@ -210,6 +237,10 @@ export default { showPtz: true, showRrecord: true, tracksNotLoaded: false, + sliderTime: 0, + seekTime: 0, + recordStartTime: 0, + showTimeText: "00:00:00", }; }, methods: { @@ -287,11 +318,13 @@ export default { // return `http://${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`; if (location.protocol === "https:") { if (streamInfo.wss_flv === null) { - this.$message({ - showClose: true, - message: '媒体服务器未配置ssl端口', - type: 'error' - }); + console.error("媒体服务器未配置ssl端口, 使用http端口") + // this.$message({ + // showClose: true, + // message: '媒体服务器未配置ssl端口, ', + // type: 'error' + // }); + return streamInfo.ws_flv }else { return streamInfo.wss_flv; } @@ -431,6 +464,14 @@ export default { }, playRecord: function (row) { let that = this; + + let startTime = row.startTime + this.recordStartTime = row.startTime + this.showTimeText = row.startTime.split(" ")[1] + let endtime = row.endTime + this.sliderTime = 0; + this.seekTime = new Date(endtime).getTime() - new Date(startTime).getTime(); + console.log(this.seekTime) if (that.streamId != "") { that.stopPlayRecord(function () { that.streamId = "", @@ -578,7 +619,45 @@ export default { } console.log(resultArray) return resultArray; + }, + gbPlay(){ + console.log('前端控制:播放'); + this.$axios({ + method: 'get', + url: '/api/playback/resume/' + this.streamId + }).then((res)=> { + this.$refs.videoPlayer.play(this.videoUrl) + }); + }, + gbPause(){ + console.log('前端控制:暂停'); + this.$axios({ + method: 'get', + url: '/api/playback/pause/' + this.streamId + }).then(function (res) {}); + }, + gbScale(command){ + console.log('前端控制:倍速 ' + command); + this.$axios({ + method: 'get', + url: `/api/playback/speed/${this.streamId }/${command}` + }).then(function (res) {}); + }, + gbSeek(val){ + console.log('前端控制:seek '); + console.log(this.seekTime); + console.log(this.sliderTime); + let showTime = new Date(new Date(this.recordStartTime).getTime() + this.seekTime * val / 100) + let hour = showTime.getHours(); + let minutes = showTime.getMinutes(); + let seconds = showTime.getSeconds(); + this.showTimeText = (hour < 10?("0" + hour):hour) + ":" + (minutes<10?("0" + minutes):minutes) + ":" + (seconds<10?("0" + seconds):seconds) + this.$axios({ + method: 'get', + url: `/api/playback/seek/${this.streamId }/` + Math.floor(this.seekTime * val / 100000) + }).then(function (res) {}); } + } }; diff --git a/web_src/src/components/dialog/platformEdit.vue b/web_src/src/components/dialog/platformEdit.vue index 968e8253f..1ee777d63 100644 --- a/web_src/src/components/dialog/platformEdit.vue +++ b/web_src/src/components/dialog/platformEdit.vue @@ -26,7 +26,7 @@ - + @@ -35,7 +35,7 @@ - + @@ -236,6 +236,17 @@ export default {