From b1084891961232e3c697ea9fc52f127cdccffb6b Mon Sep 17 00:00:00 2001
From: liusuyi <1951119284@qq.com>
Date: 周四, 11 7月 2024 17:28:44 +0800
Subject: [PATCH] 优化:流媒体
---
ard-work/src/main/resources/templates/test.html | 548 ++++++++---
ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java | 18
ard-work/src/main/resources/templates/test1.html | 1161 +++++++++++++++++++++++++
ard-work/src/main/java/com/ruoyi/device/camera/domain/ArdCameras.java | 4
ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java | 6
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/SyncTask.java | 19
ard-work/src/main/java/com/ruoyi/device/camera/factory/CameraSDK.java | 2
ard-work/src/main/resources/mapper/device/ArdCamerasMapper.xml | 7
/dev/null | 484 ----------
ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java | 22
ard-work/src/main/java/com/ruoyi/device/channel/service/impl/ArdChannelServiceImpl.java | 74
ard-work/src/main/java/com/ruoyi/media/service/impl/VtduServiceImpl.java | 82 +
ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/service/impl/HikvisionSDK.java | 124 +-
ard-work/src/main/java/com/ruoyi/device/channel/service/IArdChannelService.java | 20
ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java | 29
ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/module/ConfigModule.java | 13
server/mediamtx/mediamtx.yml | 6
ard-work/src/main/java/com/ruoyi/media/service/IVtduService.java | 4
ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/lib/LoginResultCallBack.java | 6
ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/service/impl/DahuaSDK.java | 65
20 files changed, 1,809 insertions(+), 885 deletions(-)
diff --git a/ard-work/src/main/java/com/ruoyi/device/camera/domain/ArdCameras.java b/ard-work/src/main/java/com/ruoyi/device/camera/domain/ArdCameras.java
index 696247d..317c818 100644
--- a/ard-work/src/main/java/com/ruoyi/device/camera/domain/ArdCameras.java
+++ b/ard-work/src/main/java/com/ruoyi/device/camera/domain/ArdCameras.java
@@ -178,7 +178,7 @@
/**
* 登录ID
*/
- private Integer loginId;
+ private Long loginId;
/**
* 在线状态 0-离线 1-在线
*/
@@ -192,7 +192,7 @@
/**
* 起始通道号
*/
- private Integer startDChan;
+ private Integer startChan;
/**
* 通道数
*/
diff --git a/ard-work/src/main/java/com/ruoyi/device/camera/factory/CameraSDK.java b/ard-work/src/main/java/com/ruoyi/device/camera/factory/CameraSDK.java
index 5b435a0..d535bbd 100644
--- a/ard-work/src/main/java/com/ruoyi/device/camera/factory/CameraSDK.java
+++ b/ard-work/src/main/java/com/ruoyi/device/camera/factory/CameraSDK.java
@@ -4,6 +4,7 @@
import com.ruoyi.device.camera.domain.ArdCameras;
import com.ruoyi.device.camera.domain.CameraCmd;
import com.ruoyi.device.channel.domain.ArdChannel;
+import com.ruoyi.media.domain.Vtdu;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@@ -116,4 +117,5 @@
//本地录像停止
AjaxResult localRecordStop(CameraCmd cmd);
+
}
diff --git a/ard-work/src/main/java/com/ruoyi/device/channel/service/IArdChannelService.java b/ard-work/src/main/java/com/ruoyi/device/channel/service/IArdChannelService.java
index eea7265..17a7a6d 100644
--- a/ard-work/src/main/java/com/ruoyi/device/channel/service/IArdChannelService.java
+++ b/ard-work/src/main/java/com/ruoyi/device/channel/service/IArdChannelService.java
@@ -1,6 +1,8 @@
package com.ruoyi.device.channel.service;
import java.util.List;
+
+import com.ruoyi.device.camera.domain.ArdCameras;
import com.ruoyi.device.channel.domain.ArdChannel;
/**
@@ -65,22 +67,6 @@
* @return 结果
*/
public int deleteArdChannelByDeviceId(String deviceId);
- /**
- * @Author 刘苏义
- * @Description 获取2个通道列表的交集
- * @Date 2024/7/10 9:38
- * @Param
- * @return
- */
- public List<ArdChannel> sameList(List<ArdChannel> oldArrayList, List<ArdChannel> newArrayList);
- /**
- * @Author 刘苏义
- * @Description 取2个通道列表的差集
- * @Date 2024/7/10 9:39
- * @Param
- * @return
- */
- public List<ArdChannel> diffList(List<ArdChannel> firstArrayList, List<ArdChannel> secondArrayList);
/**
* @Author 刘苏义
@@ -89,5 +75,5 @@
* @Param
* @return
*/
- public void asyncChannel(List<ArdChannel> oldArrayList, List<ArdChannel> newArrayList);
+ public void asyncChannel(ArdCameras ardCameras, List<ArdChannel> oldArrayList, List<ArdChannel> newArrayList);
}
diff --git a/ard-work/src/main/java/com/ruoyi/device/channel/service/impl/ArdChannelServiceImpl.java b/ard-work/src/main/java/com/ruoyi/device/channel/service/impl/ArdChannelServiceImpl.java
index ce6565d..c715669 100644
--- a/ard-work/src/main/java/com/ruoyi/device/channel/service/impl/ArdChannelServiceImpl.java
+++ b/ard-work/src/main/java/com/ruoyi/device/channel/service/impl/ArdChannelServiceImpl.java
@@ -1,9 +1,16 @@
package com.ruoyi.device.channel.service.impl;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
import com.ruoyi.common.utils.uuid.IdUtils;
+import com.ruoyi.device.camera.domain.ArdCameras;
+import com.ruoyi.device.camera.factory.CameraSDK;
+import com.ruoyi.device.camera.factory.CameraSDKFactory;
+import com.ruoyi.device.camera.mapper.ArdCamerasMapper;
+import com.ruoyi.media.mapper.VtduMapper;
+import com.ruoyi.media.service.IVtduService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.device.channel.mapper.ArdChannelMapper;
@@ -22,7 +29,10 @@
public class ArdChannelServiceImpl implements IArdChannelService {
@Resource
private ArdChannelMapper ardChannelMapper;
-
+ @Resource
+ private CameraSDKFactory cameraSDKFactory;
+ @Resource
+ private IVtduService vtduService;
/**
* 查询通道管理
*
@@ -101,38 +111,40 @@
return ardChannelMapper.deleteArdChannelByDeviceId(deviceId);
}
- //求两个对象List的交集
@Override
- public List<ArdChannel> sameList(List<ArdChannel> oldArrayList, List<ArdChannel> newArrayList) {
- List<ArdChannel> resultList = newArrayList.stream()
- .filter(item -> oldArrayList.stream().map(e -> e.getChanNo())
- .collect(Collectors.toList()).contains(item.getChanNo()))
- .collect(Collectors.toList());
- return resultList;
- }
+ public void asyncChannel(ArdCameras ardCameras,List<ArdChannel> oldArrayList, List<ArdChannel> newArrayList) {
+ // 将列表转换为映射以提高性能
+ Map<Integer, ArdChannel> oldMap = oldArrayList.stream()
+ .collect(Collectors.toMap(ArdChannel::getChanNo, channel -> channel));
+ Map<Integer, ArdChannel> newMap = newArrayList.stream()
+ .collect(Collectors.toMap(ArdChannel::getChanNo, channel -> channel));
- //求两个对象List的差集
- @Override
- public List<ArdChannel> diffList(List<ArdChannel> firstArrayList, List<ArdChannel> secondArrayList) {
- List<ArdChannel> resultList = firstArrayList.stream()
- .filter(item -> !secondArrayList.stream().map(e -> e.getChanNo()).collect(Collectors.toList()).contains(item.getChanNo()))
- .collect(Collectors.toList());
- return resultList;
- }
+ // 需要更新的数据
+ newArrayList.stream()
+ .filter(channel -> {
+ ArdChannel oldChannel = oldMap.get(channel.getChanNo());
+ return oldChannel != null && !oldChannel.getName().equals(channel.getName());
+ })
+ .forEach(channel -> {
+ ArdChannel oldChannel = oldMap.get(channel.getChanNo());
+ channel.setId(oldChannel.getId());
+ updateArdChannel(channel);
+ });
- @Override
- public void asyncChannel(List<ArdChannel> oldArrayList, List<ArdChannel> newArrayList) {
- //需要更新的数据,参数顺序注意
- sameList(oldArrayList, newArrayList).stream().forEach(ardChannel -> {
- updateArdChannel(ardChannel);
- });
- //需要删除的数据
- diffList(oldArrayList, newArrayList).stream().forEach(ardChannel -> {
- deleteArdChannelById(ardChannel.getId());
- });
- //需要新增的数据
- diffList(newArrayList, oldArrayList).stream().forEach(ardChannel -> {
- insertArdChannel(ardChannel);
- });
+ // 需要删除的数据
+ oldArrayList.stream()
+ .filter(channel -> !newMap.containsKey(channel.getChanNo()))
+ .forEach(channel -> {
+ deleteArdChannelById(channel.getId());
+ vtduService.deleteVtduByName(channel.getDeviceId() + "_" + channel.getChanNo());
+ });
+
+ // 需要新增的数据
+ newArrayList.stream()
+ .filter(channel -> !oldMap.containsKey(channel.getChanNo()))
+ .forEach(channel -> {
+ insertArdChannel(channel);
+ vtduService.addChanToVtdu(ardCameras, channel);
+ });
}
}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java b/ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java
index 7232194..6d89b58 100644
--- a/ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java
+++ b/ard-work/src/main/java/com/ruoyi/media/controller/MediaController.java
@@ -4,10 +4,12 @@
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.media.domain.Config;
+import com.ruoyi.media.domain.Paths;
import com.ruoyi.media.domain.StreamInfo;
import com.ruoyi.media.domain.Vtdu;
import com.ruoyi.media.service.IMediaService;
@@ -112,7 +114,7 @@
@PreAuthorize("@ss.hasPermi('media:stream:remove')")
@DeleteMapping("/{sessionId}")
public AjaxResult removePullStreamSession(@PathVariable String sessionId) {
- List<StreamInfo> pullStreamList = mediaService.getPullStreamList();
+ List<StreamInfo> pullStreamList = mediaService.getPullStreamList(1,1000);
StreamInfo streamInfo = pullStreamList.stream()
.filter(object -> object.getId().equals(sessionId))
.collect(Collectors.toList()).get(0);
@@ -143,9 +145,9 @@
@GetMapping("/path/list")
@ApiOperation("获取当前通道列表")
@ApiOperationSupport(order = 5)
- public TableDataInfo getPaths() {
+ public TableDataInfo getPaths(Integer pageNum,Integer pageSize) {
startPage();
- return getDataTable(mediaService.paths());
+ return getDataTable(mediaService.paths(pageNum,pageSize));
}
/**
@@ -154,7 +156,7 @@
@GetMapping("/getRtspSessionById")
@ApiOperation("按ID查询拉流详情")
public AjaxResult getRtspSessionById(String sessionId) {
- List<StreamInfo> pullStreamList = mediaService.getPullStreamList();
+ List<StreamInfo> pullStreamList = mediaService.getPullStreamList(1,1000);
StreamInfo streamInfo = pullStreamList.stream()
.filter(object -> object.getId().equals(sessionId))
.collect(Collectors.toList()).get(0);
@@ -176,9 +178,9 @@
@GetMapping("/pushList")
@ApiOperation("获取推流列表")
@ApiOperationSupport(order = 6)
- public TableDataInfo getPushStreamList() {
+ public TableDataInfo getPushStreamList(Integer pageNum,Integer pageSize) {
startPage();
- return getDataTable(mediaService.getPushStreamList());
+ return getDataTable(mediaService.getPushStreamList(pageNum,pageSize));
}
/**
@@ -188,9 +190,9 @@
@GetMapping("/pullList")
@ApiOperation("获取拉流列表")
@ApiOperationSupport(order = 7)
- public TableDataInfo getPullStreamList() {
+ public TableDataInfo getPullStreamList(Integer pageNum,Integer pageSize) {
startPage();
- return getDataTable(mediaService.getPullStreamList());
+ return getDataTable(mediaService.getPullStreamList(pageNum,pageSize));
}
}
diff --git a/ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java b/ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java
index 360b7fe..9c0602e 100644
--- a/ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java
+++ b/ard-work/src/main/java/com/ruoyi/media/service/IMediaService.java
@@ -33,7 +33,7 @@
void removePath(String name);
- List<StreamInfo> paths();
+ List<StreamInfo> paths(Integer pageNum,Integer pageSize);
List<String> getNameList();
@@ -45,9 +45,9 @@
RtmpSession getRtmpSessionById(String sessionId);
- List<StreamInfo> getPushStreamList();
+ List<StreamInfo> getPushStreamList(Integer pageNum,Integer pageSize);
- List<StreamInfo> getPullStreamList();
+ List<StreamInfo> getPullStreamList(Integer pageNum,Integer pageSize);
Boolean kickRtspSession(String sessionId);
diff --git a/ard-work/src/main/java/com/ruoyi/media/service/IMediaV2Service.java b/ard-work/src/main/java/com/ruoyi/media/service/IMediaV2Service.java
deleted file mode 100644
index cccbc36..0000000
--- a/ard-work/src/main/java/com/ruoyi/media/service/IMediaV2Service.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.ruoyi.media.service;
-
-import com.ruoyi.media.domain.*;
-
-import java.util.List;
-import java.util.Map;
-
-public interface IMediaV2Service {
- /**
- * 增加路径
- * name 名称
- * rtspPath rtsp地址
- * mode 模式:gpu硬解码/cpu软解码
- * isCode 是否转码
- * 刘苏义
- * 2023/8/12 13:56:52
- */
- Map<String, String> addPath(String name, String sourceUrl, String mode, String isCode);
-
- /**
- * 修改路径
- * name 名称
- * rtspPath rtsp地址
- * mode 模式:实时/按需
- * isCode 是否转码
- * 刘苏义
- * 2023/8/12 13:56:52
- */
- Map<String, String> editPath(String name, String sourceUrl, String mode, String isCode);
-
- StreamInfo getPathInfo(String name);
-
- void removePath(String[] names);
-
- void removePath(String name);
-
- List<StreamInfo> paths();
-
- List<String> getNameList();
-
- boolean checkNameExist(String name);
-
- RtspSession getRtspSessionById(String sessionId);
-
- WebrtcSession getWebrtcSessionById(String sessionId);
-
- RtmpSession getRtmpSessionById(String sessionId);
-
- List<StreamInfo> getPushStreamList();
-
- List<StreamInfo> getPullStreamList();
-
- Boolean kickRtspSession(String sessionId);
-
- Boolean kickRtmpSession(String sessionId);
-
- Boolean kickWebrtcSession(String sessionId);
-
- /**
- * 配置流媒体参数
- * 刘苏义
- * 2023/10/13 15:17:57
- */
- public String setConfig(Config config);
-}
diff --git a/ard-work/src/main/java/com/ruoyi/media/service/IVtduService.java b/ard-work/src/main/java/com/ruoyi/media/service/IVtduService.java
index 7775461..dca1a55 100644
--- a/ard-work/src/main/java/com/ruoyi/media/service/IVtduService.java
+++ b/ard-work/src/main/java/com/ruoyi/media/service/IVtduService.java
@@ -2,6 +2,7 @@
import java.util.List;
+import com.ruoyi.device.camera.domain.ArdCameras;
import com.ruoyi.device.channel.domain.ArdChannel;
import com.ruoyi.media.domain.Vtdu;
@@ -103,4 +104,7 @@
* @Param
*/
public void asyncVtdu(List<Vtdu> vtdus, List<String> names);
+
+ //添加通道至流媒体
+ void addChanToVtdu(ArdCameras camera, ArdChannel channel);
}
diff --git a/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java b/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java
index de4f9fc..0079687 100644
--- a/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java
+++ b/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaServiceImpl.java
@@ -8,6 +8,7 @@
import com.ruoyi.media.service.IMediaService;
import com.ruoyi.utils.forest.MediaClient;
import com.ruoyi.utils.tools.ArdTool;
+import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
@@ -146,6 +147,8 @@
if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
runOn = conf.getRunOnDemand();
info.setMode("0");
+ } else {
+ info.setMode("1");
}
//RTSP源地址
Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
@@ -184,8 +187,8 @@
}
@Override
- public List<StreamInfo> paths() {
- Paths paths = mediaClient.paths();
+ public List<StreamInfo> paths(Integer pageNum, Integer pageSize) {
+ Paths paths = mediaClient.paths(pageNum - 1, pageSize);
List<Items> items = paths.getItems();
List<StreamInfo> pathInfoList = new ArrayList<>();
for (Items item : items) {
@@ -221,23 +224,17 @@
@Override
public RtspSession getRtspSessionById(String sessionId) {
- String list = mediaClient.getRtspsessionById(sessionId);
- RtspSession rtspSession = JSONObject.parseObject(list, RtspSession.class);
- return rtspSession;
+ return mediaClient.getRtspsessionById(sessionId);
}
@Override
public WebrtcSession getWebrtcSessionById(String sessionId) {
- String list = mediaClient.getWebrtcsessionById(sessionId);
- WebrtcSession webrtcSession = JSONObject.parseObject(list, WebrtcSession.class);
- return webrtcSession;
+ return mediaClient.getWebrtcsessionById(sessionId);
}
@Override
public RtmpSession getRtmpSessionById(String sessionId) {
- String list = mediaClient.getRtmpsessionById(sessionId);
- RtmpSession rtmpSession = JSONObject.parseObject(list, RtmpSession.class);
- return rtmpSession;
+ return mediaClient.getRtmpsessionById(sessionId);
}
/**
@@ -246,9 +243,9 @@
* 2023/8/29 9:37:05
*/
@Override
- public List<StreamInfo> getPushStreamList() {
+ public List<StreamInfo> getPushStreamList(Integer pageNum, Integer pageSize) {
List<StreamInfo> PushStreamInfoList = new ArrayList<>();
- Paths paths = mediaClient.paths();
+ Paths paths = mediaClient.paths(pageNum - 1, pageSize);
List<Items> items = paths.getItems();
for (Items item : items) {
StreamInfo info = new StreamInfo();
@@ -321,9 +318,9 @@
* 2023/8/29 9:37:05
*/
@Override
- public List<StreamInfo> getPullStreamList() {
+ public List<StreamInfo> getPullStreamList(Integer pageNum, Integer pageSize) {
List<StreamInfo> PullStreamInfoList = new ArrayList<>();
- Paths paths = mediaClient.paths();
+ Paths paths = mediaClient.paths(pageNum - 1, pageSize);
List<Items> items = paths.getItems();
for (Items item : items) {
List<Readers> readers = item.getReaders();
@@ -467,7 +464,7 @@
public List<String> getNameList() {
List<String> nameList = new ArrayList<>();
try {
- Paths paths = mediaClient.paths();
+ Paths paths = mediaClient.paths(0, 1000);
List<Items> items = paths.getItems();
for (Items item : items) {
nameList.add(item.getName());
diff --git a/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaV2ServiceImpl.java b/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaV2ServiceImpl.java
deleted file mode 100644
index ec24786..0000000
--- a/ard-work/src/main/java/com/ruoyi/media/service/impl/MediaV2ServiceImpl.java
+++ /dev/null
@@ -1,484 +0,0 @@
-package com.ruoyi.media.service.impl;
-
-import com.alibaba.fastjson2.JSONObject;
-import com.dtflys.forest.exceptions.ForestNetworkException;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.media.domain.*;
-import com.ruoyi.media.service.IMediaV2Service;
-import com.ruoyi.utils.forest.MediaClient;
-import com.ruoyi.utils.tools.ArdTool;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.core.annotation.Order;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * @Description: 流媒体业务
- * @ClassName: MediaService
- * @Author: 刘苏义
- * @Date: 2023年07月13日9:28
- * @Version: 1.0
- **/
-@Service
-@Slf4j(topic = "cmd")
-@Order(2)
-public class MediaV2ServiceImpl implements IMediaV2Service {
-
- @Resource
- MediaClient mediaClient;
-
- @Value("${mediamtx.host}")
- String mediamtxHost;
-
- /**
- * 添加流媒体
- * name 相机ID
- * sourceUrl rtsp地址
- * isCode 0-不转码 1-转码
- * mode 0-gpu硬解码 1-cpu软解码
- * <p>
- * 刘苏义
- * 2023/10/12 9:03:41
- */
- @Override
- public Map<String, String> addPath(String name, String sourceUrl, String mode, String isCode) {
-
- String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
- String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
- String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
-
- Conf conf = new Conf();
- String rootPath = System.getProperty("user.dir").replaceAll("\\\\", "/") + "/lib/mediamtx/";
- if (isCode.equals("1")) {
- conf.setSource("publisher");
- //默认软解码
- String cmd = "ffmpeg -rtsp_transport tcp -i " + sourceUrl + " -vcodec libx264 -preset:v ultrafast -r 25 -keyint_min 25 -g 60 -sc_threshold 0 -threads 6 -b:v 2048k -acodec opus -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
- if (mode.equals("0")) {//硬解码
- cmd = "ffmpeg -hwaccel cuvid -c:v hevc_cuvid -rtsp_transport tcp -i " + sourceUrl + " -c:v h264_nvenc -r 25 -g 60 -sc_threshold 0 -threads 6 -b:v 2048k -bf 0 -acodec opus -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
- }
- conf.setRunOnDemand(cmd);
- conf.setRunOnDemandRestart(true);
- conf.setRunOnDemandCloseAfter("5s");
- } else {
- conf.setSource(sourceUrl);
- conf.setRunOnDemandRestart(true);
- }
- conf.setMaxReaders(100);
- conf.setSourceProtocol("tcp");
-
- List<String> nameList = new ArrayList<>();
- Paths paths = mediaClient.paths();
- List<Items> items = paths.getItems();
- for (Items item : items) {
- nameList.add(item.getName());
- }
- if (!nameList.contains(name)) {
- mediaClient.addPath(name, conf);
- }
- Map<String, String> map = new HashMap<>();
- map.put("rtspUrl", rtspUrl);
- map.put("rtmpUrl", rtmpUrl);
- map.put("webrtcUrl", webrtcUrl);
- return map;
- }
-
- @Override
- public Map<String, String> editPath(String name, String sourceUrl, String mode, String isCode) {
- Map<String, String> map = new HashMap<>();
- try {
- String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
- String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
- String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
-
- Conf mediaInfo = new Conf();
- String rootPath = System.getProperty("user.dir").replaceAll("\\\\", "/") + "/lib/mediamtx/";
- if (isCode.equals("1")) {
- mediaInfo.setSource("publisher");
- //默认软解码
- String cmd = "ffmpeg -rtsp_transport tcp -i " + sourceUrl + " -vcodec libx264 -preset:v ultrafast -r 25 -keyint_min 25 -g 60 -sc_threshold 0 -threads 6 -b:v 2048k -acodec opus -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
- if (mode.equals("0")) {//硬解码
- cmd = "ffmpeg -hwaccel cuvid -c:v hevc_cuvid -rtsp_transport tcp -i " + sourceUrl + " -c:v h264_nvenc -r 25 -g 60 -sc_threshold 0 -threads 6 -b:v 2048k -bf 0 -acodec opus -strict -2 -f rtsp rtsp://localhost:$RTSP_PORT/$MTX_PATH";
- }
- mediaInfo.setRunOnDemand(cmd);
- mediaInfo.setRunOnDemandRestart(true);
- mediaInfo.setRunOnDemandCloseAfter("5s");
- } else {
- mediaInfo.setSource(sourceUrl);
- }
- mediaInfo.setMaxReaders(100);
- mediaInfo.setSourceProtocol("tcp");
-
- if (checkNameExist(name)) {
- mediaClient.editPath(name, mediaInfo);
- }
-
- map.put("rtspUrl", rtspUrl);
- map.put("rtmpUrl", rtmpUrl);
- map.put("webrtcUrl", webrtcUrl);
- } catch (ForestNetworkException ex) {
- log.error(ex.getMessage());
- }
- return map;
- }
-
- @Override
- public StreamInfo getPathInfo(String name) {
- Conf conf = mediaClient.getPathInfo(name);
- StreamInfo info = new StreamInfo();
- //ID
- info.setName(name);
- String runOn="";
- if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
- runOn = conf.getRunOnDemand();
- }
- //RTSP源地址
- Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
- if (matcher.find()) {
- info.setRtspSource(matcher.group());
- info.setIsCode("1");
- } else {
- info.setRtspSource(conf.getSource());
- info.setIsCode("0");
- }
- return info;
- }
-
- @Override
- public void removePath(String[] names) {
- for (String name : names) {
- if(checkNameExist(name)) {
- mediaClient.removePath(name);}
- }
- }
-
- @Override
- public void removePath(String name) {
- if(checkNameExist(name)) {
- mediaClient.removePath(name);
- }
- }
-
- @Override
- public List<StreamInfo> paths() {
- Paths paths = mediaClient.paths();
- List<Items> items = paths.getItems();
- List<StreamInfo> pathInfoList = new ArrayList<>();
- for (Items item : items) {
- StreamInfo info = new StreamInfo();
- //ID
- String name = item.getName();
- info.setName(name);
- Conf conf = mediaClient.getPathInfo(name);
-
- String runOn = "";
- if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
- runOn = conf.getRunOnDemand();
- }
- //RTSP源地址
- Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
- if (matcher.find()) {
- info.setRtspSource(matcher.group());
- info.setIsCode("1");
- } else {
- info.setRtspSource(conf.getSource());
- info.setIsCode("0");
- }
- //传输协议
- matcher = Pattern.compile("-rtsp_transport\\s+(\\w+)").matcher(runOn);
- if (matcher.find()) {
- info.setProtocol(matcher.group(1));
- }
-
- pathInfoList.add(info);
- }
- return pathInfoList;
- }
-
-
- @Override
- public RtspSession getRtspSessionById(String sessionId) {
- String list = mediaClient.getRtspsessionById(sessionId);
- RtspSession rtspSession = JSONObject.parseObject(list, RtspSession.class);
- return rtspSession;
- }
-
- @Override
- public WebrtcSession getWebrtcSessionById(String sessionId) {
- String list = mediaClient.getWebrtcsessionById(sessionId);
- WebrtcSession webrtcSession = JSONObject.parseObject(list, WebrtcSession.class);
- return webrtcSession;
- }
-
- @Override
- public RtmpSession getRtmpSessionById(String sessionId) {
- String list = mediaClient.getRtmpsessionById(sessionId);
- RtmpSession rtmpSession = JSONObject.parseObject(list, RtmpSession.class);
- return rtmpSession;
- }
-
- /**
- * 获取推流列表
- * 刘苏义
- * 2023/8/29 9:37:05
- */
- @Override
- public List<StreamInfo> getPushStreamList() {
- List<StreamInfo> PushStreamInfoList = new ArrayList<>();
- Paths paths = mediaClient.paths();
- List<Items> items = paths.getItems();
- for (Items item : items) {
- StreamInfo info = new StreamInfo();
- //ID
- String name = item.getName();
- info.setName(name);
-
- Conf conf = mediaClient.getPathInfo(name);
- //RTMP播放地址
- String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
- info.setRtmpUrl(rtmpUrl);
- //RTSP播放地址
- String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
- info.setRtspUrl(rtspUrl);
- //WEBRTC播放地址
- String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
- info.setWebrtcUrl(webrtcUrl);
- Source source = item.getSource();
- if (source == null || source.getId().equals("")) {
- //会话ID
- info.setId("0");
- //上行流量
- long bytesReceived = item.getBytesReceived();
- String formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
- info.setUpTraffic(formatReceivedSize);
- } else {
- RtspSession rtspSession = getRtspSessionById(source.getId());
- //会话ID
- info.setId(rtspSession.getId());
- //开始推流时间
- info.setBeginTime(rtspSession.getCreated());
- //上行流量
- long bytesReceived = rtspSession.getBytesReceived();
- String formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
- info.setUpTraffic(formatReceivedSize);
- //下行流量
- long bytesSent = rtspSession.getBytesSent();
- String formatSentSize = ArdTool.formatFileSize(bytesSent);
- info.setDownTraffic(formatSentSize);
- //推流服务器
- info.setRemoteAddr(rtspSession.getRemoteAddr());
- }
- //RTSP源地址
- String runOn = "";
- if (StringUtils.isNotEmpty(conf.getRunOnDemand())) {
- runOn = conf.getRunOnDemand();
- }
- Matcher matcher = Pattern.compile("rtsp://[^\\s\"]+").matcher(runOn);
- if (matcher.find()) {
- info.setRtspSource(matcher.group());
- } else {
- info.setRtspSource(conf.getSource());
- }
- //传输协议
- info.setProtocol(conf.getSourceProtocol());
- //拉流数量
- List<Readers> readers = item.getReaders();
- info.setNum(readers.size());
-
- PushStreamInfoList.add(info);
- }
- return PushStreamInfoList;
- }
-
- /**
- * 获取拉流列表
- * 刘苏义
- * 2023/8/29 9:37:05
- */
- @Override
- public List<StreamInfo> getPullStreamList() {
- List<StreamInfo> PullStreamInfoList = new ArrayList<>();
- Paths paths = mediaClient.paths();
- List<Items> items = paths.getItems();
- for (Items item : items) {
- List<Readers> readers = item.getReaders();
- for (Readers reader : readers) {
- StreamInfo info = new StreamInfo();
- //ID
- String name = item.getName();
- info.setName(name);
-
- Conf conf = mediaClient.getPathInfo(name);
- //传输协议
- info.setProtocol(conf.getSourceProtocol());
-
- String type = reader.getType();
- switch (type) {
- case "rtmpConn":
- info.setSessionType("rtmp");
- //webrtc播放地址
- String url = "rtmp://" + mediamtxHost + ":1935/" + name;
- info.setRtspUrl(url);
- RtmpSession rtmpSession = getRtmpSessionById(reader.getId());
- //会话ID
- info.setId(rtmpSession.getId());
- //开始拉流时间
- info.setBeginTime(rtmpSession.getCreated());
- //上行流量
- long bytesReceived = rtmpSession.getBytesReceived();
- String formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
- info.setUpTraffic(formatReceivedSize);
- //下行流量
- long bytesSent = rtmpSession.getBytesSent();
- String formatSentSize = ArdTool.formatFileSize(bytesSent);
- info.setDownTraffic(formatSentSize);
- //拉流服务器
- info.setRemoteAddr(rtmpSession.getRemoteAddr());
- PullStreamInfoList.add(info);
- break;
- case "webRTCSession":
- info.setSessionType("webrtc");
- //webrtc播放地址
- url = "http://" + mediamtxHost + ":8889/" + name;
- info.setRtspUrl(url);
- WebrtcSession webrtcSession = getWebrtcSessionById(reader.getId());
- //会话ID
- info.setId(webrtcSession.getId());
- //开始拉流时间
- info.setBeginTime(webrtcSession.getCreated());
- //上行流量
- bytesReceived = webrtcSession.getBytesReceived();
- formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
- info.setUpTraffic(formatReceivedSize);
- //下行流量
- bytesSent = webrtcSession.getBytesSent();
- formatSentSize = ArdTool.formatFileSize(bytesSent);
- info.setDownTraffic(formatSentSize);
- //拉流服务器
- info.setRemoteAddr(webrtcSession.getRemoteAddr());
- PullStreamInfoList.add(info);
- break;
- case "rtspSession":
- info.setSessionType("rtsp");
- //RTSP播放地址
- String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
- info.setRtspUrl(rtspUrl);
- RtspSession rtspSession = getRtspSessionById(reader.getId());
- //会话ID
- info.setId(rtspSession.getId());
- //开始拉流时间
- info.setBeginTime(rtspSession.getCreated());
- //上行流量
- bytesReceived = rtspSession.getBytesReceived();
- formatReceivedSize = ArdTool.formatFileSize(bytesReceived);
- info.setUpTraffic(formatReceivedSize);
- //下行流量
- bytesSent = rtspSession.getBytesSent();
- formatSentSize = ArdTool.formatFileSize(bytesSent);
- info.setDownTraffic(formatSentSize);
- //拉流服务器
- info.setRemoteAddr(rtspSession.getRemoteAddr());
- PullStreamInfoList.add(info);
- break;
- }
- }
- }
- Comparator<StreamInfo> comparator = Comparator.comparing(streamInfo -> streamInfo.getBeginTime()); // 使用Collections.sort方法进行排序 Collections.sort(personList, comparator);
- Collections.sort(PullStreamInfoList, comparator.reversed());
- return PullStreamInfoList;
- }
-
- /**
- * 踢出rtsp会话
- * 刘苏义
- * 2023/8/29 9:37:05
- */
- @Override
- public Boolean kickRtspSession(String sessionId) {
- try {
- mediaClient.kickRtspSessions(sessionId);
- return true;
- } catch (Exception ex) {
- return false;
- }
- }
-
- /**
- * 踢出rtmp会话
- * 刘苏义
- * 2023/8/29 9:37:05
- */
- @Override
- public Boolean kickRtmpSession(String sessionId) {
- try {
- mediaClient.kickRtmpSessions(sessionId);
- return true;
- } catch (Exception ex) {
- return false;
- }
- }
-
- /**
- * 踢出webrtc会话
- * 刘苏义
- * 2023/8/29 9:37:05
- */
- @Override
- public Boolean kickWebrtcSession(String sessionId) {
- try {
- mediaClient.kickWebrtcSessions(sessionId);
- return true;
- } catch (Exception ex) {
- return false;
- }
- }
-
- /**
- * 获取流媒体name列表
- * 刘苏义
- * 2023/10/13 14:19:07
- */
- @Override
- public List<String> getNameList() {
- List<String> nameList = new ArrayList<>();
- try {
- Paths paths = mediaClient.paths();
- List<Items> items = paths.getItems();
- for (Items item : items) {
- nameList.add(item.getName());
- }
- }
- catch (Exception ex)
- {
- log.error(ex.getMessage());
- }
- return nameList;
- }
-
- /**
- * 检查名称是否存在
- * 刘苏义
- * 2023/10/19 15:18:45
- */
- @Override
- public boolean checkNameExist(String name) {
- boolean result = false;
- List<String> nameList = getNameList();
- if (nameList.contains(name)) {
- result = true;
- }
- return result;
- }
-
- /**
- * 配置流媒体参数
- */
- @Override
- public String setConfig(Config config) {
- return mediaClient.setConfig(config);
- }
-}
diff --git a/ard-work/src/main/java/com/ruoyi/media/service/impl/VtduServiceImpl.java b/ard-work/src/main/java/com/ruoyi/media/service/impl/VtduServiceImpl.java
index 36a8628..f0be3d3 100644
--- a/ard-work/src/main/java/com/ruoyi/media/service/impl/VtduServiceImpl.java
+++ b/ard-work/src/main/java/com/ruoyi/media/service/impl/VtduServiceImpl.java
@@ -6,9 +6,15 @@
import java.util.stream.Collectors;
import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.device.camera.domain.ArdCameras;
+import com.ruoyi.device.camera.domain.CameraCmd;
+import com.ruoyi.device.camera.factory.CameraSDK;
+import com.ruoyi.device.camera.factory.CameraSDKFactory;
import com.ruoyi.device.channel.domain.ArdChannel;
+import com.ruoyi.media.domain.StreamInfo;
import com.ruoyi.media.service.IMediaService;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.ruoyi.media.mapper.VtduMapper;
import com.ruoyi.media.domain.Vtdu;
@@ -30,7 +36,10 @@
private VtduMapper vtduMapper;
@Resource
private IMediaService mediaService;
-
+ @Resource
+ private CameraSDKFactory cameraSDKFactory;
+ @Value("${mediamtx.host}")
+ String mediamtxHost;
/**
* 查询流媒体管理
*
@@ -39,7 +48,6 @@
*/
@Override
public Vtdu selectVtduByName(String name) {
-
return vtduMapper.selectVtduByName(name);
}
@@ -62,17 +70,6 @@
*/
@Override
public int insertVtdu(Vtdu vtdu) {
- log.debug("流媒体【" + vtdu.getName() + "】通道添加");
- Map<String, String> map = mediaService.addPath(vtdu.getName(), vtdu.getRtspSource(), vtdu.getMode(), vtdu.getIsCode());
- vtdu.setRtspUrl(map.get("rtspUrl"));
- vtdu.setRtmpUrl(map.get("rtmpUrl"));
- vtdu.setWebrtcUrl(map.get("webrtcUrl"));
-
- vtdu.setName(vtdu.getName());
- vtdu.setRtspSource(vtdu.getRtspSource());
- vtdu.setIsCode(vtdu.getIsCode());
- vtdu.setMode(vtdu.getMode());
- vtdu.setCreateTime(DateUtils.getNowDate());
return vtduMapper.insertVtdu(vtdu);
}
@@ -84,7 +81,6 @@
*/
@Override
public int updateVtdu(Vtdu vtdu) {
- log.debug("流媒体【" + vtdu.getName() + "】通道更新");
Map<String, String> map = mediaService.editPath(vtdu.getName(), vtdu.getRtspSource(), vtdu.getMode(), vtdu.getIsCode());
vtdu.setName(vtdu.getName());
vtdu.setRtspSource(vtdu.getRtspSource());
@@ -171,12 +167,10 @@
.collect(Collectors.toList());
return nameList;
} else {
-
List<String> nameList = names.stream().filter(item -> !vtdus.stream().map(Vtdu::getName).collect(Collectors.toList()).contains(item))
.collect(Collectors.toList());
return nameList;
}
-
}
//需要新增的(流媒体中少的)
@@ -186,18 +180,21 @@
}
/**
- * @Author 刘苏义
- * @Description 同步本地vtdu库和流媒体中的数据
- * @Date 2024/7/10 15:26
- * @Param vtdus vtdu库的集合
- * @Param names 流媒体的name集合
* @return
+ * @Author 刘苏义
+ * @Description 同步本地vtdu库和流媒体中的数据
+ * @Date 2024/7/10 15:26
+ * @Param vtdus vtdu库的集合
+ * @Param names 流媒体的name集合
*/
@Override
public void asyncVtdu(List<Vtdu> vtdus, List<String> names) {
//需要更新的数据,参数顺序注意
sameList(vtdus, names).stream().forEach(vtdu -> {
- mediaService.editPath(vtdu.getName(), vtdu.getRtspSource(), vtdu.getMode(), vtdu.getIsCode());
+ StreamInfo streamInfo = mediaService.getPathInfo(vtdu.getName());
+ if (!streamInfo.getRtspSource().equals(vtdu.getRtspSource())) {
+ mediaService.editPath(vtdu.getName(), vtdu.getRtspSource(), vtdu.getMode(), vtdu.getIsCode());
+ }
});
//需要删除的数据
diffListToDel(vtdus, names).stream().forEach(name -> {
@@ -208,4 +205,45 @@
mediaService.addPath(vtdu.getName(), vtdu.getRtspSource(), vtdu.getMode(), vtdu.getIsCode());
});
}
+ @Override
+ public void addChanToVtdu(ArdCameras camera, ArdChannel channel) {
+ String name = camera.getId() + "_" + channel.getChanNo();
+ String rtspSource="";
+ switch (camera.getFactory())
+ {
+ case "1": rtspSource = "rtsp://" + camera.getUsername() + ":" + camera.getPassword() + "@" + camera.getIp() + ":" + camera.getRtspPort() + "/h264/ch" + channel.getChanNo() + "/main/av_stream";break;
+ case "2": rtspSource = "rtsp://" + camera.getUsername() + ":" + camera.getPassword() + "@" + camera.getIp() + ":" + camera.getRtspPort() + "/cam/realmonitor?channel=" + channel.getChanNo() + "&subtype=0";break;
+ }
+
+ //删除流媒体
+ if (selectVtduByName(name) != null) {
+ deleteVtduByName(name);
+ }
+ //添加到流媒体
+ CameraCmd cmd = new CameraCmd(camera.getId(), channel.getChanNo());
+ CameraSDK cameraSDK = cameraSDKFactory.createCameraSDK(camera.getFactory());
+ Map<String, Object> videoCompressionCfg = cameraSDK.getVideoCompressionCfg(cmd);
+ Vtdu vtdu = new Vtdu();
+ if (videoCompressionCfg.get("videoEncType") != null) {
+ if (videoCompressionCfg.get("videoEncType").equals("标准h264")) {
+ vtdu.setIsCode("0");//默认不转码
+ } else {
+ vtdu.setIsCode("1");//默认转码
+ }
+ } else {
+ vtdu.setIsCode("0");//默认不转码
+ }
+ vtdu.setRtspSource(rtspSource);
+ vtdu.setName(name);
+ vtdu.setMode("1");//默认CPU软解码
+ vtdu.setCameraId(camera.getId());
+ String rtspUrl = "rtsp://" + mediamtxHost + ":8554/" + name;
+ String rtmpUrl = "rtmp://" + mediamtxHost + ":1935/" + name;
+ String webrtcUrl = "http://" + mediamtxHost + ":8889/" + name;
+ vtdu.setRtmpUrl(rtmpUrl);
+ vtdu.setWebrtcUrl(webrtcUrl);
+ vtdu.setRtspUrl(rtspUrl);
+ insertVtdu(vtdu);
+ }
+
}
diff --git a/ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java b/ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java
index 158a6ee..e441bc0 100644
--- a/ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java
+++ b/ard-work/src/main/java/com/ruoyi/utils/forest/MediaClient.java
@@ -1,10 +1,7 @@
package com.ruoyi.utils.forest;
import com.dtflys.forest.annotation.*;
-import com.ruoyi.media.domain.Conf;
-import com.ruoyi.media.domain.Config;
-import com.ruoyi.media.domain.Items;
-import com.ruoyi.media.domain.Paths;
+import com.ruoyi.media.domain.*;
import java.nio.file.Path;
@@ -38,14 +35,14 @@
/**
* 获取路径详情
*/
- @Get(url ="/config/paths/get/{name}")
+ @Get(url = "/config/paths/get/{name}")
public Conf getPathInfo(@Var("name") String name);
/**
* 查询所有路径
*/
- @Get("/paths/list")
- public Paths paths();
+ @Get("/paths/list?page={pageNum}&itemsPerPage={pageSize}")
+ public Paths paths(@Var("pageNum") Integer pageNum, @Var("pageSize") Integer pageSize);
/**
* 查询所有rtsp会话
@@ -57,19 +54,19 @@
* 按sessionId查询rtsp会话
*/
@Get("/rtspsessions/get/{sessionId}")
- public String getRtspsessionById(@Var("sessionId") String sessionId);
+ public RtspSession getRtspsessionById(@Var("sessionId") String sessionId);
/**
* 按sessionId查询webrtc会话
*/
@Get("/webrtcsessions/get/{sessionId}")
- public String getWebrtcsessionById(@Var("sessionId") String sessionId);
+ public WebrtcSession getWebrtcsessionById(@Var("sessionId") String sessionId);
/**
* 按sessionId查询rtmp会话
*/
@Get("/rtmpconns/get/{sessionId}")
- public String getRtmpsessionById(@Var("sessionId") String sessionId);
+ public RtmpSession getRtmpsessionById(@Var("sessionId") String sessionId);
/**
* 按sessionId删除rtsp会话
@@ -89,9 +86,4 @@
@Post("/webrtcsessions/kick/{sessionId}")
public String kickWebrtcSessions(@Var("sessionId") String sessionId);
- /**
- * 配置流媒体参数
- */
- @Post("/config/set")
- public String setConfig(@JSONBody Config config);
}
diff --git a/ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/module/ConfigModule.java b/ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/module/ConfigModule.java
index 631218d..e5c60f4 100644
--- a/ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/module/ConfigModule.java
+++ b/ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/module/ConfigModule.java
@@ -81,10 +81,12 @@
return result;
}
- public static String getChannelName(NetSDKLib.LLong hLoginHandle,Integer channel) {
+
+ //查询通道名称
+ public static String getChannelName(NetSDKLib.LLong hLoginHandle, Integer channel) {
String channelName = "";
NetSDKLib.AV_CFG_ChannelName channelTitleName = new NetSDKLib.AV_CFG_ChannelName();
- if (ToolKits.GetDevConfig(hLoginHandle, channel, NetSDKLib.CFG_CMD_CHANNELTITLE, channelTitleName)) {
+ if (ToolKits.GetDevConfig(hLoginHandle, channel - 1, NetSDKLib.CFG_CMD_CHANNELTITLE, channelTitleName)) {
try {
channelName = new String(channelTitleName.szName, "GBK");
} catch (Exception e) {
@@ -95,6 +97,7 @@
}
return channelName;
}
+
public static boolean GetDevConfig(NetSDKLib.LLong hLoginHandle, int nChn, String strCmd, Structure cmdObject) {
boolean result = true;
IntByReference error = new IntByReference(0);
@@ -111,6 +114,7 @@
return result;
}
+ //查询相机状态
public static boolean queryCameraState(NetSDKLib.LLong hLoginHandle, Integer chanNum, Integer chanNo) {
boolean bRet = false;
try {
@@ -141,9 +145,9 @@
stOut.read();
ToolKits.GetPointerDataToStructArr(stOut.pCameraStateInfo, arrCameraStatus); // 将Pointer拷贝到数组内存
final String[] connectionState = {"未知", "正在连接", "已连接", "未连接", "通道未配置,无信息", "通道有配置,但被禁用"};
- log.debug(connectionState[arrCameraStatus[chanNo - 1].emConnectionState]);
+
if (connectionState[arrCameraStatus[chanNo - 1].emConnectionState].equals("已连接")) {
- log.debug("通道" + arrCameraStatus[chanNo - 1].nChannel + connectionState[arrCameraStatus[chanNo - 1].emConnectionState]);
+ //log.debug("通道:" + arrCameraStatus[chanNo - 1].nChannel + "状态:" + connectionState[arrCameraStatus[chanNo - 1].emConnectionState]);
bRet = true;
} else {
bRet = false;
@@ -205,6 +209,5 @@
return result;
}
-
}
diff --git a/ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/service/impl/DahuaSDK.java b/ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/service/impl/DahuaSDK.java
index 3957ce6..be1a771 100644
--- a/ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/service/impl/DahuaSDK.java
+++ b/ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/service/impl/DahuaSDK.java
@@ -104,7 +104,7 @@
NetSDKLib.LLong loginId = LoginModule.login(camera.getIp(), camera.getPort(), camera.getUsername(), camera.getPassword(), m_stDeviceInfo);
if (loginId.longValue() <= 0) {
camera.setChanNum(0);
- camera.setLoginId(-1);
+ camera.setLoginId(-1l);
camera.setState("0");
ardCamerasService.updateArdCameras(camera);
//删除管理通道
@@ -112,11 +112,11 @@
log.error("设备[" + camera.getIp() + ":" + camera.getPort() + "]登录失败:" + getErrorCodePrint());
return AjaxResult.warn(ErrorCode.getErrorCode(LoginModule.netsdk.CLIENT_GetLastError()));
}
-
+ log.debug("设备[" + camera.getIp() + ":" + camera.getPort() + "]登录成功:" + (int) loginId.longValue());
camera.setState("1");
camera.setChanNum(m_stDeviceInfo.byChanNum);
- camera.setStartDChan(1);
- camera.setLoginId((int) loginId.longValue());
+ camera.setStartChan(1);
+ camera.setLoginId(loginId.longValue());
GlobalVariable.loginMap.put(camera.getId(), loginId);
//获取最新通道
List<ArdChannel> ardChannelList = getChannels(camera);
@@ -125,12 +125,11 @@
ardChannelList.stream().forEach(channel -> {
ardChannelService.insertArdChannel(channel);
});
- camera.setChanNum(ardChannelList.size());
camera.setChannelList(ardChannelList);
- ardCamerasService.updateArdCameras(camera);
- //配置到流媒体
- addVtdu(camera);
+ //通道批量添加到流媒体
+ batchAddVtdu(camera);
}
+ ardCamerasService.updateArdCameras(camera);
//创建引导队列
createGuideQueue(camera);
return AjaxResult.success("设备登录成功");
@@ -149,7 +148,7 @@
NetSDKLib.LLong loginId = LoginModule.login(camera.getIp(), camera.getPort(), camera.getUsername(), camera.getPassword(), m_stDeviceInfo);
if (loginId.longValue() <= 0) {
camera.setChanNum(0);
- camera.setLoginId(-1);
+ camera.setLoginId(-1l);
camera.setState("0");
ardCamerasService.updateArdCameras(camera);
//删除管理通道
@@ -157,10 +156,11 @@
log.error("设备[" + camera.getIp() + ":" + camera.getPort() + "]登录失败:" + getErrorCodePrint());
return AjaxResult.warn(getErrorCodePrint());
}
+ log.debug("设备[" + camera.getIp() + ":" + camera.getPort() + "]登录成功:" + loginId);
camera.setState("1");
camera.setChanNum(m_stDeviceInfo.byChanNum);
- camera.setStartDChan(1);
- camera.setLoginId((int) loginId.longValue());
+ camera.setStartChan(1);
+ camera.setLoginId((Long) loginId.longValue());
ardCamerasService.updateArdCameras(camera);
GlobalVariable.loginMap.put(camera.getId(), loginId);
@@ -171,12 +171,11 @@
ardChannelList.stream().forEach(channel -> {
ardChannelService.insertArdChannel(channel);
});
- camera.setChanNum(ardChannelList.size());
camera.setChannelList(ardChannelList);
- ardCamerasService.updateArdCameras(camera);
- //配置到流媒体
- addVtdu(camera);
+ //通道批量添加到流媒体
+ batchAddVtdu(camera);
}
+ ardCamerasService.updateArdCameras(camera);
//创建引导队列
createGuideQueue(camera);
return AjaxResult.success("登录成功");
@@ -186,35 +185,13 @@
}
}
- //添加到流媒体
- private void addVtdu(ArdCameras camera) {
+ //通道批量添加到流媒体
+ public void batchAddVtdu(ArdCameras camera) {
camera.getChannelList().stream().forEach(channel -> {
- String name = camera.getId() + "_" + channel.getChanNo();
- String rtspSource = "rtsp://" + camera.getUsername() + ":" + camera.getPassword() + "@" + camera.getIp() + ":" + camera.getRtspPort() + "/cam/realmonitor?channel=" + channel.getChanNo() + "&subtype=0";
- //删除流媒体
- if (vtduService.selectVtduByName(name) != null) {
- vtduService.deleteVtduByName(name);
- }
- //添加到流媒体
- CameraCmd cmd = new CameraCmd(camera.getId(), channel.getChanNo());
- Map<String, Object> videoCompressionCfg = getVideoCompressionCfg(cmd);
- Vtdu vtdu = new Vtdu();
- if (videoCompressionCfg.get("videoEncType") != null) {
- if (videoCompressionCfg.get("videoEncType").equals("标准h264")) {
- vtdu.setIsCode("0");//默认不转码
- } else {
- vtdu.setIsCode("1");//默认转码
- }
- } else {
- vtdu.setIsCode("0");//默认不转码
- }
- vtdu.setRtspSource(rtspSource);
- vtdu.setName(name);
- vtdu.setMode("1");//默认CPU软解码
- vtdu.setCameraId(camera.getId());
- vtduService.insertVtdu(vtdu);
+ vtduService.addChanToVtdu(camera, channel);
});
}
+
//创建引导队列
private void createGuideQueue(ArdCameras camera) {
@@ -231,12 +208,14 @@
//获取通道
public List<ArdChannel> getChannels(ArdCameras camera) {
+ if (camera.getLoginId().equals(-1)) {
+ return new ArrayList<>();
+ }
LLong loginId = new LLong(camera.getLoginId());
List<ArdChannel> ardChannelList = new ArrayList<>();
for (int i = 1; i < camera.getChanNum() + 1; i++) {
ArdChannel channel = new ArdChannel();
- String chanName = ConfigModule.getChannelName(loginId, i - 1).trim();
- log.debug("获取通道名称:" + chanName);
+ String chanName = ConfigModule.getChannelName(loginId, i).trim();
channel.setName(chanName.equals("") ? "通道" + i : chanName);
channel.setDeviceId(camera.getId());
channel.setChanNo(i);
diff --git a/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/lib/LoginResultCallBack.java b/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/lib/LoginResultCallBack.java
index 8e05ebe..b1ecad8 100644
--- a/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/lib/LoginResultCallBack.java
+++ b/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/lib/LoginResultCallBack.java
@@ -48,10 +48,10 @@
if (dwResult == 1) {
GlobalVariable.loginMap.put(camera.getId(), lUserID);
log.debug(camera.getIp() + ":" + camera.getPort() + "登录成功");
- camera.setLoginId(lUserID);
+ camera.setLoginId((long)lUserID);
camera.setState("1");
camera.setChanNum((int) lpDeviceinfo.byChanNum);
- camera.setStartDChan((int) lpDeviceinfo.byStartDChan);
+ camera.setStartChan((int) lpDeviceinfo.byStartDChan);
//获取最新通道
List<ArdChannel> cameraChannelList = hikClientService.getChannels(camera);
if (cameraChannelList.size() > 0) {
@@ -89,7 +89,7 @@
} else {
log.debug(camera.getIp() + ":" + camera.getPort() + "登录失败");
camera.setChanNum(0);
- camera.setLoginId(-1);
+ camera.setLoginId(-1l);
camera.setState("0");
}
ardCamerasService.updateArdCameras(camera);
diff --git a/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/service/impl/HikvisionSDK.java b/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/service/impl/HikvisionSDK.java
index 0269e99..36d5d94 100644
--- a/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/service/impl/HikvisionSDK.java
+++ b/ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/service/impl/HikvisionSDK.java
@@ -94,7 +94,7 @@
String WIN_PATH = System.getProperty("user.dir") + File.separator + "ardLog" + File.separator + "logs" + File.separator;
hCNetSDK.NET_DVR_SetLogToFile(3, WIN_PATH, true);
} else {
- hCNetSDK.NET_DVR_SetLogToFile(3, "/home/ardLog/hiklog" , true);
+ hCNetSDK.NET_DVR_SetLogToFile(3, "/home/ardLog/hiklog", true);
}
String m_sDeviceIP = camera.getIp();
String m_sUsername = camera.getUsername();
@@ -125,7 +125,7 @@
if (lUserID < 0) {
int errorCode = hCNetSDK.NET_DVR_GetLastError();
camera.setChanNum(0);
- camera.setLoginId(-1);
+ camera.setLoginId(-1l);
camera.setState("0");
//删除管理通道
ardChannelService.deleteArdChannelByDeviceId(camera.getId());
@@ -146,7 +146,7 @@
}
GlobalVariable.loginMap.put(camera.getId(), lUserID);
GlobalVariable.loginCameraMap.put(lUserID, camera);
- camera.setLoginId(lUserID);
+ camera.setLoginId((long)lUserID);
camera.setState("1");
int chanNum = m_strDeviceInfo.struDeviceV30.byChanNum;
int startDchan = m_strDeviceInfo.struDeviceV30.byStartDChan + 1;
@@ -155,7 +155,7 @@
startDchan = m_strDeviceInfo.struDeviceV30.byStartDChan;
}
camera.setChanNum(chanNum);
- camera.setStartDChan(startDchan);
+ camera.setStartChan(startDchan);
//获取最新通道
List<ArdChannel> cameraChannelList = getChannels(camera);
if (cameraChannelList.size() > 0) {
@@ -164,16 +164,16 @@
ardChannelService.insertArdChannel(channel);
});
camera.setChannelList(cameraChannelList);
- camera.setChanNum(cameraChannelList.size());
- ardCamerasService.updateArdCameras(camera);
- //添加到流媒体
- addVtdu(camera);
+ //camera.setChanNum(cameraChannelList.size());
+ //通道批量添加到流媒体
+ batchAddVtdu(camera);
}
+ ardCamerasService.updateArdCameras(camera);
//创建引导队列
createGuideQueue(camera);
return AjaxResult.success("设备登录成功");
} catch (Exception ex) {
- log.error("设备登录异常" , ex);
+ log.error("设备登录异常", ex);
return AjaxResult.error("设备登录异常" + ex.getMessage());
}
}
@@ -200,7 +200,7 @@
String WIN_PATH = System.getProperty("user.dir") + File.separator + "ardLog" + File.separator + "logs" + File.separator;
hCNetSDK.NET_DVR_SetLogToFile(3, WIN_PATH, true);
} else {
- hCNetSDK.NET_DVR_SetLogToFile(3, "/home/ardLog/hiklog" , true);
+ hCNetSDK.NET_DVR_SetLogToFile(3, "/home/ardLog/hiklog", true);
}
String m_sDeviceIP = camera.getIp();
String m_sUsername = camera.getUsername();
@@ -231,7 +231,7 @@
if (lUserID < 0) {
int errorCode = hCNetSDK.NET_DVR_GetLastError();
camera.setChanNum(0);
- camera.setLoginId(-1);
+ camera.setLoginId(-1l);
camera.setState("0");
//删除管理通道
ardChannelService.deleteArdChannelByDeviceId(camera.getId());
@@ -254,7 +254,7 @@
GlobalVariable.loginMap.put(camera.getId(), lUserID);
GlobalVariable.loginCameraMap.put(lUserID, camera);
- camera.setLoginId(lUserID);
+ camera.setLoginId((long)lUserID);
camera.setState("1");
int chanNum = m_strDeviceInfo.struDeviceV30.byChanNum;
int startDchan = m_strDeviceInfo.struDeviceV30.byStartDChan + 1;
@@ -263,7 +263,7 @@
startDchan = m_strDeviceInfo.struDeviceV30.byStartDChan;
}
camera.setChanNum(chanNum);
- camera.setStartDChan(startDchan);
+ camera.setStartChan(startDchan);
//获取最新通道
List<ArdChannel> cameraChannelList = getChannels(camera);
if (cameraChannelList.size() > 0) {
@@ -272,16 +272,16 @@
ardChannelService.insertArdChannel(channel);
});
camera.setChannelList(cameraChannelList);
- camera.setChanNum(cameraChannelList.size());
- ardCamerasService.updateArdCameras(camera);
- //添加到流媒体
- addVtdu(camera);
+ //camera.setChanNum(cameraChannelList.size());
+ //通道批量添加到流媒体
+ batchAddVtdu(camera);
}
+ ardCamerasService.updateArdCameras(camera);
//创建引导队列
createGuideQueue(camera);
return AjaxResult.success("设备登录成功");
} catch (Exception ex) {
- log.error("注册设备异常" , ex);
+ log.error("注册设备异常", ex);
return AjaxResult.error("注册设备异常" + ex.getMessage());
}
}
@@ -299,36 +299,19 @@
}
}
- //添加到流媒体
- private void addVtdu(ArdCameras camera) {
+ //通道批量添加到流媒体
+ public void batchAddVtdu(ArdCameras camera) {
try {
- camera.getChannelList().stream().forEach(channel->{
- String name = camera.getId() + "_" + channel.getChanNo();
- String rtspSource = "rtsp://" + camera.getUsername() + ":" + camera.getPassword() + "@" + camera.getIp() + ":" + camera.getRtspPort() + "/h264/ch" + channel.getChanNo() + "/main/av_stream";
- //删除流媒体
- if (vtduService.selectVtduByName(name) != null) {
- vtduService.deleteVtduByName(name);
- }
- //添加到流媒体
- Vtdu vtdu = new Vtdu();
- vtdu.setRtspSource(rtspSource);
- vtdu.setName(name);
- CameraCmd cmd = new CameraCmd(camera.getId(), channel.getChanNo());
- Map<String, Object> videoCompressionCfg = getVideoCompressionCfg(cmd);
- if (videoCompressionCfg.get("videoEncType").equals("标准h264")) {
- vtdu.setIsCode("0");//默认不转码
- } else {
- vtdu.setIsCode("1");//默认转码
- }
- vtdu.setMode("1");//默认CPU软解码
- vtdu.setCameraId(camera.getId());
- vtduService.insertVtdu(vtdu);
+ camera.getChannelList().stream().forEach(channel -> {
+ vtduService.addChanToVtdu(camera, channel);
});
} catch (Exception ex) {
log.error("通道添加到流媒体异常:" + ex.getMessage());
}
}
+
+
/**
* @描述 注销登录
@@ -875,10 +858,10 @@
nFrameRate = "未知";
break;
}
- map.put("resolution" , resolution);//分辨率
- map.put("videoBitrate" , videoBitrate);//比特率
- map.put("videoEncType" , videoEncType);//编码
- map.put("nFrameRate" , nFrameRate);//帧率
+ map.put("resolution", resolution);//分辨率
+ map.put("videoBitrate", videoBitrate);//比特率
+ map.put("videoEncType", videoEncType);//编码
+ map.put("nFrameRate", nFrameRate);//帧率
} else {
int code = hCNetSDK.NET_DVR_GetLastError();
@@ -931,9 +914,9 @@
double z = d.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();
//log.debug("T垂直参数为: " + p + "P水平参数为: " + t + "Z变倍参数为: " + z);
Map<String, Object> ptzMap = new HashMap<>();
- ptzMap.put("p" , p);
- ptzMap.put("t" , t);
- ptzMap.put("z" , z);
+ ptzMap.put("p", p);
+ ptzMap.put("t", t);
+ ptzMap.put("z", z);
return AjaxResult.success(ptzMap);
}
@@ -980,9 +963,9 @@
float fTilt = lpPTZAbsoluteEX_cfg.struPTZCtrl.fTilt;
float t = fTilt < 0 ? fTilt + 360 : fTilt;
float z = lpPTZAbsoluteEX_cfg.struPTZCtrl.fZoom;
- ptzMap.put("p" , p);
- ptzMap.put("t" , t);
- ptzMap.put("z" , z);
+ ptzMap.put("p", p);
+ ptzMap.put("t", t);
+ ptzMap.put("z", z);
return AjaxResult.success(ptzMap);
} catch (Exception ex) {
log.error("获取高精度PTZ绝对位置异常:" + ex.getMessage());
@@ -1087,7 +1070,7 @@
return AjaxResult.success("设置高精度PTZ参数成功");
} catch (Exception ex) {
- log.error("设置高精度PTZ参数异常" , ex);
+ log.error("设置高精度PTZ参数异常", ex);
return AjaxResult.error("设置高精度PTZ参数异常:" + ex);
}
}
@@ -1125,7 +1108,7 @@
log.error("设置ptz失败,请稍后重试" + code);
return AjaxResult.warn("设置ptz失败:" + SdkErrorCodeEnum.getDescByCode(code) + "(" + code + ")");
}
- return AjaxResult.success("引导坐标成功",correctPitch);
+ return AjaxResult.success("引导坐标成功", correctPitch);
} catch (Exception ex) {
log.error("引导坐标异常:" + ex.getMessage());
return AjaxResult.error("引导坐标异常:" + ex.getMessage());
@@ -1234,12 +1217,12 @@
String wZoomPosMax = df.format((float) Integer.parseInt(Integer.toHexString(m_ptzPosCurrent.wZoomPosMax)) / 10);
String wZoomPosMin = df.format((float) Integer.parseInt(Integer.toHexString(m_ptzPosCurrent.wZoomPosMin)) / 10);
Map<String, Object> ptzScopeMap = new HashMap<>();
- ptzScopeMap.put("pMax" , wPanPosMax);
- ptzScopeMap.put("pMin" , wPanPosMin);
- ptzScopeMap.put("tMax" , wTiltPosMax);
- ptzScopeMap.put("tMin" , wTiltPosMin);
- ptzScopeMap.put("zMax" , wZoomPosMax);
- ptzScopeMap.put("zMin" , wZoomPosMin);
+ ptzScopeMap.put("pMax", wPanPosMax);
+ ptzScopeMap.put("pMin", wPanPosMin);
+ ptzScopeMap.put("tMax", wTiltPosMax);
+ ptzScopeMap.put("tMin", wTiltPosMin);
+ ptzScopeMap.put("zMax", wZoomPosMax);
+ ptzScopeMap.put("zMin", wZoomPosMin);
return AjaxResult.success(ptzScopeMap);
}
}
@@ -1335,7 +1318,7 @@
boolean bool = hCNetSDK.NET_DVR_SetDVRConfig(userId, NET_DVR_SET_CCDPARAMCFG, chanNo, point, struDayNigh.size());
if (!bool) {
int code = hCNetSDK.NET_DVR_GetLastError();
- log.error("切换红外失败 ErrorCode:{},ErrorInfo:{}" , code, SdkErrorCodeEnum.getDescByCode(code));
+ log.error("切换红外失败 ErrorCode:{},ErrorInfo:{}", code, SdkErrorCodeEnum.getDescByCode(code));
return AjaxResult.warn("切换红外失败:" + SdkErrorCodeEnum.getDescByCode(code) + "(" + code + ")");
}
log.debug("切换红外成功");
@@ -1848,6 +1831,9 @@
//获取IP通道
public List<ArdChannel> getChannels(ArdCameras camera) {
+ if (camera.getLoginId().equals(-1)) {
+ return new ArrayList<>();
+ }
//获取通道
List<ArdChannel> channelList = new ArrayList<>();
try {
@@ -1856,19 +1842,19 @@
m_strIpparaCfg.write();
//lpIpParaConfig 接收数据的缓冲指针
Pointer lpIpParaConfig = m_strIpparaCfg.getPointer();
- boolean bRet = hCNetSDK.NET_DVR_GetDVRConfig(camera.getLoginId(), HCNetSDK.NET_DVR_GET_IPPARACFG_V40, 0, lpIpParaConfig, m_strIpparaCfg.size(), ibrBytesReturned);
+ boolean bRet = hCNetSDK.NET_DVR_GetDVRConfig(camera.getLoginId().intValue(), HCNetSDK.NET_DVR_GET_IPPARACFG_V40, 0, lpIpParaConfig, m_strIpparaCfg.size(), ibrBytesReturned);
m_strIpparaCfg.read();
//log.debug("起始数字通道号:" + m_strIpparaCfg.dwStartDChan);//m_strIpparaCfg.dwDChanNum
for (int iChannum = 0; iChannum < camera.getChanNum(); iChannum++) {
ArdChannel channel = new ArdChannel();
- int chanNo = iChannum + camera.getStartDChan();
+ int chanNo = iChannum + camera.getStartChan();
HCNetSDK.NET_DVR_PICCFG_V40 strPicCfg = new HCNetSDK.NET_DVR_PICCFG_V40();
strPicCfg.dwSize = strPicCfg.size();
strPicCfg.write();
Pointer pStrPicCfg = strPicCfg.getPointer();
NativeLong lChannel = new NativeLong(chanNo);
IntByReference pInt = new IntByReference(0);
- boolean b_GetPicCfg = hCNetSDK.NET_DVR_GetDVRConfig(camera.getLoginId(), HCNetSDK.NET_DVR_GET_PICCFG_V40, lChannel.intValue(), pStrPicCfg, strPicCfg.size(), pInt);
+ boolean b_GetPicCfg = hCNetSDK.NET_DVR_GetDVRConfig(camera.getLoginId().intValue(), HCNetSDK.NET_DVR_GET_PICCFG_V40, lChannel.intValue(), pStrPicCfg, strPicCfg.size(), pInt);
if (!b_GetPicCfg) {
// log.error("获取图像参数失败,错误码:" + hCNetSDK.NET_DVR_GetLastError());
}
@@ -1932,11 +1918,11 @@
}
struGisInfo.read();
Map<String, Object> map = new HashMap<>();
- map.put("p" , struGisInfo.struPtzPos.fPanPos);
- map.put("t" , struGisInfo.struPtzPos.fTiltPos < 0 ? struGisInfo.struPtzPos.fTiltPos + 360 : struGisInfo.struPtzPos.fTiltPos);
- map.put("z" , struGisInfo.struPtzPos.fZoomPos);
- map.put("fHorFieldAngle" , struGisInfo.fHorizontalValue);// 水平视场角
- map.put("fVerFieldAngle" , struGisInfo.fVerticalValue);// 垂直视场角
+ map.put("p", struGisInfo.struPtzPos.fPanPos);
+ map.put("t", struGisInfo.struPtzPos.fTiltPos < 0 ? struGisInfo.struPtzPos.fTiltPos + 360 : struGisInfo.struPtzPos.fTiltPos);
+ map.put("z", struGisInfo.struPtzPos.fZoomPos);
+ map.put("fHorFieldAngle", struGisInfo.fHorizontalValue);// 水平视场角
+ map.put("fVerFieldAngle", struGisInfo.fVerticalValue);// 垂直视场角
return AjaxResult.success(map);
}
@@ -1996,7 +1982,7 @@
return AjaxResult.warn("本地录像取流失败:" + SdkErrorCodeEnum.getDescByCode(code) + "(" + code + ")");
}
log.debug("本地录像开始");
- return AjaxResult.success("录像开始" , lRealHandle);
+ return AjaxResult.success("录像开始", lRealHandle);
} catch (Exception ex) {
log.error("本地录像开始异常" + ex.getMessage());
return AjaxResult.error("本地录像开始异常" + ex.getMessage());
diff --git a/ard-work/src/main/resources/mapper/device/ArdCamerasMapper.xml b/ard-work/src/main/resources/mapper/device/ArdCamerasMapper.xml
index 78083d2..318e511 100644
--- a/ard-work/src/main/resources/mapper/device/ArdCamerasMapper.xml
+++ b/ard-work/src/main/resources/mapper/device/ArdCamerasMapper.xml
@@ -15,6 +15,7 @@
<result property="gdtype" column="gdtype"/>
<result property="factory" column="factory"/>
<result property="chanNum" column="channel"/>
+ <result property="startChan" column="start_chan"/>
<result property="longitude" column="longitude"/>
<result property="latitude" column="latitude"/>
<result property="altitude" column="altitude"/>
@@ -48,6 +49,7 @@
c.gdtype,
c.factory,
c.channel,
+ c.start_chan,
c.longitude,
c.latitude,
c.altitude,
@@ -86,6 +88,7 @@
<if test="gdtype != null and gdtype != ''">and c.gdtype = #{gdtype}</if>
<if test="factory != null and factory != ''">and c.factory = #{factory}</if>
<if test="chanNum != null ">and c.channel = #{chanNum}</if>
+ <if test="startChan != null ">and c.start_chan = #{startChan}</if>
<if test="longitude != null ">and c.longitude = #{longitude}</if>
<if test="latitude != null ">and c.latitude = #{latitude}</if>
<if test="altitude != null ">and c.altitude = #{altitude}</if>
@@ -123,6 +126,7 @@
<if test="gdtype != null and gdtype != ''">and c.gdtype = #{gdtype}</if>
<if test="factory != null and factory != ''">and c.factory = #{factory}</if>
<if test="chanNum != null ">and c.channel = #{chanNum}</if>
+ <if test="startChan != null ">and c.start_chan= #{startChan}</if>
<if test="longitude != null ">and c.longitude = #{longitude}</if>
<if test="latitude != null ">and c.latitude = #{latitude}</if>
<if test="altitude != null ">and c.altitude = #{altitude}</if>
@@ -163,6 +167,7 @@
<if test="factory != null">factory,</if>
<if test="towerId != null">tower_id,</if>
<if test="chanNum != null">channel,</if>
+ <if test="startChan != null">start_chan,</if>
<if test="longitude != null">longitude,</if>
<if test="latitude != null">latitude,</if>
<if test="altitude != null">altitude,</if>
@@ -197,6 +202,7 @@
<if test="factory != null">#{factory},</if>
<if test="towerId != null">#{towerId},</if>
<if test="chanNum != null">#{chanNum},</if>
+ <if test="startChan != null">#{startChan},</if>
<if test="longitude != null">#{longitude},</if>
<if test="latitude != null">#{latitude},</if>
<if test="altitude != null">#{altitude},</if>
@@ -234,6 +240,7 @@
<if test="factory != null">factory = #{factory},</if>
<if test="towerId != null">tower_id = #{towerId},</if>
<if test="chanNum != null">channel = #{chanNum},</if>
+ <if test="startChan != null">start_chan = #{startChan},</if>
<if test="longitude != null">longitude = #{longitude},</if>
<if test="latitude != null">latitude = #{latitude},</if>
<if test="altitude != null">altitude = #{altitude},</if>
diff --git a/ard-work/src/main/resources/templates/test.html b/ard-work/src/main/resources/templates/test.html
index 5623b4b..e805ee4 100644
--- a/ard-work/src/main/resources/templates/test.html
+++ b/ard-work/src/main/resources/templates/test.html
@@ -916,10 +916,32 @@
let webrtcClient;
//whep操作方法
- const restartPause = 2000;
+ const retryPause = 2000;
+
+ const video = document.getElementById('video');
+ const message = document.getElementById('message');
+
+ let nonAdvertisedCodecs = [];
+ let pc = null;
+ let restartTimeout = null;
+ let sessionUrl = '';
+ let offerData = '';
+ let queuedCandidates = [];
+ let defaultControls = false;
+
+ const setMessage = (str) => {
+ if (str !== '') {
+ video.controls = false;
+ } else {
+ video.controls = defaultControls;
+ }
+ message.innerText = str;
+ };
+
const unquoteCredential = (v) => (
JSON.parse(`"${v}"`)
);
+
const linkToIceServers = (links) => (
(links !== null) ? links.split(', ').map((link) => {
const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
@@ -930,20 +952,21 @@
if (m[3] !== undefined) {
ret.username = unquoteCredential(m[3]);
ret.credential = unquoteCredential(m[4]);
- ret.credentialType = "password";
+ ret.credentialType = 'password';
}
return ret;
}) : []
);
- const parseOffer = (offer) => {
+
+ const parseOffer = (sdp) => {
const ret = {
iceUfrag: '',
icePwd: '',
medias: [],
};
- for (const line of offer.split('\r\n')) {
+ for (const line of sdp.split('\r\n')) {
if (line.startsWith('m=')) {
ret.medias.push(line.slice('m='.length));
} else if (ret.iceUfrag === '' && line.startsWith('a=ice-ufrag:')) {
@@ -955,7 +978,131 @@
return ret;
};
- const generateSdpFragment = (offerData, candidates) => {
+
+ const enableStereoPcmau = (section) => {
+ let lines = section.split('\r\n');
+
+ lines[0] += ' 118';
+ lines.splice(lines.length - 1, 0, 'a=rtpmap:118 PCMU/8000/2');
+ lines.splice(lines.length - 1, 0, 'a=rtcp-fb:118 transport-cc');
+
+ lines[0] += ' 119';
+ lines.splice(lines.length - 1, 0, 'a=rtpmap:119 PCMA/8000/2');
+ lines.splice(lines.length - 1, 0, 'a=rtcp-fb:119 transport-cc');
+
+ return lines.join('\r\n');
+ };
+
+ const enableMultichannelOpus = (section) => {
+ let lines = section.split('\r\n');
+
+ lines[0] += " 112";
+ lines.splice(lines.length - 1, 0, "a=rtpmap:112 multiopus/48000/3");
+ lines.splice(lines.length - 1, 0, "a=fmtp:112 channel_mapping=0,2,1;num_streams=2;coupled_streams=1");
+ lines.splice(lines.length - 1, 0, "a=rtcp-fb:112 transport-cc");
+
+ lines[0] += " 113";
+ lines.splice(lines.length - 1, 0, "a=rtpmap:113 multiopus/48000/4");
+ lines.splice(lines.length - 1, 0, "a=fmtp:113 channel_mapping=0,1,2,3;num_streams=2;coupled_streams=2");
+ lines.splice(lines.length - 1, 0, "a=rtcp-fb:113 transport-cc");
+
+ lines[0] += " 114";
+ lines.splice(lines.length - 1, 0, "a=rtpmap:114 multiopus/48000/5");
+ lines.splice(lines.length - 1, 0, "a=fmtp:114 channel_mapping=0,4,1,2,3;num_streams=3;coupled_streams=2");
+ lines.splice(lines.length - 1, 0, "a=rtcp-fb:114 transport-cc");
+
+ lines[0] += " 115";
+ lines.splice(lines.length - 1, 0, "a=rtpmap:115 multiopus/48000/6");
+ lines.splice(lines.length - 1, 0, "a=fmtp:115 channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2");
+ lines.splice(lines.length - 1, 0, "a=rtcp-fb:115 transport-cc");
+
+ lines[0] += " 116";
+ lines.splice(lines.length - 1, 0, "a=rtpmap:116 multiopus/48000/7");
+ lines.splice(lines.length - 1, 0, "a=fmtp:116 channel_mapping=0,4,1,2,3,5,6;num_streams=4;coupled_streams=4");
+ lines.splice(lines.length - 1, 0, "a=rtcp-fb:116 transport-cc");
+
+ lines[0] += " 117";
+ lines.splice(lines.length - 1, 0, "a=rtpmap:117 multiopus/48000/8");
+ lines.splice(lines.length - 1, 0, "a=fmtp:117 channel_mapping=0,6,1,4,5,2,3,7;num_streams=5;coupled_streams=4");
+ lines.splice(lines.length - 1, 0, "a=rtcp-fb:117 transport-cc");
+
+ return lines.join('\r\n');
+ };
+
+ const enableL16 = (section) => {
+ let lines = section.split('\r\n');
+
+ lines[0] += " 120";
+ lines.splice(lines.length - 1, 0, "a=rtpmap:120 L16/8000/2");
+ lines.splice(lines.length - 1, 0, "a=rtcp-fb:120 transport-cc");
+
+ lines[0] += " 121";
+ lines.splice(lines.length - 1, 0, "a=rtpmap:121 L16/16000/2");
+ lines.splice(lines.length - 1, 0, "a=rtcp-fb:121 transport-cc");
+
+ lines[0] += " 122";
+ lines.splice(lines.length - 1, 0, "a=rtpmap:122 L16/48000/2");
+ lines.splice(lines.length - 1, 0, "a=rtcp-fb:122 transport-cc");
+
+ return lines.join('\r\n');
+ };
+
+ const enableStereoOpus = (section) => {
+ let opusPayloadFormat = '';
+ let lines = section.split('\r\n');
+
+ for (let i = 0; i < lines.length; i++) {
+ if (lines[i].startsWith('a=rtpmap:') && lines[i].toLowerCase().includes('opus/')) {
+ opusPayloadFormat = lines[i].slice('a=rtpmap:'.length).split(' ')[0];
+ break;
+ }
+ }
+
+ if (opusPayloadFormat === '') {
+ return section;
+ }
+
+ for (let i = 0; i < lines.length; i++) {
+ if (lines[i].startsWith('a=fmtp:' + opusPayloadFormat + ' ')) {
+ if (!lines[i].includes('stereo')) {
+ lines[i] += ';stereo=1';
+ }
+ if (!lines[i].includes('sprop-stereo')) {
+ lines[i] += ';sprop-stereo=1';
+ }
+ }
+ }
+
+ return lines.join('\r\n');
+ };
+
+ const editOffer = (sdp) => {
+ const sections = sdp.split('m=');
+
+ for (let i = 0; i < sections.length; i++) {
+ if (sections[i].startsWith('audio')) {
+ sections[i] = enableStereoOpus(sections[i]);
+
+ if (nonAdvertisedCodecs.includes('pcma/8000/2')) {
+ sections[i] = enableStereoPcmau(sections[i]);
+ }
+
+ if (nonAdvertisedCodecs.includes('multiopus/48000/6')) {
+ sections[i] = enableMultichannelOpus(sections[i]);
+ }
+
+ if (nonAdvertisedCodecs.includes('L16/48000/2')) {
+ sections[i] = enableL16(sections[i]);
+ }
+
+ break;
+ }
+ }
+
+ return sections.join('m=');
+ };
+
+ const generateSdpFragment = (od, candidates) => {
const candidatesByMedia = {};
for (const candidate of candidates) {
const mid = candidate.sdpMLineIndex;
@@ -965,12 +1112,12 @@
candidatesByMedia[mid].push(candidate);
}
- let frag = 'a=ice-ufrag:' + offerData.iceUfrag + '\r\n'
- + 'a=ice-pwd:' + offerData.icePwd + '\r\n';
+ let frag = 'a=ice-ufrag:' + od.iceUfrag + '\r\n'
+ + 'a=ice-pwd:' + od.icePwd + '\r\n';
let mid = 0;
- for (const media of offerData.medias) {
+ for (const media of od.medias) {
if (candidatesByMedia[mid] !== undefined) {
frag += 'm=' + media + '\r\n'
+ 'a=mid:' + mid + '\r\n';
@@ -983,174 +1130,269 @@
}
return frag;
- }
+ };
- class WHEPClient {
- constructor(whepUrl, videoId) {
- this.video = videoId;
- this.wurl = new URL('whep', whepUrl);
- this.pc = null;
- this.restartTimeout = null;
- this.eTag = '';
- this.queuedCandidates = [];
- this.start();
- }
+ const loadStream = () => {
+ requestICEServers();
+ };
- start() {
- console.log("requesting ICE servers");
- fetch(this.wurl, {
- method: 'OPTIONS',
- })
- .then((res) => this.onIceServers(res))
+ const supportsNonAdvertisedCodec = (codec, fmtp) => (
+ new Promise((resolve, reject) => {
+ const pc = new RTCPeerConnection({ iceServers: [] });
+ pc.addTransceiver('audio', { direction: 'recvonly' });
+ pc.createOffer()
+ .then((offer) => {
+ if (offer.sdp.includes(' ' + codec)) { // codec is advertised, there's no need to add it manually
+ resolve(false);
+ return;
+ }
+ const sections = offer.sdp.split('m=audio');
+ const lines = sections[1].split('\r\n');
+ lines[0] += ' 118';
+ lines.splice(lines.length - 1, 0, 'a=rtpmap:118 ' + codec);
+ if (fmtp !== undefined) {
+ lines.splice(lines.length - 1, 0, 'a=fmtp:118 ' + fmtp);
+ }
+ sections[1] = lines.join('\r\n');
+ offer.sdp = sections.join('m=audio');
+ return pc.setLocalDescription(offer);
+ })
+ .then(() => {
+ return pc.setRemoteDescription(new RTCSessionDescription({
+ type: 'answer',
+ sdp: 'v=0\r\n'
+ + 'o=- 6539324223450680508 0 IN IP4 0.0.0.0\r\n'
+ + 's=-\r\n'
+ + 't=0 0\r\n'
+ + 'a=fingerprint:sha-256 0D:9F:78:15:42:B5:4B:E6:E2:94:3E:5B:37:78:E1:4B:54:59:A3:36:3A:E5:05:EB:27:EE:8F:D2:2D:41:29:25\r\n'
+ + 'm=audio 9 UDP/TLS/RTP/SAVPF 118\r\n'
+ + 'c=IN IP4 0.0.0.0\r\n'
+ + 'a=ice-pwd:7c3bf4770007e7432ee4ea4d697db675\r\n'
+ + 'a=ice-ufrag:29e036dc\r\n'
+ + 'a=sendonly\r\n'
+ + 'a=rtcp-mux\r\n'
+ + 'a=rtpmap:118 ' + codec + '\r\n'
+ + ((fmtp !== undefined) ? 'a=fmtp:118 ' + fmtp + '\r\n' : ''),
+ }));
+ })
+ .then(() => {
+ resolve(true);
+ })
.catch((err) => {
- console.log('error: ' + err);
- this.scheduleRestart();
+ resolve(false);
+ })
+ .finally(() => {
+ pc.close();
});
- }
+ })
+ );
- onIceServers(res) {
- this.pc = new RTCPeerConnection({
- iceServers: linkToIceServers(res.headers.get('Link')),
+ const getNonAdvertisedCodecs = () => {
+ Promise.all([
+ ['pcma/8000/2'],
+ ['multiopus/48000/6', 'channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2'],
+ ['L16/48000/2']
+ ].map((c) => supportsNonAdvertisedCodec(c[0], c[1]).then((r) => (r) ? c[0] : false)))
+ .then((c) => c.filter((e) => e !== false))
+ .then((codecs) => {
+ nonAdvertisedCodecs = codecs;
+ loadStream();
});
+ };
- const direction = "sendrecv";
- this.pc.addTransceiver("video", {direction});
- this.pc.addTransceiver("audio", {direction});
+ const onError = (err) => {
+ if (restartTimeout === null) {
+ setMessage(err + ', retrying in some seconds');
- this.pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
- this.pc.oniceconnectionstatechange = () => this.onConnectionState();
+ if (pc !== null) {
+ pc.close();
+ pc = null;
+ }
- this.pc.ontrack = (evt) => {
- console.log("new track:", evt.track.kind);
- document.getElementById(this.video).srcObject = evt.streams[0];
- };
+ restartTimeout = window.setTimeout(() => {
+ restartTimeout = null;
+ loadStream();
+ }, retryPause);
- this.pc.createOffer()
- .then((offer) => this.onLocalOffer(offer));
- }
-
- onLocalOffer(offer) {
- this.offerData = parseOffer(offer.sdp);
- this.pc.setLocalDescription(offer);
-
- console.log("sending offer");
- console.log(this.wurl);
- fetch(this.wurl, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/sdp',
- },
- body: offer.sdp,
- })
- .then((res) => {
- if (res.status !== 201) {
- throw new Error('bad status code');
- }
- // this.eTag = res.headers.get('ETag');
- this.eTag = res.headers.get("ETag") || res.headers.get('E-Tag');
- return res.text();
- })
- .then((sdp) => this.onRemoteAnswer(new RTCSessionDescription({
- type: 'answer',
- sdp,
- })))
- .catch((err) => {
- console.log('error: ' + err);
- this.scheduleRestart();
+ if (sessionUrl) {
+ fetch(sessionUrl, {
+ method: 'DELETE',
});
+ }
+ sessionUrl = '';
+
+ queuedCandidates = [];
}
+ };
- onConnectionState() {
- if (this.restartTimeout !== null) {
- return;
- }
-
- console.log("peer connection state:", this.pc.iceConnectionState);
-
- switch (this.pc.iceConnectionState) {
- case "disconnected":
- this.scheduleRestart();
- }
- }
-
- onRemoteAnswer(answer) {
- if (this.restartTimeout !== null) {
- return;
- }
-
- this.pc.setRemoteDescription(new RTCSessionDescription(answer));
-
- if (this.queuedCandidates.length !== 0) {
- this.sendLocalCandidates(this.queuedCandidates);
- this.queuedCandidates = [];
- }
- }
-
- onLocalCandidate(evt) {
- if (this.restartTimeout !== null) {
- return;
- }
-
- if (evt.candidate !== null) {
- if (this.eTag === '') {
- this.queuedCandidates.push(evt.candidate);
- } else {
- this.sendLocalCandidates([evt.candidate])
+ const sendLocalCandidates = (candidates) => {
+ fetch(sessionUrl + window.location.search, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/trickle-ice-sdpfrag',
+ 'If-Match': '*',
+ },
+ body: generateSdpFragment(offerData, candidates),
+ })
+ .then((res) => {
+ switch (res.status) {
+ case 204:
+ break;
+ case 404:
+ throw new Error('stream not found');
+ default:
+ throw new Error(`bad status code ${res.status}`);
}
- }
- }
-
- sendLocalCandidates(candidates) {
- fetch(this.wurl, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/trickle-ice-sdpfrag',
- 'If-Match': this.eTag,
- },
-
- body: generateSdpFragment(this.offerData, candidates),
})
- .then((res) => {
- if (res.status !== 204) {
- throw new Error('bad status code');
- }
- })
- .catch((err) => {
- console.log('error: ' + err);
- this.scheduleRestart();
- });
+ .catch((err) => {
+ onError(err.toString());
+ });
+ };
+
+ const onLocalCandidate = (evt) => {
+ if (restartTimeout !== null) {
+ return;
}
- scheduleRestart() {
- if (this.restartTimeout !== null) {
- return;
+ if (evt.candidate !== null) {
+ if (sessionUrl === '') {
+ queuedCandidates.push(evt.candidate);
+ } else {
+ sendLocalCandidates([evt.candidate])
}
+ }
+ };
- if (this.pc !== null) {
- this.pc.close();
- this.pc = null;
- }
-
- this.restartTimeout = window.setTimeout(() => {
- this.restartTimeout = null;
- this.start();
- }, restartPause);
-
- this.eTag = '';
- this.queuedCandidates = [];
+ const onRemoteAnswer = (sdp) => {
+ if (restartTimeout !== null) {
+ return;
}
- stop() {
- if (this.pc) {
- try {
- this.pc.close();
- } catch (e) {
- console.log("Failure close peer connection:" + e);
+ pc.setRemoteDescription(new RTCSessionDescription({
+ type: 'answer',
+ sdp,
+ }))
+ .then(() => {
+ if (queuedCandidates.length !== 0) {
+ sendLocalCandidates(queuedCandidates);
+ queuedCandidates = [];
}
- this.pc = null;
- }
+ })
+ .catch((err) => {
+ onError(err.toString());
+ });
+ };
+
+ const sendOffer = (offer) => {
+ fetch(new URL('whep', window.location.href) + window.location.search, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/sdp',
+ },
+ body: offer.sdp,
+ })
+ .then((res) => {
+ switch (res.status) {
+ case 201:
+ break;
+ case 404:
+ throw new Error('stream not found');
+ case 400:
+ return res.json().then((e) => { throw new Error(e.error); });
+ default:
+ throw new Error(`bad status code ${res.status}`);
+ }
+
+ sessionUrl = new URL(res.headers.get('location'), window.location.href).toString();
+
+ return res.text()
+ .then((sdp) => onRemoteAnswer(sdp));
+ })
+ .catch((err) => {
+ onError(err.toString());
+ });
+ };
+
+ const createOffer = () => {
+ pc.createOffer()
+ .then((offer) => {
+ offer.sdp = editOffer(offer.sdp);
+ offerData = parseOffer(offer.sdp);
+ pc.setLocalDescription(offer)
+ .then(() => {
+ sendOffer(offer);
+ })
+ .catch((err) => {
+ onError(err.toString());
+ });
+ })
+ .catch((err) => {
+ onError(err.toString());
+ });
+ };
+
+ const onConnectionState = () => {
+ if (restartTimeout !== null) {
+ return;
}
- }
+
+ if (pc.iceConnectionState === 'disconnected') {
+ onError('peer connection closed');
+ }
+ };
+
+ const onTrack = (evt) => {
+ setMessage('');
+ video.srcObject = evt.streams[0];
+ };
+
+ const requestICEServers = () => {
+ fetch(new URL('whep', window.location.href) + window.location.search, {
+ method: 'OPTIONS',
+ })
+ .then((res) => {
+ pc = new RTCPeerConnection({
+ iceServers: linkToIceServers(res.headers.get('Link')),
+ // https://webrtc.org/getting-started/unified-plan-transition-guide
+ sdpSemantics: 'unified-plan',
+ });
+
+ const direction = 'sendrecv';
+ pc.addTransceiver('video', { direction });
+ pc.addTransceiver('audio', { direction });
+
+ pc.onicecandidate = (evt) => onLocalCandidate(evt);
+ pc.oniceconnectionstatechange = () => onConnectionState();
+ pc.ontrack = (evt) => onTrack(evt);
+
+ createOffer();
+ })
+ .catch((err) => {
+ onError(err.toString());
+ });
+ };
+
+ const parseBoolString = (str, defaultVal) => {
+ str = (str || '');
+
+ if (['1', 'yes', 'true'].includes(str.toLowerCase())) {
+ return true;
+ }
+ if (['0', 'no', 'false'].includes(str.toLowerCase())) {
+ return false;
+ }
+ return defaultVal;
+ };
+
+ const loadAttributesFromQuery = () => {
+ const params = new URLSearchParams(window.location.search);
+ video.controls = parseBoolString(params.get('controls'), true);
+ video.muted = parseBoolString(params.get('muted'), true);
+ video.autoplay = parseBoolString(params.get('autoplay'), true);
+ video.playsInline = parseBoolString(params.get('playsinline'), true);
+ defaultControls = video.controls;
+ };
+
function realView(whepUrl, videoId) {
console.log(whepUrl)
diff --git a/ard-work/src/main/resources/templates/test1.html b/ard-work/src/main/resources/templates/test1.html
new file mode 100644
index 0000000..5623b4b
--- /dev/null
+++ b/ard-work/src/main/resources/templates/test1.html
@@ -0,0 +1,1161 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org">
+<head>
+ <meta charset="UTF-8">
+ <title>测试页</title>
+ <script th:src="@{/js/jquery-3.6.4.min.js}"></script>
+ <link rel="stylesheet" th:href="@{/css/bootstrap.css}"/>
+ <script th:src="@{/js/bootstrap.js}"></script>
+ <style>
+ .top-buffer {
+ margin-top: 10px;
+ }
+ .container {
+ border: 2px solid #1b6d85;
+ padding: 15px;
+ }
+ </style>
+<body>
+<div class="container">
+ <div class="row">
+ <div class="col-md-5">
+ <div class="row top-buffer">
+ 设备:<select id="selectDev" style="width: 330px;">
+ </select>
+ </div>
+ <div class="row top-buffer">
+ 通道:<select id="selectChn" style="width: 330px;">
+ </select>
+ </div>
+ <div class="row top-buffer">
+ <div class="col-md-1 col-md-offset-1">
+ <button id="up" type="button" class="btn btn-primary">上</button>
+ </div>
+ <div class="col-md-6 col-md-offset-2">
+ <div class="btn-group" role="group">
+ <button id="controlZoomIn" type="button" class="btn btn-primary">调焦-</button>
+ <button id="controlZoomOut" type="button" class="btn btn-primary">调焦+</button>
+ </div>
+ </div>
+ </div>
+ <div class="row ">
+ <div class="col-md-1">
+ <button id="left" type="button" class="btn btn-primary">左</button>
+ </div>
+ <div class="col-md-1 col-md-offset-1">
+ <button id="right" type="button" class="btn btn-primary">右</button>
+ </div>
+ <div class="col-md-6 col-md-offset-1">
+ <div class="btn-group" role="group">
+ <button id="controlFocusNear" type="button" class="btn btn-primary">聚焦-</button>
+ <button id="controlFocusFar" type="button" class="btn btn-primary">聚焦+</button>
+ </div>
+ </div>
+ </div>
+ <div class="row ">
+ <div class="col-md-1 col-md-offset-1">
+ <button id="down" type="button" class="btn btn-primary">下</button>
+ </div>
+ <div class="col-md-6 col-md-offset-2">
+ <div class="btn-group" role="group">
+ <button id="controlIrisOpen" type="button" class="btn btn-primary">光圈-</button>
+ <button id="controlIrisClose" type="button" class="btn btn-primary">光圈+</button>
+ </div>
+ </div>
+ </div>
+ <div class="row ">
+ <div class="col-md-10">
+ <div class="row top-buffer">
+ <div class="input-group">
+ <span class="input-group-addon">目的坐标值:</span>
+ <input id="targetPostion" class="form-control" placeholder="目的坐标"/>
+ <button id="setTargetPostion" type="button" class="btn btn-default">指向坐标</button>
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon">P值:</span>
+ <input id="p" class="form-control" placeholder="请输入P值"/>
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon">T值:</span>
+ <input id="t" class="form-control" placeholder="请输入T值"/>
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon">Z值:</span>
+ <input id="z" class="form-control" placeholder="请输入Z值"/>
+ </div>
+ </div>
+ <div class="row top-buffer">
+ <div class="btn-group" role="group">
+ <button id="getPTZ" type="button" class="btn btn-default">获取ptz</button>
+ <button id="setPTZ" type="button" class="btn btn-default">设置ptz</button>
+ <button id="setPreset" type="button" class="btn btn-default">设预置点</button>
+ <button id="gotoPreset" type="button" class="btn btn-default">调预置点</button>
+ <button id="setZeroPTZ" type="button" class="btn btn-default">设置零方位角</button>
+ </div>
+ </div>
+ <div class="row top-buffer">
+ <div class="btn-group" role="group">
+ <button id="FocusMode" type="button" class="btn btn-default">手动聚焦</button>
+ <div id="focusDiv" class="input-group">
+ <span class="input-group-addon">聚焦值:</span>
+ <input id="focus" class="form-control" placeholder="聚焦值"/>
+ </div>
+ <button id="getFocusPos" type="button" class="btn btn-default">获取聚焦值</button>
+ <button id="setFocusPos" type="button" class="btn btn-default">设置聚焦值</button>
+ </div>
+ </div>
+ <div class="row top-buffer">
+ <div class="btn-group" role="group">
+ <button id="WiperPwron" type="button" class="btn btn-default">开启雨刷</button>
+ <button id="Defogcfg" type="button" class="btn btn-default">开启透雾</button>
+ <button id="Infrarecfg" type="button" class="btn btn-default">开启红外</button>
+ <button id="HeateRpwron" type="button" class="btn btn-default">开启云台加热</button>
+ <button id="CameraDeicing" type="button" class="btn btn-default">开启镜头加热</button>
+ </div>
+ </div>
+ <div class="row top-buffer">
+ <div class="btn-group" role="group">
+ <button id="voice" type="button" class="btn btn-default">开始语音对讲</button>
+ <button id="record" type="button" class="btn btn-default">开始录像</button>
+ <button id="saveCutPic" type="button" class="btn btn-default">存储抓图</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-6">
+ <div class="row">
+ <div class="row top-buffer">
+ <button id="preview" type="button" class="btn btn-primary">预览</button>
+ <button id="previewStop" type="button" class="btn btn-primary">停止</button>
+ </div>
+ <div class="row top-buffer">
+ <video id="video" muted autoplay loop controls
+ style="width: 100%; height: 360px; object-fit: fill; border: 2px solid #3498db;"/>
+ </div>
+ <div class="row">
+ <img class="thumbnail" id="imgContainer"
+ style="width: 100%; height: 360px; border: 2px solid #3498db;"/>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<script th:inline="javascript">
+ var cameraId, chanNo,opt, optOpen, optClose, token;
+ window.onload = function () {
+ console.log(RTCRtpReceiver.getCapabilities('video').codecs)
+ opt = {"username": "admin", "password": "admin123"};
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ url: "../login",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(opt),
+ success: function (data) {
+ token = data.token;
+ getDeviceList();// 获取设备列表
+ }
+ })
+ // 初始化内容
+ console.log(cameraMap);
+ }
+
+ //获取设备
+ function getDeviceList() {
+ $.ajax({
+ url: "../cameraSdk/list",
+ type: "get",
+ success: function (data) {
+ console.log(data);
+ var arr = data.data;
+ for (var i = 0; i < arr.length; i++) {
+ console.log(arr[i].id);
+ console.log(arr[i].name)
+ var camera = {
+ name: arr[i].name,
+ factory: arr[i].factory,
+ ipaddr: arr[i].ip,
+ username: arr[i].username,
+ password: arr[i].password,
+ port: arr[i].rtspPort,
+ longitude: arr[i].longitude,
+ latitude: arr[i].latitude,
+ altitude: arr[i].altitude
+ };
+ cameraMap.set(arr[i].id, camera);
+ //先创建好select里面的option元素
+ var option = $("<option>");
+ //给option的text赋值,这就是你点开下拉框能够看到的东西
+ $(option).val(arr[i].id);
+ $(option).text(arr[i].name);
+ //获取select 下拉框对象,并将option添加进select
+ $('#selectDev').append(option);
+ }
+ $("#selectDev").trigger("change");
+ }
+ })
+ }
+
+ //选择设备
+ $("#selectDev").change(function () {
+ // 在这里处理选择事件
+ var cameraId = $(this).find("option:selected").val();
+ var name = $(this).find("option:selected").text();
+ getChannelList(cameraId);
+ console.log("选择了:" + cameraId + "---" + name);
+ });
+
+ //获取通道
+ function getChannelList(cameraId) {
+ console.log(cameraId)
+ var myEntity = {
+ deviceId: cameraId,
+ pageNum: 1,
+ pageSize: 64
+ }
+ var queryString = $.param(myEntity);
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../device/channel/list?" + queryString,
+ type: "get",
+ success: function (data) {
+ console.log(data);
+ var arr = data.rows;
+ $('#selectChn').empty();
+ for (var i = 0; i < arr.length; i++) {
+ console.log(arr[i].chanNo);
+ console.log(arr[i].name);
+ //先创建好select里面的option元素
+ var option = document.createElement("option");
+ //给option的text赋值,这就是你点开下拉框能够看到的东西
+ $(option).text(arr[i].name);
+ $(option).val(arr[i].chanNo);
+ //获取select 下拉框对象,并将option添加进select
+ $('#selectChn').append(option);
+ }
+ }
+ })
+ }
+
+ //预览
+ $('#preview').click(() => {
+ var cameraId = $('#selectDev option:selected').val();
+ var chanNo = $('#selectChn option:selected').val();
+ console.log(cameraId + " " + chanNo)
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../vtdu/media/" + cameraId + "_" + chanNo,
+ type: "get",
+ dataType: "json",
+ success: function (data) {
+ realView(data.data.webrtcUrl + "/", "video");
+ }
+ })
+ });
+ //停止
+ $('#previewStop').click(() => {
+ webrtcClient.stop();
+ });
+ //云台上下左右
+ $("#up").mousedown(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 2;
+ var enable = true;
+ commondMethod(url, code, enable);
+
+ })
+ $("#up").mouseup(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 2;
+ var enable = false;
+ commondMethod(url, code, enable);
+ })
+ $("#down").mousedown(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 8;
+ var enable = true;
+ commondMethod(url, code, enable);
+ })
+ $("#down").mouseup(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 8;
+ var enable = false;
+ commondMethod(url, code, enable);
+ })
+ $("#left").mousedown(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 4;
+ var enable = true;
+ commondMethod(url, code, enable);
+ })
+ $("#left").mouseup(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 4;
+ var enable = false;
+ commondMethod(url, code, enable);
+ })
+ $("#right").mousedown(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 6;
+ var enable = true;
+ commondMethod(url, code, enable);
+ })
+ $("#right").mouseup(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 6;
+ var enable = false;
+ commondMethod(url, code, enable);
+ })
+ //变倍
+ $("#controlZoomIn").mousedown(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 10;
+ var enable = true;
+ commondMethod(url, code, enable);
+ })
+ $("#controlZoomIn").mouseup(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 10;
+ var enable = false;
+ commondMethod(url, code, enable);
+ })
+ $("#controlZoomOut").mousedown(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 11;
+ var enable = true;
+ commondMethod(url, code, enable);
+ })
+ $("#controlZoomOut").mouseup(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 11;
+ var enable = false;
+ commondMethod(url, code, enable);
+ })
+ //变焦
+ $("#controlFocusNear").mousedown(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 12;
+ var enable = true;
+ commondMethod(url, code, enable);
+ })
+ $("#controlFocusNear").mouseup(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 12;
+ var enable = false;
+ commondMethod(url, code, enable);
+ })
+ $("#controlFocusFar").mousedown(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 13;
+ var enable = true;
+ commondMethod(url, code, enable);
+ })
+ $("#controlFocusFar").mouseup(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 13;
+ var enable = false;
+ commondMethod(url, code, enable);
+ })
+ //光圈
+ $("#controlIrisOpen").mousedown(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 14;
+ var enable = true;
+ commondMethod(url, code, enable);
+ })
+ $("#controlIrisOpen").mouseup(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 14;
+ var enable = false;
+ commondMethod(url, code, enable);
+ })
+ $("#controlIrisClose").mousedown(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 15;
+ var enable = true;
+ commondMethod(url, code, enable);
+ })
+ $("#controlIrisClose").mouseup(function () {
+ var url = "../cameraSdk/PTZControlWithSpeed";
+ var code = 15;
+ var enable = false;
+ commondMethod(url, code, enable);
+ })
+
+ $("#setPreset").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 8, "presetIndex": 1};
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/setPreset",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(opt),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ })
+ $("#gotoPreset").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 8, "presetIndex": 1};
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/gotoPreset",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(opt),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ })
+ $("#getPTZ").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ opt = {"cameraId": cameraId, "chanNo": chanNo};
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/getPTZ",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(opt),
+ success: function (datas) {
+ console.log(datas);
+ $("#p").val(datas.data.p);
+ $("#t").val(datas.data.t);
+ $("#z").val(datas.data.z);
+ }
+ })
+ })
+ $("#setPTZ").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ var p = $('#p').val();
+ var t = $('#t').val();
+ var z = $('#z').val();
+ //定义一个带有Map字段的实体对象
+ var myEntity = {
+ chanNo: chanNo,
+ cameraId: cameraId,
+ ptzMap: {
+ p: p,
+ t: t,
+ z: z
+ }
+ };
+ console.log(opt)
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/setPTZ",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(myEntity),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ })
+ $("#setTargetPostion").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ var camera = cameraMap.get(cameraId);
+ var camP = camera.longitude + ',' + camera.latitude + ',' + camera.altitude;
+ var targetP = $('#targetPostion').val();
+ var arr = targetP.split(",");
+ arr = arr.map(item => parseFloat(item));
+ //定义一个带有Map字段的实体对象
+ var myEntity = {
+ chanNo: chanNo,
+ cameraId: cameraId,
+ targetPosition: arr
+ };
+ console.log(myEntity)
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/setTargetPosition",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(myEntity),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ })
+ $("#setZeroPTZ").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ opt = {"cameraId": cameraId, "chanNo": chanNo};
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/setZeroPTZ",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(opt),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ })
+ $("#WiperPwron").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 8, "enable": true, "code": 16};
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/PTZControlWithSpeed",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(opt),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ })
+ var defogflag = true;
+ $("#Defogcfg").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true};
+ optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false};
+ if (defogflag) {
+ $(this).text("关闭透雾");
+ defogflag = false;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/defogcfg",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(optOpen),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ } else {
+ $(this).text("开启透雾");
+ defogflag = true;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/defogcfg",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(optClose),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ }
+
+ })
+ var infrareflag = true;
+ $("#Infrarecfg").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true};
+ optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false};
+ if (infrareflag) {
+ $(this).text("关闭红外");
+ infrareflag = false;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/infrarecfg",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(optOpen),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ } else {
+
+ $(this).text("开启红外");
+ infrareflag = true;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/infrarecfg",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(optClose),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ }
+ })
+ var focusModeflag = true;
+ $("#FocusMode").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true};
+ optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false};
+ if (focusModeflag) {
+ $(this).text("自动聚焦");
+ focusModeflag = false;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/focusMode",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(optOpen),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ } else {
+ $(this).text("手动聚焦");
+ focusModeflag = true;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/focusMode",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(optClose),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ }
+ })
+ $("#getFocusPos").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ opt = {"cameraId": cameraId, "chanNo": chanNo};
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/getFocusPos",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(opt),
+ success: function (datas) {
+ console.log(datas);
+ $("#focus").val(datas.data);
+ }
+ })
+ })
+ var heateRpwronflag = true;
+ $("#HeateRpwron").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true};
+ optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false};
+ if (heateRpwronflag) {
+ $(this).text("关闭云台加热");
+ heateRpwronflag = false;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/heateRpwron",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(optOpen),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ } else {
+ $(this).text("开启云台加热");
+ heateRpwronflag = true;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/heateRpwron",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(optClose),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ }
+ })
+ var CameraDeicingflag = true;
+ $("#CameraDeicing").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true};
+ optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false};
+ if (CameraDeicingflag) {
+ $(this).text("关闭镜头加热");
+ CameraDeicingflag = false;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/cameraDeicing",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(optOpen),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ } else {
+ $(this).text("开启镜头加热");
+ CameraDeicingflag = true;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/cameraDeicing",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(optClose),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ }
+ })
+ $("#realCutPic").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ opt = {"cameraId": cameraId, "chanNo": chanNo};
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/captureJPEGPicture",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(opt),
+ success: function (data) {
+ console.log(data.data);
+ $("#imgContainer").attr("src", "data:image/png;base64," + data.data);
+ }
+ })
+ })
+ $("#saveCutPic").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ opt = {"cameraId": cameraId, "chanNo": chanNo};
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/picCutCate",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(opt),
+ success: function (data) {
+ console.log(data.data);
+ setTimeout(() => {
+ $('#imgContainer').attr('src', data.data);
+ }, 1000)
+
+ }
+ })
+ })
+ var recordflag = true;
+ $("#record").click(function () {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ optOpen = {"cameraId": cameraId, "chanNo": chanNo, "enable": true};
+ optClose = {"cameraId": cameraId, "chanNo": chanNo, "enable": false};
+ if (recordflag) {
+ $(this).text("停止录像");
+ recordflag = false;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/record",
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(optOpen),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ } else {
+ $(this).text("开始录像");
+ recordflag = true;
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../cameraSdk/record",
+ dataType: "json",
+ data: JSON.stringify(optClose),
+ type: "post",
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ }
+ })
+
+ var cameraMap = new Map();
+
+ /*云台公共方法*/
+ function commondMethod(url, code, enable) {
+ cameraId = $('#selectDev option:selected').val();
+ chanNo = $('#selectChn option:selected').val();
+ opt = {"cameraId": cameraId, "chanNo": chanNo, "speed": 4, "enable": enable, "code": code};
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: url,
+ type: "post",
+ dataType: "json",
+ data: JSON.stringify(opt),
+ success: function (data) {
+ console.log(data);
+ }
+ })
+ }
+
+ $('video').click(function (e) {
+ var cameraId = $('#selectDev option:selected').val();
+ var chanNo = $('#selectChn option:selected').val();
+ console.log(cameraId + " " + chanNo)
+ $.ajax({
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': token
+ },
+ url: "../vtdu/media/" + cameraId + "_" + chanNo,
+ type: "get",
+ dataType: "json",
+ success: function (data) {
+ realView(data.data.webrtcUrl + "/", e.target.id);
+ }
+ })
+ });
+
+ let webrtcClient;
+ //whep操作方法
+ const restartPause = 2000;
+ const unquoteCredential = (v) => (
+ JSON.parse(`"${v}"`)
+ );
+ const linkToIceServers = (links) => (
+ (links !== null) ? links.split(', ').map((link) => {
+ const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
+ const ret = {
+ urls: [m[1]],
+ };
+
+ if (m[3] !== undefined) {
+ ret.username = unquoteCredential(m[3]);
+ ret.credential = unquoteCredential(m[4]);
+ ret.credentialType = "password";
+ }
+
+ return ret;
+ }) : []
+ );
+ const parseOffer = (offer) => {
+ const ret = {
+ iceUfrag: '',
+ icePwd: '',
+ medias: [],
+ };
+
+ for (const line of offer.split('\r\n')) {
+ if (line.startsWith('m=')) {
+ ret.medias.push(line.slice('m='.length));
+ } else if (ret.iceUfrag === '' && line.startsWith('a=ice-ufrag:')) {
+ ret.iceUfrag = line.slice('a=ice-ufrag:'.length);
+ } else if (ret.icePwd === '' && line.startsWith('a=ice-pwd:')) {
+ ret.icePwd = line.slice('a=ice-pwd:'.length);
+ }
+ }
+
+ return ret;
+ };
+ const generateSdpFragment = (offerData, candidates) => {
+ const candidatesByMedia = {};
+ for (const candidate of candidates) {
+ const mid = candidate.sdpMLineIndex;
+ if (candidatesByMedia[mid] === undefined) {
+ candidatesByMedia[mid] = [];
+ }
+ candidatesByMedia[mid].push(candidate);
+ }
+
+ let frag = 'a=ice-ufrag:' + offerData.iceUfrag + '\r\n'
+ + 'a=ice-pwd:' + offerData.icePwd + '\r\n';
+
+ let mid = 0;
+
+ for (const media of offerData.medias) {
+ if (candidatesByMedia[mid] !== undefined) {
+ frag += 'm=' + media + '\r\n'
+ + 'a=mid:' + mid + '\r\n';
+
+ for (const candidate of candidatesByMedia[mid]) {
+ frag += 'a=' + candidate.candidate + '\r\n';
+ }
+ }
+ mid++;
+ }
+
+ return frag;
+ }
+
+ class WHEPClient {
+ constructor(whepUrl, videoId) {
+ this.video = videoId;
+ this.wurl = new URL('whep', whepUrl);
+ this.pc = null;
+ this.restartTimeout = null;
+ this.eTag = '';
+ this.queuedCandidates = [];
+ this.start();
+ }
+
+ start() {
+ console.log("requesting ICE servers");
+ fetch(this.wurl, {
+ method: 'OPTIONS',
+ })
+ .then((res) => this.onIceServers(res))
+ .catch((err) => {
+ console.log('error: ' + err);
+ this.scheduleRestart();
+ });
+ }
+
+ onIceServers(res) {
+ this.pc = new RTCPeerConnection({
+ iceServers: linkToIceServers(res.headers.get('Link')),
+ });
+
+ const direction = "sendrecv";
+ this.pc.addTransceiver("video", {direction});
+ this.pc.addTransceiver("audio", {direction});
+
+ this.pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
+ this.pc.oniceconnectionstatechange = () => this.onConnectionState();
+
+ this.pc.ontrack = (evt) => {
+ console.log("new track:", evt.track.kind);
+ document.getElementById(this.video).srcObject = evt.streams[0];
+ };
+
+ this.pc.createOffer()
+ .then((offer) => this.onLocalOffer(offer));
+ }
+
+ onLocalOffer(offer) {
+ this.offerData = parseOffer(offer.sdp);
+ this.pc.setLocalDescription(offer);
+
+ console.log("sending offer");
+ console.log(this.wurl);
+ fetch(this.wurl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/sdp',
+ },
+ body: offer.sdp,
+ })
+ .then((res) => {
+ if (res.status !== 201) {
+ throw new Error('bad status code');
+ }
+ // this.eTag = res.headers.get('ETag');
+ this.eTag = res.headers.get("ETag") || res.headers.get('E-Tag');
+ return res.text();
+ })
+ .then((sdp) => this.onRemoteAnswer(new RTCSessionDescription({
+ type: 'answer',
+ sdp,
+ })))
+ .catch((err) => {
+ console.log('error: ' + err);
+ this.scheduleRestart();
+ });
+ }
+
+ onConnectionState() {
+ if (this.restartTimeout !== null) {
+ return;
+ }
+
+ console.log("peer connection state:", this.pc.iceConnectionState);
+
+ switch (this.pc.iceConnectionState) {
+ case "disconnected":
+ this.scheduleRestart();
+ }
+ }
+
+ onRemoteAnswer(answer) {
+ if (this.restartTimeout !== null) {
+ return;
+ }
+
+ this.pc.setRemoteDescription(new RTCSessionDescription(answer));
+
+ if (this.queuedCandidates.length !== 0) {
+ this.sendLocalCandidates(this.queuedCandidates);
+ this.queuedCandidates = [];
+ }
+ }
+
+ onLocalCandidate(evt) {
+ if (this.restartTimeout !== null) {
+ return;
+ }
+
+ if (evt.candidate !== null) {
+ if (this.eTag === '') {
+ this.queuedCandidates.push(evt.candidate);
+ } else {
+ this.sendLocalCandidates([evt.candidate])
+ }
+ }
+ }
+
+ sendLocalCandidates(candidates) {
+ fetch(this.wurl, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/trickle-ice-sdpfrag',
+ 'If-Match': this.eTag,
+ },
+
+ body: generateSdpFragment(this.offerData, candidates),
+ })
+ .then((res) => {
+ if (res.status !== 204) {
+ throw new Error('bad status code');
+ }
+ })
+ .catch((err) => {
+ console.log('error: ' + err);
+ this.scheduleRestart();
+ });
+ }
+
+ scheduleRestart() {
+ if (this.restartTimeout !== null) {
+ return;
+ }
+
+ if (this.pc !== null) {
+ this.pc.close();
+ this.pc = null;
+ }
+
+ this.restartTimeout = window.setTimeout(() => {
+ this.restartTimeout = null;
+ this.start();
+ }, restartPause);
+
+ this.eTag = '';
+ this.queuedCandidates = [];
+ }
+
+ stop() {
+ if (this.pc) {
+ try {
+ this.pc.close();
+ } catch (e) {
+ console.log("Failure close peer connection:" + e);
+ }
+ this.pc = null;
+ }
+ }
+ }
+
+ function realView(whepUrl, videoId) {
+ console.log(whepUrl)
+ webrtcClient = new WHEPClient(whepUrl, videoId);
+ }
+</script>
+</body>
+</html>
diff --git a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/SyncTask.java b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/SyncTask.java
index 4e7d4ff..0b089e4 100644
--- a/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/SyncTask.java
+++ b/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/SyncTask.java
@@ -84,28 +84,24 @@
/**
* @Author 刘苏义
- * @Description 同步nvr通道状态实时同步流媒体
+ * @Description 同步通道状态实时同步流媒体
* @Date 2024/7/9 16:01
*/
- public void nvrChannelState() {
+ public void syncChannelState() {
+ log.warn("定时同步通道任务开始");
//获取所有nvr设备
List<ArdCameras> ardCamerasList = iArdCamerasService.selectArdCamerasListNoDataScope(new ArdCameras());
if (ardCamerasList.size() > 0) {
ardCamerasList.stream()
- .filter(ardCameras -> ardCameras.getGdtype().equals("2"))
.forEach(ardCameras -> {
- //通过SDK获取NVR当前通道
+ //通过SDK获取NVR实际通道
CameraSDK cameraSDK = cameraSDKFactory.createCameraSDK(ardCameras.getFactory());
List<ArdChannel> ardChannelList = cameraSDK.getChannels(ardCameras);
//同步通道表
ArdChannel ardChannel=new ArdChannel();
ardChannel.setDeviceId(ardCameras.getId());
List<ArdChannel> ardChannelListDb = ardChannelService.selectArdChannelList(ardChannel);
- ardChannelService.asyncChannel(ardChannelListDb,ardChannelList);
- //同步流媒体表
-
- //同步流媒体api
-
+ ardChannelService.asyncChannel(ardCameras,ardChannelListDb,ardChannelList);
});
}
}
@@ -142,9 +138,10 @@
* 刘苏义
* 2023/10/13 14:13:53
*/
- public void vtdu() {
+ public void syncVtdu() {
+ log.warn("定时同步流媒体任务开始");
List<String> nameList = mediaService.getNameList();
List<Vtdu> vtduList = vtduService.selectVtduList(new Vtdu());
vtduService.asyncVtdu(vtduList,nameList);
}
-}
+}
\ No newline at end of file
diff --git a/server/mediamtx/mediamtx.yml b/server/mediamtx/mediamtx.yml
index cbe09e8..81ca38d 100644
--- a/server/mediamtx/mediamtx.yml
+++ b/server/mediamtx/mediamtx.yml
@@ -225,17 +225,17 @@
webrtcICEInterfaces: []
# List of public IP addresses that are to be used as a host.
# This is used typically for servers that are behind 1:1 D-NAT.
-webrtcICEHostNAT1To1IPs: []
+webrtcICEHostNAT1To1IPs: [192.168.1.227]
# Address of a ICE UDP listener in format host:port.
# If filled, ICE traffic will pass through a single UDP port,
# allowing the deployment of the server inside a container or behind a NAT.
-webrtcICEUDPMuxAddress:
+webrtcICEUDPMuxAddress: :8189
# Address of a ICE TCP listener in format host:port.
# If filled, ICE traffic will pass through a single TCP port,
# allowing the deployment of the server inside a container or behind a NAT.
# Using this setting forces usage of the TCP protocol, which is not
# optimal for WebRTC.
-webrtcICETCPMuxAddress: 192.168.1.227:19302
+webrtcICETCPMuxAddress:
###############################################
# Global settings -> SRT
--
Gitblit v1.9.3