From f4f4fc53260eb67483dce406a963628273786a61 Mon Sep 17 00:00:00 2001
From: liusuyi <1951119284@qq.com>
Date: Sat, 30 May 2026 17:22:17 +0800
Subject: [PATCH] 优化
---
ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/IMediaServerService.java | 10
ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/impl/MediaServerServiceImpl.java | 287 ++++++++++++++
ard-modules/ard-modules-work/src/main/java/com/ard/work/websocket/utils/PTZWebSocketUtils.java | 93 +--
ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzTask.java | 71 ++
ard-modules/ard-modules-work/src/main/java/com/ard/work/config/AsyncConfiguration.java | 17
ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/domain/PtzParamDTO.java | 17
ard-modules/ard-modules-agent/src/main/java/com/ard/agent/controller/ChatClientController.java | 45 ++
ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/controller/ZlmController.java | 147 +++++++
ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/factory/RemoteGb28181FallbackFactory.java | 27 +
ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzDataCollector.java | 65 ++
ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/api/Gb28181ApiController.java | 190 +++++++++
ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzCacheManager.java | 51 +
ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/RemoteGb28181Service.java | 32 +
ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceChannelServiceImpl.java | 55 ++
ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceServiceImpl.java | 39 +
15 files changed, 1,037 insertions(+), 109 deletions(-)
diff --git a/ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/RemoteGb28181Service.java b/ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/RemoteGb28181Service.java
index da3574a..03b561f 100644
--- a/ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/RemoteGb28181Service.java
+++ b/ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/RemoteGb28181Service.java
@@ -7,6 +7,8 @@
import com.ard.common.core.domain.RtpServerParam;
import com.ard.gb28181.api.domain.Device;
import com.ard.gb28181.api.domain.DeviceChannel;
+import com.ard.gb28181.api.domain.GbChannelDTO;
+import com.ard.gb28181.api.domain.GbDeviceDTO;
import com.ard.gb28181.api.factory.RemoteGb28181FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
@@ -76,6 +78,36 @@
R<List<Device>> getAllDevices(@RequestHeader(SecurityConstants.FROM_SOURCE) String inner);
/**
+ * 根据国标设备ID获取GbDevice
+ */
+ @GetMapping("/api/gb28181/getGbDevice/{gbDeviceId}")
+ R<GbDeviceDTO> getGbDevice(@PathVariable String gbDeviceId, @RequestHeader(SecurityConstants.FROM_SOURCE) String inner);
+
+ /**
+ * 获取所有GbDevice列表
+ */
+ @GetMapping("/api/gb28181/getAllGbDevices")
+ R<List<GbDeviceDTO>> getAllGbDevices(@RequestHeader(SecurityConstants.FROM_SOURCE) String inner);
+
+ /**
+ * 根据国标设备ID和通道ID获取GbChannel
+ */
+ @GetMapping("/api/gb28181/getGbChannel/{gbDeviceId}/{gbChannelId}")
+ R<GbChannelDTO> getGbChannel(@PathVariable String gbDeviceId, @PathVariable String gbChannelId, @RequestHeader(SecurityConstants.FROM_SOURCE) String inner);
+
+ /**
+ * 根据国标设备ID获取所有通道
+ */
+ @GetMapping("/api/gb28181/getGbChannelsByDeviceId/{gbDeviceId}")
+ R<List<GbChannelDTO>> getGbChannelsByDeviceId(@PathVariable String gbDeviceId, @RequestHeader(SecurityConstants.FROM_SOURCE) String inner);
+
+ /**
+ * 更新GbChannel流状态
+ */
+ @PostMapping("/api/gb28181/updateGbChannelStream")
+ R<Boolean> updateGbChannelStream(@RequestBody GbChannelDTO gbChannel, @RequestHeader(SecurityConstants.FROM_SOURCE) String inner);
+
+ /**
* 通用前端控制命令(参考国标文档A.3.1指令格式)
*
* @param deviceId 设备国标编号
diff --git a/ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/factory/RemoteGb28181FallbackFactory.java b/ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/factory/RemoteGb28181FallbackFactory.java
index bd159d3..1000237 100644
--- a/ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/factory/RemoteGb28181FallbackFactory.java
+++ b/ard-api/ard-api-gb28181/src/main/java/com/ard/gb28181/api/factory/RemoteGb28181FallbackFactory.java
@@ -5,6 +5,8 @@
import com.ard.gb28181.api.RemoteGb28181Service;
import com.ard.gb28181.api.domain.Device;
import com.ard.gb28181.api.domain.DeviceChannel;
+import com.ard.gb28181.api.domain.GbChannelDTO;
+import com.ard.gb28181.api.domain.GbDeviceDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FallbackFactory;
@@ -55,6 +57,31 @@
}
@Override
+ public R<GbDeviceDTO> getGbDevice(String gbDeviceId, String inner) {
+ return R.fail("gb28181 获取GbDevice失败:" + throwable.getMessage());
+ }
+
+ @Override
+ public R<List<GbDeviceDTO>> getAllGbDevices(String inner) {
+ return R.fail("gb28181 获取所有GbDevice失败:" + throwable.getMessage());
+ }
+
+ @Override
+ public R<GbChannelDTO> getGbChannel(String gbDeviceId, String gbChannelId, String inner) {
+ return R.fail("gb28181 获取GbChannel失败:" + throwable.getMessage());
+ }
+
+ @Override
+ public R<List<GbChannelDTO>> getGbChannelsByDeviceId(String gbDeviceId, String inner) {
+ return R.fail("gb28181 获取设备通道列表失败:" + throwable.getMessage());
+ }
+
+ @Override
+ public R<Boolean> updateGbChannelStream(GbChannelDTO gbChannel, String inner) {
+ return R.fail("gb28181 更新GbChannel流状态失败:" + throwable.getMessage());
+ }
+
+ @Override
public R<Void> frontEndCommand(String deviceId, String channelId, Integer cmdCode, Integer parameter1, Integer parameter2, Integer combindCode2, String inner) {
return R.fail("gb28181 通用前端控制命令失败:" + throwable.getMessage());
}
diff --git a/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/controller/ChatClientController.java b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/controller/ChatClientController.java
new file mode 100644
index 0000000..f340113
--- /dev/null
+++ b/ard-modules/ard-modules-agent/src/main/java/com/ard/agent/controller/ChatClientController.java
@@ -0,0 +1,45 @@
+package com.ard.agent.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+
+@Tag(name = "ChatClient使用案例")
+@RestController
+@RequestMapping("/chatclient")
+public class ChatClientController {
+
+ @Resource(name = "ollamaChatClient")
+ private ChatClient chatClient;
+
+ @Operation(summary = "同步聊天 - ChatClient基础用法")
+ @GetMapping("/chat")
+ public String chat(@RequestParam String message) {
+ return chatClient.prompt()
+ .user(message)
+ .call()
+ .content();
+ }
+
+ @Operation(summary = "流式聊天 - ChatClient流式输出")
+ @GetMapping(value = "/chat/stream", produces = "text/html;charset=UTF-8")
+ public Flux<String> streamChat(@RequestParam String message) {
+ return chatClient.prompt()
+ .user(message)
+ .stream()
+ .content();
+ }
+
+ @Operation(summary = "带系统提示词的聊天")
+ @GetMapping("/chat/withSystem")
+ public String chatWithSystem(@RequestParam String message) {
+ return chatClient.prompt()
+ .system(s -> s.text("你是一个智能助手,请用简洁的中文回答问题,回答不超过100字。"))
+ .user(message)
+ .call()
+ .content();
+ }
+}
\ No newline at end of file
diff --git a/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/api/Gb28181ApiController.java b/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/api/Gb28181ApiController.java
index bbaa09c..6e8a4ce 100644
--- a/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/api/Gb28181ApiController.java
+++ b/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/api/Gb28181ApiController.java
@@ -7,8 +7,14 @@
import com.ard.common.core.exception.ServiceException;
import com.ard.gb28181.api.domain.Device;
import com.ard.gb28181.api.domain.DeviceChannel;
+import com.ard.gb28181.api.domain.GbChannelDTO;
+import com.ard.gb28181.api.domain.GbDeviceDTO;
import com.ard.gb28181.config.UserSetting;
+import com.ard.gb28181.domain.GbChannel;
+import com.ard.gb28181.domain.GbDevice;
import com.ard.gb28181.service.IDeviceService;
+import com.ard.gb28181.service.IGbChannelService;
+import com.ard.gb28181.service.IGbDeviceService;
import com.ard.gb28181.service.ISIPCommander;
import com.ard.gb28181.session.SipInviteSessionManager;
import com.ard.zlm.api.RemoteZlmService;
@@ -22,6 +28,7 @@
import org.springframework.web.context.request.async.DeferredResult;
import javax.sip.ResponseEvent;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -48,6 +55,12 @@
@Autowired
private RemoteZlmService remoteZlmService;
+
+ @Autowired
+ private IGbDeviceService gbDeviceService;
+
+ @Autowired
+ private IGbChannelService gbChannelService;
/**
* 根据设备id获取设备
@@ -686,4 +699,181 @@
}
frontEndCommand(deviceId, channelId, cmdCode, switchId, 0, 0);
}
+
+ // ==================== GbDevice / GbChannel 管理接口 ====================
+
+ /**
+ * 根据国标设备ID获取GbDevice
+ */
+ @GetMapping("/getGbDevice/{gbDeviceId}")
+ public R<GbDeviceDTO> getGbDevice(@PathVariable String gbDeviceId) {
+ GbDevice gbDevice = gbDeviceService.selectGbDeviceByGbDeviceId(gbDeviceId);
+ if (gbDevice == null) {
+ return R.fail("GbDevice不存在 gbDeviceId:" + gbDeviceId);
+ }
+ return R.ok(toGbDeviceDTO(gbDevice));
+ }
+
+ /**
+ * 获取所有GbDevice列表
+ */
+ @GetMapping("/getAllGbDevices")
+ public R<List<GbDeviceDTO>> getAllGbDevices() {
+ List<GbDevice> list = gbDeviceService.selectGbDeviceList(new GbDevice());
+ List<GbDeviceDTO> dtoList = new ArrayList<>();
+ for (GbDevice d : list) {
+ dtoList.add(toGbDeviceDTO(d));
+ }
+ return R.ok(dtoList);
+ }
+
+ /**
+ * 根据国标设备ID和通道ID获取GbChannel
+ */
+ @GetMapping("/getGbChannel/{gbDeviceId}/{gbChannelId}")
+ public R<GbChannelDTO> getGbChannel(@PathVariable String gbDeviceId, @PathVariable String gbChannelId) {
+ GbChannel gbChannel = gbChannelService.selectByGbDeviceIdAndGbChannelId(gbDeviceId, gbChannelId);
+ if (gbChannel == null) {
+ return R.fail("GbChannel不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId);
+ }
+ return R.ok(toGbChannelDTO(gbChannel));
+ }
+
+ /**
+ * 根据国标设备ID获取所有通道
+ */
+ @GetMapping("/getGbChannelsByDeviceId/{gbDeviceId}")
+ public R<List<GbChannelDTO>> getGbChannelsByDeviceId(@PathVariable String gbDeviceId) {
+ List<GbChannel> list = gbChannelService.selectGbChannelByGbDeviceId(gbDeviceId);
+ List<GbChannelDTO> dtoList = new ArrayList<>();
+ for (GbChannel c : list) {
+ dtoList.add(toGbChannelDTO(c));
+ }
+ return R.ok(dtoList);
+ }
+
+ /**
+ * 更新GbChannel流状态
+ */
+ @PostMapping("/updateGbChannelStream")
+ R<Boolean> updateGbChannelStream(@RequestBody GbChannelDTO dto) {
+ GbChannel gbChannel = new GbChannel();
+ gbChannel.setId(dto.getId());
+ gbChannel.setStreamStatus(dto.getStreamStatus());
+ gbChannel.setStreamKey(dto.getStreamKey());
+ gbChannel.setMediaServerId(dto.getMediaServerId());
+ gbChannel.setSnap(dto.getSnap());
+ gbChannelService.updateGbChannelStream(gbChannel);
+ return R.ok(true);
+ }
+
+ private GbDeviceDTO toGbDeviceDTO(GbDevice d) {
+ GbDeviceDTO dto = new GbDeviceDTO();
+ dto.setId(d.getId());
+ dto.setGbDeviceId(d.getGbDeviceId());
+ dto.setDeviceName(d.getDeviceName());
+ dto.setDeviceCode(d.getDeviceCode());
+ dto.setStreamMode(d.getStreamMode());
+ dto.setEnableMp4(d.getEnableMp4());
+ dto.setStreamStatus(d.getStreamStatus());
+ dto.setMediaServerId(d.getMediaServerId());
+ dto.setStreamKey(d.getStreamKey());
+ dto.setSnap(d.getSnap());
+ return dto;
+ }
+
+ private GbChannelDTO toGbChannelDTO(GbChannel c) {
+ GbChannelDTO dto = new GbChannelDTO();
+ dto.setId(c.getId());
+ dto.setGbDeviceId(c.getGbDeviceId());
+ dto.setGbChannelId(c.getGbChannelId());
+ dto.setChannelName(c.getChannelName());
+ dto.setDeviceCode(c.getDeviceCode());
+ dto.setStreamMode(c.getStreamMode());
+ dto.setEnableMp4(c.getEnableMp4());
+ dto.setStreamStatus(c.getStreamStatus());
+ dto.setMediaServerId(c.getMediaServerId());
+ dto.setStreamKey(c.getStreamKey());
+ dto.setSnap(c.getSnap());
+ return dto;
+ }
+
+ /**
+ * 一键迁移:将 Redis 中已注册的国标设备同步到 MySQL(供首次执行 SQL 后使用)
+ */
+ @Operation(summary = "迁移Redis国标设备到MySQL")
+ @PostMapping("/migrateGbDevices")
+ public R<String> migrateGbDevices() {
+ List<Device> allDevices = deviceService.getAllDevices();
+ int deviceCount = 0;
+ int channelCount = 0;
+
+ for (Device device : allDevices) {
+ try {
+ // 创建设备记录
+ GbDevice existingDevice = gbDeviceService.selectGbDeviceByGbDeviceId(device.getDeviceId());
+ if (existingDevice == null) {
+ GbDevice gbDevice = new GbDevice();
+ gbDevice.setGbDeviceId(device.getDeviceId());
+ gbDevice.setDeviceName(device.getName() != null ? device.getName() : device.getDeviceId());
+ gbDevice.setDeviceCode(device.getDeviceId());
+ gbDevice.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE");
+ gbDevice.setEnableMp4("0");
+ gbDeviceService.insertGbDevice(gbDevice);
+ deviceCount++;
+ log.info("[迁移] 创建设备: {}", device.getDeviceId());
+ }
+
+ // 创建默认通道(设备自身)
+ GbChannel existingDefaultChannel = gbChannelService.selectByGbDeviceIdAndGbChannelId(
+ device.getDeviceId(), device.getDeviceId());
+ if (existingDefaultChannel == null) {
+ GbChannel gbChannel = new GbChannel();
+ gbChannel.setGbDeviceId(device.getDeviceId());
+ gbChannel.setGbChannelId(device.getDeviceId());
+ gbChannel.setChannelName(device.getName() != null ? device.getName() : device.getDeviceId());
+ gbChannel.setDeviceCode(device.getDeviceId());
+ gbChannel.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE");
+ gbChannel.setEnableMp4("0");
+ gbChannelService.insertGbChannel(gbChannel);
+ channelCount++;
+ }
+
+ // 创建所有通道
+ List<DeviceChannel> channels = deviceService.getChannelsByDeviceId(device.getDeviceId());
+ if (channels != null) {
+ for (DeviceChannel channel : channels) {
+ String channelId = channel.getDeviceId();
+ if (channelId == null || channelId.length() <= 8) {
+ continue;
+ }
+ if (channelId.length() == 20) {
+ try {
+ com.ard.gb28181.api.domain.GbCode gbCode = com.ard.gb28181.api.domain.GbCode.decode(channelId);
+ if (gbCode != null && ("215".equals(gbCode.getTypeCode()) || "216".equals(gbCode.getTypeCode()))) {
+ continue;
+ }
+ } catch (Exception ignored) {}
+ }
+ GbChannel existing = gbChannelService.selectByGbDeviceIdAndGbChannelId(device.getDeviceId(), channelId);
+ if (existing == null) {
+ GbChannel gbChannel = new GbChannel();
+ gbChannel.setGbDeviceId(device.getDeviceId());
+ gbChannel.setGbChannelId(channelId);
+ gbChannel.setChannelName(channel.getName() != null ? channel.getName() : channelId);
+ gbChannel.setDeviceCode(channelId);
+ gbChannel.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE");
+ gbChannel.setEnableMp4("0");
+ gbChannelService.insertGbChannel(gbChannel);
+ channelCount++;
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error("[迁移] 处理设备失败: {}", device.getDeviceId(), e);
+ }
+ }
+
+ return R.ok("迁移完成: 设备 " + deviceCount + " 条, 通道 " + channelCount + " 条");
+ }
}
diff --git a/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceChannelServiceImpl.java b/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceChannelServiceImpl.java
index 9078f9d..1344e69 100644
--- a/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceChannelServiceImpl.java
+++ b/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceChannelServiceImpl.java
@@ -2,7 +2,10 @@
import com.ard.gb28181.api.domain.Device;
import com.ard.gb28181.api.domain.DeviceChannel;
+import com.ard.gb28181.api.domain.GbCode;
+import com.ard.gb28181.domain.GbChannel;
import com.ard.gb28181.service.IDeviceChannelService;
+import com.ard.gb28181.service.IGbChannelService;
import com.ard.gb28181.service.IRedisCatchStorage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -21,6 +24,9 @@
@Autowired
private IRedisCatchStorage redisCatchStorage;
+
+ @Autowired
+ private IGbChannelService gbChannelService;
/**
* 根据设备id清楚设备通道
@@ -47,6 +53,55 @@
List<DeviceChannel> deviceChannelsInRedis = redisCatchStorage.queryAllChannelsForRefresh(device.getDeviceId());
redisCatchStorage.batchAdd(device.getDeviceId(), mergeWithNewChannels(deviceChannelsInRedis, channels, device.isOnLine()));
+
+ // 自动为每个通道创建 GbChannel 记录(若不存在)
+ autoCreateGbChannelForChannels(device, channels);
+ }
+
+ /**
+ * 自动为设备下的每个通道创建 GbChannel 记录
+ */
+ private void autoCreateGbChannelForChannels(Device device, List<DeviceChannel> channels) {
+ try {
+ String parentDeviceId = device.getDeviceId();
+ for (DeviceChannel channel : channels) {
+ String channelId = channel.getDeviceId();
+ if (channelId == null) {
+ continue;
+ }
+ // 跳过行政区划(设备ID长度 <= 8)
+ if (channelId.length() <= 8) {
+ continue;
+ }
+ // 跳过业务分组(215)和虚拟组织(216)
+ if (channelId.length() == 20) {
+ try {
+ GbCode gbCode = GbCode.decode(channelId);
+ if (gbCode != null && ("215".equals(gbCode.getTypeCode()) || "216".equals(gbCode.getTypeCode()))) {
+ continue;
+ }
+ } catch (Exception ignored) {
+ }
+ }
+
+ // 检查是否已存在
+ GbChannel existing = gbChannelService.selectByGbDeviceIdAndGbChannelId(parentDeviceId, channelId);
+ if (existing == null) {
+ GbChannel gbChannel = new GbChannel();
+ gbChannel.setGbDeviceId(parentDeviceId);
+ gbChannel.setGbChannelId(channelId);
+ gbChannel.setChannelName(channel.getName() != null ? channel.getName() : channelId);
+ gbChannel.setDeviceCode(channelId);
+ gbChannel.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE");
+ gbChannel.setEnableMp4("0");
+ gbChannelService.insertGbChannel(gbChannel);
+ log.info("[GbChannel自动创建] parentDeviceId={}, channelId={}, id={}",
+ parentDeviceId, channelId, gbChannel.getId());
+ }
+ }
+ } catch (Exception e) {
+ log.error("[GbChannel自动创建] 失败 parentDeviceId={}", device.getDeviceId(), e);
+ }
}
/**
diff --git a/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceServiceImpl.java b/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceServiceImpl.java
index f60ea6e..6d6390d 100644
--- a/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceServiceImpl.java
+++ b/ard-modules/ard-modules-gb28181/src/main/java/com/ard/gb28181/service/impl/DeviceServiceImpl.java
@@ -13,7 +13,11 @@
import com.ard.gb28181.api.utils.DateUtil;
import com.ard.gb28181.common.ErrorCode;
import com.ard.gb28181.config.UserSetting;
+import com.ard.gb28181.domain.GbChannel;
+import com.ard.gb28181.domain.GbDevice;
import com.ard.gb28181.service.IDeviceService;
+import com.ard.gb28181.service.IGbChannelService;
+import com.ard.gb28181.service.IGbDeviceService;
import com.ard.gb28181.service.IRedisCatchStorage;
import com.ard.gb28181.service.ISIPCommander;
import com.ard.gb28181.session.SipInviteSessionManager;
@@ -71,6 +75,12 @@
@Autowired
private RemoteQsDeviceService remoteQsDeviceService;
+
+ @Autowired
+ private IGbDeviceService gbDeviceService;
+
+ @Autowired
+ private IGbChannelService gbChannelService;
/**
* 查询设备信息
@@ -176,6 +186,35 @@
} catch (Exception e) {
log.error("[同步设备状态] 设备上线,同步到 QS 模块失败:{}", device.getDeviceId(), e);
}
+
+ // 设备注册成功时,自动在 ard_gb_device 表中创建设备记录(若不存在)
+ try {
+ GbDevice existingDevice = gbDeviceService.selectGbDeviceByGbDeviceId(device.getDeviceId());
+ if (existingDevice == null) {
+ GbDevice gbDevice = new GbDevice();
+ gbDevice.setGbDeviceId(device.getDeviceId());
+ gbDevice.setDeviceName(device.getName() != null ? device.getName() : device.getDeviceId());
+ gbDevice.setDeviceCode(device.getDeviceId());
+ gbDevice.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE");
+ gbDevice.setEnableMp4("0");
+ gbDeviceService.insertGbDevice(gbDevice);
+ log.info("[GbDevice自动创建] deviceId: {}, id: {}", device.getDeviceId(), gbDevice.getId());
+
+ // 同时创建默认通道(设备自身)
+ GbChannel gbChannel = new GbChannel();
+ gbChannel.setGbDeviceId(device.getDeviceId());
+ gbChannel.setGbChannelId(device.getDeviceId());
+ gbChannel.setChannelName(device.getName() != null ? device.getName() : device.getDeviceId());
+ gbChannel.setDeviceCode(device.getDeviceId());
+ gbChannel.setStreamMode(device.getStreamMode() != null ? device.getStreamMode() : "TCP-PASSIVE");
+ gbChannel.setEnableMp4("0");
+ gbChannelService.insertGbChannel(gbChannel);
+ log.info("[GbChannel默认创建] deviceId: {}, channelId: {}, id: {}",
+ device.getDeviceId(), device.getDeviceId(), gbChannel.getId());
+ }
+ } catch (Exception e) {
+ log.error("[GbDevice自动创建] 设备上线,自动创建失败:{}", device.getDeviceId(), e);
+ }
}
@Override
diff --git a/ard-modules/ard-modules-work/src/main/java/com/ard/work/config/AsyncConfiguration.java b/ard-modules/ard-modules-work/src/main/java/com/ard/work/config/AsyncConfiguration.java
index 896ac64..cdc6717 100644
--- a/ard-modules/ard-modules-work/src/main/java/com/ard/work/config/AsyncConfiguration.java
+++ b/ard-modules/ard-modules-work/src/main/java/com/ard/work/config/AsyncConfiguration.java
@@ -64,4 +64,21 @@
executor.initialize();
return executor;
}
+
+ /**
+ * WebSocket推送线程池(避免慢客户端阻塞调度线程)
+ * DiscardOldestPolicy: 推送任务允许丢弃旧消息以保证实时性
+ */
+ @Bean("wsPushExecutor")
+ public ThreadPoolTaskExecutor wsPushExecutor() {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ executor.setCorePoolSize(4);
+ executor.setMaxPoolSize(16);
+ executor.setQueueCapacity(2000);
+ executor.setKeepAliveSeconds(60);
+ executor.setThreadNamePrefix("ws-push-");
+ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
+ executor.initialize();
+ return executor;
+ }
}
\ No newline at end of file
diff --git a/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/domain/PtzParamDTO.java b/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/domain/PtzParamDTO.java
index 89e3e43..2ba946a 100644
--- a/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/domain/PtzParamDTO.java
+++ b/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/domain/PtzParamDTO.java
@@ -1,5 +1,6 @@
package com.ard.work.device.camera.domain;
+import com.alibaba.fastjson2.annotation.JSONField;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -13,22 +14,26 @@
**/
@Data
@Builder
-@NoArgsConstructor // 1. 显式添加无参构造函数
+@NoArgsConstructor
@AllArgsConstructor
public class PtzParamDTO {
String cameraId;
private Integer chanNo;
@Builder.Default
- private Float p=0.0f;
+ private Float p = 0.0f;
@Builder.Default
- private Float t=0.0f;
+ private Float t = 0.0f;
@Builder.Default
- private Float z=0.0f;
+ private Float z = 0.0f;
@Builder.Default
- private Float fHorFieldAngle=1.8f;
+ private Float fHorFieldAngle = 1.8f;
@Builder.Default
- private Float fVerFieldAngle=1f;
+ private Float fVerFieldAngle = 1f;
private Double longitude;
private Double latitude;
private Double altitude;
+
+ /** 数据更新时间戳,仅服务端使用,不参与前端序列化 */
+ @JSONField(serialize = false)
+ private long updateTime;
}
diff --git a/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzCacheManager.java b/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzCacheManager.java
index 9ba82b3..02c6af6 100644
--- a/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzCacheManager.java
+++ b/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzCacheManager.java
@@ -2,10 +2,11 @@
import com.ard.work.device.camera.domain.PtzParamDTO;
import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
-import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
/**
* ptz缓存管理器
@@ -13,36 +14,50 @@
* @author 刘苏义
* @date 2026-03-20
*/
-
@Component
public class PtzCacheManager {
- // Key: cameraId_chanNo (例如 "CAM001_1"), Value: 最新的 PTZ 数据对象
- // 使用 ConcurrentHashMap 保证多线程安全
+
+ /** Key: cameraId_chanNo, Value: 最新PTZ数据(自带 updateTime) */
private final ConcurrentHashMap<String, PtzParamDTO> ptzDataMap = new ConcurrentHashMap<>();
-
- // 记录最后更新时间,用于判断数据是否过期
- private final ConcurrentHashMap<String, Long> updateTimeMap = new ConcurrentHashMap<>();
public void updatePtzData(String key, PtzParamDTO data) {
+ if (data == null) return;
+ data.setUpdateTime(System.currentTimeMillis());
ptzDataMap.put(key, data);
- updateTimeMap.put(key, System.currentTimeMillis());
}
public PtzParamDTO getPtzData(String key) {
return ptzDataMap.get(key);
}
-
- // 获取所有有效数据快照
+
+ /** 获取所有未过期数据快照 */
public List<PtzParamDTO> getAllValidData(long timeoutMs) {
long now = System.currentTimeMillis();
- return ptzDataMap.entrySet().stream()
- .filter(entry -> (now - updateTimeMap.get(entry.getKey())) < timeoutMs)
- .map(Map.Entry::getValue)
- .collect(Collectors.toList());
+ Collection<PtzParamDTO> values = ptzDataMap.values();
+ List<PtzParamDTO> result = new ArrayList<>(values.size());
+ for (PtzParamDTO dto : values) {
+ if (now - dto.getUpdateTime() < timeoutMs) {
+ result.add(dto);
+ }
+ }
+ return result;
}
-
+
public void removeData(String key) {
ptzDataMap.remove(key);
- updateTimeMap.remove(key);
}
-}
\ No newline at end of file
+
+ /** 清理过期数据,返回清理条数 */
+ public int evictStale(long timeoutMs) {
+ long now = System.currentTimeMillis();
+ int[] count = {0};
+ ptzDataMap.entrySet().removeIf(e -> {
+ if (now - e.getValue().getUpdateTime() > timeoutMs) {
+ count[0]++;
+ return true;
+ }
+ return false;
+ });
+ return count[0];
+ }
+}
diff --git a/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzDataCollector.java b/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzDataCollector.java
index 432300e..7ed9362 100644
--- a/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzDataCollector.java
+++ b/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzDataCollector.java
@@ -12,10 +12,14 @@
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
+
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
@Slf4j
@Component
@@ -25,37 +29,69 @@
private final CameraSDKService cameraSDKService;
private final PtzCacheManager ptzCacheManager;
+ @Resource(name = "timeoutExecutor")
+ private ThreadPoolTaskExecutor sdkExecutor;
+
private static final Set<String> EXCLUDE_FACTORY = new HashSet<>(Arrays.asList("7"));
private static final String CAMERA_ENABLE = "1";
private static final Set<String> SUPPORT_TYPES = new HashSet<>(Arrays.asList("1", "4"));
+ /** 单通道 SDK 调用超时 */
+ private static final long SDK_CALL_TIMEOUT_MS = 2000L;
+ /** 整相机所有通道总采集超时 */
+ private static final long CAMERA_TOTAL_TIMEOUT_MS = 3000L;
+
@Async("cameraPTZExecutor")
public void collectCamera(ArdCamera camera, Set<String> runningCamera) {
-
String camId = camera.getId();
-
try {
if (!isValidCamera(camera)) return;
List<ArdChannel> channels = camera.getChannelList();
if (channels == null || channels.isEmpty()) return;
- for (ArdChannel channel : channels) {
- String key = camId + "_" + channel.getChanNo();
- PtzParamDTO dto= fetchPtz(camera, channel);
- if (dto != null) {
- ptzCacheManager.updatePtzData(key, dto);
- }
+
+ CompletableFuture<?>[] futures = channels.stream()
+ .map(ch -> CompletableFuture.runAsync(
+ () -> collectChannel(camera, ch), sdkExecutor))
+ .toArray(CompletableFuture[]::new);
+
+ try {
+ CompletableFuture.allOf(futures).get(CAMERA_TOTAL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ log.warn("采集相机 {} 整体超时,部分通道数据可能未更新", camId);
+ } catch (Exception e) {
+ log.error("采集相机 {} 异常", camId, e);
}
} finally {
- // ✅ 必须释放(防死锁)
runningCamera.remove(camId);
}
}
- /**
- * 原始SDK调用(不改)
- */
- private PtzParamDTO fetchPtz(ArdCamera camera, ArdChannel channel) {
+ private void collectChannel(ArdCamera camera, ArdChannel channel) {
+ PtzParamDTO dto = fetchPtzWithTimeout(camera, channel);
+ if (dto != null) {
+ ptzCacheManager.updatePtzData(camera.getId() + "_" + channel.getChanNo(), dto);
+ }
+ }
+ /**
+ * 带超时的 SDK 调用,防止 SDK 卡死拖垮线程池
+ */
+ private PtzParamDTO fetchPtzWithTimeout(ArdCamera camera, ArdChannel channel) {
+ CompletableFuture<PtzParamDTO> future = CompletableFuture.supplyAsync(
+ () -> fetchPtz(camera, channel), sdkExecutor);
+ try {
+ return future.get(SDK_CALL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ log.warn("相机 {} 通道 {} SDK 调用超时", camera.getId(), channel.getChanNo());
+ future.cancel(true);
+ return null;
+ } catch (Exception e) {
+ log.error("相机 {} 通道 {} SDK 调用异常", camera.getId(), channel.getChanNo(), e);
+ return null;
+ }
+ }
+
+ private PtzParamDTO fetchPtz(ArdCamera camera, ArdChannel channel) {
CameraCmd cmd = new CameraCmd();
cmd.setCameraId(camera.getId());
cmd.setChanNo(channel.getChanNo());
@@ -78,7 +114,6 @@
dto.setT(ptz.getT());
dto.setZ(ptz.getZ());
}
-
return dto;
}
@@ -87,4 +122,4 @@
&& !EXCLUDE_FACTORY.contains(camera.getFactory())
&& SUPPORT_TYPES.contains(camera.getType());
}
-}
\ No newline at end of file
+}
diff --git a/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzTask.java b/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzTask.java
index 546430a..273e488 100644
--- a/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzTask.java
+++ b/ard-modules/ard-modules-work/src/main/java/com/ard/work/device/camera/service/PtzTask.java
@@ -9,6 +9,9 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -28,33 +31,38 @@
private final Set<String> runningCamera = ConcurrentHashMap.newKeySet();
+ /** 缓存数据有效期: 超过此时间不再推送 */
+ private static final long DATA_VALID_MS = 5_000L;
+ /** 缓存数据淘汰阈值: 超过此时间从内存清掉 */
+ private static final long DATA_EVICT_MS = 60_000L;
+ /** 相机列表本地缓存 TTL */
+ private static final long CAMERA_LIST_TTL_MS = 30_000L;
+
+ private volatile List<ArdCamera> cachedCameraList = Collections.emptyList();
+ private volatile long cameraListExpireAt = 0L;
+
/**
- * 采集任务(不会堆积)
+ * 采集任务
*/
@Scheduled(initialDelay = 5000, fixedDelay = 1000)
public void startCollectTask() {
+ List<ArdCamera> cameraList = getCameraList();
+ if (cameraList.isEmpty()) return;
- List<Object> cameraList = redisService.getCacheMapValues(CacheConstants.CAMERA_LIST);
- if (cameraList == null || cameraList.isEmpty()) return;
-
- for (Object obj : cameraList) {
- ArdCamera camera = (ArdCamera) obj;
+ for (ArdCamera camera : cameraList) {
String camId = camera.getId();
-
- // ✅ 防重复执行
if (!runningCamera.add(camId)) continue;
-
ptzDataCollector.collectCamera(camera, runningCamera);
}
}
/**
- * 推送
+ * 推送任务
*/
@Scheduled(fixedDelay = 1000)
public void pushPTZ() {
try {
- List<PtzParamDTO> list = ptzCacheManager.getAllValidData(5000);
+ List<PtzParamDTO> list = ptzCacheManager.getAllValidData(DATA_VALID_MS);
if (!list.isEmpty()) {
PTZWebSocketUtils.sendMessageAll(list);
}
@@ -62,4 +70,43 @@
log.error("推送PTZ异常", e);
}
}
-}
\ No newline at end of file
+
+ /**
+ * 清理过期缓存,防止已禁用/删除的相机数据残留
+ */
+ @Scheduled(fixedDelay = 60_000)
+ public void evictStaleCache() {
+ int n = ptzCacheManager.evictStale(DATA_EVICT_MS);
+ if (n > 0) {
+ log.debug("清理过期PTZ缓存 {} 条", n);
+ }
+ }
+
+ /**
+ * 本地缓存相机列表,降低 Redis QPS
+ */
+ private List<ArdCamera> getCameraList() {
+ long now = System.currentTimeMillis();
+ if (now < cameraListExpireAt) {
+ return cachedCameraList;
+ }
+ synchronized (this) {
+ if (now < cameraListExpireAt) {
+ return cachedCameraList;
+ }
+ List<Object> raw = redisService.getCacheMapValues(CacheConstants.CAMERA_LIST);
+ List<ArdCamera> list;
+ if (raw == null || raw.isEmpty()) {
+ list = Collections.emptyList();
+ } else {
+ list = new ArrayList<>(raw.size());
+ for (Object o : raw) {
+ list.add((ArdCamera) o);
+ }
+ }
+ cachedCameraList = list;
+ cameraListExpireAt = now + CAMERA_LIST_TTL_MS;
+ return list;
+ }
+ }
+}
diff --git a/ard-modules/ard-modules-work/src/main/java/com/ard/work/websocket/utils/PTZWebSocketUtils.java b/ard-modules/ard-modules-work/src/main/java/com/ard/work/websocket/utils/PTZWebSocketUtils.java
index 905f768..3ae10a5 100644
--- a/ard-modules/ard-modules-work/src/main/java/com/ard/work/websocket/utils/PTZWebSocketUtils.java
+++ b/ard-modules/ard-modules-work/src/main/java/com/ard/work/websocket/utils/PTZWebSocketUtils.java
@@ -12,6 +12,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -25,13 +26,15 @@
@Slf4j
public final class PTZWebSocketUtils {
- // 存储 websocket session
public static final ConcurrentMap<String, WebSocketSession> ONLINE_USER_SESSIONS = new ConcurrentHashMap<>();
- /**
- * @param session 用户 session
- * @param message 发送内容
- */
+ /** 推送线程池,由 PTZWebSocketInitializer 在启动时注入;未注入时降级为同步执行 */
+ private static volatile Executor pushExecutor;
+
+ public static void setPushExecutor(Executor executor) {
+ pushExecutor = executor;
+ }
+
public static void sendMessage(WebSocketSession session, String message) {
if (session == null) {
return;
@@ -42,84 +45,58 @@
}
synchronized (session) {
try {
- //log.debug("发送消息:" + message);
- session.sendMessage(new TextMessage(String.join(", ", message)));
+ session.sendMessage(new TextMessage(message));
} catch (IOException e) {
log.error("sendMessage IOException ", e);
}
}
}
- /**
- * @param session 用户 session
- * @param message 发送内容
- */
public static void sendMessage(WebSocketSession session, Map message) {
- if (session == null) {
- return;
- }
- final InetSocketAddress remoteAddress = session.getRemoteAddress();
- if (remoteAddress == null) {
- return;
- }
- synchronized (session) {
- try {
- session.sendMessage(new TextMessage(JSONObject.toJSONString(message)));
- } catch (IOException e) {
- log.error("sendMessage IOException ", e);
- }
- }
+ sendMessage(session, JSONObject.toJSONString(message));
}
public static void sendMessage(WebSocketSession session, List message) {
- if (session == null) {
- return;
- }
- final InetSocketAddress remoteAddress = session.getRemoteAddress();
- if (remoteAddress == null) {
- return;
- }
- synchronized (session) {
- try {
- session.sendMessage(new TextMessage(JSONObject.toJSONString(message)));
- } catch (IOException e) {
- log.error("sendMessage IOException ", e);
- }
- }
+ sendMessage(session, JSON.toJSONString(message));
}
/**
- * 推送消息到其他客户端
- *
- * @param message
+ * 推送字符串消息到所有客户端
*/
public static void sendMessageAll(String message) {
- ONLINE_USER_SESSIONS.forEach((sessionId, session) -> sendMessage(session, message));
+ broadcast(message);
}
/**
- * 推送消息到其他客户端
- *
- * @param message
+ * 推送Map消息到所有客户端
*/
public static void sendMessageAll(Map message) {
- JSONObject jsonObject = new JSONObject(message);
- ONLINE_USER_SESSIONS.forEach((sessionId, session) -> sendMessage(session, jsonObject.toString()));
+ broadcast(JSONObject.toJSONString(message));
}
+
/**
- * 推送消息到其他客户端
- *
- * @param message
+ * 推送List消息到所有客户端
*/
public static void sendMessageAll(List message) {
- ONLINE_USER_SESSIONS.forEach((sessionId, session) -> sendMessage(session, JSON.toJSONString(message)));
+ broadcast(JSON.toJSONString(message));
}
+
/**
- * @Author 刘苏义
- * @Description 发送消息给userId_前缀的人
- * @Date 2024/7/16 10:24
- * @Param
- * @return
+ * 一次序列化,并行分发到所有 session
+ */
+ private static void broadcast(String payload) {
+ if (ONLINE_USER_SESSIONS.isEmpty()) return;
+ Executor executor = pushExecutor;
+ if (executor == null) {
+ ONLINE_USER_SESSIONS.values().forEach(session -> sendMessage(session, payload));
+ return;
+ }
+ ONLINE_USER_SESSIONS.values().forEach(session ->
+ executor.execute(() -> sendMessage(session, payload)));
+ }
+
+ /**
+ * 发送消息给 userId_ 前缀的人
*/
public static void sendMessagePrefix(String targetId, String message) {
String regex = "^" + Pattern.quote(targetId) + "_\\d+$";
@@ -129,4 +106,4 @@
return matcher.matches();
}).map(Map.Entry::getValue).forEach(session -> PTZWebSocketUtils.sendMessage(session, message));
}
-}
\ No newline at end of file
+}
diff --git a/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/controller/ZlmController.java b/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/controller/ZlmController.java
index fb017db..940b32e 100644
--- a/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/controller/ZlmController.java
+++ b/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/controller/ZlmController.java
@@ -10,6 +10,7 @@
import com.ard.gb28181.api.RemoteGb28181Service;
import com.ard.gb28181.api.domain.Device;
import com.ard.gb28181.api.domain.DeviceChannel;
+import com.ard.gb28181.api.domain.GbChannelDTO;
import com.ard.qs.api.RemoteQsDeviceService;
import com.ard.qs.api.domain.QsDevice;
import com.ard.work.api.RemoteCameraService;
@@ -492,6 +493,7 @@
* @param id 设备id
* @return
*/
+ @Operation(summary = "gb28181播放")
@GetMapping("/startGb28181Play/{id}")
public DeferredResult<R<StreamContent>> startGb28181Play(
HttpServletRequest request,
@@ -633,4 +635,149 @@
return AjaxResult.success(json);
}
+ /**
+ * gb28181 播放(基于GbDevice,不依赖QS,按国标编码播放)
+ *
+ * @param request
+ * @param gbDeviceId 国标设备编码
+ * @param gbChannelId 国标通道编码
+ * @return
+ */
+ @Operation(summary = "gb28181播放(按国标编码)")
+ @GetMapping("/startGb28181PlayByGbDeviceId/{gbDeviceId}/{gbChannelId}")
+ public DeferredResult<R<StreamContent>> startGb28181PlayByGbDeviceId(
+ HttpServletRequest request,
+ @PathVariable String gbDeviceId,
+ @PathVariable String gbChannelId
+ ) {
+ log.info("[gb28181 开始点播] gbDeviceId:{}, gbChannelId:{}", gbDeviceId, gbChannelId);
+ Assert.hasText(gbDeviceId, "国标设备编码不可为空");
+ Assert.hasText(gbChannelId, "国标通道编码不可为空");
+
+ // 1. 查询 GbChannel 配置
+ R<GbChannelDTO> channelR = remoteGb28181Service.getGbChannel(gbDeviceId, gbChannelId, SecurityConstants.INNER);
+ if (channelR.getCode() != Constants.SUCCESS) {
+ throw new RuntimeException("获取GbChannel配置失败 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId
+ + "。设备注册后会自动创建,请确认设备已上线并完成目录同步。");
+ }
+ Assert.notNull(channelR.getData(), "GbChannel不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId);
+
+ GbChannelDTO gbChannelDTO = channelR.getData();
+
+ // 2. 查询国标设备
+ R<Device> deviceR = remoteGb28181Service.getDeviceByDeviceId(gbDeviceId, SecurityConstants.INNER);
+ if (deviceR.getCode() != Constants.SUCCESS) {
+ throw new RuntimeException("gb28181 获取设备信息失败 deviceId:" + gbDeviceId);
+ }
+ Assert.notNull(deviceR.getData(), "gb28181 国标设备不存在 deviceId:" + gbDeviceId);
+
+ if (!deviceR.getData().isOnLine()) {
+ throw new RuntimeException("gb28181 国标设备不在线 deviceId:" + gbDeviceId);
+ }
+
+ // 3. 查询国标通道
+ R<DeviceChannel> deviceChannelR = remoteGb28181Service.getDeviceChannelByChannelId(gbDeviceId, gbChannelId, SecurityConstants.INNER);
+ if (deviceChannelR.getCode() != Constants.SUCCESS) {
+ throw new RuntimeException("gb28181 获取设备通道失败 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId);
+ }
+ Assert.notNull(deviceChannelR.getData(), "gb28181 国标设备通道不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId);
+
+ if (!"ON".equals(deviceChannelR.getData().getStatus())) {
+ throw new RuntimeException("gb28181 国标设备通道不在线 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId);
+ }
+
+ DeferredResult<R<StreamContent>> result = new DeferredResult<>(userSetting.getPlayTimeout().longValue());
+
+ result.onTimeout(() -> {
+ log.info("[点播等待超时] gbDeviceId:{}, gbChannelId:{}", gbDeviceId, gbChannelId);
+ R<StreamContent> fail = R.fail();
+ fail.setMsg("点播超时");
+ result.setResult(fail);
+
+ inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, gbChannelDTO.getId());
+ mediaServerService.stopGb28181PlayByGbChannel(InviteSessionType.PLAY, gbChannelDTO, deviceR.getData(), gbChannelDTO.getDeviceCode());
+ });
+
+ ErrorCallback<StreamInfo> callback = (code, msg, streamInfo) -> {
+ if (code == InviteErrorCode.SUCCESS.getCode()) {
+ R<StreamContent> r = R.ok();
+ if (streamInfo != null) {
+ if (userSetting.getUseSourceIpAsStreamIp()) {
+ streamInfo = streamInfo.clone();
+ String host;
+ try {
+ URL url = new URL(request.getRequestURL().toString());
+ host = url.getHost();
+ } catch (MalformedURLException e) {
+ host = request.getLocalAddr();
+ }
+ streamInfo.changeStreamIp(host);
+ }
+ if (!ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix())
+ && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) {
+ streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix());
+ }
+ r.setData(new StreamContent(streamInfo));
+ } else {
+ r.setCode(code);
+ r.setMsg(msg);
+ }
+ result.setResult(r);
+ } else {
+ result.setResult(R.fail(code, msg));
+ }
+ };
+
+ mediaServerService.startGb28181PlayByGbChannel(gbChannelDTO, deviceR.getData(), callback);
+ return result;
+ }
+
+ /**
+ * gb28181 停止点播(基于GbDevice,不依赖QS,按国标编码停止)
+ *
+ * @param gbDeviceId 国标设备编码
+ * @param gbChannelId 国标通道编码
+ * @return
+ */
+ @Operation(summary = "gb28181停止点播(按国标编码)")
+ @GetMapping("/stopGb28181PlayByGbDeviceId/{gbDeviceId}/{gbChannelId}")
+ public AjaxResult stopGb28181PlayByGbDeviceId(
+ @PathVariable String gbDeviceId,
+ @PathVariable String gbChannelId
+ ) {
+ log.info("[gb28181 停止点播] gbDeviceId:{}, gbChannelId:{}", gbDeviceId, gbChannelId);
+ Assert.hasText(gbDeviceId, "国标设备编码不可为空");
+ Assert.hasText(gbChannelId, "国标通道编码不可为空");
+
+ // 1. 查询 GbChannel 配置
+ R<GbChannelDTO> channelR = remoteGb28181Service.getGbChannel(gbDeviceId, gbChannelId, SecurityConstants.INNER);
+ if (channelR.getCode() != Constants.SUCCESS || channelR.getData() == null) {
+ throw new RuntimeException("GbChannel不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId);
+ }
+
+ GbChannelDTO gbChannelDTO = channelR.getData();
+
+ // 2. 查询国标设备
+ R<Device> deviceR = remoteGb28181Service.getDeviceByDeviceId(gbDeviceId, SecurityConstants.INNER);
+ if (deviceR.getCode() != Constants.SUCCESS) {
+ throw new RuntimeException("gb28181 获取设备信息失败 deviceId:" + gbDeviceId);
+ }
+ Assert.notNull(deviceR.getData(), "gb28181 国标设备不存在 deviceId:" + gbDeviceId);
+
+ // 3. 查询国标通道
+ R<DeviceChannel> deviceChannelR = remoteGb28181Service.getDeviceChannelByChannelId(gbDeviceId, gbChannelId, SecurityConstants.INNER);
+ if (deviceChannelR.getCode() != Constants.SUCCESS) {
+ throw new RuntimeException("gb28181 获取设备通道失败 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId);
+ }
+ Assert.notNull(deviceChannelR.getData(), "gb28181 国标设备通道不存在 gbDeviceId:" + gbDeviceId + " gbChannelId:" + gbChannelId);
+
+ mediaServerService.stopGb28181PlayByGbChannel(InviteSessionType.PLAY, gbChannelDTO,
+ deviceR.getData(), gbChannelDTO.getDeviceCode());
+
+ JSONObject json = new JSONObject();
+ json.put("gbDeviceId", gbDeviceId);
+ json.put("gbChannelId", gbChannelId);
+ return AjaxResult.success(json);
+ }
+
}
diff --git a/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/IMediaServerService.java b/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/IMediaServerService.java
index 05949f3..7d49b49 100644
--- a/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/IMediaServerService.java
+++ b/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/IMediaServerService.java
@@ -1,6 +1,7 @@
package com.ard.zlm.service;
import com.ard.gb28181.api.domain.Device;
+import com.ard.gb28181.api.domain.GbChannelDTO;
import com.ard.qs.api.domain.QsDevice;
import com.ard.work.api.domian.ArdChannel;
import com.ard.zlm.api.domain.*;
@@ -362,4 +363,13 @@
*/
void stopGb28181Play(InviteSessionType type, QsDevice qsDevice, Device device, String stream);
+ /**
+ * gb28181 播放(基于GbChannel,不依赖QS)
+ */
+ void startGb28181PlayByGbChannel(GbChannelDTO gbChannelDTO, Device gbDevice, ErrorCallback<StreamInfo> callback);
+
+ /**
+ * gb28181 停止点播(基于GbChannel,不依赖QS)
+ */
+ void stopGb28181PlayByGbChannel(InviteSessionType type, GbChannelDTO gbChannelDTO, Device device, String stream);
}
diff --git a/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/impl/MediaServerServiceImpl.java b/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/impl/MediaServerServiceImpl.java
index e95947c..f43e858 100644
--- a/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/impl/MediaServerServiceImpl.java
+++ b/ard-modules/ard-modules-zlm/src/main/java/com/ard/zlm/service/impl/MediaServerServiceImpl.java
@@ -11,6 +11,7 @@
import com.ard.common.core.utils.file.FileMultipartFile;
import com.ard.gb28181.api.RemoteGb28181Service;
import com.ard.gb28181.api.domain.Device;
+import com.ard.gb28181.api.domain.GbChannelDTO;
import com.ard.qs.api.RemoteQsDeviceService;
import com.ard.qs.api.domain.QsDevice;
import com.ard.system.api.RemoteFileService;
@@ -1811,6 +1812,101 @@
}
}
+ /**
+ * gb28181 播放(基于GbDevice,不依赖QS)
+ */
+ @Override
+ public void startGb28181PlayByGbChannel(GbChannelDTO gbChannelDTO, Device gbDevice, ErrorCallback<StreamInfo> callback) {
+ ZlmMediaServer mediaServer = getMediaServerForMinimumLoad(null);
+
+ if (mediaServer == null) {
+ callback.run(InviteErrorCode.FAIL.getCode(), "无可用的节点", null);
+ return;
+ }
+
+ String streamMode = gbChannelDTO.getStreamMode() != null
+ ? gbChannelDTO.getStreamMode() : gbDevice.getStreamMode();
+ int tcpMode = streamMode.equals("TCP-ACTIVE") ? 2
+ : (streamMode.equals("TCP-PASSIVE") ? 1 : 0);
+
+ RTPServerParam rtpServerParam = new RTPServerParam();
+ rtpServerParam.setApp("gb28181");
+ rtpServerParam.setMediaServer(mediaServer);
+ rtpServerParam.setType(LiveStreamType.GB28181.getCode());
+ rtpServerParam.setStreamId(gbChannelDTO.getDeviceCode());
+ rtpServerParam.setTcpMode(tcpMode);
+ rtpServerParam.setId(gbChannelDTO.getId());
+
+ startGb28181PlayFunByGbChannel(mediaServer, gbChannelDTO, gbDevice, rtpServerParam, null, callback);
+ }
+
+ /**
+ * gb28181 停止点播(基于GbDevice,不依赖QS)
+ */
+ @Override
+ public void stopGb28181PlayByGbChannel(InviteSessionType type, GbChannelDTO gbChannelDTO, Device device, String stream) {
+ InviteInfo inviteInfo = inviteStreamService.getInviteInfo(type, gbChannelDTO.getId(), stream);
+ if (inviteInfo == null) {
+ if (type == InviteSessionType.PLAY) {
+ GbChannelDTO update = new GbChannelDTO();
+ update.setId(gbChannelDTO.getId());
+ update.setStreamKey("");
+ update.setMediaServerId("");
+ update.setStreamStatus("0");
+ R<Boolean> r = remoteGb28181Service.updateGbChannelStream(update, SecurityConstants.INNER);
+ if (r.getCode() != Constants.SUCCESS) {
+ throw new RuntimeException("更新GbDevice失败");
+ }
+ }
+ return;
+ }
+ inviteStreamService.removeInviteInfo(inviteInfo);
+ if (InviteSessionStatus.ok == inviteInfo.getStatus()) {
+ try {
+ log.info("[停止点播/回放/下载] {}/{}", gbChannelDTO.getGbDeviceId(), gbChannelDTO.getGbChannelId());
+
+ RtpServerParam rtpServer = new RtpServerParam();
+ rtpServer.setApp("gb28181");
+ rtpServer.setStream(gbChannelDTO.getDeviceCode());
+ rtpServer.setGbDeviceId(gbChannelDTO.getGbDeviceId());
+ rtpServer.setGbChannelId(gbChannelDTO.getGbChannelId());
+
+ R<Void> r = remoteGb28181Service.streamByeCmd(rtpServer, SecurityConstants.INNER);
+ if (r.getCode() != Constants.SUCCESS) {
+ log.error("[命令发送失败] 停止点播/回放/下载, deviceId:{}", gbChannelDTO.getGbDeviceId());
+ throw new RuntimeException("[命令发送失败] 停止点播/回放/下载, deviceId:" + gbChannelDTO.getGbDeviceId());
+ }
+ } catch (Exception e) {
+ log.error("[命令发送失败] 停止点播/回放/下载, 发送BYE: {}", e.getMessage());
+ throw new RuntimeException("命令发送失败: " + e.getMessage());
+ }
+ }
+
+ if (inviteInfo.getType() == InviteSessionType.PLAY) {
+ GbChannelDTO update = new GbChannelDTO();
+ update.setId(gbChannelDTO.getId());
+ update.setStreamKey("");
+ update.setMediaServerId("");
+ update.setStreamStatus("0");
+ R<Boolean> r = remoteGb28181Service.updateGbChannelStream(update, SecurityConstants.INNER);
+ if (r.getCode() != Constants.SUCCESS) {
+ throw new RuntimeException("更新GbDevice失败");
+ }
+ }
+
+ ZlmMediaServer mediaServer = null;
+ if (inviteInfo.getStreamInfo() != null) {
+ mediaServer = inviteInfo.getStreamInfo().getMediaServer();
+ } else {
+ mediaServer = getOne(inviteInfo.getMediaServerId());
+ }
+
+ if (mediaServer != null && inviteInfo.getSsrcInfo() != null) {
+ closeRTPServer(mediaServer, inviteInfo.getSsrcInfo().getStream());
+ ssrcFactory.releaseSsrc(inviteInfo.getMediaServerId(), inviteInfo.getSsrcInfo().getSsrc());
+ }
+ }
+
/**
* 开启国标28181播放
@@ -2029,6 +2125,197 @@
}
/**
+ * 开启国标28181播放(基于GbDevice,不依赖QS)
+ */
+ private SSRCInfo startGb28181PlayFunByGbChannel(ZlmMediaServer mediaServer, GbChannelDTO gbChannelDTO,
+ Device gbDevice, RTPServerParam rtpServerParam,
+ String ssrc, ErrorCallback<StreamInfo> callback) {
+ // 获取点播的状态信息
+ InviteInfo inviteInfoInCatch = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY,
+ gbChannelDTO.getId());
+ if (inviteInfoInCatch != null) {
+ if (inviteInfoInCatch.getStreamInfo() == null) {
+ ssrcFactory.releaseSsrc(mediaServer.getId(), null);
+ inviteStreamService.once(InviteSessionType.PLAY, gbChannelDTO.getId(), null, callback);
+ log.info("[点播开始] 已经请求中,等待结果, deviceId: {}, channel: {}", gbChannelDTO.getId(), gbChannelDTO.getId());
+ return inviteInfoInCatch.getSsrcInfo();
+ } else {
+ StreamInfo streamInfo = inviteInfoInCatch.getStreamInfo();
+ String streamId = streamInfo.getStream();
+ if (streamId == null) {
+ callback.run(InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null);
+ inviteStreamService.call(InviteSessionType.PLAY, gbChannelDTO.getId(), null,
+ InviteErrorCode.ERROR_FOR_CATCH_DATA.getCode(), "点播失败, redis缓存streamId等于null", null);
+ return inviteInfoInCatch.getSsrcInfo();
+ }
+ ZlmMediaServer mediaInfo = streamInfo.getMediaServer();
+ Boolean ready = isStreamReady(mediaInfo, rtpServerParam.getApp(), streamId);
+ if (ready != null && ready) {
+ if (callback != null) {
+ callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
+ }
+ inviteStreamService.call(InviteSessionType.PLAY, gbChannelDTO.getId(), null,
+ InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
+ log.info("[点播已存在] 直接返回, 设备编号: {}", gbChannelDTO.getId());
+ return inviteInfoInCatch.getSsrcInfo();
+ } else {
+ inviteStreamService.once(InviteSessionType.PLAY, gbChannelDTO.getId(), null, callback);
+ RTPServerParam stopRtp = new RTPServerParam();
+ stopRtp.setId(gbChannelDTO.getId());
+ stopRtp.setType(rtpServerParam.getType());
+ stopRtp.setStreamId(rtpServerParam.getStreamId());
+ stopRtpPlay(stopRtp);
+ inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, gbChannelDTO.getId());
+ }
+ }
+ }
+
+ rtpServerParam.setMediaServer(mediaServer);
+ if (rtpServerParam.getPresetSsrc() != null) {
+ ssrc = rtpServerParam.getPresetSsrc();
+ } else {
+ if (rtpServerParam.isPlayback()) {
+ ssrc = ssrcFactory.getPlayBackSsrc(mediaServer.getId());
+ } else {
+ ssrc = ssrcFactory.getPlaySsrc(mediaServer.getId());
+ }
+ }
+ rtpServerParam.setSsrc(ssrc);
+
+ SSRCInfo ssrcInfo = receiveRtpServerService.openRTPServer(rtpServerParam, (code, msg, result) -> {
+ if (code == InviteErrorCode.SUCCESS.getCode() && result != null && result.getHookData() != null) {
+ log.info("[创建RTP服务器] 成功, code: {}, msg: {}, result: {}", code, msg, result);
+ StreamInfo streamInfo = getStreamInfoByAppAndStream(mediaServer, rtpServerParam.getApp(),
+ rtpServerParam.getStreamId(), result.getHookData().getMediaInfo());
+ if (streamInfo == null) {
+ if (callback != null) {
+ callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
+ InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
+ }
+ inviteStreamService.call(InviteSessionType.PLAY, gbChannelDTO.getId(), null,
+ InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
+ InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
+ if (result != null && result.getSsrcInfo() != null) {
+ closeRTPServer(mediaServer, result.getSsrcInfo().getStream());
+ ssrcFactory.releaseSsrc(mediaServer.getId(), result.getSsrcInfo().getSsrc());
+ }
+ return;
+ }
+ if (callback != null) {
+ callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
+ inviteStreamService.call(InviteSessionType.PLAY, gbChannelDTO.getId(), null,
+ InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
+
+ InviteInfo inviteInfo = inviteStreamService.getInviteInfoByDeviceAndChannel(
+ InviteSessionType.PLAY, gbChannelDTO.getId());
+ if (inviteInfo != null) {
+ inviteInfo.setStatus(InviteSessionStatus.ok);
+ inviteInfo.setStreamInfo(streamInfo);
+ inviteStreamService.updateInviteInfo(inviteInfo);
+ }
+
+ String filePath = snapOnPlay(streamInfo.getMediaServer(), streamInfo.getApp(),
+ streamInfo.getStream());
+
+ // 更新GbDevice流状态
+ GbChannelDTO update = new GbChannelDTO();
+ update.setId(rtpServerParam.getId());
+ update.setStreamKey(rtpServerParam.getStreamId());
+ update.setMediaServerId(mediaServer.getId());
+ update.setStreamStatus("1");
+ update.setSnap(filePath);
+ R<Boolean> r = remoteGb28181Service.updateGbChannelStream(update, SecurityConstants.INNER);
+ if (r.getCode() != Constants.SUCCESS) {
+ throw new RuntimeException("更新GbDevice失败");
+ }
+ }
+ } else {
+ log.error("[创建RTP服务器] 失败, code: {}, msg: {}, result: {}", code, msg, result);
+ if (callback != null) {
+ callback.run(code, msg, null);
+ }
+ inviteStreamService.call(InviteSessionType.PLAY, gbChannelDTO.getId(), null, code, msg, null);
+ inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, gbChannelDTO.getId());
+ if (result != null && result.getSsrcInfo() != null) {
+ closeRTPServer(mediaServer, result.getSsrcInfo().getStream());
+ ssrcFactory.releaseSsrc(mediaServer.getId(), result.getSsrcInfo().getSsrc());
+ }
+ }
+ });
+
+ if (ssrcInfo == null || ssrcInfo.getPort() <= 0) {
+ log.info("[点播端口/SSRC]获取失败,设备编号:{}, 通道编号:{}, ssrcInfo: {}", gbChannelDTO.getId(), gbChannelDTO.getId(), ssrcInfo);
+ if (rtpServerParam.getPresetSsrc() == null) {
+ ssrcFactory.releaseSsrc(mediaServer.getId(), ssrc);
+ }
+ callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), "获取端口或者ssrc失败", null);
+ inviteStreamService.call(InviteSessionType.PLAY, gbChannelDTO.getId(), null,
+ InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(),
+ InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null);
+ return null;
+ }
+
+ int port = ssrcInfo.getPort();
+ String ip = mediaServer.getIp();
+ RtpServerParam rtpServer = new RtpServerParam();
+ rtpServer.setPort(port);
+ rtpServer.setIp(ip);
+ rtpServer.setId(rtpServerParam.getId());
+ rtpServer.setSsrc(rtpServerParam.getSsrc());
+ rtpServer.setGbDeviceId(gbDevice.getDeviceId());
+ rtpServer.setGbChannelId(gbChannelDTO.getGbChannelId());
+ rtpServer.setStreamMode(gbDevice.getStreamMode());
+ rtpServer.setMediaServerId(mediaServer.getId());
+ rtpServer.setApp(rtpServerParam.getApp());
+ rtpServer.setStream(rtpServerParam.getStreamId());
+
+ log.info("[国标28181点播开始(基于GbDevice)] ===============================");
+ log.info("[国标28181] GbDeviceId: {}, 设备国标ID: {}, 通道国标ID: {}", gbChannelDTO.getId(),
+ gbDevice.getDeviceId(), gbChannelDTO.getGbChannelId());
+ log.info("[国标28181] 流模式: {}, ZLM tcpMode: {}, ssrcCheck: {}", gbDevice.getStreamMode(),
+ rtpServerParam.getTcpMode(), rtpServerParam.isSsrcCheck());
+ log.info("[国标28181] ZLM媒体服务器IP: {}, 收流端口: {}, 流ID: {}, SSRC: {}", ip, port, ssrcInfo.getStream(),
+ ssrcInfo.getSsrc());
+ log.info("[国标28181] =======================================");
+
+ InviteInfo inviteInfo = InviteInfo.getInviteInfo(gbChannelDTO.getId().toString(), gbChannelDTO.getId(),
+ ssrcInfo.getStream(), ssrcInfo, mediaServer.getId(), mediaServer.getSdpIp(), ssrcInfo.getPort(),
+ gbDevice.getStreamMode(), InviteSessionType.PLAY, InviteSessionStatus.ready,
+ userSetting.getRecordSip());
+
+ if ("1".equals(gbChannelDTO.getEnableMp4())) {
+ inviteInfo.setRecord(true);
+ }
+
+ inviteStreamService.updateInviteInfo(inviteInfo);
+
+ R<Void> r = remoteGb28181Service.playStreamCmd(rtpServer, SecurityConstants.INNER);
+
+ if (r.getCode() != Constants.SUCCESS) {
+ log.info("[点播失败]{}:{} deviceId: {}, channelId:{}", r.getCode(), r.getMsg(),
+ gbChannelDTO.getGbDeviceId(), gbChannelDTO.getGbChannelId());
+ inviteInfo = inviteStreamService.getInviteInfo(InviteSessionType.PLAY, gbChannelDTO.getId(),
+ rtpServerParam.getStreamId());
+ if (inviteInfo != null) {
+ inviteStreamService.removeInviteInfo(inviteInfo);
+ if (inviteInfo.getSsrcInfo() != null) {
+ ssrcFactory.releaseSsrc(mediaServer.getId(), inviteInfo.getSsrcInfo().getSsrc());
+ }
+ }
+ closeRTPServer(mediaServer, ssrcInfo.getStream());
+ ssrcFactory.releaseSsrc(mediaServer.getId(), ssrcInfo.getSsrc());
+ if (callback != null) {
+ callback.run(r.getCode(), r.getMsg(), null);
+ }
+ inviteStreamService.call(InviteSessionType.PLAY, gbChannelDTO.getId(), null,
+ r.getCode(), r.getMsg(), null);
+ inviteStreamService.removeInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, gbChannelDTO.getId());
+ return ssrcInfo;
+ }
+ return ssrcInfo;
+ }
+
+ /**
* 将 WebSocket 协议地址转换为 HTTP 协议地址
* ws:// -> http://
* wss:// -> https://
--
Gitblit v1.9.3