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"]
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
WORKDIR /home
RUN git clone https://gitee.com/18010473990/maven.git && \
RUN git clone https://gitee.com/pan648540858/maven.git && \
cp maven/settings.xml /usr/share/maven/conf/ && \
git clone https://gitee.com/18010473990/wvp-GB28181.git && \
git clone https://gitee.com/18010473990/wvp-pro-assist.git
git clone https://gitee.com/pan648540858/wvp-GB28181-pro.git && \
git clone https://gitee.com/pan648540858/wvp-pro-assist.git
# 编译前端界面
WORKDIR /home/wvp-GB28181/web_src
WORKDIR /home/wvp-GB28181-pro/web_src
RUN npm install && \
npm run build && \
mkdir -p /opt/wvp/config && \
mkdir -p /opt/assist/config && \
cp /home/wvp-GB28181/src/main/resources/application-dev.yml /opt/wvp/config/application.yml && \
cp /home/wvp-GB28181-pro/src/main/resources/application-docker.yml /opt/wvp/config/application.yml && \
cp /home/wvp-pro-assist/src/main/resources/application-dev.yml /opt/assist/config/application.yml
# wvp打包
WORKDIR /home/wvp-GB28181
WORKDIR /home/wvp-GB28181-pro
RUN mvn compile && \
mvn package && \
cp /home/wvp-GB28181/target/wvp*.jar /opt/wvp/
cp /home/wvp-GB28181-pro/target/wvp*.jar /opt/wvp/
# wvp 录像管理打包
WORKDIR /home/wvp-pro-assist
@ -72,7 +72,7 @@ RUN mkdir -p /opt/media && \
# 清理
RUN rm -rf /home/wiki && \
rm -rf /home/wvp-GB28181 && \
rm -rf /home/wvp-GB28181-pro && \
apt-get autoremove -y git maven nodejs npm && \
apt-get clean -y && \
rm -rf /var/lib/apt/lists/*dic
@ -89,7 +89,7 @@ RUN echo '#!/bin/bash' > run.sh && \
echo 'if [${WVP_CONFIG}]; then' >> run.sh && \
echo ' java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 ${WVP_CONFIG}' >> run.sh && \
echo 'else' >> run.sh && \
echo ' java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 --media.ip=127.0.0.1 --media.sdp-ip=${WVP_IP} --sip.ip=${WVP_IP} --media.stream-ip=${WVP_IP}' >> run.sh && \
echo ' java -jar *.jar --spring.config.location=/opt/wvp/config/application.yml --media.record-assist-port=18081 --media.ip=127.0.0.1 --media.hook-ip=127.0.0.1 --media.sdp-ip=${WVP_IP} --sip.ip=${WVP_IP} --media.stream-ip=${WVP_IP}' >> run.sh && \
echo 'fi' >> run.sh
RUN chmod +x run.sh

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协议视频平台
[![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit)
@ -13,13 +13,7 @@ WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的开箱即用的网
流媒体服务基于ZLMediaKit-https://github.com/xiongziliang/ZLMediaKit
前端页面基于MediaServerUI进行修改.
# 快速体验
```shell
docker pull 648540858/wvp_pro
docker run --env WVP_IP="你的IP" -it -p 18080:18080 -p 30000-30500:30000-30500/udp -p 30000-30500:30000-30500/tcp -p 80:80 -p 5060:5060 -p 5060:5060/udp 648540858/wvp_pro
```
docker使用详情查看[https://hub.docker.com/r/648540858/wvp_pro](https://hub.docker.com/r/648540858/wvp_pro)
# 应用场景:
支持浏览器无插件播放摄像头视频。
支持摄像机、平台、NVR等设备接入。
@ -34,7 +28,7 @@ docker使用详情查看[https://hub.docker.com/r/648540858/wvp_pro](https://
[https://github.com/648540858/wvp-GB28181-pro/wiki](https://github.com/648540858/wvp-GB28181-pro/wiki)
# gitee同步仓库
https://gitee.com/18010473990/wvp-GB28181.git
https://gitee.com/pan648540858/wvp-GB28181-pro.git
# 截图
![build_1.png](https://github.com/648540858/wiki/blob/master/images/Screenshot_1.png)
@ -112,10 +106,16 @@ https://gitee.com/18010473990/wvp-GB28181.git
- [ ] 添加用户管理
- [X] WEB端支持播放H264与H265音频支持G.711A/G.711U/AAC,覆盖国标常用编码格式。
# docker快速体验
```shell
docker pull 648540858/wvp_pro
docker run --env WVP_IP="你的IP" -it -p 18080:18080 -p 30000-30500:30000-30500/udp -p 30000-30500:30000-30500/tcp -p 80:80 -p 5060:5060 -p 5060:5060/udp 648540858/wvp_pro
```
docker使用详情查看[https://hub.docker.com/r/648540858/wvp_pro](https://hub.docker.com/r/648540858/wvp_pro)
# gitee同步仓库
https://gitee.com/18010473990/wvp-GB28181.git
https://gitee.com/pan648540858/wvp-GB28181-pro.git
# 使用帮助
QQ群: 901799015, 690854210(ZLM大群)

View File

@ -1,6 +1,6 @@
FROM ubuntu:20.04 AS build
ARG gitUrl="https://gitee.com/18010473990"
ARG gitUrl="https://gitee.com/pan648540858"
ARG zlmGitUrl="https://gitee.com/xia-chu/ZLMediaKit"
RUN export DEBIAN_FRONTEND=noninteractive &&\
@ -9,9 +9,9 @@ RUN export DEBIAN_FRONTEND=noninteractive &&\
cmake ca-certificates openssl ffmpeg
RUN cd /home && \
git clone "https://gitee.com/18010473990/maven.git" && \
git clone "https://gitee.com/pan648540858/maven.git" && \
cp maven/settings.xml /usr/share/maven/conf/ && \
git clone "${gitUrl}/wvp-GB28181.git" && \
git clone "${gitUrl}/wvp-GB28181-pro.git" && \
git clone "${gitUrl}/wvp-pro-assist.git" && \
git clone --depth=1 "${zlmGitUrl}" && \
mkdir -p /opt/wvp/config /opt/assist/config /opt/media/www/record
@ -23,7 +23,7 @@ RUN cd /home/wvp-GB28181/web_src && \
RUN cd /home/wvp-GB28181 && \
mvn clean package -Dmaven.test.skip=true && \
cp /home/wvp-GB28181/target/*.jar /opt/wvp/ && \
cp /home/wvp-GB28181/src/main/resources/application-dev.yml /opt/wvp/config/application.yml
cp /home/wvp-GB28181/src/main/resources/application-docker.yml /opt/wvp/config/application.yml
RUN cd /home/wvp-pro-assist && \
mvn clean package -Dmaven.test.skip=true && \

Binary file not shown.

11
pom.xml
View File

@ -212,17 +212,6 @@
<!-- <version>1.0.8</version>-->
<!-- </dependency>-->
<!-- onvif协议栈 -->
<dependency>
<groupId>be.teletask</groupId>
<artifactId>onvif-java</artifactId>
<version>1.0.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/onvif-java-1.0.2.jar</systemPath>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

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

View File

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

View File

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

View File

@ -8,7 +8,9 @@ package com.genersoft.iot.vmp.common;
*/
public class VideoManagerConstants {
public static final String WVP_SERVER_PREFIX = "VMP_wvp_server";
public static final String WVP_SERVER_PREFIX = "VMP_SIGNALLING_SERVER_INFO_";
public static final String WVP_SERVER_STREAM_PUSH_PREFIX = "VMP_SIGNALLING_STREAM_PUSH_";
public static final String MEDIA_SERVER_PREFIX = "VMP_MEDIA_SERVER_";
@ -25,6 +27,7 @@ public class VideoManagerConstants {
public static final String PLAYER_PREFIX = "VMP_PLAYER_";
public static final String PLAY_BLACK_PREFIX = "VMP_PLAYBACK_";
public static final String DOWNLOAD_PREFIX = "VMP_DOWNLOAD_";
public static final String PLATFORM_KEEPLIVEKEY_PREFIX = "VMP_PLATFORM_KEEPLIVE_";
@ -51,4 +54,7 @@ public class VideoManagerConstants {
public static final String MEDIA_SSRC_USED_PREFIX = "VMP_media_used_ssrc_";
public static final String MEDIA_TRANSACTION_USED_PREFIX = "VMP_media_transaction_";
//************************** redis 消息*********************************
public static final String WVP_MSG_STREAM_PUSH_CHANGE_PREFIX = "WVP_MSG_STREAM_PUSH_CHANGE";
}

View File

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

View File

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

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

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

View File

@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.transmit.ISIPProcessorObserver;
import com.genersoft.iot.vmp.gb28181.transmit.SIPProcessorObserver;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.SipStackImpl;
@ -28,28 +29,12 @@ public class SipLayer{
private SipConfig sipConfig;
@Autowired
private SIPProcessorObserver sipProcessorObserver;
@Autowired
private SipSubscribe sipSubscribe;
private ISIPProcessorObserver sipProcessorObserver;
private SipStackImpl sipStack;
private SipFactory sipFactory;
/**
* 消息处理器线程池
*/
private ThreadPoolExecutor processThreadPool;
public SipLayer() {
int processThreadNum = Runtime.getRuntime().availableProcessors() * 10;
LinkedBlockingQueue<Runnable> processQueue = new LinkedBlockingQueue<>(10000);
processThreadPool = new ThreadPoolExecutor(processThreadNum,processThreadNum,
0L,TimeUnit.MILLISECONDS,processQueue,
new ThreadPoolExecutor.CallerRunsPolicy());
}
@Bean("sipFactory")
private SipFactory createSipFactory() {

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.gb28181.transmit.cmd;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZLMHttpHookSubscribe;
@ -121,6 +122,26 @@ public interface ISIPCommander {
void streamByeCmd(String deviceId, String channelId, SipSubscribe.Event okEvent);
void streamByeCmd(String deviceId, String channelId);
/**
* 回放暂停
*/
void playPauseCmd(Device device, StreamInfo streamInfo);
/**
* 回放恢复
*/
void playResumeCmd(Device device, StreamInfo streamInfo);
/**
* 回放拖动播放
*/
void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime);
/**
* 回放倍速播放
*/
void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed);
/**
* 语音广播
*
@ -235,8 +256,9 @@ public interface ISIPCommander {
* @param device 视频设备
* @param startTime 开始时间,格式要求yyyy-MM-dd HH:mm:ss
* @param endTime 结束时间,格式要求yyyy-MM-dd HH:mm:ss
* @param sn
*/
boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime);
boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, SipSubscribe.Event errorEvent);
/**
* 查询报警信息

View File

@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.gb28181.transmit.cmd;
import java.text.ParseException;
import java.util.ArrayList;
import javax.sip.Dialog;
import javax.sip.InvalidArgumentException;
import javax.sip.PeerUnavailableException;
import javax.sip.SipFactory;
@ -11,6 +12,9 @@ import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.Request;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.vmanager.gb28181.session.InfoCseqCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@ -30,6 +34,9 @@ public class SIPRequestHeaderProvider {
@Autowired
private SipFactory sipFactory;
@Autowired
private VideoStreamSessionManager streamSession;
public Request createMessageRequest(Device device, String content, String viaTag, String fromTag, String toTag, CallIdHeader callIdHeader) throws ParseException, InvalidArgumentException, PeerUnavailableException {
Request request = null;
@ -210,4 +217,50 @@ public class SIPRequestHeaderProvider {
request.setContent(content, contentTypeHeader);
return request;
}
public Request createInfoRequest(Device device, StreamInfo streamInfo, String content)
throws PeerUnavailableException, ParseException, InvalidArgumentException {
Request request = null;
Dialog dialog = streamSession.getDialog(streamInfo.getDeviceID(), streamInfo.getChannelId());
SipURI requestLine = sipFactory.createAddressFactory().createSipURI(device.getDeviceId(),
device.getHostAddress());
// via
ArrayList<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;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetup;
import com.genersoft.iot.vmp.gb28181.bean.Device;
@ -17,6 +18,7 @@ import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.gb28181.session.InfoCseqCache;
import gov.nist.javax.sip.SipProviderImpl;
import gov.nist.javax.sip.SipStackImpl;
import gov.nist.javax.sip.message.SIPRequest;
@ -1194,14 +1196,15 @@ public class SIPCommander implements ISIPCommander {
* @param endTime 结束时间,格式要求yyyy-MM-dd HH:mm:ss
*/
@Override
public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime) {
public boolean recordInfoQuery(Device device, String channelId, String startTime, String endTime, int sn, SipSubscribe.Event errorEvent) {
try {
StringBuffer recordInfoXml = new StringBuffer(200);
recordInfoXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
recordInfoXml.append("<Query>\r\n");
recordInfoXml.append("<CmdType>RecordInfo</CmdType>\r\n");
recordInfoXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
recordInfoXml.append("<SN>" + sn + "</SN>\r\n");
recordInfoXml.append("<DeviceID>" + channelId + "</DeviceID>\r\n");
recordInfoXml.append("<StartTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(startTime) + "</StartTime>\r\n");
recordInfoXml.append("<EndTime>" + DateUtil.yyyy_MM_dd_HH_mm_ssToISO8601(endTime) + "</EndTime>\r\n");
@ -1218,7 +1221,7 @@ public class SIPCommander implements ISIPCommander {
Request request = headerProvider.createMessageRequest(device, recordInfoXml.toString(),
"z9hG4bK-ViaRecordInfo-" + tm, "fromRec" + tm, null, callIdHeader);
transmitRequest(device, request);
transmitRequest(device, request, errorEvent);
} catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
return false;
@ -1486,7 +1489,7 @@ public class SIPCommander implements ISIPCommander {
StringBuffer cmdXml = new StringBuffer(200);
cmdXml.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\r\n");
cmdXml.append("<Query>\r\n");
cmdXml.append("<CmdType>CataLog</CmdType>\r\n");
cmdXml.append("<CmdType>Catalog</CmdType>\r\n");
cmdXml.append("<SN>" + (int)((Math.random()*9+1)*100000) + "</SN>\r\n");
cmdXml.append("<DeviceID>" + device.getDeviceId() + "</DeviceID>\r\n");
cmdXml.append("</Query>\r\n");
@ -1496,7 +1499,7 @@ public class SIPCommander implements ISIPCommander {
CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId()
: udpSipProvider.getNewCallId();
Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), "z9hG4bK-viaPos-" + tm, "fromTagPos" + tm, null, device.getSubscribeCycleForCatalog(), "presence" , callIdHeader);
Request request = headerProvider.createSubscribeRequest(device, cmdXml.toString(), "z9hG4bK-viaPos-" + tm, "fromTagPos" + tm, null, device.getSubscribeCycleForCatalog(), "Catalog" , callIdHeader);
transmitRequest(device, request, errorEvent, okEvent);
return true;
@ -1543,4 +1546,111 @@ public class SIPCommander implements ISIPCommander {
clientTransaction.sendRequest();
return clientTransaction;
}
/**
* 回放暂停
*/
@Override
public void playPauseCmd(Device device, StreamInfo streamInfo) {
try {
StringBuffer content = new StringBuffer(200);
content.append("PAUSE RTSP/1.0\r\n");
content.append("CSeq: " + InfoCseqCache.CSEQCACHE.get(streamInfo.getStreamId()) + "\r\n");
content.append("PauseTime: now\r\n");
Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
logger.info(request.toString());
ClientTransaction clientTransaction = null;
if ("TCP".equals(device.getTransport())) {
clientTransaction = tcpSipProvider.getNewClientTransaction(request);
} else if ("UDP".equals(device.getTransport())) {
clientTransaction = udpSipProvider.getNewClientTransaction(request);
}
if (clientTransaction != null) {
clientTransaction.sendRequest();
}
} catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
}
}
/**
* 回放恢复
*/
@Override
public void playResumeCmd(Device device, StreamInfo streamInfo) {
try {
StringBuffer content = new StringBuffer(200);
content.append("PLAY RTSP/1.0\r\n");
content.append("CSeq: " + InfoCseqCache.CSEQCACHE.get(streamInfo.getStreamId()) + "\r\n");
content.append("Range: npt=now-\r\n");
Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
logger.info(request.toString());
ClientTransaction clientTransaction = null;
if ("TCP".equals(device.getTransport())) {
clientTransaction = tcpSipProvider.getNewClientTransaction(request);
} else if ("UDP".equals(device.getTransport())) {
clientTransaction = udpSipProvider.getNewClientTransaction(request);
}
clientTransaction.sendRequest();
} catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
}
}
/**
* 回放拖动播放
*/
@Override
public void playSeekCmd(Device device, StreamInfo streamInfo, long seekTime) {
try {
StringBuffer content = new StringBuffer(200);
content.append("PLAY RTSP/1.0\r\n");
content.append("CSeq: " + InfoCseqCache.CSEQCACHE.get(streamInfo.getStreamId()) + "\r\n");
content.append("Range: npt=" + Math.abs(seekTime) + "-\r\n");
Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
logger.info(request.toString());
ClientTransaction clientTransaction = null;
if ("TCP".equals(device.getTransport())) {
clientTransaction = tcpSipProvider.getNewClientTransaction(request);
} else if ("UDP".equals(device.getTransport())) {
clientTransaction = udpSipProvider.getNewClientTransaction(request);
}
clientTransaction.sendRequest();
} catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
}
}
/**
* 回放倍速播放
*/
@Override
public void playSpeedCmd(Device device, StreamInfo streamInfo, Double speed) {
try {
StringBuffer content = new StringBuffer(200);
content.append("PLAY RTSP/1.0\r\n");
content.append("CSeq: " + InfoCseqCache.CSEQCACHE.get(streamInfo.getStreamId()) + "\r\n");
content.append("Scale: " + String.format("%.1f",speed) + "\r\n");
Request request = headerProvider.createInfoRequest(device, streamInfo, content.toString());
logger.info(request.toString());
ClientTransaction clientTransaction = null;
if ("TCP".equals(device.getTransport())) {
clientTransaction = tcpSipProvider.getNewClientTransaction(request);
} else if ("UDP".equals(device.getTransport())) {
clientTransaction = udpSipProvider.getNewClientTransaction(request);
}
clientTransaction.sendRequest();
} catch (SipException | ParseException | InvalidArgumentException e) {
e.printStackTrace();
}
}
}

View File

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

View File

@ -28,15 +28,14 @@ import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
/**
* @description: BYE请求处理器
* @author: lawrencehj
* @date: 2021年3月9日
/**
* SIP命令类型 BYE请求
*/
@Component
public class ByeRequestProcessor extends SIPRequestProcessorParent implements InitializingBean, ISIPRequestProcessor {
private Logger logger = LoggerFactory.getLogger(ByeRequestProcessor.class);
private final Logger logger = LoggerFactory.getLogger(ByeRequestProcessor.class);
private final String method = "BYE";
@Autowired
private ISIPCommander cmder;
@ -53,8 +52,6 @@ public class ByeRequestProcessor extends SIPRequestProcessorParent implements In
@Autowired
private IMediaServerService mediaServerService;
private String method = "BYE";
@Autowired
private SIPProcessorObserver sipProcessorObserver;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 static Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
@Autowired
public MessageRequestProcessor messageRequestProcessor;
public Map<String, IMessageHandler> messageHandlerMap = new ConcurrentHashMap<>();
public void addHandler(String cmdType, IMessageHandler messageHandler) {
messageHandlerMap.put(cmdType, messageHandler);

View File

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

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

View File

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

View File

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

View File

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

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.stereotype.Component;
/**
* 命令类型 请求动作的应答
* 命令类型 设备控制, 报警通知, 设备目录信息查询, 目录信息查询, 目录收到, 设备信息查询, 设备状态信息查询 ......
*/
@Component
public class ResponseMessageHandler extends MessageHandlerAbstract implements InitializingBean {

View File

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

View File

@ -42,7 +42,6 @@ public class ByeResponseProcessor extends SIPResponseProcessorAbstract {
@Override
public void process(ResponseEvent evt) {
// 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.UUID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.conf.UserSetup;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IMediaService;
import com.genersoft.iot.vmp.service.IStreamProxyService;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
@ -56,6 +61,12 @@ public class ZLMHttpHookListener {
@Autowired
private IMediaServerService mediaServerService;
@Autowired
private IStreamProxyService streamProxyService;
@Autowired
private IMediaService mediaService;
@Autowired
private ZLMRESTfulUtils zlmresTfulUtils;
@ -153,12 +164,20 @@ public class ZLMHttpHookListener {
subscribe.response(mediaInfo, json);
}
}
String app = json.getString("app");
String stream = json.getString("stream");
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByStreamId(stream);
JSONObject ret = new JSONObject();
// 录像回放时不进行录像下载
if (streamInfo != null) {
ret.put("enableMP4", false);
}else {
ret.put("enableMP4", userSetup.isRecordPushLive());
}
ret.put("code", 0);
ret.put("msg", "success");
ret.put("enableHls", true);
ret.put("enableMP4", userSetup.isRecordPushLive());
ret.put("enableRtxp", true);
return new ResponseEntity<String>(ret.toString(), HttpStatus.OK);
}
@ -254,12 +273,13 @@ public class ZLMHttpHookListener {
*/
@ResponseBody
@PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> onStreamChanged(@RequestBody JSONObject json){
public ResponseEntity<String> onStreamChanged(@RequestBody MediaItem item){
if (logger.isDebugEnabled()) {
logger.debug("ZLM HOOK on_stream_changed API调用参数" + json.toString());
logger.debug("ZLM HOOK on_stream_changed API调用参数" + JSONObject.toJSONString(item));
}
String mediaServerId = json.getString("mediaServerId");
String mediaServerId = item.getMediaServerId();
JSONObject json = (JSONObject) JSON.toJSON(item);
ZLMHttpHookSubscribe.Event subscribe = this.subscribe.getSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, json);
if (subscribe != null ) {
MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
@ -268,13 +288,12 @@ public class ZLMHttpHookListener {
}
}
// 流消失移除redis play
String app = json.getString("app");
String streamId = json.getString("stream");
String schema = json.getString("schema");
JSONArray tracks = json.getJSONArray("tracks");
boolean regist = json.getBoolean("regist");
String app = item.getApp();
String streamId = item.getStream();
String schema = item.getSchema();
List<MediaItem.MediaTrack> tracks = item.getTracks();
boolean regist = item.isRegist();
if (tracks != null) {
logger.info("[stream: " + streamId + "] on_stream_changed->>" + schema);
}
@ -294,12 +313,34 @@ public class ZLMHttpHookListener {
redisCatchStorage.stopPlayback(streamInfo);
}
}else {
if (!"rtp".equals(app) ){
if (!"rtp".equals(app)){
boolean pushChange = false;
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
if (regist) {
zlmMediaListManager.addMedia(mediaServerItem, app, streamId);
if ((item.getOriginType() == 1 || item.getOriginType() == 2 || item.getOriginType() == 8)) {
pushChange = true;
zlmMediaListManager.addMedia(item);
StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem, app, streamId, tracks);
redisCatchStorage.addPushStream(mediaServerItem, app, streamId, streamInfo);
}
}else {
zlmMediaListManager.removeMedia( app, streamId);
int result = zlmMediaListManager.removeMedia( app, streamId);
redisCatchStorage.removePushStream(mediaServerItem, app, streamId);
if (result > 0) {
pushChange = true;
}
}
if(pushChange) {
// 发送流变化redis消息
JSONObject jsonObject = new JSONObject();
jsonObject.put("serverId", userSetup.getServerId());
jsonObject.put("app", app);
jsonObject.put("stream", streamId);
jsonObject.put("register", regist);
jsonObject.put("mediaServerId", mediaServerId);
redisCatchStorage.sendStreamChangeMsg(jsonObject);
}
}
}
@ -325,14 +366,13 @@ public class ZLMHttpHookListener {
String mediaServerId = json.getString("mediaServerId");
String streamId = json.getString("stream");
String app = json.getString("app");
// TODO 如果在给上级推流也不停止
JSONObject ret = new JSONObject();
ret.put("code", 0);
if ("rtp".equals(app)){
JSONObject ret = new JSONObject();
ret.put("code", 0);
ret.put("close", true);
StreamInfo streamInfoForPlayCatch = redisCatchStorage.queryPlayByStreamId(streamId);
if (streamInfoForPlayCatch != null) {
// 如果在给上级推流也不停止
if (redisCatchStorage.isChannelSendingRTP(streamInfoForPlayCatch.getChannelId())) {
ret.put("close", false);
} else {
@ -345,6 +385,12 @@ public class ZLMHttpHookListener {
if (streamInfoForPlayBackCatch != null) {
cmder.streamByeCmd(streamInfoForPlayBackCatch.getDeviceID(), streamInfoForPlayBackCatch.getChannelId());
redisCatchStorage.stopPlayback(streamInfoForPlayBackCatch);
}else {
StreamInfo streamInfoForDownload = redisCatchStorage.queryDownloadByStreamId(streamId);
// 进行录像下载时无人观看不断流
if (streamInfoForDownload != null) {
ret.put("close", false);
}
}
}
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
@ -353,9 +399,15 @@ public class ZLMHttpHookListener {
}
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}else {
JSONObject ret = new JSONObject();
ret.put("code", 0);
ret.put("close", false);
StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, streamId);
if (streamProxyItem != null && streamProxyItem.isEnable_remove_none_reader()) {
ret.put("close", true);
streamProxyService.del(app, streamId);
String url = streamProxyItem.getUrl() != null?streamProxyItem.getUrl():streamProxyItem.getSrc_url();
logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", app, streamId, url);
}else {
ret.put("close", false);
}
return new ResponseEntity<String>(ret.toString(),HttpStatus.OK);
}

View File

@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.media.zlm;
import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.media.zlm.dto.MediaItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
@ -87,6 +88,10 @@ public class ZLMMediaListManager {
updateMedia(mediaServerItem, app, streamId);
}
public void addMedia(MediaItem mediaItem) {
storager.updateMedia(streamPushService.transform(mediaItem));
}
public void updateMedia(MediaServerItem mediaServerItem, String app, String streamId) {
//使用异步更新推流
@ -113,14 +118,16 @@ public class ZLMMediaListManager {
}
public void removeMedia(String app, String streamId) {
public int removeMedia(String app, String streamId) {
// 查找是否关联了国标 关联了不删除 置为离线
StreamProxyItem streamProxyItem = gbStreamMapper.selectOne(app, streamId);
int result = 0;
if (streamProxyItem == null) {
storager.removeMedia(app, streamId);
result = storager.removeMedia(app, streamId);
}else {
storager.mediaOutline(app, streamId);
result =storager.mediaOutline(app, streamId);
}
return result;
}
// public void clearAllSessions() {

View File

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

View File

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

View File

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

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);
void delete(String id);
MediaServerItem getDefaultMediaServer();
}

View File

@ -32,7 +32,7 @@ public interface IMediaService {
* @param stream
* @return
*/
StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaServerItem, String app, String stream, JSONArray tracks);
StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaServerItem, String app, String stream, Object tracks);
/**
* 根据应用名和流ID获取播放地址, 只是地址拼接返回的ip使用远程访问ip适用与zlm与wvp在一台主机的情况
@ -40,5 +40,5 @@ public interface IMediaService {
* @param stream
* @return
*/
StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, JSONArray tracks, String addr);
StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -51,33 +51,38 @@ public class StreamPushServiceImpl implements IStreamPushService {
for (MediaItem item : mediaItems) {
// 不保存国标推理以及拉流代理的流
if (item.getOriginType() == 3 || item.getOriginType() == 4 || item.getOriginType() == 5) {
continue;
}
String key = item.getApp() + "_" + item.getStream();
StreamPushItem streamPushItem = result.get(key);
if (streamPushItem == null) {
streamPushItem = new StreamPushItem();
streamPushItem.setApp(item.getApp());
streamPushItem.setMediaServerId(mediaServerItem.getId());
streamPushItem.setStream(item.getStream());
streamPushItem.setAliveSecond(item.getAliveSecond());
streamPushItem.setCreateStamp(item.getCreateStamp());
streamPushItem.setOriginSock(item.getOriginSock());
streamPushItem.setTotalReaderCount(item.getTotalReaderCount());
streamPushItem.setOriginType(item.getOriginType());
streamPushItem.setOriginTypeStr(item.getOriginTypeStr());
streamPushItem.setOriginUrl(item.getOriginUrl());
streamPushItem.setCreateStamp(item.getCreateStamp());
streamPushItem.setAliveSecond(item.getAliveSecond());
streamPushItem.setStatus(true);
streamPushItem.setVhost(item.getVhost());
result.put(key, streamPushItem);
if (item.getOriginType() == 1 || item.getOriginType() == 2 || item.getOriginType() == 8) {
String key = item.getApp() + "_" + item.getStream();
StreamPushItem streamPushItem = result.get(key);
if (streamPushItem == null) {
streamPushItem = transform(item);
result.put(key, streamPushItem);
}
}
}
return new ArrayList<>(result.values());
}
@Override
public StreamPushItem transform(MediaItem item) {
StreamPushItem streamPushItem = new StreamPushItem();
streamPushItem.setApp(item.getApp());
streamPushItem.setMediaServerId(item.getMediaServerId());
streamPushItem.setStream(item.getStream());
streamPushItem.setAliveSecond(item.getAliveSecond());
streamPushItem.setCreateStamp(item.getCreateStamp());
streamPushItem.setOriginSock(item.getOriginSock());
streamPushItem.setTotalReaderCount(item.getTotalReaderCount());
streamPushItem.setOriginType(item.getOriginType());
streamPushItem.setOriginTypeStr(item.getOriginTypeStr());
streamPushItem.setOriginUrl(item.getOriginUrl());
streamPushItem.setCreateStamp(item.getCreateStamp());
streamPushItem.setAliveSecond(item.getAliveSecond());
streamPushItem.setStatus(true);
streamPushItem.setVhost(item.getVhost());
return streamPushItem;
}
@Override
public PageInfo<StreamPushItem> getPushList(Integer page, Integer count) {

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.ParentPlatformCatch;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import java.util.List;
import java.util.Map;
@ -120,5 +121,35 @@ public interface IRedisCatchStorage {
/**
* 在redis添加wvp的信息
*/
void updateWVPInfo(JSONObject jsonObject);
void updateWVPInfo(String id, JSONObject jsonObject, int time);
/**
* 发送推流生成与推流消失消息
* @param jsonObject 消息内容
*/
void sendStreamChangeMsg(JSONObject jsonObject);
/**
* 添加流信息到redis
* @param mediaServerItem
* @param app
* @param streamId
*/
void addPushStream(MediaServerItem mediaServerItem, String app, String streamId, StreamInfo streamInfo);
/**
* 移除流信息从redis
* @param mediaServerItem
* @param app
* @param streamId
*/
void removePushStream(MediaServerItem mediaServerItem, String app, String streamId);
/**
* 开始下载录像时存入
* @param streamInfo
*/
boolean startDownload(StreamInfo streamInfo);
StreamInfo queryDownloadByStreamId(String streamId);
}

View File

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

View File

@ -28,7 +28,7 @@ public interface GbStreamMapper {
"latitude=#{latitude}," +
"mediaServerId=#{mediaServerId}," +
"status=${status} " +
"WHERE app=#{app} AND stream=#{stream} AND gbId=#{gbId}")
"WHERE app=#{app} AND stream=#{stream}")
int update(GbStream gbStream);
@Delete("DELETE FROM gb_stream WHERE app=#{app} AND stream=#{stream}")
@ -53,7 +53,7 @@ public interface GbStreamMapper {
@Update("UPDATE gb_stream " +
"SET status=${status} " +
"WHERE app=#{app} AND stream=#{stream}")
void setStatus(String app, String stream, boolean status);
int setStatus(String app, String stream, boolean status);
@Select("SELECT gs.*, pgs.platformId FROM gb_stream gs LEFT JOIN platform_gb_stream pgs ON gs.app = pgs.app AND gs.stream = pgs.stream WHERE mediaServerId=#{mediaServerId} ")
List<GbStream> selectAllByMediaServerId(String mediaServerId);

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
@ -63,15 +64,15 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
streamInfo.getChannelId()));
}
@Override
public StreamInfo queryPlayByStreamId(String steamId) {
List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, steamId));
public StreamInfo queryPlayByStreamId(String streamId) {
List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAYER_PREFIX, streamId));
if (playLeys == null || playLeys.size() == 0) return null;
return (StreamInfo)redis.get(playLeys.get(0).toString());
}
@Override
public StreamInfo queryPlaybackByStreamId(String steamId) {
List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, steamId));
public StreamInfo queryPlaybackByStreamId(String streamId) {
List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.PLAY_BLACK_PREFIX, streamId));
if (playLeys == null || playLeys.size() == 0) return null;
return (StreamInfo)redis.get(playLeys.get(0).toString());
}
@ -103,10 +104,15 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
@Override
public boolean startPlayback(StreamInfo stream) {
return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, stream.getStreamId(),stream.getDeviceID(), stream.getChannelId()),
stream);
return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAY_BLACK_PREFIX, stream.getStreamId(),
stream.getDeviceID(), stream.getChannelId()), stream);
}
@Override
public boolean startDownload(StreamInfo streamInfo) {
return redis.set(String.format("%S_%s_%s_%s", VideoManagerConstants.DOWNLOAD_PREFIX, streamInfo.getStreamId(),
streamInfo.getDeviceID(), streamInfo.getChannelId()), streamInfo);
}
@Override
public boolean stopPlayback(StreamInfo streamInfo) {
@ -295,8 +301,33 @@ public class RedisCatchStorageImpl implements IRedisCatchStorage {
}
@Override
public void updateWVPInfo(JSONObject jsonObject) {
public void updateWVPInfo(String id, JSONObject jsonObject, int time) {
String key = VideoManagerConstants.WVP_SERVER_PREFIX + id;
redis.set(key, jsonObject, time);
}
@Override
public void sendStreamChangeMsg(JSONObject jsonObject) {
String key = VideoManagerConstants.WVP_MSG_STREAM_PUSH_CHANGE_PREFIX;
redis.convertAndSend(key, jsonObject);
}
@Override
public void addPushStream(MediaServerItem mediaServerItem, String app, String streamId, StreamInfo streamInfo) {
String key = VideoManagerConstants.WVP_SERVER_STREAM_PUSH_PREFIX + app + "_" + streamId + "_" + mediaServerItem.getId();
redis.set(key, streamInfo);
}
@Override
public void removePushStream(MediaServerItem mediaServerItem, String app, String streamId) {
String key = VideoManagerConstants.WVP_SERVER_STREAM_PUSH_PREFIX + app + "_" + streamId + "_" + mediaServerItem.getId();
redis.del(key);
}
@Override
public StreamInfo queryDownloadByStreamId(String streamId) {
List<Object> playLeys = redis.scan(String.format("%S_%s_*", VideoManagerConstants.DOWNLOAD_PREFIX, streamId));
if (playLeys == null || playLeys.size() == 0) return null;
return (StreamInfo)redis.get(playLeys.get(0).toString());
}
}

View File

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

View File

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

View File

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

View File

@ -44,11 +44,11 @@ public class MediaController {
@ApiImplicitParams({
@ApiImplicitParam(name = "app", value = "应用名", dataTypeClass = String.class),
@ApiImplicitParam(name = "stream", value = "流id", dataTypeClass = String.class),
@ApiImplicitParam(name = "mediaServerId", value = "媒体服务器id", dataTypeClass = String.class),
@ApiImplicitParam(name = "mediaServerId", value = "媒体服务器id", dataTypeClass = String.class, required = false),
})
@GetMapping(value = "/stream_info_by_app_and_stream")
@ResponseBody
public WVPResult<StreamInfo> getStreamInfoByAppAndStream(@RequestParam String app, @RequestParam String stream, @RequestParam String mediaServerId){
public WVPResult<StreamInfo> getStreamInfoByAppAndStream(@RequestParam String app, @RequestParam String stream, @RequestParam(required = false) String mediaServerId){
StreamInfo streamInfoByAppAndStreamWithCheck = mediaService.getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId);
WVPResult<StreamInfo> result = new WVPResult<>();
if (streamInfoByAppAndStreamWithCheck != null){

View File

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

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.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.service.IPlayService;
import com.genersoft.iot.vmp.vmanager.gb28181.session.InfoCseqCache;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@ -77,7 +78,7 @@ public class PlaybackController {
logger.debug(String.format("设备回放 API调用deviceId%s channelId%s", deviceId, channelId));
}
String uuid = UUID.randomUUID().toString();
String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId;
DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(30000L);
Device device = storager.queryVideoDevice(deviceId);
if (device == null) {
@ -152,4 +153,103 @@ public class PlaybackController {
return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@ApiOperation("回放暂停")
@ApiImplicitParams({
@ApiImplicitParam(name = "streamId", value = "回放流ID", dataTypeClass = String.class),
})
@GetMapping("/pause/{streamId}")
public ResponseEntity<String> playPause(@PathVariable String streamId) {
logger.info("playPause: "+streamId);
JSONObject json = new JSONObject();
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
if (null == streamInfo) {
json.put("msg", "streamId不存在");
logger.warn("streamId不存在!");
return new ResponseEntity<String>(json.toString(), HttpStatus.BAD_REQUEST);
}
setCseq(streamId);
Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
cmder.playPauseCmd(device, streamInfo);
json.put("msg", "ok");
return new ResponseEntity<String>(json.toString(), HttpStatus.OK);
}
@ApiOperation("回放恢复")
@ApiImplicitParams({
@ApiImplicitParam(name = "streamId", value = "回放流ID", dataTypeClass = String.class),
})
@GetMapping("/resume/{streamId}")
public ResponseEntity<String> playResume(@PathVariable String streamId) {
logger.info("playResume: "+streamId);
JSONObject json = new JSONObject();
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
if (null == streamInfo) {
json.put("msg", "streamId不存在");
logger.warn("streamId不存在!");
return new ResponseEntity<String>(json.toString(), HttpStatus.BAD_REQUEST);
}
setCseq(streamId);
Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
cmder.playResumeCmd(device, streamInfo);
json.put("msg", "ok");
return new ResponseEntity<String>(json.toString(), HttpStatus.OK);
}
@ApiOperation("回放拖动播放")
@ApiImplicitParams({
@ApiImplicitParam(name = "streamId", value = "回放流ID", dataTypeClass = String.class),
@ApiImplicitParam(name = "seekTime", value = "拖动偏移量单位s", dataTypeClass = Long.class),
})
@GetMapping("/seek/{streamId}/{seekTime}")
public ResponseEntity<String> playSeek(@PathVariable String streamId, @PathVariable long seekTime) {
logger.info("playSeek: "+streamId+", "+seekTime);
JSONObject json = new JSONObject();
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
if (null == streamInfo) {
json.put("msg", "streamId不存在");
logger.warn("streamId不存在!");
return new ResponseEntity<String>(json.toString(), HttpStatus.BAD_REQUEST);
}
setCseq(streamId);
Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
cmder.playSeekCmd(device, streamInfo, seekTime);
json.put("msg", "ok");
return new ResponseEntity<String>(json.toString(), HttpStatus.OK);
}
@ApiOperation("回放倍速播放")
@ApiImplicitParams({
@ApiImplicitParam(name = "streamId", value = "回放流ID", dataTypeClass = String.class),
@ApiImplicitParam(name = "speed", value = "倍速0.25 0.5 1、2、4", dataTypeClass = Double.class),
})
@GetMapping("/speed/{streamId}/{speed}")
public ResponseEntity<String> playSpeed(@PathVariable String streamId, @PathVariable Double speed) {
logger.info("playSpeed: "+streamId+", "+speed);
JSONObject json = new JSONObject();
StreamInfo streamInfo = redisCatchStorage.queryPlaybackByStreamId(streamId);
if (null == streamInfo) {
json.put("msg", "streamId不存在");
logger.warn("streamId不存在!");
return new ResponseEntity<String>(json.toString(), HttpStatus.BAD_REQUEST);
}
if(speed != 0.25 && speed != 0.5 && speed != 1 && speed != 2.0 && speed != 4.0) {
json.put("msg", "不支持的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);
cmder.recordInfoQuery(device, channelId, startTime, endTime);
// 指定超时时间 1分钟30秒
DeferredResult<ResponseEntity<RecordInfo>> result = new DeferredResult<>(90*1000L);
String uuid = UUID.randomUUID().toString();
String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + channelId;
int sn = (int)((Math.random()*9+1)*100000);
String key = DeferredResultHolder.CALLBACK_CMD_RECORDINFO + deviceId + sn;
RequestMessage msg = new RequestMessage();
msg.setId(uuid);
msg.setKey(key);
cmder.recordInfoQuery(device, channelId, startTime, endTime, sn, (eventResult -> {
msg.setData("查询录像失败, status: " + eventResult.statusCode + ", message: " + eventResult.msg );
resultHolder.invokeResult(msg);
}));
// 录像查询以channelId作为deviceId查询
resultHolder.put(key, uuid, result);
result.onTimeout(()->{
RequestMessage msg = new RequestMessage();
msg.setId(uuid);
msg.setKey(key);
msg.setData("timeout");
resultHolder.invokeResult(msg);
});

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

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

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

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.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetup;
import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IPlayService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.vmanager.gb28181.play.PlayController;
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
@ -34,15 +36,13 @@ public class ApiStreamController {
private IVideoManagerStorager storager;
@Autowired
private IRedisCatchStorage redisCatchStorage;
// @Autowired
// private ZLMRESTfulUtils zlmresTfulUtils;
private UserSetup userSetup;
@Autowired
private PlayController playController;
private IRedisCatchStorage redisCatchStorage;
@Autowired
private IPlayService playService;
/**
* 实时直播 - 开始直播
@ -69,7 +69,7 @@ public class ApiStreamController {
@RequestParam(required = false)String timeout
){
DeferredResult<JSONObject> resultDeferredResult = new DeferredResult<JSONObject>();
DeferredResult<JSONObject> resultDeferredResult = new DeferredResult<>(userSetup.getPlayTimeout() + 10);
Device device = storager.queryVideoDevice(serial);
if (device == null ) {
JSONObject result = new JSONObject();
@ -99,11 +99,9 @@ public class ApiStreamController {
result.put("error","channel[ " + code + " ]offline");
resultDeferredResult.setResult(result);
}
DeferredResult<ResponseEntity<String>> play = playController.play(serial, code);
play.setResultHandler((Object o)->{
ResponseEntity<String> responseEntity = (ResponseEntity)o;
StreamInfo streamInfo = JSON.parseObject(responseEntity.getBody(), StreamInfo.class);
MediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device);
PlayResult play = playService.play(newMediaServerItem, serial, code, (mediaServerItem, response)->{
StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(serial, code);
JSONObject result = new JSONObject();
result.put("StreamID", streamInfo.getStreamId());
result.put("DeviceID", device.getDeviceId());
@ -134,7 +132,23 @@ public class ApiStreamController {
result.put("NumOutputs", "");
result.put("CascadeSize", "");
result.put("RelaySize", "");
result.put("ChannelPTZType", 0);
result.put("ChannelPTZType", "0");
resultDeferredResult.setResult(result);
// Class<?> aClass = responseEntity.getClass().getSuperclass();
// Field body = null;
// try {
// // 使用反射动态修改返回的body
// body = aClass.getDeclaredField("body");
// body.setAccessible(true);
// body.set(responseEntity, result);
// } catch (NoSuchFieldException e) {
// e.printStackTrace();
// } catch (IllegalAccessException e) {
// e.printStackTrace();
// }
}, (eventResult) -> {
JSONObject result = new JSONObject();
result.put("error", "channel[ " + code + " ] " + eventResult.msg);
resultDeferredResult.setResult(result);
});
return resultDeferredResult;

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.storager.dao.dto.User;

View File

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

View File

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

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-main>
<div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;">
<span style="font-size: 1rem; font-weight: bold;">云端录像</span>
<span v-if="!recordDetail" >云端录像</span>
<el-page-header v-if="recordDetail" @back="backToList" content="云端录像">
</el-page-header>
<div style="position: absolute; right: 5rem; top: 0.3rem;">
节点选择:
<el-select size="mini" @change="chooseMediaChange" style="width: 16rem; margin-right: 1rem;" v-model="mediaServerId" placeholder="请选择" :disabled="recordDetail">
@ -20,7 +22,6 @@
</div>
<div style="position: absolute; right: 1rem; top: 0.3rem;">
<el-button v-if="!recordDetail" icon="el-icon-refresh-right" circle size="mini" :loading="loading" @click="getRecordList()"></el-button>
<el-button v-if="recordDetail" icon="el-icon-arrow-left" circle size="mini" @click="backToList()"></el-button>
</div>
</div>
<div v-if="!recordDetail">
@ -53,7 +54,7 @@
:total="total">
</el-pagination>
</div>
<cloud-record-detail ref="cloudRecordDetail" v-if="recordDetail" :recordFile="chooseRecord" :mediaServerId="mediaServerId" ></cloud-record-detail>
<cloud-record-detail ref="cloudRecordDetail" v-if="recordDetail" :recordFile="chooseRecord" :mediaServerId="mediaServerId" :mediaServerPath="mediaServerPath" ></cloud-record-detail>
</el-main>
</el-container>
</div>
@ -72,6 +73,7 @@
return {
mediaServerList: [], //
mediaServerId: null, //
mediaServerPath: null, //
recordList: [], //
chooseRecord: null, //
@ -115,6 +117,11 @@
that.mediaServerList = data.data;
if (that.mediaServerList.length > 0) {
that.mediaServerId = that.mediaServerList[0].id
let port = that.mediaServerList[0].httpPort;
if (location.protocol === "https:" && that.mediaServerList[0].httpSSlPort) {
port = that.mediaServerList[0].httpSSlPort
}
that.mediaServerPath = location.protocol + "//" + that.mediaServerList[0].streamIp + ":" + port
that.getRecordList();
}
})

View File

@ -15,7 +15,8 @@
<i class="el-icon-video-camera" ></i>
{{ item.substring(0,17)}}
</el-tag>
<a class="el-icon-download" style="color: #409EFF;font-weight: 600;margin-left: 10px;" :href="`${basePath}/${mediaServerId}/record/${recordFile.app}/${recordFile.stream}/${chooseDate}/${item}`" download />
<!-- <a class="el-icon-download" style="color: #409EFF;font-weight: 600;margin-left: 10px;" :href="`${basePath}/${mediaServerId}/record/${recordFile.app}/${recordFile.stream}/${chooseDate}/${item}`" download />-->
<a class="el-icon-download" style="color: #409EFF;font-weight: 600;margin-left: 10px;" :href="`${basePath}/download.html?url=${recordFile.app}/${recordFile.stream}/${chooseDate}/${item}`" target="_blank" />
</li>
</ul>
</div>
@ -75,7 +76,7 @@
<li class="task-list-item" v-for="item in taskListEnded">
<div class="task-list-item-box" style="height: 2rem;line-height: 2rem;">
<span>{{ item.startTime.substr(10) }}-{{item.endTime.substr(10)}}</span>
<a class="el-icon-download download-btn" :href="basePath + '/' + item.recordFile" download >
<a class="el-icon-download download-btn" :href="basePath + '/download.html?url=../' + item.recordFile" target="_blank">
</a>
</div>
</li>
@ -111,10 +112,10 @@
components: {
uiHeader, player
},
props: ['recordFile', 'mediaServerId', 'dateFiles'],
props: ['recordFile', 'mediaServerId', 'dateFiles', 'mediaServerPath'],
data() {
return {
basePath: process.env.NODE_ENV === 'development'?`${location.origin}/debug/zlm/${this.mediaServerId}`:`${location.origin}/zlm/${this.mediaServerId}`,
basePath: `${this.mediaServerPath}/record`,
dateFilesObj: [],
detailFiles: [],
chooseDate: null,
@ -264,7 +265,7 @@
this.videoUrl = "";
}else {
// TODO
this.videoUrl = `${this.basePath}/${this.mediaServerId}/record/${this.recordFile.app}/${this.recordFile.stream}/${this.chooseDate}/${this.choosedFile}`
this.videoUrl = `${this.basePath}/${this.recordFile.app}/${this.recordFile.stream}/${this.chooseDate}/${this.choosedFile}`
console.log(this.videoUrl)
}

View File

@ -66,6 +66,14 @@
</div>
</template>
</el-table-column>
<el-table-column label="无人观看自动删除" width="160" align="center">
<template slot-scope="scope">
<div slot="reference" class="name-wrapper">
<el-tag size="medium" v-if="scope.row.enable_remove_none_reader">已启用</el-tag>
<el-tag size="medium" type="info" v-if="!scope.row.enable_remove_none_reader">未启用</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="360" align="center" fixed="right">

View File

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

View File

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

View File

@ -39,7 +39,34 @@
</el-tab-pane>
<!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}-->
<el-tab-pane label="录像查询" name="record" v-if="showRrecord">
<el-date-picker size="mini" v-model="videoHistory.date" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="queryRecords()"></el-date-picker>
<div style="width: 100%;">
<div style="width: 100%; text-align: left">
<span>录像控制</span>
<el-button-group style="margin-left: 1rem;">
<el-button size="mini" class="iconfont icon-zanting" title="开始" @click="gbPause()"></el-button>
<el-button size="mini" class="iconfont icon-kaishi" title="暂停" @click="gbPlay()"></el-button>
<el-dropdown size="mini" title="播放倍速" style="margin-left: 1px;" @command="gbScale">
<el-button size="mini">
倍速 <i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="0.25">0.25倍速</el-dropdown-item>
<el-dropdown-item command="0.5">0.5倍速</el-dropdown-item>
<el-dropdown-item command="1.0">1倍速</el-dropdown-item>
<el-dropdown-item command="2.0">2倍速</el-dropdown-item>
<el-dropdown-item command="4.0">4倍速</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-button-group>
<el-date-picker style="float: right;" size="mini" v-model="videoHistory.date" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="queryRecords()"></el-date-picker>
</div>
<div style="width: 100%; text-align: left">
<span class="demonstration" style="padding: 12px 36px 12px 0;float: left;">{{showTimeText}}</span>
<el-slider style="width: 80%; float:left;" v-model="sliderTime" @change="gbSeek" :show-tooltip="false"></el-slider>
</div>
</div>
<el-table :data="videoHistory.searchHistoryResult" height="150" v-loading="recordsLoading">
<el-table-column label="名称" prop="name"></el-table-column>
<el-table-column label="文件" prop="filePath"></el-table-column>
@ -210,6 +237,10 @@ export default {
showPtz: true,
showRrecord: true,
tracksNotLoaded: false,
sliderTime: 0,
seekTime: 0,
recordStartTime: 0,
showTimeText: "00:00:00",
};
},
methods: {
@ -287,11 +318,13 @@ export default {
// return `http://${baseZlmApi}/${streamInfo.app}/${streamInfo.streamId}.flv`;
if (location.protocol === "https:") {
if (streamInfo.wss_flv === null) {
this.$message({
showClose: true,
message: '媒体服务器未配置ssl端口',
type: 'error'
});
console.error("媒体服务器未配置ssl端口, 使用http端口")
// this.$message({
// showClose: true,
// message: 'ssl, ',
// type: 'error'
// });
return streamInfo.ws_flv
}else {
return streamInfo.wss_flv;
}
@ -431,6 +464,14 @@ export default {
},
playRecord: function (row) {
let that = this;
let startTime = row.startTime
this.recordStartTime = row.startTime
this.showTimeText = row.startTime.split(" ")[1]
let endtime = row.endTime
this.sliderTime = 0;
this.seekTime = new Date(endtime).getTime() - new Date(startTime).getTime();
console.log(this.seekTime)
if (that.streamId != "") {
that.stopPlayRecord(function () {
that.streamId = "",
@ -578,7 +619,45 @@ export default {
}
console.log(resultArray)
return resultArray;
},
gbPlay(){
console.log('前端控制:播放');
this.$axios({
method: 'get',
url: '/api/playback/resume/' + this.streamId
}).then((res)=> {
this.$refs.videoPlayer.play(this.videoUrl)
});
},
gbPause(){
console.log('前端控制:暂停');
this.$axios({
method: 'get',
url: '/api/playback/pause/' + this.streamId
}).then(function (res) {});
},
gbScale(command){
console.log('前端控制:倍速 ' + command);
this.$axios({
method: 'get',
url: `/api/playback/speed/${this.streamId }/${command}`
}).then(function (res) {});
},
gbSeek(val){
console.log('前端控制seek ');
console.log(this.seekTime);
console.log(this.sliderTime);
let showTime = new Date(new Date(this.recordStartTime).getTime() + this.seekTime * val / 100)
let hour = showTime.getHours();
let minutes = showTime.getMinutes();
let seconds = showTime.getSeconds();
this.showTimeText = (hour < 10?("0" + hour):hour) + ":" + (minutes<10?("0" + minutes):minutes) + ":" + (seconds<10?("0" + seconds):seconds)
this.$axios({
method: 'get',
url: `/api/playback/seek/${this.streamId }/` + Math.floor(this.seekTime * val / 100000)
}).then(function (res) {});
}
}
};
</script>

View File

@ -26,7 +26,7 @@
<el-input v-model="platform.serverIP" clearable></el-input>
</el-form-item>
<el-form-item label="SIP服务端口" prop="serverPort">
<el-input v-model="platform.serverPort" clearable></el-input>
<el-input v-model="platform.serverPort" clearable type="number"></el-input>
</el-form-item>
<el-form-item label="设备国标编号" prop="deviceGBId">
<el-input v-model="platform.deviceGBId" clearable></el-input>
@ -35,7 +35,7 @@
<el-input v-model="platform.deviceIp" :disabled="true"></el-input>
</el-form-item>
<el-form-item label="本地端口" prop="devicePort">
<el-input v-model="platform.devicePort" :disabled="true"></el-input>
<el-input v-model="platform.devicePort" :disabled="true" type="number"></el-input>
</el-form-item>
</el-form>
</el-col>
@ -236,6 +236,17 @@ export default {
</script>
<style>
/* 谷歌 */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
/* 火狐 */
input{
-moz-appearance:textfield;
}
.control-wrapper-not-used {
position: relative;
width: 6.25rem;

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 1291092 */
src: url('iconfont.woff2?t=1631767887536') format('woff2'),
url('iconfont.woff?t=1631767887536') format('woff'),
url('iconfont.ttf?t=1631767887536') format('truetype');
src: url('iconfont.woff2?t=1637741914969') format('woff2'),
url('iconfont.woff?t=1637741914969') format('woff'),
url('iconfont.ttf?t=1637741914969') format('truetype');
}
.iconfont {
@ -13,6 +13,226 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-suoxiao:before {
content: "\e79a";
}
.icon-shanchu3:before {
content: "\e79b";
}
.icon-chehui:before {
content: "\e79c";
}
.icon-wenben:before {
content: "\e79d";
}
.icon-zhongzuo:before {
content: "\e79e";
}
.icon-jianqie:before {
content: "\e79f";
}
.icon-fangda:before {
content: "\e7a0";
}
.icon-fangdazhanshi:before {
content: "\e7a1";
}
.icon-qianjin:before {
content: "\e7a2";
}
.icon-kuaijin:before {
content: "\e7a3";
}
.icon-diyigeshipin:before {
content: "\e7a4";
}
.icon-kuaitui:before {
content: "\e7a5";
}
.icon-kaishi:before {
content: "\e7a7";
}
.icon-zuihouyigeshipin:before {
content: "\e7a8";
}
.icon-zanting:before {
content: "\e7a9";
}
.icon-zhankai:before {
content: "\e7aa";
}
.icon-bendisucai:before {
content: "\e7ab";
}
.icon-luzhi:before {
content: "\e7ac";
}
.icon-ossziyuan:before {
content: "\e7ad";
}
.icon-chuangjianzhinengfenxirenwu:before {
content: "\e7ae";
}
.icon-sousuo3:before {
content: "\e7af";
}
.icon-gengduo:before {
content: "\e7b0";
}
.icon-tianjia:before {
content: "\e7b1";
}
.icon-xiazai:before {
content: "\e7b2";
}
.icon-biaojibeifen:before {
content: "\e7b3";
}
.icon-bendisucaibeifen:before {
content: "\e7b4";
}
.icon-luzhibeifen:before {
content: "\e7b5";
}
.icon-ossziyuanbeifen:before {
content: "\e7b6";
}
.icon-bianji3:before {
content: "\e7b7";
}
.icon-cuti:before {
content: "\e7b8";
}
.icon-xieti:before {
content: "\e7b9";
}
.icon-xiahuaxian:before {
content: "\e7ba";
}
.icon-wuxiaoguo:before {
content: "\e7bb";
}
.icon-sousuo4:before {
content: "\e7bc";
}
.icon-gouwuche:before {
content: "\e7bd";
}
.icon-shuaxin2:before {
content: "\e7be";
}
.icon-xiaoxi:before {
content: "\e7bf";
}
.icon-wushouquan:before {
content: "\e7c0";
}
.icon-tishi2:before {
content: "\e7c1";
}
.icon-tishi1:before {
content: "\e7c2";
}
.icon-shouquanchenggong:before {
content: "\e7c3";
}
.icon-sousuo5:before {
content: "\e7c4";
}
.icon-shuaxin3:before {
content: "\e7c5";
}
.icon-xiazai1:before {
content: "\e7c6";
}
.icon-shangchuan:before {
content: "\e7c7";
}
.icon-guanbi:before {
content: "\e7c8";
}
.icon-wangye-loading:before {
content: "\e7c9";
}
.icon-bianzubeifen3:before {
content: "\e7ca";
}
.icon-xingzhuangbeifen:before {
content: "\e7cb";
}
.icon-bianzubeifen:before {
content: "\e7cc";
}
.icon-zhuanchang:before {
content: "\e7cd";
}
.icon-meizi:before {
content: "\e7ce";
}
.icon-daimabeifen:before {
content: "\e7cf";
}
.icon-suoxiao1:before {
content: "\e7d0";
}
.icon-ai19:before {
content: "\e799";
}
.icon-online:before {
content: "\e600";
}

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