mirror of
https://gitee.com/pan648540858/wvp-GB28181-pro.git
synced 2026-05-27 15:37:50 +08:00
Merge branch 'wvp-28181-2.0' of gitee.com:pan648540858/wvp-GB28181-pro into wvp-28181-2.0
This commit is contained in:
commit
3c68f088d1
10
.github/ISSUE_TEMPLATE/-------.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/-------.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: "[ 新功能 ]"
|
||||
about: 新功能
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
29
.github/ISSUE_TEMPLATE/--bug---.md
vendored
Normal file
29
.github/ISSUE_TEMPLATE/--bug---.md
vendored
Normal file
@ -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. 你做过哪些尝试
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -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
|
||||
|
||||
18
DOCKERFILE
18
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
|
||||
|
||||
|
||||
18
README.md
18
README.md
@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
# 开箱即用的的28181协议视频平台
|
||||
|
||||
[](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
|
||||
|
||||
# 截图
|
||||

|
||||
@ -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大群)
|
||||
|
||||
@ -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 && \
|
||||
|
||||
Binary file not shown.
11
pom.xml
11
pom.xml
@ -212,17 +212,6 @@
|
||||
<!-- <version>1.0.8</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
|
||||
|
||||
<!-- onvif协议栈 -->
|
||||
<dependency>
|
||||
<groupId>be.teletask</groupId>
|
||||
<artifactId>onvif-java</artifactId>
|
||||
<version>1.0.2</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/libs/onvif-java-1.0.2.jar</systemPath>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
@ -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)
|
||||
);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,8 @@ public class UserSetup {
|
||||
|
||||
private Boolean logInDatebase = Boolean.TRUE;
|
||||
|
||||
private String serverId = "000000";
|
||||
|
||||
private List<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Runnable> processQueue = new LinkedBlockingQueue<>(10000);
|
||||
processThreadPool = new ThreadPoolExecutor(processThreadNum,processThreadNum,
|
||||
0L,TimeUnit.MILLISECONDS,processQueue,
|
||||
new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
}
|
||||
|
||||
|
||||
@Bean("sipFactory")
|
||||
private SipFactory createSipFactory() {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<String, Date> 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();
|
||||
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";
|
||||
|
||||
@ -66,6 +66,7 @@ public class PlatformKeepaliveExpireEventLister implements ApplicationListener<P
|
||||
storager.updateParentPlatformStatus(event.getPlatformGbID(), false);
|
||||
publisher.platformNotRegisterEventPublish(event.getPlatformGbID());
|
||||
parentPlatformCatch.setKeepAliveReply(0);
|
||||
redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
|
||||
}else {
|
||||
// 再次发送心跳
|
||||
String callId = sipCommanderForPlatform.keepalive(parentPlatform);
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
package com.genersoft.iot.vmp.gb28181.transmit;
|
||||
|
||||
import javax.sip.SipListener;
|
||||
|
||||
public interface ISIPProcessorObserver extends SipListener {
|
||||
}
|
||||
@ -7,6 +7,9 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.timeout.ITimeoutProcessor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sip.*;
|
||||
@ -22,7 +25,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* @date: 2021年11月5日 下午15:32
|
||||
*/
|
||||
@Component
|
||||
public class SIPProcessorObserver implements SipListener {
|
||||
public class SIPProcessorObserver implements ISIPProcessorObserver {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(SIPProcessorObserver.class);
|
||||
|
||||
@ -33,6 +36,10 @@ public class SIPProcessorObserver implements SipListener {
|
||||
@Autowired
|
||||
private SipSubscribe sipSubscribe;
|
||||
|
||||
// @Autowired
|
||||
// @Qualifier(value = "taskExecutor")
|
||||
// private ThreadPoolTaskExecutor poolTaskExecutor;
|
||||
|
||||
/**
|
||||
* 添加 request订阅
|
||||
* @param method 方法名
|
||||
@ -64,6 +71,7 @@ public class SIPProcessorObserver implements SipListener {
|
||||
* @param requestEvent RequestEvent事件
|
||||
*/
|
||||
@Override
|
||||
@Async
|
||||
public void processRequest(RequestEvent requestEvent) {
|
||||
String method = requestEvent.getRequest().getMethod();
|
||||
ISIPRequestProcessor sipRequestProcessor = requestProcessorMap.get(method);
|
||||
@ -72,6 +80,7 @@ public class SIPProcessorObserver implements SipListener {
|
||||
return;
|
||||
}
|
||||
requestProcessorMap.get(method).process(requestEvent);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,18 +88,9 @@ public class SIPProcessorObserver implements SipListener {
|
||||
* @param responseEvent responseEvent事件
|
||||
*/
|
||||
@Override
|
||||
@Async
|
||||
public void processResponse(ResponseEvent responseEvent) {
|
||||
logger.debug(responseEvent.getResponse().toString());
|
||||
// CSeqHeader cseqHeader = (CSeqHeader) responseEvent.getResponse().getHeader(CSeqHeader.NAME);
|
||||
// String method = cseqHeader.getMethod();
|
||||
// ISIPResponseProcessor sipRequestProcessor = responseProcessorMap.get(method);
|
||||
// if (sipRequestProcessor == null) {
|
||||
// logger.warn("不支持方法{}的response", method);
|
||||
// return;
|
||||
// }
|
||||
// sipRequestProcessor.process(responseEvent);
|
||||
|
||||
|
||||
Response response = responseEvent.getResponse();
|
||||
logger.debug(responseEvent.getResponse().toString());
|
||||
int status = response.getStatusCode();
|
||||
@ -126,7 +126,11 @@ public class SIPProcessorObserver implements SipListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (responseEvent.getDialog() != null) {
|
||||
responseEvent.getDialog().delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -143,18 +147,15 @@ public class SIPProcessorObserver implements SipListener {
|
||||
|
||||
@Override
|
||||
public void processIOException(IOExceptionEvent exceptionEvent) {
|
||||
// System.out.println("processIOException");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
|
||||
// System.out.println("processTransactionTerminated");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
|
||||
CallIdHeader callId = dialogTerminatedEvent.getDialog().getCallId();
|
||||
System.out.println("processDialogTerminated:::::" + callId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ public class CheckForAllRecordsThread extends Thread {
|
||||
// 自然顺序排序, 元素进行升序排列
|
||||
this.recordInfo.getRecordList().sort(Comparator.naturalOrder());
|
||||
RequestMessage msg = new RequestMessage();
|
||||
msg.setKey(DeferredResultHolder.CALLBACK_CMD_RECORDINFO + recordInfo.getDeviceId() + recordInfo.getChannelId());
|
||||
msg.setKey(DeferredResultHolder.CALLBACK_CMD_RECORDINFO + recordInfo.getDeviceId() + recordInfo.getSn());
|
||||
msg.setData(recordInfo);
|
||||
deferredResultHolder.invokeAllResult(msg);
|
||||
logger.info("处理完成,返回结果");
|
||||
|
||||
@ -35,9 +35,11 @@ public class DeferredResultHolder {
|
||||
|
||||
public static final String CALLBACK_CMD_PLAY = "CALLBACK_PLAY";
|
||||
|
||||
public static final String CALLBACK_CMD_STOP = "CALLBACK_STOP";
|
||||
public static final String CALLBACK_CMD_PLAYBACK = "CALLBACK_PLAY";
|
||||
|
||||
public static final String CALLBACK_ONVIF = "CALLBACK_ONVIF";
|
||||
public static final String CALLBACK_CMD_DOWNLOAD = "CALLBACK_DOWNLOAD";
|
||||
|
||||
public static final String CALLBACK_CMD_STOP = "CALLBACK_STOP";
|
||||
|
||||
public static final String CALLBACK_CMD_MOBILEPOSITION = "CALLBACK_MOBILEPOSITION";
|
||||
|
||||
@ -110,7 +112,7 @@ public class DeferredResultHolder {
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
result.setResult(new ResponseEntity<>(msg.getData(),HttpStatus.OK));
|
||||
result.setResult(ResponseEntity.ok().body(msg.getData()));
|
||||
}
|
||||
map.remove(msg.getKey());
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
/**
|
||||
* 查询报警信息
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -31,6 +35,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;
|
||||
// sipuri
|
||||
@ -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<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
|
||||
recordInfoXml.append("<Query>\r\n");
|
||||
recordInfoXml.append("<CmdType>RecordInfo</CmdType>\r\n");
|
||||
recordInfoXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
|
||||
recordInfoXml.append("<SN>" + sn + "</SN>\r\n");
|
||||
recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
|
||||
recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>\r\n");
|
||||
recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</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("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
|
||||
cmdXml.append("<Query>\r\n");
|
||||
cmdXml.append("<CmdType>CataLog</CmdType>\r\n");
|
||||
cmdXml.append("<CmdType>Catalog</CmdType>\r\n");
|
||||
cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
|
||||
cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
|
||||
cmdXml.append("</Query>\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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,9 +26,7 @@ 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 {
|
||||
|
||||
@ -29,14 +29,13 @@ 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;
|
||||
|
||||
|
||||
@ -10,9 +10,7 @@ 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 {
|
||||
|
||||
@ -34,9 +34,7 @@ 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=");
|
||||
// 检查是否有y字段
|
||||
String ssrcDefault = "0000000000";
|
||||
String ssrc;
|
||||
SessionDescription sdp;
|
||||
if (ssrcIndex >= 0) {
|
||||
//ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段
|
||||
String ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
|
||||
ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
|
||||
String substring = contentString.substring(0, contentString.indexOf("y="));
|
||||
SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);
|
||||
sdp = SdpFactory.getInstance().createSessionDescription(substring);
|
||||
}else {
|
||||
ssrc = ssrcDefault;
|
||||
sdp = SdpFactory.getInstance().createSessionDescription(contentString);
|
||||
}
|
||||
|
||||
// 获取支持的格式
|
||||
Vector mediaDescriptions = sdp.getMediaDescriptions(true);
|
||||
|
||||
@ -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("无录像数据");
|
||||
|
||||
@ -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")) {
|
||||
|
||||
@ -36,9 +36,7 @@ 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 {
|
||||
|
||||
@ -19,9 +19,7 @@ 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 {
|
||||
|
||||
@ -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<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
public MessageRequestProcessor messageRequestProcessor;
|
||||
public Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
|
||||
|
||||
public void addHandler(String cmdType, IMessageHandler messageHandler) {
|
||||
messageHandlerMap.put(cmdType, messageHandler);
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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("无录像数据");
|
||||
|
||||
@ -42,7 +42,6 @@ public class ByeResponseProcessor extends SIPResponseProcessorAbstract {
|
||||
@Override
|
||||
public void process(ResponseEvent evt) {
|
||||
// TODO Auto-generated method stub
|
||||
System.out.println("收到bye");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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<String>(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<String> onStreamChanged(@RequestBody JSONObject json){
|
||||
public ResponseEntity<String> 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<MediaItem.MediaTrack> tracks = item.getTracks();
|
||||
boolean regist = item.isRegist();
|
||||
if (tracks != null) {
|
||||
logger.info("[stream: " + streamId + "] on_stream_changed->>" + schema);
|
||||
}
|
||||
@ -295,11 +314,33 @@ public class ZLMHttpHookListener {
|
||||
}
|
||||
}else {
|
||||
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 如果在给上级推流,也不停止。
|
||||
if ("rtp".equals(app)){
|
||||
JSONObject ret = new JSONObject();
|
||||
ret.put("code", 0);
|
||||
if ("rtp".equals(app)){
|
||||
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<String>(ret.toString(),HttpStatus.OK);
|
||||
}else {
|
||||
JSONObject ret = new JSONObject();
|
||||
ret.put("code", 0);
|
||||
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<String>(ret.toString(),HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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<String, Object> 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()));
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<List<String>> callBack);
|
||||
|
||||
void getRTSPUrl(int timeout, OnvifDevice device, ONVIFCallBack<String> callBack);
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package com.genersoft.iot.vmp.onvif.dto;
|
||||
|
||||
public interface ONVIFCallBack<T> {
|
||||
void run(int errorCode, T t);
|
||||
}
|
||||
@ -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<List<String>> callBack) {
|
||||
DiscoveryManager manager = new DiscoveryManager();
|
||||
manager.setDiscoveryTimeout(timeout);
|
||||
Map<String, Device> deviceMap = new HashMap<>();
|
||||
// 搜索设备
|
||||
manager.discover(new DiscoveryListener() {
|
||||
@Override
|
||||
public void onDiscoveryStarted() {
|
||||
logger.info("Discovery started");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDevicesFound(List<Device> 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<String> 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<String> 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<OnvifMediaProfile> 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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -61,4 +61,6 @@ public interface IMediaServerService {
|
||||
boolean checkMediaRecordServer(String ip, int port);
|
||||
|
||||
void delete(String id);
|
||||
|
||||
MediaServerItem getDefaultMediaServer();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<StreamInfo> 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);
|
||||
}
|
||||
|
||||
@ -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<StreamPushItem> getPushList(Integer page, Integer count);
|
||||
|
||||
StreamPushItem transform(MediaItem item);
|
||||
}
|
||||
|
||||
@ -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 * * * ?";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<ResponseEntity<String>> 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<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(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");
|
||||
|
||||
@ -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<StreamInfo> save(StreamProxyItem param) {
|
||||
MediaServerItem mediaInfo;
|
||||
WVPResult<StreamInfo> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,15 +51,24 @@ public class StreamPushServiceImpl implements IStreamPushService {
|
||||
for (MediaItem item : mediaItems) {
|
||||
|
||||
// 不保存国标推理以及拉流代理的流
|
||||
if (item.getOriginType() == 3 || item.getOriginType() == 4 || item.getOriginType() == 5) {
|
||||
continue;
|
||||
}
|
||||
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 = new StreamPushItem();
|
||||
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(mediaServerItem.getId());
|
||||
streamPushItem.setMediaServerId(item.getMediaServerId());
|
||||
streamPushItem.setStream(item.getStream());
|
||||
streamPushItem.setAliveSecond(item.getAliveSecond());
|
||||
streamPushItem.setCreateStamp(item.getCreateStamp());
|
||||
@ -72,11 +81,7 @@ public class StreamPushServiceImpl implements IStreamPushService {
|
||||
streamPushItem.setAliveSecond(item.getAliveSecond());
|
||||
streamPushItem.setStatus(true);
|
||||
streamPushItem.setVhost(item.getVhost());
|
||||
result.put(key, streamPushItem);
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(result.values());
|
||||
return streamPushItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<GbStream> selectAllByMediaServerId(String mediaServerId);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, steamId));
|
||||
public StreamInfo queryPlayByStreamId(String streamId) {
|
||||
List<Object> 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<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, steamId));
|
||||
public StreamInfo queryPlaybackByStreamId(String streamId) {
|
||||
List<Object> 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<Object> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<StreamInfo> getStreamInfoByAppAndStream(@RequestParam String app, @RequestParam String stream, @RequestParam String mediaServerId){
|
||||
public WVPResult<StreamInfo> getStreamInfoByAppAndStream(@RequestParam String app, @RequestParam String stream, @RequestParam(required = false) String mediaServerId){
|
||||
StreamInfo streamInfoByAppAndStreamWithCheck = mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId);
|
||||
WVPResult<StreamInfo> result = new WVPResult<>();
|
||||
if (streamInfoByAppAndStreamWithCheck != null){
|
||||
|
||||
@ -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<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(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);
|
||||
|
||||
@ -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<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(30000L);
|
||||
Device device = storager.queryVideoDevice(deviceId);
|
||||
if (device == null) {
|
||||
@ -152,4 +153,103 @@ public class PlaybackController {
|
||||
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation("回放暂停")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "streamId", value = "回放流ID", dataTypeClass = String.class),
|
||||
})
|
||||
@GetMapping("/pause/{streamId}")
|
||||
public ResponseEntity<String> 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<String>(json.toString(), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
setCseq(streamId);
|
||||
Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
|
||||
cmder.playPauseCmd(device, streamInfo);
|
||||
json.put("msg", "ok");
|
||||
return new ResponseEntity<String>(json.toString(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@ApiOperation("回放恢复")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "streamId", value = "回放流ID", dataTypeClass = String.class),
|
||||
})
|
||||
@GetMapping("/resume/{streamId}")
|
||||
public ResponseEntity<String> 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<String>(json.toString(), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
setCseq(streamId);
|
||||
Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
|
||||
cmder.playResumeCmd(device, streamInfo);
|
||||
json.put("msg", "ok");
|
||||
return new ResponseEntity<String>(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<String> 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<String>(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<String>(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<String> 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<String>(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<String>(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<String>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,17 +56,22 @@ public class GBRecordController {
|
||||
}
|
||||
|
||||
Device device = storager.queryVideoDevice(deviceId);
|
||||
cmder.recordInfoQuery(device, channelId, startTime, endTime);
|
||||
// 指定超时时间 1分钟30秒
|
||||
DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<>(90*1000L);
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + channelId;
|
||||
// 录像查询以channelId作为deviceId查询
|
||||
resultHolder.put(key, uuid, result);
|
||||
result.onTimeout(()->{
|
||||
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(()->{
|
||||
msg.setData("timeout");
|
||||
resultHolder.invokeResult(msg);
|
||||
});
|
||||
|
||||
@ -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<String, Long> CSEQCACHE = new ConcurrentHashMap<>();
|
||||
|
||||
}
|
||||
@ -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<ResponseEntity<WVPResult>> search(@RequestParam(required = false)Integer timeout){
|
||||
DeferredResult<ResponseEntity<WVPResult>> 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<String> 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<List<String>> 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<ResponseEntity<WVPResult>> 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<ResponseEntity<WVPResult>> 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<String> 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<String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<Object> result = new WVPResult<>();
|
||||
result.setCode(0);
|
||||
result.setMsg(msg);
|
||||
if (StringUtils.isEmpty(param.getType())) param.setType("default");
|
||||
WVPResult<StreamInfo> result = streamProxyService.save(param);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ -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<StreamInfo> getStreamInfoByAppAndStream(HttpServletRequest request, @RequestParam String app, @RequestParam String stream){
|
||||
String localAddr = request.getLocalAddr();
|
||||
StreamInfo streamINfo = mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream, localAddr);
|
||||
WVPResult<StreamInfo> wvpResult = new WVPResult<>();
|
||||
wvpResult.setCode(0);
|
||||
wvpResult.setMsg("success");
|
||||
wvpResult.setData(streamINfo);
|
||||
return wvpResult;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -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());
|
||||
@ -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<JSONObject> resultDeferredResult = new DeferredResult<JSONObject>();
|
||||
DeferredResult<JSONObject> 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<ResponseEntity<String>> play = playController.play(serial, code);
|
||||
|
||||
play.setResultHandler((Object o)->{
|
||||
ResponseEntity<String> 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;
|
||||
@ -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;
|
||||
@ -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,需要打开此设置
|
||||
|
||||
@ -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:
|
||||
|
||||
98
src/main/resources/application-docker.yml
Normal file
98
src/main/resources/application-docker.yml
Normal file
@ -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@"
|
||||
Binary file not shown.
@ -6,7 +6,9 @@
|
||||
</el-header>
|
||||
<el-main>
|
||||
<div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;">
|
||||
<span style="font-size: 1rem; font-weight: bold;">云端录像</span>
|
||||
<span v-if="!recordDetail" >云端录像</span>
|
||||
<el-page-header v-if="recordDetail" @back="backToList" content="云端录像">
|
||||
</el-page-header>
|
||||
<div style="position: absolute; right: 5rem; top: 0.3rem;">
|
||||
节点选择:
|
||||
<el-select size="mini" @change="chooseMediaChange" style="width: 16rem; margin-right: 1rem;" v-model="mediaServerId" placeholder="请选择" :disabled="recordDetail">
|
||||
@ -20,7 +22,6 @@
|
||||
</div>
|
||||
<div style="position: absolute; right: 1rem; top: 0.3rem;">
|
||||
<el-button v-if="!recordDetail" icon="el-icon-refresh-right" circle size="mini" :loading="loading" @click="getRecordList()"></el-button>
|
||||
<el-button v-if="recordDetail" icon="el-icon-arrow-left" circle size="mini" @click="backToList()"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!recordDetail">
|
||||
@ -53,7 +54,7 @@
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
<cloud-record-detail ref="cloudRecordDetail" v-if="recordDetail" :recordFile="chooseRecord" :mediaServerId="mediaServerId" ></cloud-record-detail>
|
||||
<cloud-record-detail ref="cloudRecordDetail" v-if="recordDetail" :recordFile="chooseRecord" :mediaServerId="mediaServerId" :mediaServerPath="mediaServerPath" ></cloud-record-detail>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
@ -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();
|
||||
}
|
||||
})
|
||||
|
||||
@ -15,7 +15,8 @@
|
||||
<i class="el-icon-video-camera" ></i>
|
||||
{{ item.substring(0,17)}}
|
||||
</el-tag>
|
||||
<a class="el-icon-download" style="color: #409EFF;font-weight: 600;margin-left: 10px;" :href="`${basePath}/${mediaServerId}/record/${recordFile.app}/${recordFile.stream}/${chooseDate}/${item}`" download />
|
||||
<!-- <a class="el-icon-download" style="color: #409EFF;font-weight: 600;margin-left: 10px;" :href="`${basePath}/${mediaServerId}/record/${recordFile.app}/${recordFile.stream}/${chooseDate}/${item}`" download />-->
|
||||
<a class="el-icon-download" style="color: #409EFF;font-weight: 600;margin-left: 10px;" :href="`${basePath}/download.html?url=${recordFile.app}/${recordFile.stream}/${chooseDate}/${item}`" target="_blank" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -75,7 +76,7 @@
|
||||
<li class="task-list-item" v-for="item in taskListEnded">
|
||||
<div class="task-list-item-box" style="height: 2rem;line-height: 2rem;">
|
||||
<span>{{ item.startTime.substr(10) }}-{{item.endTime.substr(10)}}</span>
|
||||
<a class="el-icon-download download-btn" :href="basePath + '/' + item.recordFile" download >
|
||||
<a class="el-icon-download download-btn" :href="basePath + '/download.html?url=../' + item.recordFile" target="_blank">
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -66,6 +66,14 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="无人观看自动删除" width="160" align="center">
|
||||
<template slot-scope="scope">
|
||||
<div slot="reference" class="name-wrapper">
|
||||
<el-tag size="medium" v-if="scope.row.enable_remove_none_reader">已启用</el-tag>
|
||||
<el-tag size="medium" type="info" v-if="!scope.row.enable_remove_none_reader">未启用</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<el-table-column label="操作" width="360" align="center" fixed="right">
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
<!-- <el-menu-item index="/setting/sip">国标服务</el-menu-item>-->
|
||||
<!-- <el-menu-item index="/setting/media">媒体服务</el-menu-item>-->
|
||||
<!-- </el-submenu>-->
|
||||
<el-switch v-model="alarmNotify" active-text="报警信息推送" style="display: block float: right" @change="sseControl"></el-switch>
|
||||
<el-switch v-model="alarmNotify" active-text="报警信息推送" style="display: block float: right" @change="alarmNotifyChannge"></el-switch>
|
||||
<!-- <el-menu-item style="float: right;" @click="loginout">退出</el-menu-item>-->
|
||||
<el-submenu index="" style="float: right;" >
|
||||
<template slot="title">欢迎,{{this.$cookies.get("session").username}}</template>
|
||||
@ -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 {
|
||||
if (this.sseSource != null) {
|
||||
this.sseSource.removeEventListener('open', null);
|
||||
this.sseSource.removeEventListener('message', null);
|
||||
this.sseSource.removeEventListener('error', null);
|
||||
this.sseSource.close();
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
created(){
|
||||
if (this.$route.path.startsWith("/channelList")){
|
||||
this.activeIndex = "/deviceList"
|
||||
getAlarmSwitchStatus(){
|
||||
if (localStorage.getItem("alarmSwitchStatus") == null) {
|
||||
localStorage.setItem("alarmSwitchStatus", false);
|
||||
}
|
||||
return localStorage.getItem("alarmSwitchStatus");
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('beforeunload', e => this.beforeunloadHandler(e))
|
||||
// window.addEventListener('unload', e => this.unloadHandler(e))
|
||||
this.sseControl();
|
||||
setAlarmSwitchStatus(){
|
||||
localStorage.setItem("alarmSwitchStatus", this.alarmNotify);
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('beforeunload', e => this.beforeunloadHandler(e))
|
||||
if (this.sseSource != null) {
|
||||
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))
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@ -106,6 +106,7 @@
|
||||
<el-checkbox label="启用" v-model="proxyParam.enable" ></el-checkbox>
|
||||
<el-checkbox label="转HLS" v-model="proxyParam.enable_hls" ></el-checkbox>
|
||||
<el-checkbox label="MP4录制" v-model="proxyParam.enable_mp4" ></el-checkbox>
|
||||
<el-checkbox label="无人观看自动删除" v-model="proxyParam.enable_remove_none_reader" ></el-checkbox>
|
||||
</div>
|
||||
|
||||
</el-form-item>
|
||||
@ -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",
|
||||
},
|
||||
|
||||
@ -39,7 +39,34 @@
|
||||
</el-tab-pane>
|
||||
<!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}-->
|
||||
<el-tab-pane label="录像查询" name="record" v-if="showRrecord">
|
||||
<el-date-picker size="mini" v-model="videoHistory.date" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="queryRecords()"></el-date-picker>
|
||||
<div style="width: 100%;">
|
||||
<div style="width: 100%; text-align: left">
|
||||
<span>录像控制</span>
|
||||
<el-button-group style="margin-left: 1rem;">
|
||||
<el-button size="mini" class="iconfont icon-zanting" title="开始" @click="gbPause()"></el-button>
|
||||
<el-button size="mini" class="iconfont icon-kaishi" title="暂停" @click="gbPlay()"></el-button>
|
||||
<el-dropdown size="mini" title="播放倍速" style="margin-left: 1px;" @command="gbScale">
|
||||
<el-button size="mini">
|
||||
倍速 <i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="0.25">0.25倍速</el-dropdown-item>
|
||||
<el-dropdown-item command="0.5">0.5倍速</el-dropdown-item>
|
||||
<el-dropdown-item command="1.0">1倍速</el-dropdown-item>
|
||||
<el-dropdown-item command="2.0">2倍速</el-dropdown-item>
|
||||
<el-dropdown-item command="4.0">4倍速</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-button-group>
|
||||
<el-date-picker style="float: right;" size="mini" v-model="videoHistory.date" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="queryRecords()"></el-date-picker>
|
||||
</div>
|
||||
<div style="width: 100%; text-align: left">
|
||||
<span class="demonstration" style="padding: 12px 36px 12px 0;float: left;">{{showTimeText}}</span>
|
||||
<el-slider style="width: 80%; float:left;" v-model="sliderTime" @change="gbSeek" :show-tooltip="false"></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<el-table :data="videoHistory.searchHistoryResult" height="150" v-loading="recordsLoading">
|
||||
<el-table-column label="名称" prop="name"></el-table-column>
|
||||
<el-table-column label="文件" prop="filePath"></el-table-column>
|
||||
@ -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) {});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
<el-input v-model="platform.serverIP" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="SIP服务端口" prop="serverPort">
|
||||
<el-input v-model="platform.serverPort" clearable></el-input>
|
||||
<el-input v-model="platform.serverPort" clearable type="number"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备国标编号" prop="deviceGBId">
|
||||
<el-input v-model="platform.deviceGBId" clearable></el-input>
|
||||
@ -35,7 +35,7 @@
|
||||
<el-input v-model="platform.deviceIp" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="本地端口" prop="devicePort">
|
||||
<el-input v-model="platform.devicePort" :disabled="true"></el-input>
|
||||
<el-input v-model="platform.devicePort" :disabled="true" type="number"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
@ -236,6 +236,17 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 谷歌 */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
/* 火狐 */
|
||||
input{
|
||||
-moz-appearance:textfield;
|
||||
}
|
||||
.control-wrapper-not-used {
|
||||
position: relative;
|
||||
width: 6.25rem;
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 1291092 */
|
||||
src: url('iconfont.woff2?t=1631767887536') format('woff2'),
|
||||
url('iconfont.woff?t=1631767887536') format('woff'),
|
||||
url('iconfont.ttf?t=1631767887536') format('truetype');
|
||||
src: url('iconfont.woff2?t=1637741914969') format('woff2'),
|
||||
url('iconfont.woff?t=1637741914969') format('woff'),
|
||||
url('iconfont.ttf?t=1637741914969') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@ -13,6 +13,226 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-suoxiao:before {
|
||||
content: "\e79a";
|
||||
}
|
||||
|
||||
.icon-shanchu3:before {
|
||||
content: "\e79b";
|
||||
}
|
||||
|
||||
.icon-chehui:before {
|
||||
content: "\e79c";
|
||||
}
|
||||
|
||||
.icon-wenben:before {
|
||||
content: "\e79d";
|
||||
}
|
||||
|
||||
.icon-zhongzuo:before {
|
||||
content: "\e79e";
|
||||
}
|
||||
|
||||
.icon-jianqie:before {
|
||||
content: "\e79f";
|
||||
}
|
||||
|
||||
.icon-fangda:before {
|
||||
content: "\e7a0";
|
||||
}
|
||||
|
||||
.icon-fangdazhanshi:before {
|
||||
content: "\e7a1";
|
||||
}
|
||||
|
||||
.icon-qianjin:before {
|
||||
content: "\e7a2";
|
||||
}
|
||||
|
||||
.icon-kuaijin:before {
|
||||
content: "\e7a3";
|
||||
}
|
||||
|
||||
.icon-diyigeshipin:before {
|
||||
content: "\e7a4";
|
||||
}
|
||||
|
||||
.icon-kuaitui:before {
|
||||
content: "\e7a5";
|
||||
}
|
||||
|
||||
.icon-kaishi:before {
|
||||
content: "\e7a7";
|
||||
}
|
||||
|
||||
.icon-zuihouyigeshipin:before {
|
||||
content: "\e7a8";
|
||||
}
|
||||
|
||||
.icon-zanting:before {
|
||||
content: "\e7a9";
|
||||
}
|
||||
|
||||
.icon-zhankai:before {
|
||||
content: "\e7aa";
|
||||
}
|
||||
|
||||
.icon-bendisucai:before {
|
||||
content: "\e7ab";
|
||||
}
|
||||
|
||||
.icon-luzhi:before {
|
||||
content: "\e7ac";
|
||||
}
|
||||
|
||||
.icon-ossziyuan:before {
|
||||
content: "\e7ad";
|
||||
}
|
||||
|
||||
.icon-chuangjianzhinengfenxirenwu:before {
|
||||
content: "\e7ae";
|
||||
}
|
||||
|
||||
.icon-sousuo3:before {
|
||||
content: "\e7af";
|
||||
}
|
||||
|
||||
.icon-gengduo:before {
|
||||
content: "\e7b0";
|
||||
}
|
||||
|
||||
.icon-tianjia:before {
|
||||
content: "\e7b1";
|
||||
}
|
||||
|
||||
.icon-xiazai:before {
|
||||
content: "\e7b2";
|
||||
}
|
||||
|
||||
.icon-biaojibeifen:before {
|
||||
content: "\e7b3";
|
||||
}
|
||||
|
||||
.icon-bendisucaibeifen:before {
|
||||
content: "\e7b4";
|
||||
}
|
||||
|
||||
.icon-luzhibeifen:before {
|
||||
content: "\e7b5";
|
||||
}
|
||||
|
||||
.icon-ossziyuanbeifen:before {
|
||||
content: "\e7b6";
|
||||
}
|
||||
|
||||
.icon-bianji3:before {
|
||||
content: "\e7b7";
|
||||
}
|
||||
|
||||
.icon-cuti:before {
|
||||
content: "\e7b8";
|
||||
}
|
||||
|
||||
.icon-xieti:before {
|
||||
content: "\e7b9";
|
||||
}
|
||||
|
||||
.icon-xiahuaxian:before {
|
||||
content: "\e7ba";
|
||||
}
|
||||
|
||||
.icon-wuxiaoguo:before {
|
||||
content: "\e7bb";
|
||||
}
|
||||
|
||||
.icon-sousuo4:before {
|
||||
content: "\e7bc";
|
||||
}
|
||||
|
||||
.icon-gouwuche:before {
|
||||
content: "\e7bd";
|
||||
}
|
||||
|
||||
.icon-shuaxin2:before {
|
||||
content: "\e7be";
|
||||
}
|
||||
|
||||
.icon-xiaoxi:before {
|
||||
content: "\e7bf";
|
||||
}
|
||||
|
||||
.icon-wushouquan:before {
|
||||
content: "\e7c0";
|
||||
}
|
||||
|
||||
.icon-tishi2:before {
|
||||
content: "\e7c1";
|
||||
}
|
||||
|
||||
.icon-tishi1:before {
|
||||
content: "\e7c2";
|
||||
}
|
||||
|
||||
.icon-shouquanchenggong:before {
|
||||
content: "\e7c3";
|
||||
}
|
||||
|
||||
.icon-sousuo5:before {
|
||||
content: "\e7c4";
|
||||
}
|
||||
|
||||
.icon-shuaxin3:before {
|
||||
content: "\e7c5";
|
||||
}
|
||||
|
||||
.icon-xiazai1:before {
|
||||
content: "\e7c6";
|
||||
}
|
||||
|
||||
.icon-shangchuan:before {
|
||||
content: "\e7c7";
|
||||
}
|
||||
|
||||
.icon-guanbi:before {
|
||||
content: "\e7c8";
|
||||
}
|
||||
|
||||
.icon-wangye-loading:before {
|
||||
content: "\e7c9";
|
||||
}
|
||||
|
||||
.icon-bianzubeifen3:before {
|
||||
content: "\e7ca";
|
||||
}
|
||||
|
||||
.icon-xingzhuangbeifen:before {
|
||||
content: "\e7cb";
|
||||
}
|
||||
|
||||
.icon-bianzubeifen:before {
|
||||
content: "\e7cc";
|
||||
}
|
||||
|
||||
.icon-zhuanchang:before {
|
||||
content: "\e7cd";
|
||||
}
|
||||
|
||||
.icon-meizi:before {
|
||||
content: "\e7ce";
|
||||
}
|
||||
|
||||
.icon-daimabeifen:before {
|
||||
content: "\e7cf";
|
||||
}
|
||||
|
||||
.icon-suoxiao1:before {
|
||||
content: "\e7d0";
|
||||
}
|
||||
|
||||
.icon-ai19:before {
|
||||
content: "\e799";
|
||||
}
|
||||
|
||||
.icon-online:before {
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user