Merge branch 'wvp-28181-2.0' of gitee.com:pan648540858/wvp-GB28181-pro into wvp-28181-2.0

This commit is contained in:
hotcoffie 2021-11-30 09:28:40 +00:00 committed by Gitee
commit 3c68f088d1
101 changed files with 1624 additions and 706 deletions

10
.github/ISSUE_TEMPLATE/-------.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
name: "[ 新功能 ]"
about: 新功能
title: ''
labels: ''
assignees: ''
---

29
.github/ISSUE_TEMPLATE/--bug---.md vendored Normal file
View 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
View File

@ -1,3 +1,3 @@
[submodule "be.teletask.onvif-java"] [submodule "be.teletask.onvif-java"]
path = 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

View File

@ -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 cmake curl vim ca-certificates tzdata libmysqlclient-dev redis-server libssl-dev libx264-dev libfaac-dev ffmpeg
WORKDIR /home 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/ && \ cp maven/settings.xml /usr/share/maven/conf/ && \
git clone https://gitee.com/18010473990/wvp-GB28181.git && \ git clone https://gitee.com/pan648540858/wvp-GB28181-pro.git && \
git clone https://gitee.com/18010473990/wvp-pro-assist.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 && \ RUN npm install && \
npm run build && \ npm run build && \
mkdir -p /opt/wvp/config && \ mkdir -p /opt/wvp/config && \
mkdir -p /opt/assist/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 cp /home/wvp-pro-assist/src/main/resources/application-dev.yml /opt/assist/config/application.yml
# wvp打包 # wvp打包
WORKDIR /home/wvp-GB28181 WORKDIR /home/wvp-GB28181-pro
RUN mvn compile && \ RUN mvn compile && \
mvn package && \ mvn package && \
cp /home/wvp-GB28181/target/wvp*.jar /opt/wvp/ cp /home/wvp-GB28181-pro/target/wvp*.jar /opt/wvp/
# wvp 录像管理打包 # wvp 录像管理打包
WORKDIR /home/wvp-pro-assist WORKDIR /home/wvp-pro-assist
@ -72,7 +72,7 @@ RUN mkdir -p /opt/media && \
# 清理 # 清理
RUN rm -rf /home/wiki && \ 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 autoremove -y git maven nodejs npm && \
apt-get clean -y && \ apt-get clean -y && \
rm -rf /var/lib/apt/lists/*dic 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 '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 ' 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 '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 echo 'fi' >> run.sh
RUN chmod +x run.sh RUN chmod +x run.sh

View File

@ -1,4 +1,4 @@
![logo](https://gitee.com/18010473990/wvp-GB28181/raw/wvp-28181-2.0/web_src/static/logo.png) ![logo](https://gitee.com/pan648540858/wvp-GB28181-pro/raw/wvp-28181-2.0/web_src/static/logo.png)
# 开箱即用的的28181协议视频平台 # 开箱即用的的28181协议视频平台
[![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit) [![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit)
@ -13,13 +13,7 @@ WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网
流媒体服务基于ZLMediaKit-https://github.com/xiongziliang/ZLMediaKit 流媒体服务基于ZLMediaKit-https://github.com/xiongziliang/ZLMediaKit
前端页面基于MediaServerUI进行修改. 前端页面基于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等设备接入。 支持摄像机、平台、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) [https://github.com/648540858/wvp-GB28181-pro/wiki](https://github.com/648540858/wvp-GB28181-pro/wiki)
# gitee同步仓库 # gitee同步仓库
https://gitee.com/18010473990/wvp-GB28181.git https://gitee.com/pan648540858/wvp-GB28181-pro.git
# 截图 # 截图
![build_1.png](https://github.com/648540858/wiki/blob/master/images/Screenshot_1.png) ![build_1.png](https://github.com/648540858/wiki/blob/master/images/Screenshot_1.png)
@ -112,10 +106,16 @@ https://gitee.com/18010473990/wvp-GB28181.git
- [ ] 添加用户管理 - [ ] 添加用户管理
- [X] WEB端支持播放H264与H265音频支持G.711A/G.711U/AAC,覆盖国标常用编码格式。 - [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同步仓库 # gitee同步仓库
https://gitee.com/18010473990/wvp-GB28181.git https://gitee.com/pan648540858/wvp-GB28181-pro.git
# 使用帮助 # 使用帮助
QQ群: 901799015, 690854210(ZLM大群) QQ群: 901799015, 690854210(ZLM大群)

View File

@ -1,6 +1,6 @@
FROM ubuntu:20.04 AS build 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" ARG zlmGitUrl="https://gitee.com/xia-chu/ZLMediaKit"
RUN export DEBIAN_FRONTEND=noninteractive &&\ RUN export DEBIAN_FRONTEND=noninteractive &&\
@ -9,9 +9,9 @@ RUN export DEBIAN_FRONTEND=noninteractive &&\
cmake ca-certificates openssl ffmpeg cmake ca-certificates openssl ffmpeg
RUN cd /home && \ 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/ && \ 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 "${gitUrl}/wvp-pro-assist.git" && \
git clone --depth=1 "${zlmGitUrl}" && \ git clone --depth=1 "${zlmGitUrl}" && \
mkdir -p /opt/wvp/config /opt/assist/config /opt/media/www/record 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 && \ RUN cd /home/wvp-GB28181 && \
mvn clean package -Dmaven.test.skip=true && \ mvn clean package -Dmaven.test.skip=true && \
cp /home/wvp-GB28181/target/*.jar /opt/wvp/ && \ 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 && \ RUN cd /home/wvp-pro-assist && \
mvn clean package -Dmaven.test.skip=true && \ mvn clean package -Dmaven.test.skip=true && \

Binary file not shown.

11
pom.xml
View File

@ -212,17 +212,6 @@
<!-- <version>1.0.8</version>--> <!-- <version>1.0.8</version>-->
<!-- </dependency>--> <!-- </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> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>

View File

@ -208,6 +208,7 @@ create table stream_proxy
enable_hls bit null, enable_hls bit null,
enable_mp4 bit null, enable_mp4 bit null,
enable bit not null, enable bit not null,
enable_remove_none_reader bit not null,
createTime varchar(50) not null, createTime varchar(50) not null,
primary key (app, stream) primary key (app, stream)
); );

View File

@ -6,6 +6,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import springfox.documentation.oas.annotations.EnableOpenApi; import springfox.documentation.oas.annotations.EnableOpenApi;

View File

@ -30,7 +30,7 @@ public class StreamInfo {
private String rtsps; private String rtsps;
private String rtc; private String rtc;
private String mediaServerId; private String mediaServerId;
private JSONArray tracks; private Object tracks;
public static class TransactionInfo{ public static class TransactionInfo{
public String callId; public String callId;
@ -105,11 +105,11 @@ public class StreamInfo {
this.rtsp = rtsp; this.rtsp = rtsp;
} }
public JSONArray getTracks() { public Object getTracks() {
return tracks; return tracks;
} }
public void setTracks(JSONArray tracks) { public void setTracks(Object tracks) {
this.tracks = tracks; this.tracks = tracks;
} }

View File

@ -8,7 +8,9 @@ package com.genersoft.iot.vmp.common;
*/ */
public class VideoManagerConstants { 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_"; 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 PLAYER_PREFIX = "VMP_PLAYER_";
public static final String PLAY_BLACK_PREFIX = "VMP_PLAYBACK_"; 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_"; 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_SSRC_USED_PREFIX = "VMP_media_used_ssrc_";
public static final String MEDIA_TRANSACTION_USED_PREFIX = "VMP_media_transaction_"; 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";
} }

View File

@ -26,9 +26,10 @@ public class DynamicTask {
return new ThreadPoolTaskScheduler(); return new ThreadPoolTaskScheduler();
} }
public String startCron(String key, Runnable task, String corn) { public String startCron(String key, Runnable task, int cycleForCatalog) {
stopCron(key); stopCron(key);
ScheduledFuture future = threadPoolTaskScheduler.schedule(task, new CronTrigger(corn)); // scheduleWithFixedDelay 必须等待上一个任务结束才开始计时period cycleForCatalog表示执行的间隔
ScheduledFuture future = threadPoolTaskScheduler.scheduleWithFixedDelay(task, cycleForCatalog * 1000L);
futureMap.put(key, future); futureMap.put(key, future);
return "startCron"; return "startCron";
} }

View File

@ -163,9 +163,9 @@ public class ProxyServletConfig {
* 异常处理 * 异常处理
*/ */
@Override @Override
protected void handleRequestException(HttpRequest proxyRequest, HttpResponse proxyResonse, Exception e){ protected void handleRequestException(HttpRequest proxyRequest, HttpResponse proxyResponse, Exception e){
try { try {
super.handleRequestException(proxyRequest, proxyResonse, e); super.handleRequestException(proxyRequest, proxyResponse, e);
} catch (ServletException servletException) { } catch (ServletException servletException) {
logger.error("录像服务 代理失败: ", e); logger.error("录像服务 代理失败: ", e);
} catch (IOException ioException) { } catch (IOException ioException) {

View File

@ -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;
}
}

View File

@ -27,6 +27,8 @@ public class UserSetup {
private Boolean logInDatebase = Boolean.TRUE; private Boolean logInDatebase = Boolean.TRUE;
private String serverId = "000000";
private List<String> interfaceAuthenticationExcludes = new ArrayList<>(); private List<String> interfaceAuthenticationExcludes = new ArrayList<>();
public Boolean getSavePositionHistory() { public Boolean getSavePositionHistory() {
@ -104,4 +106,12 @@ public class UserSetup {
public void setLogInDatebase(Boolean logInDatebase) { public void setLogInDatebase(Boolean logInDatebase) {
this.logInDatebase = logInDatebase; this.logInDatebase = logInDatebase;
} }
public String getServerId() {
return serverId;
}
public void setServerId(String serverId) {
this.serverId = serverId;
}
} }

View File

@ -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;
}
}

View File

@ -1,7 +1,10 @@
package com.genersoft.iot.vmp.conf; 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 com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -12,13 +15,22 @@ public class WVPTimerTask {
private IRedisCatchStorage redisCatchStorage; private IRedisCatchStorage redisCatchStorage;
@Autowired @Autowired
private SipConfig sipConfig; private IMediaServerService mediaServerService;
@Autowired @Autowired
private MediaConfig mediaConfig; private UserSetup userSetup;
// @Scheduled(cron="0/2 * * * * ? ") //每3秒执行一次 @Value("${server.port}")
// public void execute(){ private int serverPort;
//// redisCatchStorage.updateWVPInfo();
// } @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);
}
} }

View File

@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181;
import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; 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 com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import gov.nist.javax.sip.SipProviderImpl; import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.SipStackImpl; import gov.nist.javax.sip.SipStackImpl;
@ -28,28 +29,12 @@ public class SipLayer{
private SipConfig sipConfig; private SipConfig sipConfig;
@Autowired @Autowired
private SIPProcessorObserver sipProcessorObserver; private ISIPProcessorObserver sipProcessorObserver;
@Autowired
private SipSubscribe sipSubscribe;
private SipStackImpl sipStack; private SipStackImpl sipStack;
private SipFactory sipFactory; 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") @Bean("sipFactory")
private SipFactory createSipFactory() { private SipFactory createSipFactory() {

View File

@ -16,6 +16,8 @@ public class RecordInfo {
private String channelId; private String channelId;
private String sn;
private String name; private String name;
private int sumNum; private int sumNum;
@ -61,4 +63,12 @@ public class RecordInfo {
public void setChannelId(String channelId) { public void setChannelId(String channelId) {
this.channelId = channelId; this.channelId = channelId;
} }
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
} }

View File

@ -6,6 +6,8 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.sip.*; import javax.sip.*;
import javax.sip.header.CallIdHeader;
import javax.sip.message.Response;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
@ -23,6 +25,7 @@ public class SipSubscribe {
private Map<String, Date> timeSubscribes = new ConcurrentHashMap<>(); private Map<String, Date> timeSubscribes = new ConcurrentHashMap<>();
// @Scheduled(cron="*/5 * * * * ?") //每五秒执行一次 // @Scheduled(cron="*/5 * * * * ?") //每五秒执行一次
// @Scheduled(fixedRate= 100 * 60 * 60 )
@Scheduled(cron="0 0 * * * ?") //每小时执行一次 每个整点 @Scheduled(cron="0 0 * * * ?") //每小时执行一次 每个整点
public void execute(){ public void execute(){
logger.info("[定时任务] 清理过期的订阅信息"); logger.info("[定时任务] 清理过期的订阅信息");
@ -58,11 +61,15 @@ public class SipSubscribe {
this.event = event; this.event = event;
if (event instanceof ResponseEvent) { if (event instanceof ResponseEvent) {
ResponseEvent responseEvent = (ResponseEvent)event; ResponseEvent responseEvent = (ResponseEvent)event;
this.type = "response"; Response response = responseEvent.getResponse();
this.msg = responseEvent.getResponse().getReasonPhrase();
this.statusCode = responseEvent.getResponse().getStatusCode();
this.callId = responseEvent.getDialog().getCallId().getCallId();
this.dialog = responseEvent.getDialog(); 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) { }else if (event instanceof TimeoutEvent) {
TimeoutEvent timeoutEvent = (TimeoutEvent)event; TimeoutEvent timeoutEvent = (TimeoutEvent)event;
this.type = "timeout"; this.type = "timeout";

View File

@ -66,6 +66,7 @@ public class PlatformKeepaliveExpireEventLister implements ApplicationListener<P
storager.updateParentPlatformStatus(event.getPlatformGbID(), false); storager.updateParentPlatformStatus(event.getPlatformGbID(), false);
publisher.platformNotRegisterEventPublish(event.getPlatformGbID()); publisher.platformNotRegisterEventPublish(event.getPlatformGbID());
parentPlatformCatch.setKeepAliveReply(0); parentPlatformCatch.setKeepAliveReply(0);
redisCatchStorage.updatePlatformCatchInfo(parentPlatformCatch);
}else { }else {
// 再次发送心跳 // 再次发送心跳
String callId = sipCommanderForPlatform.keepalive(parentPlatform); String callId = sipCommanderForPlatform.keepalive(parentPlatform);

View File

@ -0,0 +1,6 @@
package com.genersoft.iot.vmp.gb28181.transmit;
import javax.sip.SipListener;
public interface ISIPProcessorObserver extends SipListener {
}

View File

@ -7,6 +7,9 @@ import com.genersoft.iot.vmp.gb28181.transmit.event.timeout.ITimeoutProcessor;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; 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 org.springframework.stereotype.Component;
import javax.sip.*; import javax.sip.*;
@ -22,7 +25,7 @@ import java.util.concurrent.ConcurrentHashMap;
* @date: 2021年11月5日 下午1532 * @date: 2021年11月5日 下午1532
*/ */
@Component @Component
public class SIPProcessorObserver implements SipListener { public class SIPProcessorObserver implements ISIPProcessorObserver {
private final static Logger logger = LoggerFactory.getLogger(SIPProcessorObserver.class); private final static Logger logger = LoggerFactory.getLogger(SIPProcessorObserver.class);
@ -33,6 +36,10 @@ public class SIPProcessorObserver implements SipListener {
@Autowired @Autowired
private SipSubscribe sipSubscribe; private SipSubscribe sipSubscribe;
// @Autowired
// @Qualifier(value = "taskExecutor")
// private ThreadPoolTaskExecutor poolTaskExecutor;
/** /**
* 添加 request订阅 * 添加 request订阅
* @param method 方法名 * @param method 方法名
@ -64,6 +71,7 @@ public class SIPProcessorObserver implements SipListener {
* @param requestEvent RequestEvent事件 * @param requestEvent RequestEvent事件
*/ */
@Override @Override
@Async
public void processRequest(RequestEvent requestEvent) { public void processRequest(RequestEvent requestEvent) {
String method = requestEvent.getRequest().getMethod(); String method = requestEvent.getRequest().getMethod();
ISIPRequestProcessor sipRequestProcessor = requestProcessorMap.get(method); ISIPRequestProcessor sipRequestProcessor = requestProcessorMap.get(method);
@ -72,6 +80,7 @@ public class SIPProcessorObserver implements SipListener {
return; return;
} }
requestProcessorMap.get(method).process(requestEvent); requestProcessorMap.get(method).process(requestEvent);
} }
/** /**
@ -79,18 +88,9 @@ public class SIPProcessorObserver implements SipListener {
* @param responseEvent responseEvent事件 * @param responseEvent responseEvent事件
*/ */
@Override @Override
@Async
public void processResponse(ResponseEvent responseEvent) { public void processResponse(ResponseEvent responseEvent) {
logger.debug(responseEvent.getResponse().toString()); 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(); Response response = responseEvent.getResponse();
logger.debug(responseEvent.getResponse().toString()); logger.debug(responseEvent.getResponse().toString());
int status = response.getStatusCode(); 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 @Override
public void processIOException(IOExceptionEvent exceptionEvent) { public void processIOException(IOExceptionEvent exceptionEvent) {
// System.out.println("processIOException");
} }
@Override @Override
public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) { public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
// System.out.println("processTransactionTerminated");
} }
@Override @Override
public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) { public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
CallIdHeader callId = dialogTerminatedEvent.getDialog().getCallId(); CallIdHeader callId = dialogTerminatedEvent.getDialog().getCallId();
System.out.println("processDialogTerminated:::::" + callId);
} }

View File

@ -53,7 +53,7 @@ public class CheckForAllRecordsThread extends Thread {
// 自然顺序排序, 元素进行升序排列 // 自然顺序排序, 元素进行升序排列
this.recordInfo.getRecordList().sort(Comparator.naturalOrder()); this.recordInfo.getRecordList().sort(Comparator.naturalOrder());
RequestMessage msg = new RequestMessage(); 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); msg.setData(recordInfo);
deferredResultHolder.invokeAllResult(msg); deferredResultHolder.invokeAllResult(msg);
logger.info("处理完成,返回结果"); logger.info("处理完成,返回结果");

View File

@ -35,9 +35,11 @@ public class DeferredResultHolder {
public static final String CALLBACK_CMD_PLAY = "CALLBACK_PLAY"; 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"; public static final String CALLBACK_CMD_MOBILEPOSITION = "CALLBACK_MOBILEPOSITION";
@ -110,7 +112,7 @@ public class DeferredResultHolder {
if (result == null) { if (result == null) {
return; return;
} }
result.setResult(new ResponseEntity<>(msg.getData(),HttpStatus.OK)); result.setResult(ResponseEntity.ok().body(msg.getData()));
} }
map.remove(msg.getKey()); map.remove(msg.getKey());

View File

@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.gb28181.transmit.cmd; 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.bean.Device;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe; import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe; 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, SipSubscribe.Event okEvent);
void streamByeCmd(String deviceId, String channelId); 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 device 视频设备
* @param startTime 开始时间,格式要求yyyy-MM-dd HH:mm:ss * @param startTime 开始时间,格式要求yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求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);
/** /**
* 查询报警信息 * 查询报警信息

View File

@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import javax.sip.Dialog;
import javax.sip.InvalidArgumentException; import javax.sip.InvalidArgumentException;
import javax.sip.PeerUnavailableException; import javax.sip.PeerUnavailableException;
import javax.sip.SipFactory; import javax.sip.SipFactory;
@ -11,6 +12,9 @@ import javax.sip.address.SipURI;
import javax.sip.header.*; import javax.sip.header.*;
import javax.sip.message.Request; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -31,6 +35,9 @@ public class SIPRequestHeaderProvider {
@Autowired @Autowired
private SipFactory sipFactory; 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 { public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
Request request = null; Request request = null;
// sipuri // sipuri
@ -210,4 +217,50 @@ public class SIPRequestHeaderProvider {
request.setContent(content, contentTypeHeader); request.setContent(content, contentTypeHeader);
return request; 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;
}
} }

View File

@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl; package com.genersoft.iot.vmp.gb28181.transmit.cmd.impl;
import com.alibaba.fastjson.JSONObject; 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.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetup; import com.genersoft.iot.vmp.conf.UserSetup;
import com.genersoft.iot.vmp.gb28181.bean.Device; 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.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager; 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.SipProviderImpl;
import gov.nist.javax.sip.SipStackImpl; import gov.nist.javax.sip.SipStackImpl;
import gov.nist.javax.sip.message.SIPRequest; import gov.nist.javax.sip.message.SIPRequest;
@ -1194,14 +1196,15 @@ public class SIPCommander implements ISIPCommander {
* @param endTime 结束时间,格式要求yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求yyyy-MM-dd HH:mm:ss
*/ */
@Override @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 { try {
StringBuffer recordInfoXml = new StringBuffer(200); StringBuffer recordInfoXml = new StringBuffer(200);
recordInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n"); recordInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
recordInfoXml.append("<Query>\r\n"); recordInfoXml.append("<Query>\r\n");
recordInfoXml.append("<CmdType>RecordInfo</CmdType>\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("<DeviceID>" + channelId + "</DeviceID>\r\n");
recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>\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"); 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(), Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(),
"z9hG4bK-ViaRecordInfo-" + tm, "fromRec" + tm, null, callIdHeader); "z9hG4bK-ViaRecordInfo-" + tm, "fromRec" + tm, null, callIdHeader);
transmitRequest(device, request); transmitRequest(device, request, errorEvent);
} catch (SipException | ParseException | InvalidArgumentException e) { } catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace(); e.printStackTrace();
return false; return false;
@ -1486,7 +1489,7 @@ public class SIPCommander implements ISIPCommander {
StringBuffer cmdXml = new StringBuffer(200); StringBuffer cmdXml = new StringBuffer(200);
cmdXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n"); cmdXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
cmdXml.append("<Query>\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("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n"); cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
cmdXml.append("</Query>\r\n"); cmdXml.append("</Query>\r\n");
@ -1496,7 +1499,7 @@ public class SIPCommander implements ISIPCommander {
CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId() CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
: udpSipProvider.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); transmitRequest(device, request, errorEvent, okEvent);
return true; return true;
@ -1543,4 +1546,111 @@ public class SIPCommander implements ISIPCommander {
clientTransaction.sendRequest(); clientTransaction.sendRequest();
return clientTransaction; 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();
}
}
} }

View File

@ -26,9 +26,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* @description:ACK请求处理器 * SIP命令类型 ACK请求
* @author: swwheihei
* @date: 2020年5月3日 下午5:31:45
*/ */
@Component @Component
public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { public class AckRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {

View File

@ -29,14 +29,13 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* @description: BYE请求处理器 * SIP命令类型 BYE请求
* @author: lawrencehj
* @date: 2021年3月9日
*/ */
@Component @Component
public class ByeRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { 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 @Autowired
private ISIPCommander cmder; private ISIPCommander cmder;
@ -53,8 +52,6 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
@Autowired @Autowired
private IMediaServerService mediaServerService; private IMediaServerService mediaServerService;
private String method = "BYE";
@Autowired @Autowired
private SIPProcessorObserver sipProcessorObserver; private SIPProcessorObserver sipProcessorObserver;

View File

@ -10,9 +10,7 @@ import org.springframework.stereotype.Component;
import javax.sip.RequestEvent; import javax.sip.RequestEvent;
/** /**
* @description:CANCEL请求处理器 * SIP命令类型 CANCEL请求
* @author: swwheihei
* @date: 2020年5月3日 下午5:32:23
*/ */
@Component @Component
public class CancelRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { public class CancelRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {

View File

@ -34,9 +34,7 @@ import java.text.ParseException;
import java.util.Vector; import java.util.Vector;
/** /**
* @description:处理INVITE请求 * SIP命令类型 INVITE请求
* @author: panll
* @date: 2021年1月14日
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Component @Component
@ -140,12 +138,21 @@ public class InviteRequestProcessor extends SIPRequestProcessorParent implements
// 解析sdp消息, 使用jainsip 自带的sdp解析方式 // 解析sdp消息, 使用jainsip 自带的sdp解析方式
String contentString = new String(request.getRawContent()); String contentString = new String(request.getRawContent());
// jainSip不支持y=字段 移除移除以解析 // jainSip不支持y=字段 移除以解析
int ssrcIndex = contentString.indexOf("y="); int ssrcIndex = contentString.indexOf("y=");
// 检查是否有y字段
String ssrcDefault = "0000000000";
String ssrc;
SessionDescription sdp;
if (ssrcIndex >= 0) {
//ssrc规定长度为10字节不取余下长度以避免后续还有f=字段 //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=")); 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); Vector mediaDescriptions = sdp.getMediaDescriptions(true);

View File

@ -914,21 +914,20 @@ public class MessageRequestProcessor1 extends SIPRequestProcessorParent implemen
String uuid = UUID.randomUUID().toString().replace("-", ""); String uuid = UUID.randomUUID().toString().replace("-", "");
RecordInfo recordInfo = new RecordInfo(); RecordInfo recordInfo = new RecordInfo();
Element rootElement = getRootElement(evt); Element rootElement = getRootElement(evt);
Element deviceIdElement = rootElement.element("DeviceID"); String sn = getText(rootElement, "SN");
String channelId = deviceIdElement.getText().toString(); String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn;
String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + channelId;
if (device != null ) { if (device != null ) {
rootElement = getRootElement(evt, device.getCharset()); rootElement = getRootElement(evt, device.getCharset());
} }
recordInfo.setDeviceId(deviceId); recordInfo.setDeviceId(deviceId);
recordInfo.setChannelId(channelId); recordInfo.setSn(sn);
recordInfo.setName(getText(rootElement, "Name")); recordInfo.setName(getText(rootElement, "Name"));
if (getText(rootElement, "SumNum")== null || getText(rootElement, "SumNum") =="") { if (getText(rootElement, "SumNum")== null || getText(rootElement, "SumNum") =="") {
recordInfo.setSumNum(0); recordInfo.setSumNum(0);
} else { } else {
recordInfo.setSumNum(Integer.parseInt(getText(rootElement, "SumNum"))); recordInfo.setSumNum(Integer.parseInt(getText(rootElement, "SumNum")));
} }
String sn = getText(rootElement, "SN");
Element recordListElement = rootElement.element("RecordList"); Element recordListElement = rootElement.element("RecordList");
if (recordListElement == null || recordInfo.getSumNum() == 0) { if (recordListElement == null || recordInfo.getSumNum() == 0) {
logger.info("无录像数据"); logger.info("无录像数据");

View File

@ -35,9 +35,7 @@ import java.text.ParseException;
import java.util.Iterator; import java.util.Iterator;
/** /**
* @description: Notify请求处理器 * SIP命令类型 NOTIFY请求
* @author: lawrencehj
* @date: 2021年1月27日
*/ */
@Component @Component
public class NotifyRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { public class NotifyRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
@ -230,8 +228,6 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader); String deviceId = SipUtils.getUserIdFromFromHeader(fromHeader);
Element rootElement = getRootElement(evt); Element rootElement = getRootElement(evt);
Element deviceIdElement = rootElement.element("DeviceID");
String channelId = deviceIdElement.getText();
Device device = storager.queryVideoDevice(deviceId); Device device = storager.queryVideoDevice(deviceId);
if (device == null) { if (device == null) {
return; return;
@ -254,22 +250,23 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
continue; continue;
} }
Element eventElement = itemDevice.element("Event"); Element eventElement = itemDevice.element("Event");
DeviceChannel channel = channelContentHander(itemDevice);
switch (eventElement.getText().toUpperCase()) { switch (eventElement.getText().toUpperCase()) {
case "ON" : // 上线 case "ON" : // 上线
logger.info("收到来自设备【{}】的通道上线【{}】通知", device.getDeviceId(), channelId); logger.info("收到来自设备【{}】的通道【{}】上线通知", device.getDeviceId(), channel.getChannelId());
storager.deviceChannelOnline(deviceId, channelId); storager.deviceChannelOnline(deviceId, channel.getChannelId());
// 回复200 OK // 回复200 OK
responseAck(evt, Response.OK); responseAck(evt, Response.OK);
break; break;
case "OFF" : // 离线 case "OFF" : // 离线
logger.info("收到来自设备【{}】的通道离线【{}】通知", device.getDeviceId(), channelId); logger.info("收到来自设备【{}】的通道【{}】离线通知", device.getDeviceId(), channel.getChannelId());
storager.deviceChannelOffline(deviceId, channelId); storager.deviceChannelOffline(deviceId, channel.getChannelId());
// 回复200 OK // 回复200 OK
responseAck(evt, Response.OK); responseAck(evt, Response.OK);
break; break;
case "VLOST" : // 视频丢失 case "VLOST" : // 视频丢失
logger.info("收到来自设备【{}】的通道视频丢失【{}】通知", device.getDeviceId(), channelId); logger.info("收到来自设备【{}】的通道【{}】视频丢失通知", device.getDeviceId(), channel.getChannelId());
storager.deviceChannelOffline(deviceId, channelId); storager.deviceChannelOffline(deviceId, channel.getChannelId());
// 回复200 OK // 回复200 OK
responseAck(evt, Response.OK); responseAck(evt, Response.OK);
break; break;
@ -278,19 +275,17 @@ public class NotifyRequestProcessor extends SIPRequestProcessorParent implements
responseAck(evt, Response.OK); responseAck(evt, Response.OK);
break; break;
case "ADD" : // 增加 case "ADD" : // 增加
logger.info("收到来自设备【{}】的增加通道【{}】通知", device.getDeviceId(), channelId); logger.info("收到来自设备【{}】的增加通道【{}】通知", device.getDeviceId(), channel.getChannelId());
DeviceChannel deviceChannel = channelContentHander(itemDevice, channelId); storager.updateChannel(deviceId, channel);
storager.updateChannel(deviceId, deviceChannel);
responseAck(evt, Response.OK); responseAck(evt, Response.OK);
break; break;
case "DEL" : // 删除 case "DEL" : // 删除
logger.info("收到来自设备【{}】的删除通道【{}】通知", device.getDeviceId(), channelId); logger.info("收到来自设备【{}】的删除通道【{}】通知", device.getDeviceId(), channel.getChannelId());
storager.delChannel(deviceId, channelId); storager.delChannel(deviceId, channel.getChannelId());
responseAck(evt, Response.OK); responseAck(evt, Response.OK);
break; break;
case "UPDATE" : // 更新 case "UPDATE" : // 更新
logger.info("收到来自设备【{}】的更新通道【{}】通知", device.getDeviceId(), channelId); logger.info("收到来自设备【{}】的更新通道【{}】通知", device.getDeviceId(), channel.getChannelId());
DeviceChannel channel = channelContentHander(itemDevice, channelId);
storager.updateChannel(deviceId, channel); storager.updateChannel(deviceId, channel);
responseAck(evt, Response.OK); responseAck(evt, Response.OK);
break; 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"); Element channdelNameElement = itemDevice.element("Name");
String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim().toString() : ""; String channelName = channdelNameElement != null ? channdelNameElement.getTextTrim().toString() : "";
Element statusElement = itemDevice.element("Status"); Element statusElement = itemDevice.element("Status");
String status = statusElement != null ? statusElement.getTextTrim().toString() : "ON"; String status = statusElement != null ? statusElement.getTextTrim().toString() : "ON";
DeviceChannel deviceChannel = new DeviceChannel(); DeviceChannel deviceChannel = new DeviceChannel();
deviceChannel.setName(channelName); deviceChannel.setName(channelName);
Element channdelIdElement = itemDevice.element("DeviceID");
String channelId = channdelIdElement != null ? channdelIdElement.getTextTrim().toString() : "";
deviceChannel.setChannelId(channelId); deviceChannel.setChannelId(channelId);
// ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理 // ONLINE OFFLINE HIKVISION DS-7716N-E4 NVR的兼容性处理
if (status.equals("ON") || status.equals("On") || status.equals("ONLINE")) { if (status.equals("ON") || status.equals("On") || status.equals("ONLINE")) {

View File

@ -36,9 +36,7 @@ import java.util.Calendar;
import java.util.Locale; import java.util.Locale;
/** /**
* @description:收到注册请求 处理 * SIP命令类型 REGISTER请求
* @author: swwheihei
* @date: 2020年5月3日 下午4:47:25
*/ */
@Component @Component
public class RegisterRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { public class RegisterRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {

View File

@ -19,9 +19,7 @@ import javax.sip.message.Response;
import java.text.ParseException; import java.text.ParseException;
/** /**
* @description:SUBSCRIBE请求处理器 * SIP命令类型 SUBSCRIBE请求
* @author: swwheihei
* @date: 2020年5月3日 下午5:31:20
*/ */
@Component @Component
public class SubscribeRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor { public class SubscribeRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {

View File

@ -14,10 +14,7 @@ import static com.genersoft.iot.vmp.gb28181.utils.XmlUtil.getText;
public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent implements IMessageHandler{ public abstract class MessageHandlerAbstract extends SIPRequestProcessorParent implements IMessageHandler{
public static Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>(); public Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
@Autowired
public MessageRequestProcessor messageRequestProcessor;
public void addHandler(String cmdType, IMessageHandler messageHandler) { public void addHandler(String cmdType, IMessageHandler messageHandler) {
messageHandlerMap.put(cmdType, messageHandler); messageHandlerMap.put(cmdType, messageHandler);

View File

@ -6,6 +6,12 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/**
* 命令类型 控制命令
* 命令类型 设备控制 远程启动, 录像控制TODO, 报警布防/撤防命令TODO, 报警复位命令TODO,
* 强制关键帧命令TODO, 拉框放大/缩小控制命令TODO, 看守位控制TODO, 报警复位TODO
* 命令类型 设备配置 SVAC编码配置TODO, 音频参数TODO, SVAC解码配置TODO
*/
@Component @Component
public class ControlMessageHandler extends MessageHandlerAbstract implements InitializingBean { public class ControlMessageHandler extends MessageHandlerAbstract implements InitializingBean {

View File

@ -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.VManageBootstrap;
import com.genersoft.iot.vmp.gb28181.bean.Device; 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.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.SIPRequestProcessorParent; 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.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.gb28181.transmit.event.request.impl.message.query.QueryMessageHandler;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.SpringBeanFactory; import com.genersoft.iot.vmp.utils.SpringBeanFactory;
@ -37,7 +38,7 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
private final String cmdType = "DeviceControl"; private final String cmdType = "DeviceControl";
@Autowired @Autowired
private QueryMessageHandler queryMessageHandler; private ControlMessageHandler controlMessageHandler;
@Autowired @Autowired
private IVideoManagerStorager storager; private IVideoManagerStorager storager;
@ -50,7 +51,7 @@ public class DeviceControlQueryMessageHandler extends SIPRequestProcessorParent
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
queryMessageHandler.addHandler(cmdType, this); controlMessageHandler.addHandler(cmdType, this);
} }
@Override @Override

View File

@ -1,14 +1,23 @@
package com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.notify; 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.MessageHandlerAbstract;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.MessageRequestProcessor;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/**
* 命令类型 通知命令
* 命令类型 状态信息(心跳)报送, 报警通知, 媒体通知, 移动设备位置数据语音广播通知(TODO), 设备预置位(TODO)
*/
@Component @Component
public class NotifyMessageHandler extends MessageHandlerAbstract implements InitializingBean { public class NotifyMessageHandler extends MessageHandlerAbstract implements InitializingBean {
private final String messageType = "Notify"; private final String messageType = "Notify";
@Autowired
private MessageRequestProcessor messageRequestProcessor;
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
messageRequestProcessor.addHandler(messageType, this); messageRequestProcessor.addHandler(messageType, this);

View File

@ -46,9 +46,6 @@ public class CatalogNotifyMessageHandler extends SIPRequestProcessorParent imple
@Autowired @Autowired
private SipConfig config; private SipConfig config;
@Autowired
private EventPublisher publisher;
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
notifyMessageHandler.addHandler(cmdType, this); notifyMessageHandler.addHandler(cmdType, this);

View File

@ -6,6 +6,10 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/**
* 命令类型 查询指令
* 命令类型 设备状态, 设备目录信息, 设备信息, 文件目录检索(TODO), 报警(TODO), 设备配置(TODO), 设备预置位(TODO), 移动设备位置数据(TODO)
*/
@Component @Component
public class QueryMessageHandler extends MessageHandlerAbstract implements InitializingBean { public class QueryMessageHandler extends MessageHandlerAbstract implements InitializingBean {

View File

@ -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();
}
}
}

View File

@ -6,6 +6,10 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/**
* 命令类型 请求动作的应答
* 命令类型 设备控制, 报警通知, 设备目录信息查询, 目录信息查询, 目录收到, 设备信息查询, 设备状态信息查询 ......
*/
@Component @Component
public class ResponseMessageHandler extends MessageHandlerAbstract implements InitializingBean { public class ResponseMessageHandler extends MessageHandlerAbstract implements InitializingBean {

View File

@ -64,18 +64,16 @@ public class RecordInfoResponseMessageHandler extends SIPRequestProcessorParent
rootElement = getRootElement(evt, device.getCharset()); rootElement = getRootElement(evt, device.getCharset());
String uuid = UUID.randomUUID().toString().replace("-", ""); String uuid = UUID.randomUUID().toString().replace("-", "");
RecordInfo recordInfo = new RecordInfo(); RecordInfo recordInfo = new RecordInfo();
Element deviceIdElement = rootElement.element("DeviceID"); String sn = getText(rootElement, "SN");
String channelId = deviceIdElement.getText(); String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + device.getDeviceId() + sn;
String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + device.getDeviceId() + channelId;
recordInfo.setDeviceId(device.getDeviceId()); recordInfo.setDeviceId(device.getDeviceId());
recordInfo.setChannelId(channelId); recordInfo.setSn(sn);
recordInfo.setName(getText(rootElement, "Name")); recordInfo.setName(getText(rootElement, "Name"));
if (getText(rootElement, "SumNum") == null || getText(rootElement, "SumNum") == "") { if (getText(rootElement, "SumNum") == null || getText(rootElement, "SumNum") == "") {
recordInfo.setSumNum(0); recordInfo.setSumNum(0);
} else { } else {
recordInfo.setSumNum(Integer.parseInt(getText(rootElement, "SumNum"))); recordInfo.setSumNum(Integer.parseInt(getText(rootElement, "SumNum")));
} }
String sn = getText(rootElement, "SN");
Element recordListElement = rootElement.element("RecordList"); Element recordListElement = rootElement.element("RecordList");
if (recordListElement == null || recordInfo.getSumNum() == 0) { if (recordListElement == null || recordInfo.getSumNum() == 0) {
logger.info("无录像数据"); logger.info("无录像数据");

View File

@ -42,7 +42,6 @@ public class ByeResponseProcessor extends SIPResponseProcessorAbstract {
@Override @Override
public void process(ResponseEvent evt) { public void process(ResponseEvent evt) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
System.out.println("收到bye");
} }

View File

@ -3,13 +3,18 @@ package com.genersoft.iot.vmp.media.zlm;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaConfig; import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.conf.UserSetup; import com.genersoft.iot.vmp.conf.UserSetup;
import com.genersoft.iot.vmp.gb28181.bean.Device; 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.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.service.IMediaServerService; 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.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
@ -56,6 +61,12 @@ public class ZLMHttpHookListener {
@Autowired @Autowired
private IMediaServerService mediaServerService; private IMediaServerService mediaServerService;
@Autowired
private IStreamProxyService streamProxyService;
@Autowired
private IMediaService mediaService;
@Autowired @Autowired
private ZLMRESTfulUtils zlmresTfulUtils; private ZLMRESTfulUtils zlmresTfulUtils;
@ -153,12 +164,20 @@ public class ZLMHttpHookListener {
subscribe.response(mediaInfo, json); subscribe.response(mediaInfo, json);
} }
} }
String app = json.getString("app");
String stream = json.getString("stream");
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByStreamId(stream);
JSONObject ret = new JSONObject(); JSONObject ret = new JSONObject();
// 录像回放时不进行录像下载
if (streamInfo != null) {
ret.put("enableMP4", false);
}else {
ret.put("enableMP4", userSetup.isRecordPushLive());
}
ret.put("code", 0); ret.put("code", 0);
ret.put("msg", "success"); ret.put("msg", "success");
ret.put("enableHls", true); ret.put("enableHls", true);
ret.put("enableMP4", userSetup.isRecordPushLive()); ret.put("enableMP4", userSetup.isRecordPushLive());
ret.put("enableRtxp", true);
return new ResponseEntity<String>(ret.toString(), HttpStatus.OK); return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
} }
@ -254,12 +273,13 @@ public class ZLMHttpHookListener {
*/ */
@ResponseBody @ResponseBody
@PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8") @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()) { 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); ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, json);
if (subscribe != null ) { if (subscribe != null ) {
MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId); MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
@ -268,13 +288,12 @@ public class ZLMHttpHookListener {
} }
} }
// 流消失移除redis play // 流消失移除redis play
String app = json.getString("app"); String app = item.getApp();
String streamId = json.getString("stream"); String streamId = item.getStream();
String schema = json.getString("schema"); String schema = item.getSchema();
JSONArray tracks = json.getJSONArray("tracks"); List<MediaItem.MediaTrack> tracks = item.getTracks();
boolean regist = json.getBoolean("regist"); boolean regist = item.isRegist();
if (tracks != null) { if (tracks != null) {
logger.info("[stream: " + streamId + "] on_stream_changed->>" + schema); logger.info("[stream: " + streamId + "] on_stream_changed->>" + schema);
} }
@ -295,11 +314,33 @@ public class ZLMHttpHookListener {
} }
}else { }else {
if (!"rtp".equals(app)){ if (!"rtp".equals(app)){
boolean pushChange = false;
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
if (regist) { 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 { }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 mediaServerId = json.getString("mediaServerId");
String streamId = json.getString("stream"); String streamId = json.getString("stream");
String app = json.getString("app"); String app = json.getString("app");
// TODO 如果在给上级推流也不停止
if ("rtp".equals(app)){
JSONObject ret = new JSONObject(); JSONObject ret = new JSONObject();
ret.put("code", 0); ret.put("code", 0);
if ("rtp".equals(app)){
ret.put("close", true); ret.put("close", true);
StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(streamId); StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(streamId);
if (streamInfoForPlayCatch != null) { if (streamInfoForPlayCatch != null) {
// 如果在给上级推流也不停止
if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) { if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) {
ret.put("close", false); ret.put("close", false);
} else { } else {
@ -345,6 +385,12 @@ public class ZLMHttpHookListener {
if (streamInfoForPlayBackCatch != null) { if (streamInfoForPlayBackCatch != null) {
cmder.streamByeCmd(streamInfoForPlayBackCatch.getDeviceID(), streamInfoForPlayBackCatch.getChannelId()); cmder.streamByeCmd(streamInfoForPlayBackCatch.getDeviceID(), streamInfoForPlayBackCatch.getChannelId());
redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch); redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch);
}else {
StreamInfo streamInfoForDownload = redisCatchStorage.queryDownloadByStreamId(streamId);
// 进行录像下载时无人观看不断流
if (streamInfoForDownload != null) {
ret.put("close", false);
}
} }
} }
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId); MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
@ -353,9 +399,15 @@ public class ZLMHttpHookListener {
} }
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK); return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}else { }else {
JSONObject ret = new JSONObject(); StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, streamId);
ret.put("code", 0); 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); ret.put("close", false);
}
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK); return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
} }

View File

@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.media.zlm; package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson.JSONObject; 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.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
@ -87,6 +88,10 @@ public class ZLMMediaListManager {
updateMedia(mediaServerItem, app, streamId); updateMedia(mediaServerItem, app, streamId);
} }
public void addMedia(MediaItem mediaItem) {
storager.updateMedia(streamPushService.transform(mediaItem));
}
public void updateMedia(MediaServerItem mediaServerItem, String app, String streamId) { 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); StreamProxyItem streamProxyItem = gbStreamMapper.selectOne(app, streamId);
int result = 0;
if (streamProxyItem == null) { if (streamProxyItem == null) {
storager.removeMedia(app, streamId); result = storager.removeMedia(app, streamId);
}else { }else {
storager.mediaOutline(app, streamId); result =storager.mediaOutline(app, streamId);
} }
return result;
} }
// public void clearAllSessions() { // public void clearAllSessions() {

View File

@ -29,7 +29,6 @@ public class ZLMRESTfulUtils {
OkHttpClient client = new OkHttpClient(); OkHttpClient client = new OkHttpClient();
String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api); String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
JSONObject responseJSON = null; JSONObject responseJSON = null;
logger.debug(url);
FormBody.Builder builder = new FormBody.Builder(); FormBody.Builder builder = new FormBody.Builder();
builder.add("secret",mediaServerItem.getSecret()); builder.add("secret",mediaServerItem.getSecret());
@ -51,8 +50,9 @@ public class ZLMRESTfulUtils {
try { try {
Response response = client.newCall(request).execute(); Response response = client.newCall(request).execute();
if (response.isSuccessful()) { if (response.isSuccessful()) {
String responseStr = response.body().string(); ResponseBody responseBody = response.body();
if (responseStr != null) { if (responseBody != null) {
String responseStr = responseBody.string();
responseJSON = JSON.parseObject(responseStr); responseJSON = JSON.parseObject(responseStr);
} }
}else { }else {
@ -100,7 +100,11 @@ public class ZLMRESTfulUtils {
public void sendGetForImg(MediaServerItem mediaServerItem, String api, Map<String, Object> params, String targetPath, String fileName) { 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); String url = String.format("http://%s:%s/index/api/%s", mediaServerItem.getIp(), mediaServerItem.getHttpPort(), api);
logger.debug(url); 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()); httpBuilder.addQueryParameter("secret", mediaServerItem.getSecret());
if (params != null) { if (params != null) {
@ -123,16 +127,20 @@ public class ZLMRESTfulUtils {
if (targetPath != null) { if (targetPath != null) {
File snapFolder = new File(targetPath); File snapFolder = new File(targetPath);
if (!snapFolder.exists()) { if (!snapFolder.exists()) {
snapFolder.mkdirs(); if (!snapFolder.mkdirs()) {
logger.warn("{}路径创建失败", snapFolder.getAbsolutePath());
}
} }
File snapFile = new File(targetPath + "/" + fileName); File snapFile = new File(targetPath + "/" + fileName);
FileOutputStream outStream = new FileOutputStream(snapFile); FileOutputStream outStream = new FileOutputStream(snapFile);
outStream.write(response.body().bytes());
outStream.write(Objects.requireNonNull(response.body()).bytes());
outStream.close(); outStream.close();
} else { } else {
logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message()));
} }
response.body().close(); Objects.requireNonNull(response.body()).close();
} else { } else {
logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message())); logger.error(String.format("[ %s ]请求失败: %s %s", url, response.code(), response.message()));
} }

View File

@ -4,6 +4,11 @@ import java.util.List;
public class MediaItem { public class MediaItem {
/**
* 注册/注销
*/
private boolean regist;
/** /**
* 应用名 * 应用名
*/ */
@ -53,6 +58,11 @@ public class MediaItem {
*/ */
private String originUrl; private String originUrl;
/**
* 服务器id
*/
private String mediaServerId;
/** /**
* GMT unix系统时间戳单位秒 * GMT unix系统时间戳单位秒
*/ */
@ -78,6 +88,14 @@ public class MediaItem {
*/ */
private String vhost; private String vhost;
public boolean isRegist() {
return regist;
}
public void setRegist(boolean regist) {
this.regist = regist;
}
/** /**
* 是否是docker部署 docker部署不会自动更新zlm使用的端口需要自己手动修改 * 是否是docker部署 docker部署不会自动更新zlm使用的端口需要自己手动修改
*/ */
@ -376,4 +394,12 @@ public class MediaItem {
public void setDocker(boolean docker) { public void setDocker(boolean docker) {
this.docker = docker; this.docker = docker;
} }
public String getMediaServerId() {
return mediaServerId;
}
public void setMediaServerId(String mediaServerId) {
this.mediaServerId = mediaServerId;
}
} }

View File

@ -17,6 +17,7 @@ public class StreamProxyItem extends GbStream {
private boolean enable; private boolean enable;
private boolean enable_hls; private boolean enable_hls;
private boolean enable_mp4; private boolean enable_mp4;
private boolean enable_remove_none_reader; // 无人观看时删除
private String platformGbId; private String platformGbId;
private String createTime; private String createTime;
@ -142,4 +143,12 @@ public class StreamProxyItem extends GbStream {
public void setCreateTime(String createTime) { public void setCreateTime(String createTime) {
this.createTime = 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;
}
} }

View File

@ -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);
}

View File

@ -1,5 +0,0 @@
package com.genersoft.iot.vmp.onvif.dto;
public interface ONVIFCallBack<T> {
void run(int errorCode, T t);
}

View File

@ -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());
}
}
}

View File

@ -61,4 +61,6 @@ public interface IMediaServerService {
boolean checkMediaRecordServer(String ip, int port); boolean checkMediaRecordServer(String ip, int port);
void delete(String id); void delete(String id);
MediaServerItem getDefaultMediaServer();
} }

View File

@ -32,7 +32,7 @@ public interface IMediaService {
* @param stream * @param stream
* @return * @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在一台主机的情况 * 根据应用名和流ID获取播放地址, 只是地址拼接返回的ip使用远程访问ip适用与zlm与wvp在一台主机的情况
@ -40,5 +40,5 @@ public interface IMediaService {
* @param stream * @param stream
* @return * @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);
} }

View File

@ -18,4 +18,6 @@ public interface IPlayService {
PlayResult play(MediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent); PlayResult play(MediaServerItem mediaServerItem, String deviceId, String channelId, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent);
MediaServerItem getNewMediaServerItem(Device device); MediaServerItem getNewMediaServerItem(Device device);
void onPublishHandlerForDownload(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId, String toString);
} }

View File

@ -1,8 +1,10 @@
package com.genersoft.iot.vmp.service; package com.genersoft.iot.vmp.service;
import com.alibaba.fastjson.JSONObject; 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.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
public interface IStreamProxyService { public interface IStreamProxyService {
@ -11,7 +13,7 @@ public interface IStreamProxyService {
* 保存视频代理 * 保存视频代理
* @param param * @param param
*/ */
String save(StreamProxyItem param); WVPResult<StreamInfo> save(StreamProxyItem param);
/** /**
* 添加视频代理到zlm * 添加视频代理到zlm
@ -63,4 +65,10 @@ public interface IStreamProxyService {
* @return * @return
*/ */
JSONObject getFFmpegCMDs(MediaServerItem mediaServerItem); JSONObject getFFmpegCMDs(MediaServerItem mediaServerItem);
/**
* 根据app与stream获取streamProxy
* @return
*/
StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId);
} }

View File

@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.service; package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.gb28181.bean.GbStream; 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.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
@ -32,4 +33,6 @@ public interface IStreamPushService {
* @return * @return
*/ */
PageInfo<StreamPushItem> getPushList(Integer page, Integer count); PageInfo<StreamPushItem> getPushList(Integer page, Integer count);
StreamPushItem transform(MediaItem item);
} }

View File

@ -10,6 +10,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/**
* 设备业务目录订阅
*/
@Service @Service
public class DeviceServiceImpl implements IDeviceService { public class DeviceServiceImpl implements IDeviceService {
@ -31,8 +34,11 @@ public class DeviceServiceImpl implements IDeviceService {
CatalogSubscribeTask catalogSubscribeTask = new CatalogSubscribeTask(device, sipCommander); CatalogSubscribeTask catalogSubscribeTask = new CatalogSubscribeTask(device, sipCommander);
catalogSubscribeTask.run(); catalogSubscribeTask.run();
// 提前开始刷新订阅 // 提前开始刷新订阅
String cron = getCron(device.getSubscribeCycleForCatalog() - 60); // TODO 使用jain sip的当时刷新订阅
dynamicTask.startCron(device.getDeviceId(), catalogSubscribeTask, cron); int subscribeCycleForCatalog = device.getSubscribeCycleForCatalog();
// 设置最小值为30
subscribeCycleForCatalog = Math.max(subscribeCycleForCatalog, 30);
dynamicTask.startCron(device.getDeviceId(), catalogSubscribeTask, subscribeCycleForCatalog - 5);
return true; return true;
} }
@ -41,21 +47,10 @@ public class DeviceServiceImpl implements IDeviceService {
if (device == null || device.getSubscribeCycleForCatalog() < 0) { if (device == null || device.getSubscribeCycleForCatalog() < 0) {
return false; return false;
} }
logger.info("移除目录订阅: {}", device.getDeviceId());
dynamicTask.stopCron(device.getDeviceId()); dynamicTask.stopCron(device.getDeviceId());
device.setSubscribeCycleForCatalog(0);
sipCommander.catalogSubscribe(device, null, null);
return true; 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 * * * ?";
}
}
} }

View File

@ -47,7 +47,7 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR
private boolean sslEnabled; private boolean sslEnabled;
@Value("${server.port}") @Value("${server.port}")
private String serverPort; private Integer serverPort;
@Autowired @Autowired
private MediaConfig mediaConfig; private MediaConfig mediaConfig;
@ -241,6 +241,11 @@ public class MediaServerServiceImpl implements IMediaServerService, CommandLineR
return mediaServerMapper.queryOneByHostAndPort(host, port); return mediaServerMapper.queryOneByHostAndPort(host, port);
} }
@Override
public MediaServerItem getDefaultMediaServer() {
return mediaServerMapper.queryDefault();
}
@Override @Override
public void clearMediaServerForOnline() { public void clearMediaServerForOnline() {
String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX; String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX;

View File

@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; 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.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService; import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@ -31,14 +32,20 @@ public class MediaServiceImpl implements IMediaService {
@Override @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); return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null);
} }
@Override @Override
public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, String addr) { public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, String addr) {
StreamInfo streamInfo = null; StreamInfo streamInfo = null;
MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
MediaServerItem mediaInfo;
if (mediaServerId == null) {
mediaInfo = mediaServerService.getDefaultMediaServer();
}else {
mediaInfo = mediaServerService.getOne(mediaServerId);
}
if (mediaInfo == null) { if (mediaInfo == null) {
return streamInfo; return streamInfo;
} }
@ -55,13 +62,15 @@ public class MediaServiceImpl implements IMediaService {
return streamInfo; return streamInfo;
} }
@Override @Override
public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId) { public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId) {
return getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, null); return getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, null);
} }
@Override @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(); StreamInfo streamInfoResult = new StreamInfo();
streamInfoResult.setStreamId(stream); streamInfoResult.setStreamId(stream);
streamInfoResult.setApp(app); streamInfoResult.setApp(app);

View File

@ -34,12 +34,8 @@ import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils; import org.springframework.util.ResourceUtils;
import org.springframework.web.context.request.async.DeferredResult; 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.io.FileNotFoundException;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@SuppressWarnings(value = {"rawtypes", "unchecked"}) @SuppressWarnings(value = {"rawtypes", "unchecked"})
@ -85,7 +81,13 @@ public class PlayServiceImpl implements IPlayService {
RequestMessage msg = new RequestMessage(); RequestMessage msg = new RequestMessage();
String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId; String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
msg.setKey(key); 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) { if (mediaServerItem == null) {
WVPResult wvpResult = new WVPResult(); WVPResult wvpResult = new WVPResult();
wvpResult.setCode(-1); wvpResult.setCode(-1);
@ -94,16 +96,9 @@ public class PlayServiceImpl implements IPlayService {
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
return playResult; return playResult;
} }
Device device = storager.queryVideoDevice(deviceId); Device device = storager.queryVideoDevice(deviceId);
StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId); StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
playResult.setDevice(device); 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(()->{ result.onTimeout(()->{
logger.warn(String.format("设备点播超时deviceId%s channelId%s", deviceId, channelId)); 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); classPath = classPath.substring(0, classPath.lastIndexOf("/") + 1);
} }
if (classPath.startsWith("file:")) { if (classPath.startsWith("file:")) {
classPath = classPath.substring(classPath.indexOf(":") + 1, classPath.length()); classPath = classPath.substring(classPath.indexOf(":") + 1);
} }
String path = classPath + "static/static/snap/"; String path = classPath + "static/static/snap/";
// 兼容Windows系统路径去除前面的/ // 兼容Windows系统路径去除前面的/
if(System.getProperty("os.name").contains("indows")) { if(System.getProperty("os.name").contains("indows")) {
path = path.substring(1, path.length()); path = path.substring(1);
} }
String fileName = deviceId + "_" + channelId + ".jpg"; String fileName = deviceId + "_" + channelId + ".jpg";
ResponseEntity responseEntity = (ResponseEntity)result.getResult(); ResponseEntity responseEntity = (ResponseEntity)result.getResult();
if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) { if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
WVPResult wvpResult = (WVPResult)responseEntity.getBody(); WVPResult wvpResult = (WVPResult)responseEntity.getBody();
if (wvpResult.getCode() == 0) { if (Objects.requireNonNull(wvpResult).getCode() == 0) {
StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData(); StreamInfo streamInfoForSuccess = (StreamInfo)wvpResult.getData();
MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId()); MediaServerItem mediaInfo = mediaServerService.getOne(streamInfoForSuccess.getMediaServerId());
String streamUrl = streamInfoForSuccess.getFmp4(); String streamUrl = streamInfoForSuccess.getFmp4();
@ -169,7 +164,7 @@ public class PlayServiceImpl implements IPlayService {
// 发送点播消息 // 发送点播消息
cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInUse, JSONObject response) -> { cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
logger.info("收到订阅消息: " + response.toJSONString()); logger.info("收到订阅消息: " + response.toJSONString());
onPublishHandlerForPlay(mediaServerItemInUse, response, deviceId, channelId, uuid.toString()); onPublishHandlerForPlay(mediaServerItemInUse, response, deviceId, channelId, uuid);
if (hookEvent != null) { if (hookEvent != null) {
hookEvent.response(mediaServerItem, response); hookEvent.response(mediaServerItem, response);
} }
@ -192,7 +187,7 @@ public class PlayServiceImpl implements IPlayService {
if (streamId == null) { if (streamId == null) {
WVPResult wvpResult = new WVPResult(); WVPResult wvpResult = new WVPResult();
wvpResult.setCode(-1); wvpResult.setCode(-1);
wvpResult.setMsg(String.format("点播失败, redis缓存streamId等于null")); wvpResult.setMsg("点播失败, redis缓存streamId等于null");
msg.setData(wvpResult); msg.setData(wvpResult);
resultHolder.invokeAllResult(msg); resultHolder.invokeAllResult(msg);
return playResult; return playResult;
@ -226,7 +221,7 @@ public class PlayServiceImpl implements IPlayService {
cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> { cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
logger.info("收到订阅消息: " + response.toJSONString()); logger.info("收到订阅消息: " + response.toJSONString());
onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, uuid.toString()); onPublishHandlerForPlay(mediaServerItemInuse, response, deviceId, channelId, uuid);
}, (event) -> { }, (event) -> {
mediaServerService.closeRTPServer(playResult.getDevice(), channelId); mediaServerService.closeRTPServer(playResult.getDevice(), channelId);
WVPResult wvpResult = new WVPResult(); WVPResult wvpResult = new WVPResult();
@ -274,7 +269,7 @@ public class PlayServiceImpl implements IPlayService {
public MediaServerItem getNewMediaServerItem(Device device) { public MediaServerItem getNewMediaServerItem(Device device) {
if (device == null) return null; if (device == null) return null;
String mediaServerId = device.getMediaServerId(); String mediaServerId = device.getMediaServerId();
MediaServerItem mediaServerItem = null; MediaServerItem mediaServerItem;
if (mediaServerId == null) { if (mediaServerId == null) {
mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(); mediaServerItem = mediaServerService.getMediaServerForMinimumLoad();
}else { }else {
@ -286,16 +281,35 @@ public class PlayServiceImpl implements IPlayService {
return mediaServerItem; return mediaServerItem;
} }
@Override @Override
public void onPublishHandlerForPlayBack(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) { public void onPublishHandlerForPlayBack(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
RequestMessage msg = new RequestMessage(); RequestMessage msg = new RequestMessage();
msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId); msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId);
msg.setId(uuid); msg.setId(uuid);
StreamInfo streamInfo = onPublishHandler(mediaServerItem, resonse, deviceId, channelId, uuid); StreamInfo streamInfo = onPublishHandler(mediaServerItem, resonse, deviceId, channelId, uuid);
if (streamInfo != null) { if (streamInfo != null) {
redisCatchStorage.startPlayback(streamInfo); redisCatchStorage.startPlayback(streamInfo);
msg.setData(JSON.toJSONString(streamInfo)); msg.setData(JSON.toJSONString(streamInfo));
resultHolder.invokeResult(msg); 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 { } else {
logger.warn("设备预览API调用失败"); logger.warn("设备预览API调用失败");
msg.setData("设备预览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) { public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId, String uuid) {
String streamId = resonse.getString("stream"); String streamId = resonse.getString("stream");
JSONArray tracks = resonse.getJSONArray("tracks"); JSONArray tracks = resonse.getJSONArray("tracks");

View File

@ -1,18 +1,21 @@
package com.genersoft.iot.vmp.service.impl; package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson.JSONObject; 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.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; 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.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.service.IGbStreamService; import com.genersoft.iot.vmp.service.IGbStreamService;
import com.genersoft.iot.vmp.service.IMediaServerService; 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.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.storager.dao.GbStreamMapper; import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
import com.genersoft.iot.vmp.storager.dao.PlatformGbStreamMapper; import com.genersoft.iot.vmp.storager.dao.PlatformGbStreamMapper;
import com.genersoft.iot.vmp.storager.dao.StreamProxyMapper; import com.genersoft.iot.vmp.storager.dao.StreamProxyMapper;
import com.genersoft.iot.vmp.service.IStreamProxyService; import com.genersoft.iot.vmp.service.IStreamProxyService;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -34,7 +37,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
private IVideoManagerStorager videoManagerStorager; private IVideoManagerStorager videoManagerStorager;
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private IMediaService mediaService;
@Autowired @Autowired
private ZLMRESTfulUtils zlmresTfulUtils;; private ZLMRESTfulUtils zlmresTfulUtils;;
@ -56,8 +59,10 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
@Override @Override
public String save(StreamProxyItem param) { public WVPResult<StreamInfo> save(StreamProxyItem param) {
MediaServerItem mediaInfo; MediaServerItem mediaInfo;
WVPResult<StreamInfo> wvpResult = new WVPResult<>();
wvpResult.setCode(0);
if ("auto".equals(param.getMediaServerId())){ if ("auto".equals(param.getMediaServerId())){
mediaInfo = mediaServerService.getMediaServerForMinimumLoad(); mediaInfo = mediaServerService.getMediaServerForMinimumLoad();
}else { }else {
@ -65,7 +70,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
} }
if (mediaInfo == null) { if (mediaInfo == null) {
logger.warn("保存代理未找到在线的ZLM..."); 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(), String dstUrl = String.format("rtmp://%s:%s/%s/%s", "127.0.0.1", mediaInfo.getRtmpPort(), param.getApp(),
param.getStream() ); param.getStream() );
@ -83,6 +89,10 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
result.append(", 但是启用失败,请检查流地址是否可用"); result.append(", 但是启用失败,请检查流地址是否可用");
param.setEnable(false); param.setEnable(false);
videoManagerStorager.updateStreamProxy(param); 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(", 但是启用失败,请检查流地址是否可用"); result.append(", 但是启用失败,请检查流地址是否可用");
param.setEnable(false); param.setEnable(false);
videoManagerStorager.updateStreamProxy(param); videoManagerStorager.updateStreamProxy(param);
}else {
StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(
mediaInfo, param.getApp(), param.getStream(), null);
wvpResult.setData(streamInfo);
} }
} }
}else { }else {
@ -113,7 +127,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
result.append(", 关联国标平台[ " + param.getPlatformGbId() + " ]失败"); result.append(", 关联国标平台[ " + param.getPlatformGbId() + " ]失败");
} }
} }
return result.toString(); wvpResult.setMsg(result.toString());
return wvpResult;
} }
@Override @Override
@ -213,4 +228,10 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
return result; return result;
} }
@Override
public StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId) {
return videoManagerStorager.getStreamProxyByAppAndStream(app, streamId);
}
} }

View File

@ -51,15 +51,24 @@ public class StreamPushServiceImpl implements IStreamPushService {
for (MediaItem item : mediaItems) { for (MediaItem item : mediaItems) {
// 不保存国标推理以及拉流代理的流 // 不保存国标推理以及拉流代理的流
if (item.getOriginType() == 3 || item.getOriginType() == 4 || item.getOriginType() == 5) { if (item.getOriginType() == 1 || item.getOriginType() == 2 || item.getOriginType() == 8) {
continue;
}
String key = item.getApp() + "_" + item.getStream(); String key = item.getApp() + "_" + item.getStream();
StreamPushItem streamPushItem = result.get(key); StreamPushItem streamPushItem = result.get(key);
if (streamPushItem == null) { 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.setApp(item.getApp());
streamPushItem.setMediaServerId(mediaServerItem.getId()); streamPushItem.setMediaServerId(item.getMediaServerId());
streamPushItem.setStream(item.getStream()); streamPushItem.setStream(item.getStream());
streamPushItem.setAliveSecond(item.getAliveSecond()); streamPushItem.setAliveSecond(item.getAliveSecond());
streamPushItem.setCreateStamp(item.getCreateStamp()); streamPushItem.setCreateStamp(item.getCreateStamp());
@ -72,11 +81,7 @@ public class StreamPushServiceImpl implements IStreamPushService {
streamPushItem.setAliveSecond(item.getAliveSecond()); streamPushItem.setAliveSecond(item.getAliveSecond());
streamPushItem.setStatus(true); streamPushItem.setStatus(true);
streamPushItem.setVhost(item.getVhost()); streamPushItem.setVhost(item.getVhost());
result.put(key, streamPushItem); return streamPushItem;
}
}
return new ArrayList<>(result.values());
} }
@Override @Override

View File

@ -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.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch; import com.genersoft.iot.vmp.gb28181.bean.ParentPlatformCatch;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem; import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -120,5 +121,35 @@ public interface IRedisCatchStorage {
/** /**
* 在redis添加wvp的信息 * 在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);
} }

View File

@ -353,7 +353,7 @@ public interface IVideoManagerStorager {
* @param app * @param app
* @param stream * @param stream
*/ */
void removeMedia(String app, String stream); int removeMedia(String app, String stream);
/** /**
@ -366,7 +366,7 @@ public interface IVideoManagerStorager {
* @param app * @param app
* @param streamId * @param streamId
*/ */
void mediaOutline(String app, String streamId); int mediaOutline(String app, String streamId);
/** /**
* 设置平台在线/离线 * 设置平台在线/离线
@ -406,4 +406,12 @@ public interface IVideoManagerStorager {
* @param channelId 通道ID * @param channelId 通道ID
*/ */
void deviceChannelOffline(String deviceId, String channelId); void deviceChannelOffline(String deviceId, String channelId);
/**
* 通过app与stream获取StreamProxy
* @param app
* @param streamId
* @return
*/
StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId);
} }

View File

@ -28,7 +28,7 @@ public interface GbStreamMapper {
"latitude=#{latitude}," + "latitude=#{latitude}," +
"mediaServerId=#{mediaServerId}," + "mediaServerId=#{mediaServerId}," +
"status=${status} " + "status=${status} " +
"WHERE app=#{app} AND stream=#{stream} AND gbId=#{gbId}") "WHERE app=#{app} AND stream=#{stream}")
int update(GbStream gbStream); int update(GbStream gbStream);
@Delete("DELETE FROM gb_stream WHERE app=#{app} AND stream=#{stream}") @Delete("DELETE FROM gb_stream WHERE app=#{app} AND stream=#{stream}")
@ -53,7 +53,7 @@ public interface GbStreamMapper {
@Update("UPDATE gb_stream " + @Update("UPDATE gb_stream " +
"SET status=${status} " + "SET status=${status} " +
"WHERE app=#{app} AND stream=#{stream}") "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} ") @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); List<GbStream> selectAllByMediaServerId(String mediaServerId);

View File

@ -105,4 +105,7 @@ public interface MediaServerMapper {
@Select("SELECT * FROM media_server WHERE ip='${host}' and httpPort=${port}") @Select("SELECT * FROM media_server WHERE ip='${host}' and httpPort=${port}")
MediaServerItem queryOneByHostAndPort(String host, int port); MediaServerItem queryOneByHostAndPort(String host, int port);
@Select("SELECT * FROM media_server WHERE defaultServer=1")
MediaServerItem queryDefault();
} }

View File

@ -11,9 +11,10 @@ import java.util.List;
public interface StreamProxyMapper { public interface StreamProxyMapper {
@Insert("INSERT INTO stream_proxy (type, app, stream,mediaServerId, url, src_url, dst_url, " + @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}', " + "('${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); int add(StreamProxyItem streamProxyDto);
@Update("UPDATE stream_proxy " + @Update("UPDATE stream_proxy " +
@ -29,6 +30,7 @@ public interface StreamProxyMapper {
"rtp_type=#{rtp_type}, " + "rtp_type=#{rtp_type}, " +
"enable_hls=#{enable_hls}, " + "enable_hls=#{enable_hls}, " +
"enable=#{enable}, " + "enable=#{enable}, " +
"enable_remove_none_reader=#{enable_remove_none_reader}, " +
"enable_mp4=#{enable_mp4} " + "enable_mp4=#{enable_mp4} " +
"WHERE app=#{app} AND stream=#{stream}") "WHERE app=#{app} AND stream=#{stream}")
int update(StreamProxyItem streamProxyDto); int update(StreamProxyItem streamProxyDto);

View File

@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.common.VideoManagerConstants; import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.gb28181.bean.*; 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.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper; import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.utils.redis.RedisUtil; import com.genersoft.iot.vmp.utils.redis.RedisUtil;
@ -63,15 +64,15 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
streamInfo.getChannelId())); streamInfo.getChannelId()));
} }
@Override @Override
public StreamInfo queryPlayByStreamId(String steamId) { public StreamInfo queryPlayByStreamId(String streamId) {
List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, steamId)); List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, streamId));
if (playLeys == null || playLeys.size() == 0) return null; if (playLeys == null || playLeys.size() == 0) return null;
return (StreamInfo)redis.get(playLeys.get(0).toString()); return (StreamInfo)redis.get(playLeys.get(0).toString());
} }
@Override @Override
public StreamInfo queryPlaybackByStreamId(String steamId) { public StreamInfo queryPlaybackByStreamId(String streamId) {
List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, steamId)); List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, streamId));
if (playLeys == null || playLeys.size() == 0) return null; if (playLeys == null || playLeys.size() == 0) return null;
return (StreamInfo)redis.get(playLeys.get(0).toString()); return (StreamInfo)redis.get(playLeys.get(0).toString());
} }
@ -103,10 +104,15 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
@Override @Override
public boolean startPlayback(StreamInfo stream) { public boolean startPlayback(StreamInfo stream) {
return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, stream.getStreamId(),stream.getDeviceID(), stream.getChannelId()), return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, stream.getStreamId(),
stream); 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 @Override
public boolean stopPlayback(StreamInfo streamInfo) { public boolean stopPlayback(StreamInfo streamInfo) {
@ -295,8 +301,33 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
} }
@Override @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());
}
} }

View File

@ -605,8 +605,8 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
} }
@Override @Override
public void removeMedia(String app, String stream) { public int removeMedia(String app, String stream) {
streamPushMapper.del(app, stream); return streamPushMapper.del(app, stream);
} }
@Override @Override
@ -615,8 +615,8 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
} }
@Override @Override
public void mediaOutline(String app, String streamId) { public int mediaOutline(String app, String streamId) {
gbStreamMapper.setStatus(app, streamId, false); return gbStreamMapper.setStatus(app, streamId, false);
} }
@Override @Override
@ -651,4 +651,9 @@ public class VideoManagerStoragerImpl implements IVideoManagerStorager {
} }
return result; return result;
} }
@Override
public StreamProxyItem getStreamProxyByAppAndStream(String app, String streamId) {
return streamProxyMapper.selectOne(app, streamId);
}
} }

View File

@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.utils.redis;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*; import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -729,4 +730,11 @@ public class RedisUtil {
return new ArrayList<>(keys); return new ArrayList<>(keys);
} }
// ============================== 消息发送与订阅 ==============================
public void convertAndSend(String channel, JSONObject msg) {
// redisTemplate.convertAndSend(channel, msg);
redisTemplate.convertAndSend(channel, msg);
}
} }

View File

@ -108,10 +108,10 @@ public class DeviceControl {
msg.setData("Timeout. Device did not response to this command."); msg.setData("Timeout. Device did not response to this command.");
resultHolder.invokeAllResult(msg); resultHolder.invokeAllResult(msg);
}); });
resultHolder.put(key, uuid, result);
if (resultHolder.exist(key, null)){ if (resultHolder.exist(key, null)){
return result; return result;
} }
resultHolder.put(key, uuid, result);
cmder.recordCmd(device, channelId, recordCmdStr, event -> { cmder.recordCmd(device, channelId, recordCmdStr, event -> {
RequestMessage msg = new RequestMessage(); RequestMessage msg = new RequestMessage();
msg.setId(uuid); msg.setId(uuid);

View File

@ -44,11 +44,11 @@ public class MediaController {
@ApiImplicitParams({ @ApiImplicitParams({
@ApiImplicitParam(name = "app", value = "应用名", dataTypeClass = String.class), @ApiImplicitParam(name = "app", value = "应用名", dataTypeClass = String.class),
@ApiImplicitParam(name = "stream", value = "流id", 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") @GetMapping(value = "/stream_info_by_app_and_stream")
@ResponseBody @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); StreamInfo streamInfoByAppAndStreamWithCheck = mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId);
WVPResult<StreamInfo> result = new WVPResult<>(); WVPResult<StreamInfo> result = new WVPResult<>();
if (streamInfoByAppAndStreamWithCheck != null){ if (streamInfoByAppAndStreamWithCheck != null){

View File

@ -76,7 +76,7 @@ public class DownloadController {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("历史媒体下载 API调用deviceId%schannelId%sdownloadSpeed%s", deviceId, channelId, downloadSpeed)); logger.debug(String.format("历史媒体下载 API调用deviceId%schannelId%sdownloadSpeed%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(); String uuid = UUID.randomUUID().toString();
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(30000L); DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(30000L);
// 超时处理 // 超时处理
@ -88,10 +88,10 @@ public class DownloadController {
msg.setData("Timeout"); msg.setData("Timeout");
resultHolder.invokeAllResult(msg); resultHolder.invokeAllResult(msg);
}); });
resultHolder.put(key, uuid, result);
if(resultHolder.exist(key, null)) { if(resultHolder.exist(key, null)) {
return result; return result;
} }
resultHolder.put(key, uuid, result);
Device device = storager.queryVideoDevice(deviceId); Device device = storager.queryVideoDevice(deviceId);
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, channelId); StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, channelId);
if (streamInfo != null) { if (streamInfo != null) {
@ -114,7 +114,7 @@ public class DownloadController {
cmder.downloadStreamCmd(newMediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, (MediaServerItem mediaServerItem, JSONObject response) -> { cmder.downloadStreamCmd(newMediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, (MediaServerItem mediaServerItem, JSONObject response) -> {
logger.info("收到订阅消息: " + response.toJSONString()); logger.info("收到订阅消息: " + response.toJSONString());
playService.onPublishHandlerForPlayBack(mediaServerItem, response, deviceId, channelId, uuid.toString()); playService.onPublishHandlerForDownload(mediaServerItem, response, deviceId, channelId, uuid.toString());
}, event -> { }, event -> {
RequestMessage msg = new RequestMessage(); RequestMessage msg = new RequestMessage();
msg.setId(uuid); msg.setId(uuid);

View File

@ -9,6 +9,7 @@ import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.SSRCInfo; import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.service.IPlayService; 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.Api;
import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiImplicitParams;
@ -77,7 +78,7 @@ public class PlaybackController {
logger.debug(String.format("设备回放 API调用deviceId%s channelId%s", deviceId, channelId)); logger.debug(String.format("设备回放 API调用deviceId%s channelId%s", deviceId, channelId));
} }
String uuid = UUID.randomUUID().toString(); 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); DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(30000L);
Device device = storager.queryVideoDevice(deviceId); Device device = storager.queryVideoDevice(deviceId);
if (device == null) { if (device == null) {
@ -152,4 +153,103 @@ public class PlaybackController {
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR); 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", "不支持的speed0.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);
}
}
} }

View File

@ -56,17 +56,22 @@ public class GBRecordController {
} }
Device device = storager.queryVideoDevice(deviceId); Device device = storager.queryVideoDevice(deviceId);
cmder.recordInfoQuery(device, channelId, startTime, endTime);
// 指定超时时间 1分钟30秒 // 指定超时时间 1分钟30秒
DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<>(90*1000L); DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<>(90*1000L);
String uuid = UUID.randomUUID().toString(); String uuid = UUID.randomUUID().toString();
String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + channelId; int sn = (int)((Math.random()*9+1)*100000);
// 录像查询以channelId作为deviceId查询 String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn;
resultHolder.put(key, uuid, result);
result.onTimeout(()->{
RequestMessage msg = new RequestMessage(); RequestMessage msg = new RequestMessage();
msg.setId(uuid); msg.setId(uuid);
msg.setKey(key); 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"); msg.setData("timeout");
resultHolder.invokeResult(msg); resultHolder.invokeResult(msg);
}); });

View File

@ -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<>();
}

View File

@ -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;
}
}

View File

@ -1,9 +1,11 @@
package com.genersoft.iot.vmp.vmanager.streamProxy; package com.genersoft.iot.vmp.vmanager.streamProxy;
import com.alibaba.fastjson.JSONObject; 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.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.service.IMediaServerService; 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.IRedisCatchStorage;
import com.genersoft.iot.vmp.service.IStreamProxyService; import com.genersoft.iot.vmp.service.IStreamProxyService;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult; import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
@ -68,10 +70,8 @@ public class StreamProxyController {
public WVPResult save(@RequestBody StreamProxyItem param){ public WVPResult save(@RequestBody StreamProxyItem param){
logger.info("添加代理: " + JSONObject.toJSONString(param)); logger.info("添加代理: " + JSONObject.toJSONString(param));
if (StringUtils.isEmpty(param.getMediaServerId())) param.setMediaServerId("auto"); if (StringUtils.isEmpty(param.getMediaServerId())) param.setMediaServerId("auto");
String msg = streamProxyService.save(param); if (StringUtils.isEmpty(param.getType())) param.setType("default");
WVPResult<Object> result = new WVPResult<>(); WVPResult<StreamInfo> result = streamProxyService.save(param);
result.setCode(0);
result.setMsg(msg);
return result; return result;
} }

View File

@ -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;
}
}

View File

@ -1,4 +1,4 @@
package com.genersoft.iot.vmp.web; package com.genersoft.iot.vmp.web.gb28181;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.bean.Device;
@ -45,49 +45,46 @@ public class ApiControlController {
serial, code, command, speed)); serial, code, command, speed));
} }
Device device = storager.queryVideoDevice(serial); Device device = storager.queryVideoDevice(serial);
int leftRight = 0; int cmdCode = 0;
int upDown = 0;
int inOut = 0;
switch (command){ switch (command){
case "left": case "left":
leftRight = 1; cmdCode = 2;
break; break;
case "right": case "right":
leftRight = 2; cmdCode = 1;
break; break;
case "up": case "up":
upDown = 1; cmdCode = 8;
break; break;
case "down": case "down":
upDown = 2; cmdCode = 4;
break; break;
case "upleft": case "upleft":
upDown = 1; cmdCode = 10;
leftRight = 1; break;
case "upright": case "upright":
upDown = 1; cmdCode = 9;
leftRight = 2;
break; break;
case "downleft": case "downleft":
upDown = 2; cmdCode = 6;
leftRight = 1;
break; break;
case "downright": case "downright":
upDown = 2; cmdCode = 5;
leftRight = 2;
break; break;
case "zoomin": case "zoomin":
inOut = 2; cmdCode = 16;
break; break;
case "zoomout": case "zoomout":
inOut = 1; cmdCode = 32;
break; break;
case "stop": case "stop":
cmdCode = 0;
break;
default:
break; break;
} }
// 默认值 50 // 默认值 50
cmder.ptzCmd(device, code, leftRight, upDown, inOut, speed==0 ? 129 : speed, 50); cmder.frontEndCmd(device, code, cmdCode, speed, speed, speed);
return null; return null;
} }
} }

View File

@ -1,4 +1,4 @@
package com.genersoft.iot.vmp.web; package com.genersoft.iot.vmp.web.gb28181;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.conf.SipConfig; import com.genersoft.iot.vmp.conf.SipConfig;

View File

@ -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.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
@ -73,7 +73,7 @@ public class ApiDeviceController {
deviceJsonObject.put("ChannelCount", device.getChannelCount()); deviceJsonObject.put("ChannelCount", device.getChannelCount());
deviceJsonObject.put("RecvStreamIP", ""); deviceJsonObject.put("RecvStreamIP", "");
deviceJsonObject.put("CatalogInterval", 3600); // 通道目录抓取周期 deviceJsonObject.put("CatalogInterval", 3600); // 通道目录抓取周期
deviceJsonObject.put("SubscribeInterval", 0); // 订阅周期(), 0 表示后台不周期订阅 deviceJsonObject.put("SubscribeInterval", device.getSubscribeCycleForCatalog()); // 订阅周期(), 0 表示后台不周期订阅
deviceJsonObject.put("Online", device.getOnline() == 1); deviceJsonObject.put("Online", device.getOnline() == 1);
deviceJsonObject.put("Password", ""); deviceJsonObject.put("Password", "");
deviceJsonObject.put("MediaTransport", device.getTransport()); deviceJsonObject.put("MediaTransport", device.getTransport());

View File

@ -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.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo; 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.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel; 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.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.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
@ -34,15 +36,13 @@ public class ApiStreamController {
private IVideoManagerStorager storager; private IVideoManagerStorager storager;
@Autowired @Autowired
private IRedisCatchStorage redisCatchStorage; private UserSetup userSetup;
// @Autowired
// private ZLMRESTfulUtils zlmresTfulUtils;
@Autowired @Autowired
private PlayController playController; private IRedisCatchStorage redisCatchStorage;
@Autowired
private IPlayService playService;
/** /**
* 实时直播 - 开始直播 * 实时直播 - 开始直播
@ -69,7 +69,7 @@ public class ApiStreamController {
@RequestParam(required = false)String timeout @RequestParam(required = false)String timeout
){ ){
DeferredResult<JSONObject> resultDeferredResult = new DeferredResult<JSONObject>(); DeferredResult<JSONObject> resultDeferredResult = new DeferredResult<>(userSetup.getPlayTimeout() + 10);
Device device = storager.queryVideoDevice(serial); Device device = storager.queryVideoDevice(serial);
if (device == null ) { if (device == null ) {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
@ -99,11 +99,9 @@ public class ApiStreamController {
result.put("error","channel[ " + code + " ]offline"); result.put("error","channel[ " + code + " ]offline");
resultDeferredResult.setResult(result); resultDeferredResult.setResult(result);
} }
DeferredResult<ResponseEntity<String>> play = playController.play(serial, code); MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
PlayResult play = playService.play(newMediaServerItem, serial, code, (mediaServerItem, response)->{
play.setResultHandler((Object o)->{ StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(serial, code);
ResponseEntity<String> responseEntity = (ResponseEntity)o;
StreamInfo streamInfo = JSON.parseObject(responseEntity.getBody(), StreamInfo.class);
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("StreamID", streamInfo.getStreamId()); result.put("StreamID", streamInfo.getStreamId());
result.put("DeviceID", device.getDeviceId()); result.put("DeviceID", device.getDeviceId());
@ -134,7 +132,23 @@ public class ApiStreamController {
result.put("NumOutputs", ""); result.put("NumOutputs", "");
result.put("CascadeSize", ""); result.put("CascadeSize", "");
result.put("RelaySize", ""); 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); resultDeferredResult.setResult(result);
}); });
return resultDeferredResult; return resultDeferredResult;

View File

@ -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.service.IUserService;
import com.genersoft.iot.vmp.storager.dao.dto.User; import com.genersoft.iot.vmp.storager.dao.dto.User;

View File

@ -137,6 +137,8 @@ logging:
com.genersoft.iot.vmp.gb28181: info com.genersoft.iot.vmp.gb28181: info
# [根据业务需求配置] # [根据业务需求配置]
user-settings: user-settings:
# [可选] 服务ID不写则为000000
server-id:
# [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true # [可选] 自动点播, 使用固定流地址进行播放时,如果未点播则自动进行点播, 需要rtp.enable=true
auto-apply-play: false auto-apply-play: false
# [可选] 部分设备需要扩展SDP需要打开此设置 # [可选] 部分设备需要扩展SDP需要打开此设置

View File

@ -2,11 +2,11 @@ spring:
# REDIS数据库配置 # REDIS数据库配置
redis: redis:
# [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1 # [必须修改] 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 # [可选] 数据库 DB
database: ${REDIS_DB:6} database: 6
# [可选] 访问密码,若你的redis服务器没有设置密码就不需要用密码去连接 # [可选] 访问密码,若你的redis服务器没有设置密码就不需要用密码去连接
password: ${REDIS_PWD:} password: ${REDIS_PWD:}
# [可选] 超时时间 # [可选] 超时时间
@ -43,11 +43,11 @@ sip:
# 后两位为行业编码定义参照附录D.3 # 后两位为行业编码定义参照附录D.3
# 3701020049标识山东济南历下区 信息行业接入 # 3701020049标识山东济南历下区 信息行业接入
# [可选] # [可选]
domain: ${WVP_DOMAIN:4401020049} domain: 4401020049
# [可选] # [可选]
id: ${WVP_ID:44010200492000000001} id: 44010200492000000001
# [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验 # [可选] 默认设备认证密码,后续扩展使用设备单独密码, 移除密码将不进行校验
password: ${WVP_PWD:admin123} password: admin123
#zlm 默认服务器配置 #zlm 默认服务器配置
media: media:

View 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.

View File

@ -6,7 +6,9 @@
</el-header> </el-header>
<el-main> <el-main>
<div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;"> <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;"> <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"> <el-select size="mini" @change="chooseMediaChange" style="width: 16rem; margin-right: 1rem;" v-model="mediaServerId" placeholder="请选择" :disabled="recordDetail">
@ -20,7 +22,6 @@
</div> </div>
<div style="position: absolute; right: 1rem; top: 0.3rem;"> <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-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> </div>
<div v-if="!recordDetail"> <div v-if="!recordDetail">
@ -53,7 +54,7 @@
:total="total"> :total="total">
</el-pagination> </el-pagination>
</div> </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-main>
</el-container> </el-container>
</div> </div>
@ -72,6 +73,7 @@
return { return {
mediaServerList: [], // mediaServerList: [], //
mediaServerId: null, // mediaServerId: null, //
mediaServerPath: null, //
recordList: [], // recordList: [], //
chooseRecord: null, // chooseRecord: null, //
@ -115,6 +117,11 @@
that.mediaServerList = data.data; that.mediaServerList = data.data;
if (that.mediaServerList.length > 0) { if (that.mediaServerList.length > 0) {
that.mediaServerId = that.mediaServerList[0].id 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(); that.getRecordList();
} }
}) })

View File

@ -15,7 +15,8 @@
<i class="el-icon-video-camera" ></i> <i class="el-icon-video-camera" ></i>
{{ item.substring(0,17)}} {{ item.substring(0,17)}}
</el-tag> </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> </li>
</ul> </ul>
</div> </div>
@ -75,7 +76,7 @@
<li class="task-list-item" v-for="item in taskListEnded"> <li class="task-list-item" v-for="item in taskListEnded">
<div class="task-list-item-box" style="height: 2rem;line-height: 2rem;"> <div class="task-list-item-box" style="height: 2rem;line-height: 2rem;">
<span>{{ item.startTime.substr(10) }}-{{item.endTime.substr(10)}}</span> <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> </a>
</div> </div>
</li> </li>
@ -111,10 +112,10 @@
components: { components: {
uiHeader, player uiHeader, player
}, },
props: ['recordFile', 'mediaServerId', 'dateFiles'], props: ['recordFile', 'mediaServerId', 'dateFiles', 'mediaServerPath'],
data() { data() {
return { return {
basePath: process.env.NODE_ENV === 'development'?`${location.origin}/debug/zlm/${this.mediaServerId}`:`${location.origin}/zlm/${this.mediaServerId}`, basePath: `${this.mediaServerPath}/record`,
dateFilesObj: [], dateFilesObj: [],
detailFiles: [], detailFiles: [],
chooseDate: null, chooseDate: null,
@ -264,7 +265,7 @@
this.videoUrl = ""; this.videoUrl = "";
}else { }else {
// TODO // 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) console.log(this.videoUrl)
} }

View File

@ -66,6 +66,14 @@
</div> </div>
</template> </template>
</el-table-column> </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"> <el-table-column label="操作" width="360" align="center" fixed="right">

View File

@ -15,7 +15,7 @@
<!-- <el-menu-item index="/setting/sip">国标服务</el-menu-item>--> <!-- <el-menu-item index="/setting/sip">国标服务</el-menu-item>-->
<!-- <el-menu-item index="/setting/media">媒体服务</el-menu-item>--> <!-- <el-menu-item index="/setting/media">媒体服务</el-menu-item>-->
<!-- </el-submenu>--> <!-- </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-menu-item style="float: right;" @click="loginout">退出</el-menu-item>-->
<el-submenu index="" style="float: right;" > <el-submenu index="" style="float: right;" >
<template slot="title">欢迎{{this.$cookies.get("session").username}}</template> <template slot="title">欢迎{{this.$cookies.get("session").username}}</template>
@ -35,11 +35,23 @@ export default {
components: { Notification, changePasswordDialog }, components: { Notification, changePasswordDialog },
data() { data() {
return { return {
alarmNotify: true, alarmNotify: false,
sseSource: null, sseSource: null,
activeIndex: this.$route.path, 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:{ methods:{
loginout(){ loginout(){
this.$axios({ this.$axios({
@ -65,6 +77,10 @@ export default {
beforeunloadHandler() { beforeunloadHandler() {
this.sseSource.close(); this.sseSource.close();
}, },
alarmNotifyChannge(){
this.setAlarmSwitchStatus()
this.sseControl()
},
sseControl() { sseControl() {
let that = this; let that = this;
if (this.alarmNotify) { if (this.alarmNotify) {
@ -90,31 +106,35 @@ export default {
} }
}, false); }, false);
} else { } else {
if (this.sseSource != null) {
this.sseSource.removeEventListener('open', null); this.sseSource.removeEventListener('open', null);
this.sseSource.removeEventListener('message', null); this.sseSource.removeEventListener('message', null);
this.sseSource.removeEventListener('error', null); this.sseSource.removeEventListener('error', null);
this.sseSource.close(); this.sseSource.close();
} }
} }
}, },
created(){ getAlarmSwitchStatus(){
if (this.$route.path.startsWith("/channelList")){ if (localStorage.getItem("alarmSwitchStatus") == null) {
this.activeIndex = "/deviceList" localStorage.setItem("alarmSwitchStatus", false);
} }
return localStorage.getItem("alarmSwitchStatus");
}, },
mounted() { setAlarmSwitchStatus(){
window.addEventListener('beforeunload', e => this.beforeunloadHandler(e)) localStorage.setItem("alarmSwitchStatus", this.alarmNotify);
// window.addEventListener('unload', e => this.unloadHandler(e)) }
this.sseControl();
}, },
destroyed() { destroyed() {
window.removeEventListener('beforeunload', e => this.beforeunloadHandler(e)) window.removeEventListener('beforeunload', e => this.beforeunloadHandler(e))
if (this.sseSource != null) {
this.sseSource.removeEventListener('open', null); this.sseSource.removeEventListener('open', null);
this.sseSource.removeEventListener('message', null); this.sseSource.removeEventListener('message', null);
this.sseSource.removeEventListener('error', null); this.sseSource.removeEventListener('error', null);
this.sseSource.close(); this.sseSource.close();
// window.removeEventListener('unload', e => this.unloadHandler(e)) }
}, },
} }
</script> </script>

View File

@ -106,6 +106,7 @@
<el-checkbox label="启用" v-model="proxyParam.enable" ></el-checkbox> <el-checkbox label="启用" v-model="proxyParam.enable" ></el-checkbox>
<el-checkbox label="转HLS" v-model="proxyParam.enable_hls" ></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="MP4录制" v-model="proxyParam.enable_mp4" ></el-checkbox>
<el-checkbox label="无人观看自动删除" v-model="proxyParam.enable_remove_none_reader" ></el-checkbox>
</div> </div>
</el-form-item> </el-form-item>
@ -160,7 +161,7 @@ export default {
type: "default", type: "default",
app: null, app: null,
stream: null, stream: null,
url: "rtmp://58.200.131.2/livetv/cctv5hd", url: "",
src_url: null, src_url: null,
timeout_ms: null, timeout_ms: null,
ffmpeg_cmd_key: null, ffmpeg_cmd_key: null,
@ -169,6 +170,7 @@ export default {
enable: true, enable: true,
enable_hls: true, enable_hls: true,
enable_mp4: false, enable_mp4: false,
enable_remove_none_reader: false,
platformGbId: null, platformGbId: null,
mediaServerId: "auto", mediaServerId: "auto",
}, },

View File

@ -39,7 +39,34 @@
</el-tab-pane> </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/"}}--> <!--{"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-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 :data="videoHistory.searchHistoryResult" height="150" v-loading="recordsLoading">
<el-table-column label="名称" prop="name"></el-table-column> <el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="文件" prop="filePath"></el-table-column> <el-table-column label="文件" prop="filePath"></el-table-column>
@ -210,6 +237,10 @@ export default {
showPtz: true, showPtz: true,
showRrecord: true, showRrecord: true,
tracksNotLoaded: false, tracksNotLoaded: false,
sliderTime: 0,
seekTime: 0,
recordStartTime: 0,
showTimeText: "00:00:00",
}; };
}, },
methods: { methods: {
@ -287,11 +318,13 @@ export default {
// return `http://${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`; // return `http://${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`;
if (location.protocol === "https:") { if (location.protocol === "https:") {
if (streamInfo.wss_flv === null) { if (streamInfo.wss_flv === null) {
this.$message({ console.error("媒体服务器未配置ssl端口, 使用http端口")
showClose: true, // this.$message({
message: '媒体服务器未配置ssl端口', // showClose: true,
type: 'error' // message: 'ssl, ',
}); // type: 'error'
// });
return streamInfo.ws_flv
}else { }else {
return streamInfo.wss_flv; return streamInfo.wss_flv;
} }
@ -431,6 +464,14 @@ export default {
}, },
playRecord: function (row) { playRecord: function (row) {
let that = this; 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 != "") { if (that.streamId != "") {
that.stopPlayRecord(function () { that.stopPlayRecord(function () {
that.streamId = "", that.streamId = "",
@ -578,7 +619,45 @@ export default {
} }
console.log(resultArray) console.log(resultArray)
return 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> </script>

View File

@ -26,7 +26,7 @@
<el-input v-model="platform.serverIP" clearable></el-input> <el-input v-model="platform.serverIP" clearable></el-input>
</el-form-item> </el-form-item>
<el-form-item label="SIP服务端口" prop="serverPort"> <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>
<el-form-item label="设备国标编号" prop="deviceGBId"> <el-form-item label="设备国标编号" prop="deviceGBId">
<el-input v-model="platform.deviceGBId" clearable></el-input> <el-input v-model="platform.deviceGBId" clearable></el-input>
@ -35,7 +35,7 @@
<el-input v-model="platform.deviceIp" :disabled="true"></el-input> <el-input v-model="platform.deviceIp" :disabled="true"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="本地端口" prop="devicePort"> <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-item>
</el-form> </el-form>
</el-col> </el-col>
@ -236,6 +236,17 @@ export default {
</script> </script>
<style> <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 { .control-wrapper-not-used {
position: relative; position: relative;
width: 6.25rem; width: 6.25rem;

View File

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 1291092 */ font-family: "iconfont"; /* Project id 1291092 */
src: url('iconfont.woff2?t=1631767887536') format('woff2'), src: url('iconfont.woff2?t=1637741914969') format('woff2'),
url('iconfont.woff?t=1631767887536') format('woff'), url('iconfont.woff?t=1637741914969') format('woff'),
url('iconfont.ttf?t=1631767887536') format('truetype'); url('iconfont.ttf?t=1637741914969') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,226 @@
-moz-osx-font-smoothing: grayscale; -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 { .icon-online:before {
content: "\e600"; content: "\e600";
} }

Some files were not shown because too many files have changed in this diff Show More